Skip to content

Commit d642f9c

Browse files
committed
feat(registry): add .npmrc config support for custom registries and auth
- Integrate npmrc-config-rs to resolve registry URLs from .npmrc files - Support scoped package registries (@Company:registry=...) - Add authentication headers (Bearer tokens, Basic auth) for private registries - Fallback to npm.jsr.io for @jsr/* packages when not explicitly configured - Change UpdateUrl to carry package_name instead of full URL Closes #220
1 parent 0ec59b6 commit d642f9c

File tree

8 files changed

+249
-45
lines changed

8 files changed

+249
-45
lines changed

Cargo.lock

Lines changed: 115 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ itertools = "0.14.0"
2727
lazy_static = "1.5.0"
2828
log = "0.4.29"
2929
node-semver = "2.2.0"
30+
npmrc-config-rs = "0.1.1"
3031
regex = { version = "1.12.2", default-features = false, features = ["std"] }
3132
reqwest = { version = "0.12", features = [
3233
"json",

src/dependency.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ use {
2323
#[path = "dependency_test.rs"]
2424
mod dependency_test;
2525

26-
/// URL information for fetching package metadata from npm registry.
26+
/// Information for fetching package metadata from npm registry.
2727
/// Used by the update command to fetch available versions.
2828
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2929
pub struct UpdateUrl {
30-
/// The name of the dependency
30+
/// The name of the dependency as used in package.json
3131
pub internal_name: String,
32-
/// Registry URL, e.g., "https://registry.npmjs.org/react"
33-
pub url: String,
32+
/// The actual npm package name (may differ from internal_name for aliases)
33+
pub package_name: String,
3434
}
3535

3636
/// All instances of a single dependency name within a version group.

src/instance.rs

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -301,15 +301,16 @@ impl Instance {
301301
Specifier::Alias(alias) => {
302302
let aliased_name = &alias.name;
303303
if !aliased_name.is_empty() {
304+
// JSR aliases are always fetched (they have their own namespace)
304305
if aliased_name.starts_with("@jsr/") {
305306
Some(UpdateUrl {
306307
internal_name: internal_name.clone(),
307-
url: format!("https://npm.jsr.io/{aliased_name}"),
308+
package_name: aliased_name.clone(),
308309
})
309310
} else if aliased_name == actual_name {
310311
Some(UpdateUrl {
311312
internal_name: internal_name.clone(),
312-
url: format!("https://registry.npmjs.org/{actual_name}"),
313+
package_name: aliased_name.clone(),
313314
})
314315
} else {
315316
debug!("'{aliased_name}' in '{raw}' does not equal the instance name '{actual_name}', skipping update as this might create mismatches");
@@ -320,17 +321,10 @@ impl Instance {
320321
}
321322
}
322323
Specifier::Exact(_) | Specifier::Range(_) | Specifier::Major(_) | Specifier::Minor(_) | Specifier::Latest(_) => {
323-
if actual_name.starts_with("@jsr/") {
324-
Some(UpdateUrl {
325-
internal_name: internal_name.clone(),
326-
url: format!("https://npm.jsr.io/{actual_name}"),
327-
})
328-
} else {
329-
Some(UpdateUrl {
330-
internal_name: internal_name.clone(),
331-
url: format!("https://registry.npmjs.org/{actual_name}"),
332-
})
333-
}
324+
Some(UpdateUrl {
325+
internal_name: internal_name.clone(),
326+
package_name: actual_name.clone(),
327+
})
334328
}
335329
_ => None,
336330
}

src/instance_test.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,47 +36,58 @@ fn returns_correct_registry_update_url() {
3636
.get_update_url()
3737
};
3838

39+
// Local packages should not have update URLs
3940
assert_eq!(get_update_url_by_name("local-package"), None);
41+
42+
// Direct JSR package
4043
assert_eq!(
4144
get_update_url_by_name("@jsr/luca__cases"),
4245
Some(UpdateUrl {
4346
internal_name: "@jsr/luca__cases".to_string(),
44-
url: "https://npm.jsr.io/@jsr/luca__cases".to_string()
47+
package_name: "@jsr/luca__cases".to_string()
4548
})
4649
);
50+
51+
// npm package with alias (aliased name matches actual name)
4752
assert_eq!(
4853
get_update_url_by_name("@lit-labs/ssr"),
4954
Some(UpdateUrl {
5055
internal_name: "@lit-labs/ssr".to_string(),
51-
url: "https://registry.npmjs.org/@lit-labs/ssr".to_string()
56+
package_name: "@lit-labs/ssr".to_string()
5257
})
5358
);
59+
60+
// Aliased JSR package - uses the JSR aliased name
5461
assert_eq!(
5562
get_update_url_by_name("@luca/cases"),
5663
Some(UpdateUrl {
5764
internal_name: "@luca/cases".to_string(),
58-
url: "https://npm.jsr.io/@jsr/luca__cases".to_string()
65+
package_name: "@jsr/luca__cases".to_string()
5966
})
6067
);
68+
69+
// More aliased JSR packages
6170
assert_eq!(
6271
get_update_url_by_name("@std/fmt"),
6372
Some(UpdateUrl {
6473
internal_name: "@std/fmt".to_string(),
65-
url: "https://npm.jsr.io/@jsr/std__fmt".to_string()
74+
package_name: "@jsr/std__fmt".to_string()
6675
})
6776
);
6877
assert_eq!(
6978
get_update_url_by_name("@std/yaml"),
7079
Some(UpdateUrl {
7180
internal_name: "@std/yaml".to_string(),
72-
url: "https://npm.jsr.io/@jsr/std__yaml".to_string()
81+
package_name: "@jsr/std__yaml".to_string()
7382
})
7483
);
84+
85+
// Regular npm package with alias
7586
assert_eq!(
7687
get_update_url_by_name("lit"),
7788
Some(UpdateUrl {
7889
internal_name: "lit".to_string(),
79-
url: "https://registry.npmjs.org/lit".to_string()
90+
package_name: "lit".to_string()
8091
})
8192
);
8293
}

src/main.rs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,19 @@ async fn main() {
7070
len => debug!("Found {len} package.json files"),
7171
}
7272

73-
let ctx = Context::create(
74-
config,
75-
packages,
76-
if is_update_command {
77-
Some(Arc::new(LiveRegistryClient::new()))
78-
} else {
79-
None
80-
},
81-
catalogs,
82-
);
73+
let registry_client = if is_update_command {
74+
match LiveRegistryClient::new() {
75+
Ok(client) => Some(Arc::new(client) as Arc<dyn crate::registry_client::RegistryClient>),
76+
Err(e) => {
77+
error!("Failed to initialize registry client: {e}");
78+
exit(1);
79+
}
80+
}
81+
} else {
82+
None
83+
};
84+
85+
let ctx = Context::create(config, packages, registry_client, catalogs);
8386

8487
let _exit_code = match ctx.config.cli.subcommand {
8588
Subcommand::Fix => {

0 commit comments

Comments
 (0)