Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(lockfile): only consider package.json beside lockfile in workspace property #22179

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions cli/factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,15 +293,38 @@ impl CliFactory {
}

pub fn maybe_lockfile(&self) -> &Option<Arc<Mutex<Lockfile>>> {
fn check_no_npm(lockfile: &Mutex<Lockfile>, options: &CliOptions) -> bool {
if options.no_npm() {
return true;
}
// Deno doesn't yet understand npm workspaces and the package.json resolution
// may be in a different folder than the deno.json/lockfile. So for now, ignore
// any package.jsons that are in different folders
options
.maybe_package_json()
.as_ref()
.map(|package_json| {
package_json.path.parent() != lockfile.lock().filename.parent()
})
.unwrap_or(false)
}

self.services.lockfile.get_or_init(|| {
let maybe_lockfile = self.options.maybe_lockfile();

// initialize the lockfile with the workspace's configuration
if let Some(lockfile) = &maybe_lockfile {
let package_json_deps = self
.package_json_deps_provider()
.reqs()
.map(|reqs| reqs.into_iter().map(|s| format!("npm:{}", s)).collect())
let no_npm = check_no_npm(lockfile, &self.options);
let package_json_deps = (!no_npm)
.then(|| {
self
.package_json_deps_provider()
.reqs()
.map(|reqs| {
reqs.into_iter().map(|s| format!("npm:{}", s)).collect()
})
.unwrap_or_default()
})
.unwrap_or_default();
let mut lockfile = lockfile.lock();
let config = match self.options.maybe_workspace_config() {
Expand Down Expand Up @@ -352,7 +375,7 @@ impl CliFactory {
};
lockfile.set_workspace_config(
deno_lockfile::SetWorkspaceConfigOptions {
no_npm: self.options.no_npm(),
no_npm,
no_config: self.options.no_config(),
config,
nv_to_jsr_url: |nv| {
Expand Down
154 changes: 139 additions & 15 deletions cli/tests/integration/run_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1042,21 +1042,8 @@ fn lock_deno_json_package_json_deps() {
.run()
.skip_output_check();
let lockfile = temp_dir.join("deno.lock");
// todo(dsherret): it would be nice if the test server didn't produce
// different hashes depending on what operating system it's running on
let esm_basic_integrity = lockfile
.read_json_value()
.get("packages")
.unwrap()
.get("npm")
.unwrap()
.get("@denotest/[email protected]")
.unwrap()
.get("integrity")
.unwrap()
.as_str()
.unwrap()
.to_string();
let esm_basic_integrity =
get_lockfile_npm_package_integrity(&lockfile, "@denotest/[email protected]");
lockfile.assert_matches_json(json!({
"version": "3",
"packages": {
Expand Down Expand Up @@ -1179,6 +1166,143 @@ fn lock_deno_json_package_json_deps() {
}));
}

#[test]
fn lock_deno_json_package_json_deps_workspace() {
let context = TestContextBuilder::new()
.use_temp_cwd()
.use_http_server()
.add_npm_env_vars()
.add_jsr_env_vars()
.build();
let temp_dir = context.temp_dir().path();

// deno.json
let deno_json = temp_dir.join("deno.json");
deno_json.write_json(&json!({}));

// package.json
let package_json = temp_dir.join("package.json");
package_json.write_json(&json!({
"workspaces": ["package-a"],
"dependencies": {
"@denotest/cjs-default-export": "1"
}
}));
// main.ts
let main_ts = temp_dir.join("main.ts");
main_ts.write("import '@denotest/cjs-default-export';");

// package-a/package.json
let a_package = temp_dir.join("package-a");
a_package.create_dir_all();
let a_package_json = a_package.join("package.json");
a_package_json.write_json(&json!({
"dependencies": {
"@denotest/esm-basic": "1"
}
}));
// package-a/main.ts
let main_ts = a_package.join("main.ts");
main_ts.write("import '@denotest/esm-basic';");
context
.new_command()
.args("run package-a/main.ts")
.run()
.skip_output_check();
let lockfile = temp_dir.join("deno.lock");
let esm_basic_integrity =
get_lockfile_npm_package_integrity(&lockfile, "@denotest/[email protected]");

// no "workspace" because deno isn't smart enough to figure this out yet
// since it discovered the package.json in a folder different from the lockfile
lockfile.assert_matches_json(json!({
"version": "3",
"packages": {
"specifiers": {
"npm:@denotest/esm-basic@1": "npm:@denotest/[email protected]"
},
"npm": {
"@denotest/[email protected]": {
"integrity": esm_basic_integrity,
"dependencies": {}
}
}
},
"remote": {},
}));

// run a command that causes discovery of the root package.json beside the lockfile
context
.new_command()
.args("run main.ts")
.run()
.skip_output_check();
// now we should see the dependencies
let cjs_default_export_integrity = get_lockfile_npm_package_integrity(
&lockfile,
"@denotest/[email protected]",
);
let expected_lockfile = json!({
"version": "3",
"packages": {
"specifiers": {
"npm:@denotest/cjs-default-export@1": "npm:@denotest/[email protected]",
"npm:@denotest/esm-basic@1": "npm:@denotest/[email protected]"
},
"npm": {
"@denotest/[email protected]": {
"integrity": cjs_default_export_integrity,
"dependencies": {}
},
"@denotest/[email protected]": {
"integrity": esm_basic_integrity,
"dependencies": {}
}
}
},
"remote": {},
"workspace": {
"packageJson": {
"dependencies": [
"npm:@denotest/cjs-default-export@1"
]
}
}
});
lockfile.assert_matches_json(expected_lockfile.clone());

// now run the command again in the package with the nested package.json
context
.new_command()
.args("run package-a/main.ts")
.run()
.skip_output_check();
// the lockfile should stay the same as the above because the package.json
// was found in a different directory
lockfile.assert_matches_json(expected_lockfile.clone());
Copy link
Member Author

Choose a reason for hiding this comment

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

Previously this would flip flop back and forth between the packages from package.json and packages-a/package.json. Now it just stays being the ones in package.json. In the future, we should get deno to understand npm workspaces beyond just for byonm and then it could include all the members in the lockfile (luckily the lockfile code already has support for this... it's just getting it into the CLI sometime).

}

fn get_lockfile_npm_package_integrity(
lockfile: &PathRef,
package_name: &str,
) -> String {
// todo(dsherret): it would be nice if the test server didn't produce
// different hashes depending on what operating system it's running on
lockfile
.read_json_value()
.get("packages")
.unwrap()
.get("npm")
.unwrap()
.get(package_name)
.unwrap()
.get("integrity")
.unwrap()
.as_str()
.unwrap()
.to_string()
}

itest!(mts_dmts_mjs {
args: "run subdir/import.mts",
output: "run/mts_dmts_mjs.out",
Expand Down
Loading