mirror of
https://github.com/denoland/deno.git
synced 2024-11-24 15:19:26 -05:00
fix(install): store tags associated with package in node_modules dir (#26000)
Fixes #25998. Fixes https://github.com/denoland/deno/issues/25928. Originally I was just going to make this an error message instead of a panic, but once I got to a minimal repro I felt that this really should work. The panic occurs when you have `nodeModulesDir: manual` (or a package.json present), and you have an npm package with a tag in your deno.json (see the spec test that illustrates this). This code path only actually executes when trying to choose an appropriate package version from `node_modules/.deno`, so we should be able to fix it by storing some extra data at install time. The fix proposed here is to repurpose the `.initialized` file that we store in `node_modules` to store the tags associated with a package. Basically, if you have a version requirement with a tag (e.g. `npm:chalk@latest`), when we set up the node_modules folder for that package, we store the tag (`latest`) in `.initialized`. Then, when doing BYONM resolution, if we have a version requirement with a tag, we read that file and check if the tag is present. The downside is that we do more work when setting up `node_modules`. We _could_ do this only when BYONM is enabled, but that would have the downside of needing to re-run `deno install` when you switch from auto -> manual, though maybe that's not a big deal.
This commit is contained in:
parent
1e0c9b8c5b
commit
275418473e
8 changed files with 109 additions and 3 deletions
|
@ -343,6 +343,14 @@ async fn sync_resolution_with_fs(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
let packages_with_deprecation_warnings = Arc::new(Mutex::new(Vec::new()));
|
let packages_with_deprecation_warnings = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
|
||||||
|
let mut package_tags: HashMap<&PackageNv, Vec<&str>> = HashMap::new();
|
||||||
|
for (package_req, package_nv) in snapshot.package_reqs() {
|
||||||
|
if let Some(tag) = package_req.version_req.tag() {
|
||||||
|
package_tags.entry(package_nv).or_default().push(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for package in &package_partitions.packages {
|
for package in &package_partitions.packages {
|
||||||
if let Some(current_pkg) =
|
if let Some(current_pkg) =
|
||||||
newest_packages_by_name.get_mut(&package.id.nv.name)
|
newest_packages_by_name.get_mut(&package.id.nv.name)
|
||||||
|
@ -357,11 +365,29 @@ async fn sync_resolution_with_fs(
|
||||||
let package_folder_name =
|
let package_folder_name =
|
||||||
get_package_folder_id_folder_name(&package.get_package_cache_folder_id());
|
get_package_folder_id_folder_name(&package.get_package_cache_folder_id());
|
||||||
let folder_path = deno_local_registry_dir.join(&package_folder_name);
|
let folder_path = deno_local_registry_dir.join(&package_folder_name);
|
||||||
|
let tags = package_tags
|
||||||
|
.get(&package.id.nv)
|
||||||
|
.map(|tags| tags.join(","))
|
||||||
|
.unwrap_or_default();
|
||||||
|
enum PackageFolderState {
|
||||||
|
UpToDate,
|
||||||
|
Uninitialized,
|
||||||
|
TagsOutdated,
|
||||||
|
}
|
||||||
let initialized_file = folder_path.join(".initialized");
|
let initialized_file = folder_path.join(".initialized");
|
||||||
|
let package_state = std::fs::read_to_string(&initialized_file)
|
||||||
|
.map(|s| {
|
||||||
|
if s != tags {
|
||||||
|
PackageFolderState::TagsOutdated
|
||||||
|
} else {
|
||||||
|
PackageFolderState::UpToDate
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(PackageFolderState::Uninitialized);
|
||||||
if !cache
|
if !cache
|
||||||
.cache_setting()
|
.cache_setting()
|
||||||
.should_use_for_npm_package(&package.id.nv.name)
|
.should_use_for_npm_package(&package.id.nv.name)
|
||||||
|| !initialized_file.exists()
|
|| matches!(package_state, PackageFolderState::Uninitialized)
|
||||||
{
|
{
|
||||||
// cache bust the dep from the dep setup cache so the symlinks
|
// cache bust the dep from the dep setup cache so the symlinks
|
||||||
// are forced to be recreated
|
// are forced to be recreated
|
||||||
|
@ -371,6 +397,7 @@ async fn sync_resolution_with_fs(
|
||||||
let bin_entries_to_setup = bin_entries.clone();
|
let bin_entries_to_setup = bin_entries.clone();
|
||||||
let packages_with_deprecation_warnings =
|
let packages_with_deprecation_warnings =
|
||||||
packages_with_deprecation_warnings.clone();
|
packages_with_deprecation_warnings.clone();
|
||||||
|
|
||||||
cache_futures.push(async move {
|
cache_futures.push(async move {
|
||||||
tarball_cache
|
tarball_cache
|
||||||
.ensure_package(&package.id.nv, &package.dist)
|
.ensure_package(&package.id.nv, &package.dist)
|
||||||
|
@ -389,7 +416,7 @@ async fn sync_resolution_with_fs(
|
||||||
move || {
|
move || {
|
||||||
clone_dir_recursive(&cache_folder, &package_path)?;
|
clone_dir_recursive(&cache_folder, &package_path)?;
|
||||||
// write out a file that indicates this folder has been initialized
|
// write out a file that indicates this folder has been initialized
|
||||||
fs::write(initialized_file, "")?;
|
fs::write(initialized_file, tags)?;
|
||||||
|
|
||||||
Ok::<_, AnyError>(())
|
Ok::<_, AnyError>(())
|
||||||
}
|
}
|
||||||
|
@ -410,6 +437,8 @@ async fn sync_resolution_with_fs(
|
||||||
drop(pb_guard); // explicit for clarity
|
drop(pb_guard); // explicit for clarity
|
||||||
Ok::<_, AnyError>(())
|
Ok::<_, AnyError>(())
|
||||||
});
|
});
|
||||||
|
} else if matches!(package_state, PackageFolderState::TagsOutdated) {
|
||||||
|
fs::write(initialized_file, tags)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let sub_node_modules = folder_path.join("node_modules");
|
let sub_node_modules = folder_path.join("node_modules");
|
||||||
|
|
|
@ -253,7 +253,24 @@ impl<Fs: DenoResolverFs> ByonmNpmResolver<Fs> {
|
||||||
let Ok(version) = Version::parse_from_npm(version) else {
|
let Ok(version) = Version::parse_from_npm(version) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if req.version_req.matches(&version) {
|
if let Some(tag) = req.version_req.tag() {
|
||||||
|
let initialized_file =
|
||||||
|
node_modules_deno_dir.join(&entry.name).join(".initialized");
|
||||||
|
let Ok(contents) = self.fs.read_to_string_lossy(&initialized_file)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let mut tags = contents.split(',').map(str::trim);
|
||||||
|
if tags.any(|t| t == tag) {
|
||||||
|
if let Some((best_version_version, _)) = &best_version {
|
||||||
|
if version > *best_version_version {
|
||||||
|
best_version = Some((version, entry.name));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
best_version = Some((version, entry.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if req.version_req.matches(&version) {
|
||||||
if let Some((best_version_version, _)) = &best_version {
|
if let Some((best_version_version, _)) = &best_version {
|
||||||
if version > *best_version_version {
|
if version > *best_version_version {
|
||||||
best_version = Some((version, entry.name));
|
best_version = Some((version, entry.name));
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"tempDir": true,
|
||||||
|
|
||||||
|
"tests": {
|
||||||
|
"tag_with_byonm": {
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"args": "install",
|
||||||
|
"output": "[WILDCARD]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"args": "run -A main.ts",
|
||||||
|
"output": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"no_tag_then_tag": {
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"args": "run -A replace-version-req.ts 1.0.0",
|
||||||
|
"output": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"args": "install",
|
||||||
|
"output": "[WILDCARD]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"args": "run -A replace-version-req.ts latest",
|
||||||
|
"output": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"args": "run -A main.ts",
|
||||||
|
"output": "node_modules_out_of_date.out",
|
||||||
|
"exitCode": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"args": "install",
|
||||||
|
"output": "[WILDCARD]"
|
||||||
|
},
|
||||||
|
{ "args": "run -A main.ts", "output": "" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"@denotest/esm-basic": "npm:@denotest/esm-basic@latest"
|
||||||
|
}
|
||||||
|
}
|
1
tests/specs/install/byonm_run_tag_after_install/main.ts
Normal file
1
tests/specs/install/byonm_run_tag_after_install/main.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
import { add } from "@denotest/esm-basic";
|
|
@ -0,0 +1,2 @@
|
||||||
|
error: Could not find a matching package for 'npm:@denotest/esm-basic@latest' in the node_modules directory. Ensure you have all your JSR and npm dependencies listed in your deno.json or package.json, then run `deno install`. Alternatively, turn on auto-install by specifying `"nodeModulesDir": "auto"` in your deno.json file.
|
||||||
|
at [WILDCARD]main.ts:1:21
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -0,0 +1,7 @@
|
||||||
|
const newReq = Deno.args[0]?.trim();
|
||||||
|
if (!newReq) {
|
||||||
|
throw new Error("Missing required argument");
|
||||||
|
}
|
||||||
|
const config = JSON.parse(Deno.readTextFileSync("deno.json"));
|
||||||
|
config.imports["@denotest/esm-basic"] = `npm:@denotest/esm-basic@${newReq}`;
|
||||||
|
Deno.writeTextFileSync("deno.json", JSON.stringify(config));
|
Loading…
Reference in a new issue