Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 35 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ instructions on installing via a package manager.
Available on [Homebrew](https://brew.sh/) in a [custom
tap](https://docs.brew.sh/Taps).

$ brew install replit/tap/upm
brew install replit/tap/upm

### Debian-based Linux

Expand All @@ -93,7 +93,7 @@ repository and install with `makepkg` using the PKGBUILD in
Available on [Scoop](https://scoop.sh/) in the [main
bucket](https://github.com/ScoopInstaller/Main/blob/master/bucket/upm.json).

$ scoop install upm
scoop install upm

### Snappy

Expand All @@ -107,7 +107,7 @@ You can try out UPM right away in a Docker image based on Ubuntu that
has all the supported languages and package managers already
installed.

$ docker run -it --rm replco/upm
docker run -it --rm replco/upm

Additional tags are also available. `replco/upm:full` is the same as
the above, while `replco/upm:light` just has the UPM binary installed
Expand All @@ -116,17 +116,27 @@ installed. If you want to run a specific tagged release, rather than
the latest development snapshot, use e.g. `replco/upm:1.0`,
`replco/upm:1.0-full`, or `replco/upm:1.0-light`.

### From source

To build UPM from a checkout, install Go and run:

git clone https://github.com/replit/upm.git
cd upm
make upm

The built binary can be found at `./cmd/upm/upm`.

## Quick start

Let's create a new Python project:

$ mkdir ~/python
$ cd ~/python
mkdir ~/python
cd ~/python

We'll start by adding Flask as a dependency. UPM will handle setting
up the project for us:

$ upm -l python add flask
upm -l python add flask
--> python3 -m poetry init --no-interaction

This command will guide you through creating your pyproject.toml config.
Expand Down Expand Up @@ -157,19 +167,19 @@ format, while the lockfile specifies exact versions for everything,
including transitive dependencies. For Python, the specfile is
`pyproject.toml` and the lockfile is `poetry.lock`:

$ ls
ls
poetry.lock pyproject.toml

We don't have to read them ourselves, because UPM can handle that.
Notice that UPM is now aware that our project uses Python, because of
the files that were created:

$ upm list
upm list
name spec
----- ----
flask ^1.1

$ upm list -a
upm list -a
name version
------------ -------
click 7.0
Expand All @@ -181,7 +191,7 @@ the files that were created:

Let's search for another dependency to add:

$ upm search nose
upm search nose
--> python3 -c '<secret sauce>' nose
Name Description Version
----------------- ---------------------------------------------------------------------- -------
Expand All @@ -208,7 +218,7 @@ Let's search for another dependency to add:

We can get more information about a package like this:

$ upm info nose
upm info nose
--> python3 -c '<secret sauce>' nose
Name: nose
Description: nose extends unittest to make testing easier
Expand All @@ -220,7 +230,7 @@ We can get more information about a package like this:
For piping into other programs, the `search` and `info` commands can
also output JSON:

$ upm info nose --format=json | jq
upm info nose --format=json | jq
--> python3 -c '<secret sauce>' nose
{
"name": "nose",
Expand All @@ -236,9 +246,9 @@ packages need to be installed. We use this on Repl.it to help
developers get started faster. To see it in action, we'll need some
source code:

$ git clone https://github.com/replit/play.git ~/play
$ cd ~/play
$ upm add --guess
git clone https://github.com/replit/play.git ~/play
cd ~/play
upm add --guess
--> python3 -c '<secret sauce>' '<secret sauce>'
--> python3 -m poetry init --no-interaction

Expand Down Expand Up @@ -268,7 +278,7 @@ You can also just get the list of guessed dependencies, if you want.
The `-a` flag lists all guessed dependencies, even the ones already
added to the specfile:

$ upm guess -a
upm guess -a
pygame
pymunk
setuptools
Expand All @@ -277,7 +287,7 @@ All of this might seem a bit too simple to justify a new tool, but the
real power of UPM is that it works exactly the same for every
programming language:

$ upm -l nodejs info express
upm -l nodejs info express
Name: express
Description: Fast, unopinionated, minimalist web framework
Version: 4.17.1
Expand All @@ -287,7 +297,7 @@ programming language:
Author: TJ Holowaychuk <tj@vision-media.ca>
License: MIT

$ upm -l ruby info jekyll
upm -l ruby info jekyll
--> ruby -e '<secret sauce>' jekyll
Name: jekyll
Description: Jekyll is a simple, blog aware, static site generator.
Expand All @@ -300,7 +310,7 @@ programming language:
License: MIT
Dependencies: addressable, colorator, em-websocket, i18n, jekyll-sass-converter, jekyll-watch, kramdown, liquid, mercenary, pathutil, rouge, safe_yaml

$ upm -l elisp info elnode
upm -l elisp info elnode
--> emacs -Q --batch --eval '<secret sauce>' /tmp/elpa552971126 info elnode
Name: elnode
Description: The Emacs webserver.
Expand All @@ -316,7 +326,7 @@ that you don't have to!

Explore the command-line interface at your leisure:

$ upm --help
upm --help
Usage:
upm [command]

Expand Down Expand Up @@ -441,7 +451,7 @@ All of these dependencies are already installed in the

## Contributing

$ make help
make help
usage:
make upm Build the UPM binary
make dev Run a shell with UPM source code and all package managers inside Docker
Expand All @@ -468,10 +478,10 @@ a test folder and use the binary and test your changes.
For example say you made a change to the `nodejs-npm` language
and want to test it, you would hit run and do the following:

$ mkdir testnpm
$ cd testnpm
$ npm init -y
$ ../cmd/upm/upm add left-pad -l nodejs-npm
mkdir testnpm
cd testnpm
npm init -y
../cmd/upm/upm add left-pad -l nodejs-npm


### Using Docker
Expand Down
3 changes: 2 additions & 1 deletion internal/backends/nodejs/nodejs.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,8 @@ var nodejsGuessRegexps = util.Regexps([]string{
`(?m)import\s*['"]([^'"]+)['"]\s*;?\s*$`,
// const mod = import("module-name")
// const mod = require("module-name")
`(?m)(?:require|import)\s*\(\s*['"]([^'"{}]+)['"]\s*\)`,
// const mod = require(`module-name`)
"(?m)(?:require|import)\\s*\\(\\s*['\"`]([^'\"`{}]+)['\"`]\\s*\\)",
})

var nodeIgnorePathSegments = map[string]bool{
Expand Down
50 changes: 43 additions & 7 deletions internal/backends/nodejs/nodejs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,9 @@ func TestNodejsYarnBackend_Guess(t *testing.T) {
require("node-fetch");
}
`,
expected: map[string]bool{},
expected: map[string]bool{
"node-fetch": true,
},
},
{
scenario: "dynamic import",
Expand All @@ -152,7 +154,9 @@ func TestNodejsYarnBackend_Guess(t *testing.T) {
import("node-fetch");
}
`,
expected: map[string]bool{},
expected: map[string]bool{
"node-fetch": true,
},
},
}

Expand All @@ -170,12 +174,28 @@ func TestNodejsYarnBackend_Guess(t *testing.T) {
}
}

func verify(t *testing.T, tc TestCase, extension string) {
dir, err := os.MkdirTemp(".", "temp")
if err != nil {
t.Error(err)
func TestNodejsGuessRegexpsMatchBacktickImports(t *testing.T) {
content := "const request = require(`request`);\nconst fetch = import(`node-fetch`);\n"
expected := map[string]bool{
"request": true,
"node-fetch": true,
}
defer os.RemoveAll(dir)

for _, re := range nodejsGuessRegexps {
for _, match := range re.FindAllStringSubmatch(content, -1) {
for _, part := range match[1:] {
delete(expected, part)
}
}
}

if len(expected) > 0 {
t.Errorf("Missing imports: %v", expected)
}
}

func verify(t *testing.T, tc TestCase, extension string) {
dir := t.TempDir()

file, err := os.CreateTemp(dir, "*."+extension)
if err != nil {
Expand All @@ -186,6 +206,22 @@ func verify(t *testing.T, tc TestCase, extension string) {
if err != nil {
t.Error(err)
}
if err := file.Close(); err != nil {
t.Error(err)
}

cwd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
if err := os.Chdir(dir); err != nil {
t.Fatal(err)
}
defer func() {
if err := os.Chdir(cwd); err != nil {
t.Fatal(err)
}
}()

result, ok := tc.backend.Guess(context.Background())
if !ok {
Expand Down
Loading