Skip to content

Commit 0a8ade2

Browse files
authored
feat(add/install): default to npm registry for unprefixed packages (#33246)
Reland of #33156, which was reverted in #33214 to be relanded for Deno v2.8. --- Before: ``` $ deno install npm:express npm:react $ deno install --npm express react $ deno install jsr:@std/async ``` After: ``` $ deno install express react jsr:@std/async ``` Makes `deno add express` and `deno install express` work without requiring the `npm:` prefix or `--npm` flag. Since npm is by far the most common registry for package installation, this removes friction for the most common use case. The `--jsr` flag is still available to override the default to jsr, and explicit `jsr:` / `npm:` prefixes always take precedence.
1 parent 7d75e0c commit 0a8ade2

12 files changed

Lines changed: 187 additions & 22 deletions

File tree

cli/args/flags.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2300,13 +2300,13 @@ fn add_subcommand() -> Command {
23002300
"add",
23012301
cstr!(
23022302
"Add dependencies to your configuration file.
2303-
<p(245)>deno add jsr:@std/path</>
2303+
<p(245)>deno add express</>
23042304
2305-
You can also add npm packages:
2306-
<p(245)>deno add npm:react</>
2305+
Unprefixed packages default to npm. Use jsr: prefix for jsr packages:
2306+
<p(245)>deno add jsr:@std/path</>
23072307
23082308
Or multiple dependencies at once:
2309-
<p(245)>deno add jsr:@std/path jsr:@std/assert npm:chalk</>"
2309+
<p(245)>deno add express jsr:@std/path</>"
23102310
),
23112311
UnstableArgsConfig::None,
23122312
)
@@ -2466,7 +2466,7 @@ fn default_registry_args() -> [Arg; 2] {
24662466
[
24672467
Arg::new("npm")
24682468
.long("npm")
2469-
.help("assume unprefixed package names are npm packages")
2469+
.help("assume unprefixed package names are npm packages (default)")
24702470
.action(ArgAction::SetTrue)
24712471
.conflicts_with("jsr"),
24722472
Arg::new("jsr")
@@ -3595,8 +3595,8 @@ in the package cache. If no dependency is specified, installs all dependencies l
35953595
If the <p(245)>--entrypoint</> flag is passed, installs the dependencies of the specified entrypoint(s).
35963596
35973597
<p(245)>deno install</>
3598+
<p(245)>deno install express</>
35983599
<p(245)>deno install jsr:@std/bytes</>
3599-
<p(245)>deno install npm:chalk</>
36003600
<p(245)>deno install --entrypoint entry1.ts entry2.ts</>
36013601
36023602
<g>Global installation</>
@@ -6288,7 +6288,9 @@ fn add_parse_inner(
62886288
} else if matches.get_flag("jsr") {
62896289
Some(DefaultRegistry::Jsr)
62906290
} else {
6291-
None
6291+
// Default to npm when no --npm or --jsr flag is provided.
6292+
// This allows `deno add express` to work without requiring `npm:` prefix.
6293+
Some(DefaultRegistry::Npm)
62926294
};
62936295
AddFlags {
62946296
packages,
@@ -14427,7 +14429,7 @@ mod tests {
1442714429
mk_flags(AddFlags {
1442814430
packages: svec!["@david/which"],
1442914431
dev: false, // default is false
14430-
default_registry: None,
14432+
default_registry: Some(DefaultRegistry::Npm),
1443114433
lockfile_only: false,
1443214434
save_exact: false,
1443314435
})
@@ -14445,7 +14447,7 @@ mod tests {
1444514447
let mut expected_flags = mk_flags(AddFlags {
1444614448
packages: svec!["@david/which", "@luca/hello"],
1444714449
dev: false,
14448-
default_registry: None,
14450+
default_registry: Some(DefaultRegistry::Npm),
1444914451
lockfile_only: true,
1445014452
save_exact: false,
1445114453
});
@@ -14459,7 +14461,7 @@ mod tests {
1445914461
mk_flags(AddFlags {
1446014462
packages: svec!["npm:chalk"],
1446114463
dev: true,
14462-
default_registry: None,
14464+
default_registry: Some(DefaultRegistry::Npm),
1446314465
lockfile_only: false,
1446414466
save_exact: false,
1446514467
}),

cli/tools/pm/mod.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,9 @@ pub async fn add(
482482

483483
match req {
484484
Ok(add_req) => package_reqs.push(add_req),
485+
// Currently unreachable: default_registry is always Some (defaults to Npm),
486+
// so parse() always resolves a prefix. Kept as a safety fallback in case
487+
// the API is called with None from elsewhere.
485488
Err(package_req) => {
486489
if jsr_resolver
487490
.req_to_nv(&package_req)
@@ -848,7 +851,6 @@ impl AddRmPackageReq {
848851
}
849852

850853
let (maybe_prefix, entry_text) = parse_prefix(entry_text);
851-
let maybe_prefix = maybe_prefix.or(default_prefix);
852854
let (prefix, maybe_alias, entry_text) = match maybe_prefix {
853855
Some(prefix) => (prefix, None, entry_text),
854856
None => match parse_alias(entry_text) {
@@ -865,7 +867,10 @@ impl AddRmPackageReq {
865867
entry_text,
866868
)
867869
}
868-
None => return Ok(Err(PackageReq::from_str(entry_text)?)),
870+
None => match default_prefix {
871+
Some(prefix) => (prefix, None, entry_text),
872+
None => return Ok(Err(PackageReq::from_str(entry_text)?)),
873+
},
869874
},
870875
};
871876

@@ -1093,6 +1098,24 @@ mod test {
10931098
(("jsr:foo", Some(Prefix::Jsr)), jsr_pkg_req("foo", "foo")),
10941099
(("npm:foo", Some(Prefix::Jsr)), npm_pkg_req("foo", "foo@*")),
10951100
(("jsr:foo", Some(Prefix::Npm)), jsr_pkg_req("foo", "foo")),
1101+
// Alias with explicit prefix still works when default is set
1102+
(
1103+
("my-alias@npm:foo", Some(Prefix::Npm)),
1104+
npm_pkg_req("my-alias", "foo@*"),
1105+
),
1106+
(
1107+
("my-alias@jsr:foo", Some(Prefix::Npm)),
1108+
jsr_pkg_req("my-alias", "foo"),
1109+
),
1110+
// Unprefixed without alias defaults to npm
1111+
(
1112+
("chalk", Some(Prefix::Npm)),
1113+
npm_pkg_req("chalk", "chalk@*"),
1114+
),
1115+
(
1116+
("@scope/pkg", Some(Prefix::Npm)),
1117+
npm_pkg_req("@scope/pkg", "@scope/pkg@*"),
1118+
),
10961119
];
10971120

10981121
for ((input, maybe_prefix), expected) in cases {
Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,33 @@
11
{
22
"tempDir": true,
3-
"steps": [
4-
{
5-
"args": "add ajv",
6-
"output": "error: ajv is missing a prefix. Did you mean `deno add npm:ajv`?\n",
7-
"exitCode": 1
3+
"tests": {
4+
// Unprefixed packages default to npm, so `deno add ajv` should work
5+
"npm_default": {
6+
"steps": [
7+
{
8+
"args": "add ajv",
9+
"output": "Add npm:ajv@8.11.0\n[WILDCARD]\n"
10+
}
11+
]
812
},
9-
{
10-
"args": "add @std/testing",
11-
"output": "error: @std/testing is missing a prefix. Did you mean `deno add jsr:@std/testing`?\n",
12-
"exitCode": 1
13+
// Packages that only exist on jsr should suggest using jsr: prefix
14+
"jsr_needs_prefix": {
15+
"steps": [
16+
{
17+
"args": "add @denotest/echo",
18+
"output": "error: npm:@denotest/echo was not found, but a matching jsr package exists. Did you mean `deno add jsr:@denotest/echo`?\n",
19+
"exitCode": 1
20+
}
21+
]
22+
},
23+
// Using --jsr flag should still work for jsr packages
24+
"jsr_flag_override": {
25+
"steps": [
26+
{
27+
"args": "add --jsr @denotest/add",
28+
"output": "Add jsr:@denotest/add@1.0.0\n[WILDCARD]\n"
29+
}
30+
]
1331
}
14-
]
32+
}
1533
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
{
2+
"tempDir": true,
3+
"tests": {
4+
// Unprefixed package name should resolve as npm by default
5+
"add_unprefixed": {
6+
"args": "add @denotest/esm-basic",
7+
"output": "add_unprefixed.out"
8+
},
9+
// Mixing unprefixed (npm default) with explicit jsr: prefix
10+
"add_mixed_with_jsr": {
11+
"args": "add @denotest/esm-basic jsr:@denotest/add",
12+
"output": "add_mixed.out"
13+
},
14+
// install subcommand should also default to npm
15+
"install_unprefixed": {
16+
"args": "install @denotest/esm-basic",
17+
"output": "add_unprefixed.out"
18+
},
19+
// Explicit npm: prefix still works (backward compat)
20+
"add_explicit_npm_prefix": {
21+
"args": "add npm:@denotest/esm-basic",
22+
"output": "add_unprefixed.out"
23+
},
24+
// --jsr flag overrides default to jsr
25+
"add_jsr_flag": {
26+
"args": "add --jsr @denotest/add",
27+
"output": "add_jsr_flag.out"
28+
},
29+
// --npm flag is now a no-op (same as default) but should still work
30+
"add_npm_flag_noop": {
31+
"args": "add --npm @denotest/esm-basic",
32+
"output": "add_unprefixed.out"
33+
},
34+
// Alias with explicit npm: prefix still works when default is npm
35+
"add_alias_with_prefix": {
36+
"steps": [
37+
{
38+
"args": "add my-alias@npm:@denotest/esm-basic",
39+
"output": "Add npm:@denotest/esm-basic@1.0.0\n[WILDCARD]\n"
40+
},
41+
{
42+
"args": [
43+
"eval",
44+
"console.log(JSON.stringify(JSON.parse(Deno.readTextFileSync('deno.json')).imports))"
45+
],
46+
"output": "{\"my-alias\":\"npm:@denotest/esm-basic@^1.0.0\"}\n"
47+
}
48+
]
49+
},
50+
// Alias with jsr: prefix works even when default is npm
51+
"add_alias_jsr_prefix": {
52+
"steps": [
53+
{
54+
"args": "add my-jsr@jsr:@denotest/add",
55+
"output": "Add jsr:@denotest/add@1.0.0\n[WILDCARD]\n"
56+
},
57+
{
58+
"args": [
59+
"eval",
60+
"console.log(JSON.stringify(JSON.parse(Deno.readTextFileSync('deno.json')).imports))"
61+
],
62+
"output": "{\"my-jsr\":\"jsr:@denotest/add@^1.0.0\"}\n"
63+
}
64+
]
65+
}
66+
}
67+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add jsr:@denotest/add@1.0.0
2+
Download http://127.0.0.1:4250/@denotest/add/1.0.0/mod.ts
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Add npm:@denotest/esm-basic@1.0.0
2+
Add jsr:@denotest/add@1.0.0
3+
[UNORDERED_START]
4+
Download http://localhost:4260/@denotest%2fesm-basic
5+
Download http://127.0.0.1:4250/@denotest/add/1.0.0/mod.ts
6+
Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz
7+
[UNORDERED_END]
8+
9+
Dependencies:
10+
+ npm:@denotest/esm-basic 1.0.0
11+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Add npm:@denotest/esm-basic@1.0.0
2+
[UNORDERED_START]
3+
Download http://localhost:4260/@denotest%2fesm-basic
4+
Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz
5+
[UNORDERED_END]
6+
7+
Dependencies:
8+
+ npm:@denotest/esm-basic 1.0.0
9+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"tempDir": true,
3+
"tests": {
4+
// Unprefixed package name should resolve as npm by default
5+
"install_unprefixed": {
6+
"args": "install @denotest/esm-basic",
7+
"output": "install_unprefixed.out"
8+
},
9+
// Explicit npm: prefix still works (backward compat)
10+
"install_explicit_npm_prefix": {
11+
"args": "install npm:@denotest/esm-basic",
12+
"output": "install_unprefixed.out"
13+
},
14+
// --jsr flag overrides default to jsr
15+
"install_jsr_flag": {
16+
"args": "install --jsr @denotest/add",
17+
"output": "install_jsr_flag.out"
18+
}
19+
}
20+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

0 commit comments

Comments
 (0)