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

feat(plugin-essentials): support specifying multiple paths in yarn link #4573

Merged
merged 1 commit into from
Jun 24, 2022
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
23 changes: 23 additions & 0 deletions .yarn/versions/21659536.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
releases:
"@yarnpkg/cli": minor
"@yarnpkg/plugin-essentials": minor

declined:
- "@yarnpkg/plugin-compat"
- "@yarnpkg/plugin-constraints"
- "@yarnpkg/plugin-dlx"
- "@yarnpkg/plugin-init"
- "@yarnpkg/plugin-interactive-tools"
- "@yarnpkg/plugin-nm"
- "@yarnpkg/plugin-npm-cli"
- "@yarnpkg/plugin-pack"
- "@yarnpkg/plugin-patch"
- "@yarnpkg/plugin-pnp"
- "@yarnpkg/plugin-pnpm"
- "@yarnpkg/plugin-stage"
- "@yarnpkg/plugin-typescript"
- "@yarnpkg/plugin-version"
- "@yarnpkg/plugin-workspace-tools"
- "@yarnpkg/builder"
- "@yarnpkg/core"
- "@yarnpkg/doctor"
Original file line number Diff line number Diff line change
Expand Up @@ -205,5 +205,41 @@ describe(`Commands`, () => {
},
),
);

test(
`it should allow linking multiple workspaces`,
makeTemporaryEnv({}, async ({path, run, source}) => {
const tmp = await createTemporaryFolder();

await writeJson(`${tmp}/my-workspace/package.json`, {
private: true,
workspaces: [`packages/*`],
});

await writeJson(`${tmp}/my-workspace/packages/workspace-a/package.json`, {
name: `workspace-a`,
});

await writeJson(`${tmp}/my-workspace/packages/workspace-b/package.json`, {
name: `workspace-b`,
});

await writeJson(`${tmp}/my-workspace/packages/workspace-c/package.json`, {
name: `workspace-c`,
});

await run(`link`, `${tmp}/my-workspace/packages/workspace-b`, `${tmp}/my-workspace/packages/workspace-c`);

const manifest = await readJson(`${path}/package.json`);

expect(manifest.resolutions).not.toHaveProperty(`workspace-a`);
await expect(manifest).toMatchObject({
resolutions: {
[`workspace-b`]: `portal:${npath.toPortablePath(`${tmp}/my-workspace/packages/workspace-b`)}`,
[`workspace-c`]: `portal:${npath.toPortablePath(`${tmp}/my-workspace/packages/workspace-c`)}`,
},
});
}),
);
});
});
60 changes: 33 additions & 27 deletions packages/plugin-essentials/sources/commands/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,27 @@ export default class LinkCommand extends BaseCommand {
This command will set a new \`resolutions\` field in the project-level manifest and point it to the workspace at the specified location (even if part of another project).
`,
examples: [[
`Register a remote workspace for use in the current project`,
`$0 link ~/ts-loader`,
`Register one or more remote workspaces for use in the current project`,
`$0 link ~/ts-loader ~/jest`,
], [
`Register all workspaces from a remote project for use in the current project`,
`$0 link ~/jest --all`,
]],
});

all = Option.Boolean(`-A,--all`, false, {
description: `Link all workspaces belonging to the target project to the current one`,
description: `Link all workspaces belonging to the target projects to the current one`,
});

private = Option.Boolean(`-p,--private`, false, {
description: `Also link private workspaces belonging to the target project to the current one`,
description: `Also link private workspaces belonging to the target projects to the current one`,
});

relative = Option.Boolean(`-r,--relative`, false, {
description: `Link workspaces using relative paths instead of absolute paths`,
});

destination = Option.String();
destinations = Option.Rest();

async execute() {
const configuration = await Configuration.find(this.context.cwd, this.context.plugins);
Expand All @@ -49,36 +49,42 @@ export default class LinkCommand extends BaseCommand {
restoreResolutions: false,
});

const absoluteDestination = ppath.resolve(this.context.cwd, npath.toPortablePath(this.destination));
const topLevelWorkspace = project.topLevelWorkspace;
const linkedWorkspaces = [];

const configuration2 = await Configuration.find(absoluteDestination, this.context.plugins, {useRc: false, strict: false});
const {project: project2, workspace: workspace2} = await Project.find(configuration2, absoluteDestination);
for (const destination of this.destinations) {
const absoluteDestination = ppath.resolve(this.context.cwd, npath.toPortablePath(destination));

if (project.cwd === project2.cwd)
throw new UsageError(`Invalid destination; Can't link the project to itself`);
const configuration2 = await Configuration.find(absoluteDestination, this.context.plugins, {useRc: false, strict: false});
const {project: project2, workspace: workspace2} = await Project.find(configuration2, absoluteDestination);

if (!workspace2)
throw new WorkspaceRequiredError(project2.cwd, absoluteDestination);
if (project.cwd === project2.cwd)
throw new UsageError(`Invalid destination '${destination}'; Can't link the project to itself`);

const topLevelWorkspace = project.topLevelWorkspace;
const linkedWorkspaces = [];
if (!workspace2)
throw new WorkspaceRequiredError(project2.cwd, absoluteDestination);

if (this.all) {
for (const workspace of project2.workspaces)
if (workspace.manifest.name && (!workspace.manifest.private || this.private))
linkedWorkspaces.push(workspace);
if (this.all) {
let found = false;
for (const workspace of project2.workspaces) {
if (workspace.manifest.name && (!workspace.manifest.private || this.private)) {
linkedWorkspaces.push(workspace);
found = true;
}
}

if (linkedWorkspaces.length === 0) {
throw new UsageError(`No workspace found to be linked in the target project`);
}
} else {
if (!workspace2.manifest.name)
throw new UsageError(`The target workspace doesn't have a name and thus cannot be linked`);
if (!found) {
throw new UsageError(`No workspace found to be linked in the target project: ${destination}`);
}
} else {
if (!workspace2.manifest.name)
throw new UsageError(`The target workspace at '${destination}' doesn't have a name and thus cannot be linked`);

if (workspace2.manifest.private && !this.private)
throw new UsageError(`The target workspace is marked private - use the --private flag to link it anyway`);
if (workspace2.manifest.private && !this.private)
throw new UsageError(`The target workspace at '${destination}' is marked private - use the --private flag to link it anyway`);

linkedWorkspaces.push(workspace2);
linkedWorkspaces.push(workspace2);
}
}

for (const workspace of linkedWorkspaces) {
Expand Down