Skip to content

feat(BREAKING/install): require -- for script arg in deno install -g and support installing multiple packages#31292

Merged
dsherret merged 6 commits intodenoland:mainfrom
dsherret:feat_multiple_install_commands
Nov 20, 2025
Merged

feat(BREAKING/install): require -- for script arg in deno install -g and support installing multiple packages#31292
dsherret merged 6 commits intodenoland:mainfrom
dsherret:feat_multiple_install_commands

Conversation

@dsherret
Copy link
Copy Markdown
Contributor

Changes the behaviour of deno install -g to require -- for the script argument.

This has the side effect of now supporting deno install -g <pkg1> <pkg2> similar to npm install -g.

Closes #31024

@dsherret dsherret added this to the 3.0 milestone Nov 14, 2025
Copilot AI review requested due to automatic review settings November 14, 2025 16:48
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a breaking change to deno install -g by requiring the -- separator for script arguments and adding support for installing multiple packages in a single command, similar to npm install -g <pkg1> <pkg2>.

Key Changes:

  • Changed InstallFlagsGlobal.module_url from a single String to module_urls as Vec<String> to support multiple packages
  • Added script_arg() parsing with .last(true) to require -- separator before script arguments
  • Added validation to prevent using --name flag when installing multiple packages

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
cli/args/flags.rs Updated flag definitions to use module_urls vector and added script argument parsing with -- separator requirement; includes test updates
cli/args/mod.rs Modified to extract first URL from module_urls vector for CLI options
cli/tools/installer/mod.rs Refactored installation logic to loop over multiple module URLs and updated function signatures; includes comprehensive test updates
tests/integration/install_tests.rs Added test for multiple package installation and updated existing test to use -- separator
tests/specs/install/global/config_file_import_map/test.jsonc Updated test to place script at end of command per new syntax requirements

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cli/tools/installer/mod.rs Outdated
Comment thread cli/tools/installer/mod.rs Outdated
dsherret and others added 3 commits November 14, 2025 11:54
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: David Sherret <dsherret@users.noreply.github.com>
Copy link
Copy Markdown
Member

@bartlomieju bartlomieju left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I agree this is desirable

@bartlomieju
Copy link
Copy Markdown
Member

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 17, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 17, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds support for installing multiple module URLs in a single global install invocation by changing InstallFlagsGlobal.module_url: String to module_urls: Vec. Parsing collects multiple module URLs and enforces that --name cannot be used when more than one package is specified. The global install flow now iterates per-module_url, updating shim resolution and error messages; create_install_shim and resolve_shim_data signatures were changed to accept an explicit module_url parameter. Tests and CLI wiring were updated to handle trailing script args via -- and multiple-module scenarios.

Sequence Diagram(s)

sequenceDiagram
    participant User as CLI
    participant Parser as install_parse()
    participant Validator as Validation
    participant Installer as install_global()
    participant Shim as create_install_shim()

    User->>Parser: deno install -g module1 module2 -- script args
    Parser->>Parser: Collect module_urls Vec<String>
    Parser->>Validator: Validate flags (e.g. --name with multiple)
    alt invalid: --name used with multiple
        Validator-->>User: Error (InvalidValue)
    else valid
        Validator-->>Installer: InstallFlagsGlobal{module_urls: [...]}
        loop for each module_url (index i)
            Installer->>Installer: Ensure URL prefix or show hint (index-specific)
            Installer->>Installer: load & type-check module graph for module_url
            Installer->>Shim: create_install_shim(..., module_url)
            Shim->>Shim: resolve_shim_data(..., module_url)
            Shim-->>Installer: shim created
        end
        Installer-->>User: exit success
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Multiple API signature changes across layers (flags, installer functions) — verify all call sites updated.
  • Per-entry install loop with index-dependent error/hint logic — verify correctness for edge cases (first vs subsequent args).
  • Validation enforcing --name restriction when multiple modules are provided.
  • CLI wiring change: script_arg().last(true) — ensure argument parsing behavior matches expectations.
  • Test updates/new tests — confirm they cover multi-module, script-argument parsing, and error messages.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 79.41% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes both main changes: requiring -- for script arguments and supporting multiple package installation in deno install -g.
Description check ✅ Passed The description directly relates to the changeset, explaining the behavior change and the multiple package support feature, with a reference to the closed issue.
Linked Issues check ✅ Passed The PR fully implements the requirement from #31024 to require -- for script arguments in deno install -g, with changes across CLI parsing, validation, and tests.
Out of Scope Changes check ✅ Passed All changes align with the stated objectives: CLI argument parsing updates, support for multiple module URLs, validation logic, and corresponding test updates. No unrelated changes detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
cli/args/flags.rs (3)

3153-3164: Consider constraining script args to --global in install subcommand

Wiring script_arg().last(true) into install_subcommand enables the desired deno install -g <pkg...> -- <script args...> flow, consistent with how bench/test handle trailing script arguments. One nuance: this also allows script_arg in non‑global installs, but those values are ignored in the local branch. If you want to avoid surprising “accepted but ignored” arguments for deno install without -g, consider adding .requires("global") to the script_arg arg in this subcommand.


5865-5898: Global install parsing & --name restriction are correct; consider one extra validation

The new global parsing that:

  • collects module_urls from "cmd",
  • collects script args from "script_arg", and
  • errors when module_urls.len() > 1 && name.is_some()

matches the PR goal (multi-package support plus -- before script args) and prevents ambiguous --name usage. One behavioral point to consider: with the current shape, deno install -g a b -- foo will apply the same args slice (["foo"]) to all installed packages; if that’s not the intended UX, a follow-up could either reject combining multiple module URLs with script args or clearly document that args are shared across all installed binaries.


9788-9871: Install tests cover main paths; add a case for the new --name error

The updated install and install_with_flags tests verify:

  • multi-package global installs populate module_urls correctly, and
  • global installs with script args require -- and populate args as expected.

To lock in the new validation, it would be good to add a unit test that asserts flags_from_vec(["deno", "install", "-g", "--name", "x", "a", "b"]) produces the expected InvalidValue error message for --name with multiple packages.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b0cfbf5 and 93d372d.

📒 Files selected for processing (5)
  • cli/args/flags.rs (6 hunks)
  • cli/args/mod.rs (1 hunks)
  • cli/tools/installer/mod.rs (25 hunks)
  • tests/integration/install_tests.rs (3 hunks)
  • tests/specs/install/global/config_file_import_map/__test__.jsonc (1 hunks)
🔇 Additional comments (11)
cli/args/mod.rs (1)

1158-1162: LGTM! Clean adaptation to multi-module support.

The change correctly accesses the first module URL from the new module_urls vector for implicit import permission handling. The use of .first() with .and_then() gracefully handles empty vectors by returning None.

tests/specs/install/global/config_file_import_map/__test__.jsonc (1)

3-3: LGTM! Test fixture updated to match new argument ordering.

The module URL now appears after all flags, which aligns with the updated CLI parsing logic that supports multiple module URLs.

tests/integration/install_tests.rs (4)

257-258: LGTM! Correctly enforces the breaking change.

The addition of "--" before the script argument aligns with the PR objective of requiring explicit delimiter for script arguments in deno install -g.


297-297: LGTM! Consistent with the breaking change.

The -- delimiter is correctly added before the script argument hello, maintaining consistency with the updated CLI behavior.


433-455: LGTM! Excellent test coverage for multi-module installation.

The test validates that:

  • Multiple modules can be installed in a single command
  • Both binaries are created successfully
  • Success messages appear for each module

This comprehensively covers the new multi-module installation feature.


457-476: LGTM! Excellent migration guidance for users.

This test validates the helpful error message for users migrating from Deno < 3.0, providing clear guidance on:

  • The requirement for -- before script arguments
  • Alternative interpretation as a package with missing prefix

The error message strikes a good balance between addressing the breaking change and suggesting the correct usage.

cli/args/flags.rs (1)

299-305: InstallFlagsGlobal: module_urls vector design looks sound

Switching from a single module_url: String to module_urls: Vec<String> cleanly enables multi-package global installs while keeping args, root, and force semantics unchanged. No structural issues here.

cli/tools/installer/mod.rs (4)

831-876: Validation logic is correct but uses indirect file existence check.

The validation logic at line 831 checks !cli_options.initial_cwd().join(entry_text).exists() to determine whether to provide helpful error messages. When entry_text is a URL (e.g., "http://..."), this check passes because the joined path won't exist locally, which triggers the prefix validation. While this works correctly, the indirection may not be immediately obvious to future maintainers.

The migration error guidance (lines 833-846) and prefix suggestion logic (lines 848-875) provide excellent UX for common mistakes.


912-926: LGTM! Function signature appropriately parameterized.

The signature change to accept module_url: &str makes the function's dependencies explicit and enables the multi-module installation flow. The function correctly creates a single install shim per invocation.


976-987: LGTM! Signature change aligns with multi-module support.

The function signature correctly accepts module_url: &str and uses it at line 987 for URL resolution. This parameterization is consistent with the refactoring approach and maintains clear data flow.


1210-1217: LGTM! Test helpers correctly updated for multi-module API.

The test helper functions appropriately access &install_flags_global.module_urls[0] since they're designed to test single-module installations. All test cases throughout the file (lines 1249, 1285, 1307, 1335, 1363, 1386-1388, 1415, 1446, 1482, 1514, 1547, 1584, 1619, 1648, 1668, 1691, 1721, 1750, 1790, 1835, 1877) have been consistently updated to use module_urls: vec![...], maintaining test coverage.

Also applies to: 1230-1237

Comment thread cli/tools/installer/mod.rs Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
cli/tools/installer/mod.rs (1)

838-908: Multi-URL loop, migration heuristics, and per-URL type-checking look sound; consider reusing the graph container.

The new for (i, module_url) in install_flags_global.module_urls.iter().enumerate() flow correctly:

  • Distinguishes existing local paths, valid jsr:/npm:/URL specs, and likely-misparsed script args (i == 1 && args.is_empty() && Url::parse(entry_text).is_err()), giving a clear 3.0 migration hint instead of a confusing failure.
  • Reuses the same jsr_resolver/npm_resolver and runs load_and_type_check_files plus create_install_shim once per URL, which matches the new “multi install” semantics.

If you expect many packages in one invocation, you could optionally hoist factory.main_module_graph_container().await? out of the loop and call load_and_type_check_files on the reused container to avoid repeated await? overhead, but that’s a micro-optimization and not required for correctness.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 93d372d and b2f1d22.

📒 Files selected for processing (1)
  • cli/tools/installer/mod.rs (25 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
cli/tools/installer/mod.rs (2)
cli/factory.rs (3)
  • npm_resolver (645-648)
  • bin_name_resolver (430-438)
  • resolver (682-685)
cli/tools/pm/mod.rs (1)
  • parse (787-860)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: test debug linux-x86_64
  • GitHub Check: test debug windows-x86_64
  • GitHub Check: test release linux-x86_64
  • GitHub Check: test debug macos-x86_64
  • GitHub Check: test debug linux-aarch64
  • GitHub Check: bench release linux-x86_64
  • GitHub Check: test debug macos-aarch64
  • GitHub Check: lint debug windows-x86_64
  • GitHub Check: build libs
  • GitHub Check: lint debug linux-x86_64
  • GitHub Check: lint debug macos-x86_64
🔇 Additional comments (2)
cli/tools/installer/mod.rs (2)

912-926: Per-URL shim creation looks correct; double-check the invariant around --name + multiple module URLs.

Passing module_url: &str into create_install_shim/resolve_shim_data and then normalizing it with resolve_url_or_path keeps the previous behavior while making per-URL processing explicit. The lockfile logic (NpmPackageReqReference::from_specifier(&module_url)) and final shim args (executable_args.push(module_url.into()) followed by install_flags_global.args) still behave as before, just on a per-entry basis, which is what you want for multi-install.

One subtle invariant: resolve_shim_data still uses install_flags_global.name as a single shared name. If module_urls.len() > 1 and name.is_some(), you’d end up installing multiple URLs to the same shim path (either getting “Existing installation found” or silently overwriting with --force). The AI summary mentions this is enforced at the CLI arg layer; please confirm that:

  • Either InstallFlagsGlobal is constructed such that name.is_some() implies module_urls.len() == 1, or
  • You guard here and bail early with a dedicated error when both name.is_some() and module_urls.len() > 1.

If the CLI already guarantees the first, this is fine as-is; otherwise, adding a local assertion/early error here would make the behavior clearer.

Also applies to: 976-1045, 1117-1142


1200-1886: Tests and helpers correctly migrated to module_urls while preserving single-entry behavior.

The test helpers now pass &install_flags_global.module_urls[0] into create_install_shim/resolve_shim_data, and every InstallFlagsGlobal in this module uses a single-entry module_urls: vec![…]. That keeps all existing tests exercising the same single-URL behavior they covered before, while aligning with the new Vec<String> API. Nothing to change here.

@dsherret dsherret merged commit 051dadf into denoland:main Nov 20, 2025
31 of 37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

deno install -g should require -- before script arguments

3 participants