From b1d703bf74be2a0527c3b96882c789051506759e Mon Sep 17 00:00:00 2001 From: Lloouujjiinn <281355774+fhwvtqdc2q-svg@users.noreply.github.com> Date: Sun, 7 Jun 2026 04:04:01 +0200 Subject: [PATCH] Fix Node.js backtick imports and README commands --- README.md | 60 ++++++++++++++----------- internal/backends/nodejs/nodejs.go | 3 +- internal/backends/nodejs/nodejs_test.go | 50 ++++++++++++++++++--- 3 files changed, 80 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 6889df64..798c2888 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 @@ -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 @@ -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. @@ -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 @@ -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 '' nose Name Description Version ----------------- ---------------------------------------------------------------------- ------- @@ -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 '' nose Name: nose Description: nose extends unittest to make testing easier @@ -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 '' nose { "name": "nose", @@ -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 '' '' --> python3 -m poetry init --no-interaction @@ -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 @@ -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 @@ -287,7 +297,7 @@ programming language: Author: TJ Holowaychuk License: MIT - $ upm -l ruby info jekyll + upm -l ruby info jekyll --> ruby -e '' jekyll Name: jekyll Description: Jekyll is a simple, blog aware, static site generator. @@ -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 '' /tmp/elpa552971126 info elnode Name: elnode Description: The Emacs webserver. @@ -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] @@ -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 @@ -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 diff --git a/internal/backends/nodejs/nodejs.go b/internal/backends/nodejs/nodejs.go index 3d86d106..3ae55363 100644 --- a/internal/backends/nodejs/nodejs.go +++ b/internal/backends/nodejs/nodejs.go @@ -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{ diff --git a/internal/backends/nodejs/nodejs_test.go b/internal/backends/nodejs/nodejs_test.go index 957bbb89..cded5072 100644 --- a/internal/backends/nodejs/nodejs_test.go +++ b/internal/backends/nodejs/nodejs_test.go @@ -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", @@ -152,7 +154,9 @@ func TestNodejsYarnBackend_Guess(t *testing.T) { import("node-fetch"); } `, - expected: map[string]bool{}, + expected: map[string]bool{ + "node-fetch": true, + }, }, } @@ -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 { @@ -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 {