Skip to content

Commit

Permalink
fix(package): prevent publishing monorepos when no packages changed (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
AlCalzone committed Mar 4, 2022
1 parent cf26715 commit 588622e
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 22 deletions.
37 changes: 37 additions & 0 deletions packages/plugin-package/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,43 @@ describe("Package plugin", () => {
await pkgPlugin.executeStage(context, DefaultStages.check);
expect(context.errors).toHaveLength(0);
});

it("raises a fatal error in yarn-monorepo mode when no packages are changed", async () => {
const pkgPlugin = new PackagePlugin();
const context = createMockContext({
plugins: [pkgPlugin],
cwd: testFSRoot,
});

const oldVersion = "1.2.3";
const newVersion = "1.2.4";

const pack = {
name: "test-package",
version: oldVersion,
workspaces: ["packages/*"],
};
await testFS.create({
"package.json": JSON.stringify(pack, null, 2),
".yarnrc.yml": fixtures.yarnrc_complete,
});

context.setData("package.json", pack);
context.setData("version", oldVersion);
context.setData("version_new", newVersion);
context.setData("monorepo", "yarn");

context.sys.mockExec((cmd) =>
cmd.includes("changed list")
? "" // no changes!
: "",
);

await assertReleaseError(() => pkgPlugin.executeStage(context, DefaultStages.check), {
fatal: true,
messageMatches: /--publishAll/i,
});
});
});

describe("edit stage", () => {
Expand Down
79 changes: 57 additions & 22 deletions packages/plugin-package/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,45 @@ import path from "path";
import semver from "semver";
import type { Argv } from "yargs";

function getEffectivePublishAllFlag(context: Context): boolean {
const oldVersion = context.getData<string>("version");
const newVersion = context.getData<string>("version_new");

// Force a publish of all packages if the version changed from a prerelease to a stable version
let publishAll = context.argv.publishAll as boolean;
if (
!publishAll &&
semver.gt(newVersion, oldVersion) &&
semver.parse(newVersion)?.prerelease.length === 0 &&
(semver.parse(oldVersion)?.prerelease.length ?? 0) > 0
) {
publishAll = true;
}
return publishAll;
}

async function getUpdatePackages(
context: Context,
publishAll: boolean,
oldVersion: string,
): Promise<{ name: string; location: string }[]> {
const { stdout: output } = await context.sys.exec(
"yarn",
publishAll
? ["workspaces", "list", "--json"]
: ["changed", "list", "--json", `--git-range=v${oldVersion}`],
{ cwd: context.cwd },
);
const updatePackages = output
.trim()
.split("\n")
.map((line) => line.trim())
.filter(Boolean)
.map((line) => JSON.parse(line));

return updatePackages;
}

class PackagePlugin implements Plugin {
public readonly id = "package";
public readonly stages = [DefaultStages.check, DefaultStages.edit, DefaultStages.commit];
Expand All @@ -27,6 +66,8 @@ class PackagePlugin implements Plugin {
commit: ["git"],
};
public readonly stageAfter = {
// Checking whether the --publishAll flag is necessary needs to know the new version
check: ["version"],
commit: (context: Context): string[] => {
// In lerna mode, we need to update the lockfile after bumping, so we do that in non-lerna mode too.
const lerna = context.hasData("lerna") && !!context.getData("lerna");
Expand Down Expand Up @@ -92,6 +133,19 @@ Alternatively, you can use ${context.cli.colors.blue("lerna")} to manage the mon

// All good, remember that we use yarn to manage the monorepo
context.setData("monorepo", "yarn");

// One last check: make sure there is anything to publish
const publishAll = getEffectivePublishAllFlag(context);
const updatePackages = await getUpdatePackages(
context,
publishAll,
pack.version,
);
if (!updatePackages.length) {
context.cli.fatal(
`The current project is a monorepo, but no packages were changed! To force a release anyways, use the "--publishAll" flag!`,
);
}
} else {
context.cli.fatal(
`The current project is a monorepo. The release script requires either lerna or the yarn package manager to handle this!`,
Expand Down Expand Up @@ -166,33 +220,14 @@ Alternatively, you can use ${context.cli.colors.blue("lerna")} to manage the mon
}

private async executeEditStageYarnMonorepo(context: Context): Promise<void> {
const oldVersion = context.getData<string>("version");
const newVersion = context.getData<string>("version_new");
const pack = context.getData<any>("package.json");

// Force a publish of all packages if the version changed from a prerelease to a stable version
let publishAll = context.argv.publishAll;
if (
!publishAll &&
semver.gt(newVersion, oldVersion) &&
semver.parse(newVersion)?.prerelease.length === 0 &&
(semver.parse(oldVersion)?.prerelease.length ?? 0) > 0
) {
publishAll = true;
}
const publishAll = getEffectivePublishAllFlag(context);

// Figure out which packages changed (or exist if all should be published)
const { stdout: output } = await context.sys.exec(
"yarn",
publishAll
? ["workspaces", "list", "--json"]
: ["changed", "list", "--json", `--git-range=v${pack.version}`],
{ cwd: context.cwd },
);
const updatePackages = output
.trim()
.split("\n")
.map((line) => JSON.parse(line));
// Figure out which packages changed (or which ones exist if all should be published)
const updatePackages = await getUpdatePackages(context, publishAll, pack.version);

// Work around https://github.com/yarnpkg/berry/issues/3868
const packageJsonFiles = updatePackages.map((info) =>
Expand Down

0 comments on commit 588622e

Please sign in to comment.