mirror of
https://github.com/vercel/workflow.git
synced 2026-09-14 19:59:43 +08:00
f5aeaa869c
Co-authored-by: Peter Wielander <peter.wielander@vercel.com>
85 lines
3.4 KiB
JavaScript
85 lines
3.4 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Fails when the pending changesets cannot be assembled into a release plan.
|
|
*
|
|
* `changeset version`, which .github/workflows/release.yml runs on every push
|
|
* to `main`, builds a release plan from every file in `.changeset/` before it
|
|
* bumps a single version. Two kinds of changeset make that step throw, and
|
|
* the throw leaves `main` unpublishable until someone reads the red Release
|
|
* job:
|
|
*
|
|
* - one naming a package that is not in the workspace (a typo, or a
|
|
* package renamed or removed after the changeset was written);
|
|
* - one mixing packages from `.changeset/config.json#ignore` (private
|
|
* workbench and simulation packages such as `@workflow/world-sim`) with
|
|
* published ones. Changesets rejects the whole file rather than dropping
|
|
* the ignored entries. #3938 shipped one of these and blocked every
|
|
* release until #3963.
|
|
*
|
|
* Nothing at PR time exercised that step. `changeset status --since=main`
|
|
* does not see a changeset once it is on the base branch, and it also fails
|
|
* for the unrelated reason of a package changing without a changeset, so it
|
|
* cannot serve as this gate. This script runs the same assembly `changeset
|
|
* version` runs, on the same inputs, and stops there: it skips the changelog
|
|
* generation, which is the slow, network-bound part of the real command, so
|
|
* it is cheap enough to run on every PR.
|
|
*
|
|
* The libraries are the same ones `@changesets/cli` uses, declared as root
|
|
* devDependencies at the ranges the CLI itself declares, so this check cannot
|
|
* drift from what the Release job will do.
|
|
*/
|
|
|
|
import { dirname, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { assembleReleasePlan } from '@changesets/assemble-release-plan';
|
|
import { readConfig } from '@changesets/config';
|
|
import { readPreState } from '@changesets/pre';
|
|
import { readChangesets } from '@changesets/read';
|
|
import { getPackages } from '@manypkg/get-packages';
|
|
|
|
const cwd = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
|
|
const packages = await getPackages(cwd);
|
|
const {
|
|
config,
|
|
errors: configErrors,
|
|
warnings: configWarnings,
|
|
} = await readConfig(cwd, packages);
|
|
for (const warning of configWarnings ?? [])
|
|
console.warn(`! .changeset/config.json: ${warning}`);
|
|
if (configErrors) {
|
|
console.error('✗ .changeset/config.json is invalid:');
|
|
for (const error of configErrors) console.error(` ${error}`);
|
|
process.exit(1);
|
|
}
|
|
const [changesets, preState] = await Promise.all([
|
|
readChangesets(cwd),
|
|
readPreState(cwd),
|
|
]);
|
|
|
|
let plan;
|
|
try {
|
|
plan = assembleReleasePlan(changesets, packages, config, preState);
|
|
} catch (error) {
|
|
console.error(
|
|
'✗ The pending changesets cannot be turned into a release plan.'
|
|
);
|
|
console.error(' `changeset version` will fail on main with this error:\n');
|
|
console.error(
|
|
String(error?.message ?? error)
|
|
.split('\n')
|
|
.map((line) => ` ${line}`)
|
|
.join('\n')
|
|
);
|
|
console.error(
|
|
'\n A changeset may only list packages that are published, and never one from the `ignore` array in .changeset/config.json.'
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
const pending = plan.changesets.length;
|
|
const releasing = plan.releases.filter((r) => r.type !== 'none').length;
|
|
console.log(
|
|
`✓ ${changesets.length} changeset(s) read, ${pending} pending${preState ? ` (${preState.mode} mode, tag "${preState.tag}")` : ''}; ${releasing} package(s) would be released.`
|
|
);
|