Skip to content
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
12 changes: 12 additions & 0 deletions packages/zpm/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,12 @@ impl InstallOpResult {
pub struct InstallState {
pub last_installed_at: u128,
pub installed_workspaces: Option<BTreeSet<Ident>>,

/// Whether the installed workspaces were installed without their
/// devDependencies (`yarn workspaces focus --production`); their
/// coverage then only includes production dependencies.
pub production: bool,

pub install_config_hash: Option<Hash64>,
pub content_flags: BTreeMap<Locator, ContentFlags>,
pub resolution_tree: ResolutionTree,
Expand Down Expand Up @@ -937,6 +943,7 @@ impl Default for InstallState {
Self {
last_installed_at: 0,
installed_workspaces: Some(BTreeSet::new()),
production: false,
install_config_hash: None,
content_flags: BTreeMap::new(),
resolution_tree: ResolutionTree::default(),
Expand Down Expand Up @@ -964,6 +971,7 @@ pub struct Install {
pub install_state: InstallState,
pub roots: BTreeSet<Descriptor>,
pub installed_workspaces: Option<BTreeSet<Ident>>,
pub production: bool,
pub resolved_islands: Vec<crate::island::ResolvedIsland>,
pub skip_build: bool,
pub skip_link_step: bool,
Expand Down Expand Up @@ -1154,6 +1162,9 @@ impl Install {
self.install_state.installed_workspaces
= self.installed_workspaces.clone();

self.install_state.production
= self.production;

self.install_state.install_config_hash
= Some(project.install_config_hash());

Expand Down Expand Up @@ -1272,6 +1283,7 @@ impl<'a> InstallManager<'a> {
}

pub fn with_context(mut self, context: InstallContext<'a>) -> Self {
self.result.production = context.prune_dev_dependencies;
self.context = context;
self
}
Expand Down
13 changes: 10 additions & 3 deletions packages/zpm/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1549,9 +1549,16 @@ impl Project {
self.local_cache_path().fs_exists()
};

// After a production install, the user explicitly asked to leave
// devDependencies out; don't undo that by extending the install
let production
= self.install_state.as_ref().is_some_and(|install_state| install_state.production);
let include_dev_dependencies
= !production;

let required_workspaces
= self.try_workspace_by_rel_path(&self.package_cwd)?
.map(|workspace| self.workspace_dependency_closure([workspace.name.clone()], true))
.map(|workspace| self.workspace_dependency_closure([workspace.name.clone()], include_dev_dependencies))
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved
.transpose()?;

if cache_exists {
Expand Down Expand Up @@ -1591,7 +1598,7 @@ impl Project {
.chain(required_workspaces.iter())
.cloned();

Some(self.workspace_dependency_closure(roots, true)?)
Some(self.workspace_dependency_closure(roots, include_dev_dependencies)?)
} else {
None
}
Expand All @@ -1605,7 +1612,7 @@ impl Project {
check_checksums: false,
check_resolutions: false,
enforced_resolutions: BTreeMap::new(),
prune_dev_dependencies: false,
prune_dev_dependencies: production,
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved
refresh_lockfile: false,
silent_or_error: true,
mode: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,39 @@ describe(`Features`, () => {
}),
);

test(
`it should not run install after a production focused install`,
makeTemporaryEnv({}, async ({path, run}) => {
await setupMonorepo(path);

// A devDependency on another workspace: production installs leave it out
await xfs.writeJsonPromise(ppath.join(path, `packages/foo/package.json` as PortablePath), {
name: `foo`,
dependencies: {
[`no-deps`]: `1.0.0`,
},
devDependencies: {
[`bar`]: `workspace:*`,
},
});

await run(`install`);
await run(`workspaces`, `focus`, `foo`, `--production`, {cwd: ppath.join(path, `packages/foo` as PortablePath)});

const installStatePath = ppath.join(path, `.yarn/ignore/install` as PortablePath);
const stateBefore = await xfs.statPromise(installStatePath);

await new Promise(resolve => setTimeout(resolve, 10));

await run(`node`, `-e`, `require('no-deps')`, {
cwd: ppath.join(path, `packages/foo` as PortablePath),
});

const stateAfter = await xfs.statPromise(installStatePath);
expect(stateAfter.mtimeMs).toEqual(stateBefore.mtimeMs);
}),
);

test(
`it should not run install after an immutable install`,
makeTemporaryEnv({}, async ({path, run}) => {
Expand All @@ -223,6 +256,34 @@ describe(`Features`, () => {
}),
);

test(
`it should keep devDependencies out when a production focused install gets extended`,
makeTemporaryEnv({}, async ({path, run}) => {
await setupMonorepo(path);

await xfs.writeJsonPromise(ppath.join(path, `packages/foo/package.json` as PortablePath), {
name: `foo`,
dependencies: {
[`no-deps`]: `1.0.0`,
},
devDependencies: {
[`one-fixed-dep`]: `1.0.0`,
},
});

await run(`install`);
await run(`workspaces`, `focus`, `foo`, `--production`, {cwd: ppath.join(path, `packages/foo` as PortablePath)});

// A command from another workspace extends the focused install
await run(`node`, `-e`, `require('no-deps')`, {
cwd: ppath.join(path, `packages/bar` as PortablePath),
});

const pnp = await xfs.readFilePromise(ppath.join(path, `.pnp.cjs` as PortablePath), `utf8`);
expect(pnp).not.toContain(`one-fixed-dep`);
}),
);

test(
`it should extend a focused install when the active workspace is missing and the lockfile is fresh`,
makeTemporaryEnv({}, async ({path, run, source}) => {
Expand Down
Loading