FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Load commands lazily by Andarist · Pull Request #2002 · changesets/changesets · GitHub

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

Filter by extension

Filter by extension .md  (1) .ts  (4) All 2 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
5 changes: 5 additions & 0 deletions .changeset/few-badgers-sing.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@changesets/cli": patch
---

Lazy-load CLI commands so `changeset` only loads the code needed for the command being run.
124 changes: 119 additions & 5 deletions packages/cli/src/commands/version/index.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import { fileURLToPath } from "node:url";
import { applyReleasePlan } from "@changesets/apply-release-plan";
import { assembleReleasePlan } from "@changesets/assemble-release-plan";
import { ExitError } from "@changesets/errors";
import { getDependentsGraph } from "@changesets/get-dependents-graph";
import * as git from "@changesets/git";
import { readPreState } from "@changesets/pre";
import { readChangesets } from "@changesets/read";
import type { Config } from "@changesets/types";
import { shouldSkipPackage } from "@changesets/should-skip-package";
import type { Config, Packages } from "@changesets/types";
import { log } from "@clack/prompts";
import { getPackages } from "@manypkg/get-packages";
import pc from "picocolors";
Expand All @@ -15,14 +17,48 @@ import { importantWarning } from "../../utils/cli-utilities.ts";

export async function version(
cwd: string,
options: { snapshot?: string | boolean },
options: {
ignore?: string[];
snapshot?: string | boolean;
snapshotPrereleaseTemplate?: string;
},
config: Config,
) {
const messages: string[] = [];
let ignore: readonly string[] | undefined;

if (options.ignore != null) {
if (config.ignore.length > 0) {
messages.push(
"It looks like you are trying to use the `--ignore` option while ignore is defined in the config file. This is currently not allowed, you can only use one of them at a time.",
);
} else {
ignore = options.ignore;
}
}
const releaseConfig = {
...config,
ignore: ignore ?? config.ignore,
snapshot: {
...config.snapshot,
prereleaseTemplate:
options.snapshotPrereleaseTemplate ??
config.snapshot.prereleaseTemplate,
},
// Disable committing when in snapshot mode
commit: options.snapshot ? false : config.commit,
};

const packages = await getPackages(cwd);

validateIgnoredPackageNames(packages, options.ignore, messages);
validateSkippedDependents(packages, releaseConfig, messages);

if (messages.length > 0) {
log.error(messages.join("\n"));
throw new ExitError(1);
}

const [changesets, preState] = await Promise.all([
readChangesets(cwd),
readPreState(cwd),
Expand Down Expand Up @@ -56,8 +92,6 @@ You can then run ${pc.cyan("changeset version")} again to do a normal release.
throw new ExitError(1);
}

const packages = await getPackages(cwd);

const releasePlan = assembleReleasePlan(
changesets,
packages,
Expand All @@ -66,7 +100,9 @@ You can then run ${pc.cyan("changeset version")} again to do a normal release.
options.snapshot
? {
tag: options.snapshot === true ? undefined : options.snapshot,
commit: config.snapshot.prereleaseTemplate?.includes("{commit}")
commit: releaseConfig.snapshot.prereleaseTemplate?.includes(
"{commit}",
)
? await git.getCurrentCommitId({ cwd })
: undefined,
}
Expand Down Expand Up @@ -114,3 +150,81 @@ You can then run ${pc.cyan("changeset version")} again to do a normal release.
);
}
}

function validateIgnoredPackageNames(
packages: Packages,
ignoreFromCli: string[] | undefined,
messages: string[],
) {
if (!ignoreFromCli) {
return;
}
const pkgNames = new Set(
packages.packages.map(({ packageJson }) => packageJson.name),
);

for (const pkgName of ignoreFromCli) {
if (pkgNames.has(pkgName)) {
continue;
}

messages.push(
`The package ${pc.blue(pkgName)} is passed to the \`--ignore\` option but it is not found in the project. You may have misspelled the package name.`,
);
}
}

function validateSkippedDependents(
packages: Packages,
config: Config,
messages: string[],
) {
const packagesByName = new Map(
packages.packages.map((pkg) => [pkg.packageJson.name, pkg]),
);

// devDependencies are excluded because they don't affect published consumers —
// a stale devDep range on a skipped package is harmless.
// Note: assemble-release-plan uses a graph WITH devDeps because it needs to
// update devDep ranges in package.json even though they don't cause version bumps.
const dependentsGraph = getDependentsGraph(packages, {
ignoreDevDependencies: true,
bumpVersionsWithWorkspaceProtocolOnly:
config.bumpVersionsWithWorkspaceProtocolOnly,
});

for (const pkg of packages.packages) {
if (
!shouldSkipPackage(pkg, {
ignore: config.ignore,
allowPrivatePackages: config.privatePackages.version,
})
) {
continue;
}

const skippedPackage = pkg.packageJson.name;
const dependents = dependentsGraph.get(skippedPackage) || [];
for (const dependent of dependents) {
const dependentPkg = packagesByName.get(dependent)!;
if (dependentPkg.packageJson.private) {
// Private packages don't publish to npm,
// so they can safely depend on skipped packages.
// This also holds for private packages with other publish targets (like a VS Code extension)
// as those typically have to prebundle dependencies.
continue;
}

if (
!shouldSkipPackage(dependentPkg, {
ignore: config.ignore,
allowPrivatePackages: config.privatePackages.version,
})
) {
messages.push(
`The package ${pc.blue(dependent)} depends on the skipped package ${pc.blue(skippedPackage)} (either by \`ignore\` option or by \`privatePackages.version\`), but ${pc.blue(dependent)} is not being skipped. Please pass ${pc.blue(dependent)} to the ${pc.cyan("--ignore")} flag.`,
);
}
}
}
}
99 changes: 99 additions & 0 deletions packages/cli/src/commands/version/version.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import fs from "node:fs/promises";
import path from "node:path";
import { stripVTControlCharacters } from "node:util";
import { defaultConfig } from "@changesets/config";
import { ExitError } from "@changesets/errors";
import * as git from "@changesets/git";
Expand Down Expand Up @@ -103,6 +104,104 @@ describe("running version in a simple project", () => {
});
});

it("should validate package name passed in from --ignore flag", async () => {
const cwd = await testdir({
"package.json": JSON.stringify({
private: true,
workspaces: ["packages/*"],
}),
"package-lock.json": "",
"packages/pkg-a/package.json": JSON.stringify({
name: "pkg-a",
version: "1.0.0",
}),
});

await expect(
version(
cwd,
{ ...defaultOptions, ignore: ["pkg-c"] },
modifiedDefaultConfig,
),
).rejects.toThrow(ExitError);

expect(mockedLogger.error).toHaveBeenCalledOnce();
const arg = mockedLogger.error.mock.calls[0][0];
expect(stripVTControlCharacters(arg)).toEqual(
`The package pkg-c is passed to the \`--ignore\` option but it is not found in the project. You may have misspelled the package name.`,
);
});

it("should throw if dependents of ignored packages are not explicitly listed in the ignore array", async () => {
const cwd = await testdir({
"package.json": JSON.stringify({
private: true,
workspaces: ["packages/*"],
}),
"package-lock.json": "",
"packages/pkg-a/package.json": JSON.stringify({
name: "pkg-a",
version: "1.0.0",
dependencies: {
"pkg-b": "1.0.0",
},
}),
"packages/pkg-b/package.json": JSON.stringify({
name: "pkg-b",
version: "1.0.0",
}),
});

await expect(
version(
cwd,
{ ...defaultOptions, ignore: ["pkg-b"] },
modifiedDefaultConfig,
),
).rejects.toThrow(ExitError);

expect(mockedLogger.error).toHaveBeenCalledOnce();
const arg = mockedLogger.error.mock.calls[0][0];
expect(stripVTControlCharacters(arg)).toEqual(
`The package pkg-a depends on the skipped package pkg-b (either by \`ignore\` option or by \`privatePackages.version\`), but pkg-a is not being skipped. Please pass pkg-a to the --ignore flag.`,
);
});

it("should throw if `--ignore` flag is used while ignore array is also defined in the config file", async () => {
const cwd = await testdir({
"package.json": JSON.stringify({
private: true,
workspaces: ["packages/*"],
}),
"package-lock.json": "",
"packages/pkg-a/package.json": JSON.stringify({
name: "pkg-a",
version: "1.0.0",
dependencies: {
"pkg-b": "1.0.0",
},
}),
"packages/pkg-b/package.json": JSON.stringify({
name: "pkg-b",
version: "1.0.0",
}),
});

await expect(
version(
cwd,
{ ...defaultOptions, ignore: ["pkg-b"] },
{ ...modifiedDefaultConfig, ignore: ["pkg-a"] },
),
).rejects.toThrow(ExitError);

expect(mockedLogger.error).toHaveBeenCalledOnce();
const arg = mockedLogger.error.mock.calls[0][0];
expect(stripVTControlCharacters(arg)).toEqual(
`It looks like you are trying to use the \`--ignore\` option while ignore is defined in the config file. This is currently not allowed, you can only use one of them at a time.`,
);
});

describe("when there is a changeset commit", () => {
it("should bump releasedPackages", async () => {
const cwd = await testdir({
Expand Down
94 changes: 0 additions & 94 deletions packages/cli/src/run.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -61,65 +61,6 @@ describe("cli", () => {
});

describe("version", () => {
it("should validate package name passed in from --ignore flag", async () => {
const cwd = await testdir({
"package.json": JSON.stringify({
private: true,
workspaces: ["packages/*"],
}),
"package-lock.json": "",
"packages/pkg-a/package.json": JSON.stringify({
name: "pkg-a",
version: "1.0.0",
}),
".changeset/config.json": JSON.stringify({}),
});
try {
await run(["version"], { ignore: "pkg-c" }, cwd);
} catch {
// ignore errors. We just want to validate the error message
}

expect(mockedLogger.error).toHaveBeenCalledOnce();
const arg = mockedLogger.error.mock.calls[0][0];
expect(stripVTControlCharacters(arg)).toEqual(
`The package pkg-c is passed to the \`--ignore\` option but it is not found in the project. You may have misspelled the package name.`,
);
});

it("should throw if dependents of ignored packages are not explicitly listed in the ignore array", async () => {
const cwd = await testdir({
"package.json": JSON.stringify({
private: true,
workspaces: ["packages/*"],
}),
"package-lock.json": "",
"packages/pkg-a/package.json": JSON.stringify({
name: "pkg-a",
version: "1.0.0",
dependencies: {
"pkg-b": "1.0.0",
},
}),
"packages/pkg-b/package.json": JSON.stringify({
name: "pkg-b",
version: "1.0.0",
}),
".changeset/config.json": JSON.stringify({}),
});
try {
await run(["version"], { ignore: ["pkg-b"] }, cwd);
} catch {
// ignore the error. We just want to validate the error message
}

expect(mockedLogger.error).toHaveBeenCalledOnce();
const arg = mockedLogger.error.mock.calls[0][0];
expect(stripVTControlCharacters(arg)).toEqual(
`The package pkg-a depends on the skipped package pkg-b (either by \`ignore\` option or by \`privatePackages.version\`), but pkg-a is not being skipped. Please pass pkg-a to the --ignore flag.`,
);
});

it("should not throw if dependents of unversioned private packages are not explicitly listed by the ignore flag", async () => {
const cwd = await testdir({
"package.json": JSON.stringify({
Expand Down Expand Up @@ -244,41 +185,6 @@ describe("cli", () => {
expect(mockedLogger.error).not.toHaveBeenCalled();
});

it("should throw if `--ignore` flag is used while ignore array is also defined in the config file", async () => {
const cwd = await testdir({
"package.json": JSON.stringify({
private: true,
workspaces: ["packages/*"],
}),
"package-lock.json": "",
"packages/pkg-a/package.json": JSON.stringify({
name: "pkg-a",
version: "1.0.0",
dependencies: {
"pkg-b": "1.0.0",
},
}),
"packages/pkg-b/package.json": JSON.stringify({
name: "pkg-b",
version: "1.0.0",
}),
".changeset/config.json": JSON.stringify({
ignore: ["pkg-a"],
}),
});
try {
await run(["version"], { ignore: "pkg-b" }, cwd);
} catch {
// ignore errors. We just want to validate the error message
}

expect(mockedLogger.error).toHaveBeenCalledOnce();
const arg = mockedLogger.error.mock.calls[0][0];
expect(stripVTControlCharacters(arg)).toEqual(
`It looks like you are trying to use the \`--ignore\` option while ignore is defined in the config file. This is currently not allowed, you can only use one of them at a time.`,
);
});

it("should not throw if `format: false` is configured", async () => {
const cwd = await testdir({
"package.json": JSON.stringify({
Expand Down
Loading

Back | FazBrowse Home | New Git URL