breakage 0.8.5 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +163 -47
- package/dist/{breakage-B7dJsgcu.js → breakage-DJiIiLzL.js} +958 -221
- package/dist/breakage-DJiIiLzL.js.map +1 -0
- package/dist/breakage.d.ts +239 -71
- package/dist/breakage.d.ts.map +1 -1
- package/dist/breakage.js +3 -1
- package/dist/breakage.x.js +216 -79
- package/dist/breakage.x.js.map +1 -1
- package/dist/changesets.d.ts +8 -0
- package/dist/changesets.d.ts.map +1 -0
- package/dist/changesets.js +27 -0
- package/dist/changesets.js.map +1 -0
- package/dist/release-BsdIDT4g.js +69 -0
- package/dist/release-BsdIDT4g.js.map +1 -0
- package/dist/release-D47BqRuB.d.ts +40 -0
- package/dist/release-D47BqRuB.d.ts.map +1 -0
- package/npm.md +198 -44
- package/package.json +45 -6
- package/src/breakage.ts +142 -73
- package/src/breakage.x.ts +174 -127
- package/src/changesets.ts +51 -0
- package/src/config.ts +131 -0
- package/src/npm/check.ts +70 -50
- package/src/npm/cli.ts +30 -22
- package/src/npm/config.ts +38 -6
- package/src/npm/environment.ts +10 -10
- package/src/npm/matrix-cli.ts +170 -0
- package/src/npm/matrix-model.ts +395 -0
- package/src/npm/matrix.ts +657 -0
- package/src/npm/release.ts +9 -25
- package/src/npm/types.ts +5 -45
- package/src/release.ts +153 -0
- package/src/{npm/command.ts → task.ts} +2 -0
- package/dist/breakage-B7dJsgcu.js.map +0 -1
- package/dist/breakage.$configPath.schema.json +0 -33
- package/dist/breakage.help.schema.json +0 -6
- package/dist/breakage.main.schema.json +0 -33
- package/dist/breakage.npm.check.schema.json +0 -16
- package/dist/breakage.npm.help.schema.json +0 -6
- package/dist/breakage.npm.schema.json +0 -6
- package/dist/breakage.npm.test.$peer.$range.schema.json +0 -16
- package/dist/breakage.npm.test.schema.json +0 -16
- package/dist/breakage.prelude.$configPath.schema.json +0 -31
- package/dist/breakage.prelude.schema.json +0 -31
- package/dist/breakage.schema.schema.json +0 -10
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import semver from "semver";
|
|
2
|
+
import { type } from "arktype";
|
|
3
|
+
// src/release.ts
|
|
4
|
+
const releaseSchema = type({
|
|
5
|
+
"+": `reject`,
|
|
6
|
+
"resolveDelta?": type(`Function`).as(),
|
|
7
|
+
"policy?": type(`Function`).as()
|
|
8
|
+
});
|
|
9
|
+
/** Choose which version increments permit breaking changes. Prerelease-only increments do not. */
|
|
10
|
+
function semverPolicy({ beforeV1 = `major` } = {}) {
|
|
11
|
+
if (![
|
|
12
|
+
`major`,
|
|
13
|
+
`minor`,
|
|
14
|
+
`patch`
|
|
15
|
+
].includes(beforeV1)) throw new Error(`Invalid beforeV1 policy.`);
|
|
16
|
+
return ({ previousVersion, version }) => {
|
|
17
|
+
const previous = new semver.SemVer(previousVersion);
|
|
18
|
+
const next = new semver.SemVer(version);
|
|
19
|
+
if (next.major > previous.major) return true;
|
|
20
|
+
if (previous.major !== 0 || beforeV1 === `major`) return false;
|
|
21
|
+
return next.minor > previous.minor || beforeV1 === `patch` && next.patch > previous.patch;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Describe a version transition without deciding whether it permits breakage. */
|
|
25
|
+
function versionTransition(previousVersion, version) {
|
|
26
|
+
if (!semver.valid(previousVersion) || !semver.valid(version)) throw new Error(`Expected exact semantic versions.`);
|
|
27
|
+
return {
|
|
28
|
+
previousVersion,
|
|
29
|
+
version,
|
|
30
|
+
delta: semver.diff(previousVersion, version) ?? `none`
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** Pure comparison; equal and older versions never permit breaking changes. */
|
|
34
|
+
function versionDelta(previousVersion, version, policy = semverPolicy()) {
|
|
35
|
+
const evidence = versionTransition(previousVersion, version);
|
|
36
|
+
const breaking = semver.gt(version, previousVersion) ? policy(evidence) : false;
|
|
37
|
+
if (typeof breaking !== `boolean`) throw new Error(`Release policy must return a boolean.`);
|
|
38
|
+
return {
|
|
39
|
+
...evidence,
|
|
40
|
+
breaking
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function validateRelease(value) {
|
|
44
|
+
if (typeof value !== `object` || value === null || Array.isArray(value)) throw new Error(`release must be an object.`);
|
|
45
|
+
if (`version` in value) throw new Error(`release.version was removed. Use release.resolveDelta to return a version transition.`);
|
|
46
|
+
releaseSchema.assert(value);
|
|
47
|
+
}
|
|
48
|
+
/** Resolve and validate release evidence; policy is applied separately. */
|
|
49
|
+
async function resolveReleaseDelta(config, context) {
|
|
50
|
+
validateRelease(config);
|
|
51
|
+
const transition = config.resolveDelta ? await config.resolveDelta(context) : versionTransition(context.baseline?.version ?? context.currentVersion, context.currentVersion);
|
|
52
|
+
if (transition === void 0) return void 0;
|
|
53
|
+
if (typeof transition !== `object` || transition === null || Array.isArray(transition) || typeof transition.previousVersion !== `string` || !semver.valid(transition.previousVersion) || typeof transition.version !== `string` || !semver.valid(transition.version) || !semver.valid(context.currentVersion) || semver.lt(transition.version, context.currentVersion)) throw new Error(`Invalid release transition for ${context.packageName}; expected exact versions and a current/newer target.`);
|
|
54
|
+
if (context.baseline && transition.previousVersion !== context.baseline.version) throw new Error(`Release transition for ${context.packageName} does not match the selected baseline.`);
|
|
55
|
+
const evidence = versionTransition(transition.previousVersion, transition.version);
|
|
56
|
+
if (transition.delta !== evidence.delta) throw new Error(`Release transition for ${context.packageName} has an inconsistent semver delta.`);
|
|
57
|
+
return evidence;
|
|
58
|
+
}
|
|
59
|
+
/** Removed hooks must fail explicitly, including in untyped configurations. */
|
|
60
|
+
function rejectCertificationOptions(value) {
|
|
61
|
+
for (const key of [
|
|
62
|
+
`certifyCommand`,
|
|
63
|
+
`releaseIntent`,
|
|
64
|
+
`certifyBreaking`
|
|
65
|
+
]) if (key in value) throw new Error(`${key} was removed. Use release.resolveDelta and release.policy instead.`);
|
|
66
|
+
}
|
|
67
|
+
export { validateRelease as a, semverPolicy as i, releaseSchema as n, versionDelta as o, resolveReleaseDelta as r, versionTransition as s, rejectCertificationOptions as t };
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=release-BsdIDT4g.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"release-BsdIDT4g.js","names":[],"sources":["../src/release.ts"],"sourcesContent":["// cspell:words premajor preminor prepatch\nimport semver from \"semver\"\nimport { type } from \"arktype\"\n\nexport type ReleaseDeltaContext = {\n\tpackageName: string\n\tpackageDirectory: string\n\tcurrentVersion: string\n\t/** The selected historical release, when checking compatibility. */\n\tbaseline?: { version: string; ref?: string; commit?: string }\n}\nexport type VersionTransition = {\n\tpreviousVersion: string\n\tversion: string\n\tdelta:\n\t\t| `major`\n\t\t| `premajor`\n\t\t| `minor`\n\t\t| `preminor`\n\t\t| `patch`\n\t\t| `prepatch`\n\t\t| `prerelease`\n\t\t| `release`\n\t\t| `none`\n}\nexport type ReleaseDeltaResolver = (\n\tcontext: ReleaseDeltaContext,\n) => VersionTransition | undefined | Promise<VersionTransition | undefined>\nexport type VersionDelta = VersionTransition & { breaking: boolean }\nexport type SemverPolicyOptions = { beforeV1?: `major` | `minor` | `patch` }\nexport type ReleasePolicyFunction = (delta: VersionTransition) => boolean\nexport type ReleasePolicy = ReleasePolicyFunction\nexport const releaseSchema = type({\n\t\"+\": `reject`,\n\t\"resolveDelta?\": type(`Function`).as<ReleaseDeltaResolver>(),\n\t\"policy?\": type(`Function`).as<ReleasePolicy>(),\n})\nexport type ReleaseConfig = typeof releaseSchema.infer\n\n/** Choose which version increments permit breaking changes. Prerelease-only increments do not. */\nexport function semverPolicy({\n\tbeforeV1 = `major`,\n}: SemverPolicyOptions = {}): ReleasePolicyFunction {\n\tif (![`major`, `minor`, `patch`].includes(beforeV1))\n\t\tthrow new Error(`Invalid beforeV1 policy.`)\n\treturn ({ previousVersion, version }) => {\n\t\tconst previous = new semver.SemVer(previousVersion)\n\t\tconst next = new semver.SemVer(version)\n\t\tif (next.major > previous.major) return true\n\t\tif (previous.major !== 0 || beforeV1 === `major`) return false\n\t\treturn (\n\t\t\tnext.minor > previous.minor ||\n\t\t\t(beforeV1 === `patch` && next.patch > previous.patch)\n\t\t)\n\t}\n}\n\n/** Describe a version transition without deciding whether it permits breakage. */\nexport function versionTransition(\n\tpreviousVersion: string,\n\tversion: string,\n): VersionTransition {\n\tif (!semver.valid(previousVersion) || !semver.valid(version))\n\t\tthrow new Error(`Expected exact semantic versions.`)\n\treturn {\n\t\tpreviousVersion,\n\t\tversion,\n\t\tdelta: semver.diff(previousVersion, version) ?? `none`,\n\t}\n}\n\n/** Pure comparison; equal and older versions never permit breaking changes. */\nexport function versionDelta(\n\tpreviousVersion: string,\n\tversion: string,\n\tpolicy: ReleasePolicy = semverPolicy(),\n): VersionDelta {\n\tconst evidence = versionTransition(previousVersion, version)\n\tconst breaking = semver.gt(version, previousVersion)\n\t\t? policy(evidence)\n\t\t: false\n\tif (typeof breaking !== `boolean`)\n\t\tthrow new Error(`Release policy must return a boolean.`)\n\treturn { ...evidence, breaking }\n}\n\nexport function validateRelease(\n\tvalue: unknown,\n): asserts value is ReleaseConfig {\n\tif (typeof value !== `object` || value === null || Array.isArray(value))\n\t\tthrow new Error(`release must be an object.`)\n\tif (`version` in value)\n\t\tthrow new Error(\n\t\t\t`release.version was removed. Use release.resolveDelta to return a version transition.`,\n\t\t)\n\treleaseSchema.assert(value)\n}\n\n/** Resolve and validate release evidence; policy is applied separately. */\nexport async function resolveReleaseDelta(\n\tconfig: ReleaseConfig,\n\tcontext: ReleaseDeltaContext,\n): Promise<VersionTransition | undefined> {\n\tvalidateRelease(config)\n\tconst transition = config.resolveDelta\n\t\t? await config.resolveDelta(context)\n\t\t: versionTransition(\n\t\t\t\tcontext.baseline?.version ?? context.currentVersion,\n\t\t\t\tcontext.currentVersion,\n\t\t\t)\n\tif (transition === undefined) return undefined\n\tif (\n\t\ttypeof transition !== `object` ||\n\t\ttransition === null ||\n\t\tArray.isArray(transition) ||\n\t\ttypeof transition.previousVersion !== `string` ||\n\t\t!semver.valid(transition.previousVersion) ||\n\t\ttypeof transition.version !== `string` ||\n\t\t!semver.valid(transition.version) ||\n\t\t!semver.valid(context.currentVersion) ||\n\t\tsemver.lt(transition.version, context.currentVersion)\n\t)\n\t\tthrow new Error(\n\t\t\t`Invalid release transition for ${context.packageName}; expected exact versions and a current/newer target.`,\n\t\t)\n\tif (\n\t\tcontext.baseline &&\n\t\ttransition.previousVersion !== context.baseline.version\n\t)\n\t\tthrow new Error(\n\t\t\t`Release transition for ${context.packageName} does not match the selected baseline.`,\n\t\t)\n\tconst evidence = versionTransition(\n\t\ttransition.previousVersion,\n\t\ttransition.version,\n\t)\n\tif (transition.delta !== evidence.delta)\n\t\tthrow new Error(\n\t\t\t`Release transition for ${context.packageName} has an inconsistent semver delta.`,\n\t\t)\n\t// Only evidence crosses this boundary; resolver-supplied policy decisions are ignored.\n\treturn evidence\n}\n\n/** Removed hooks must fail explicitly, including in untyped configurations. */\nexport function rejectCertificationOptions(value: object): void {\n\tfor (const key of [`certifyCommand`, `releaseIntent`, `certifyBreaking`]) {\n\t\tif (key in value)\n\t\t\tthrow new Error(\n\t\t\t\t`${key} was removed. Use release.resolveDelta and release.policy instead.`,\n\t\t\t)\n\t}\n}\n"],"mappings":";;;AAgCA,MAAa,gBAAgB,KAAK;CACjC,KAAK;CACL,iBAAiB,KAAK,UAAU,CAAC,CAAC,GAAyB;CAC3D,WAAW,KAAK,UAAU,CAAC,CAAC,GAAkB;AAC/C,CAAC;;AAID,SAAgB,aAAa,EAC5B,WAAW,YACa,CAAC,GAA0B;CACnD,IAAI,CAAC;EAAC;EAAS;EAAS;CAAO,CAAC,CAAC,SAAS,QAAQ,GACjD,MAAM,IAAI,MAAM,0BAA0B;CAC3C,QAAQ,EAAE,iBAAiB,cAAc;EACxC,MAAM,WAAW,IAAI,OAAO,OAAO,eAAe;EAClD,MAAM,OAAO,IAAI,OAAO,OAAO,OAAO;EACtC,IAAI,KAAK,QAAQ,SAAS,OAAO,OAAO;EACxC,IAAI,SAAS,UAAU,KAAK,aAAa,SAAS,OAAO;EACzD,OACC,KAAK,QAAQ,SAAS,SACrB,aAAa,WAAW,KAAK,QAAQ,SAAS;CAEjD;AACD;;AAGA,SAAgB,kBACf,iBACA,SACoB;CACpB,IAAI,CAAC,OAAO,MAAM,eAAe,KAAK,CAAC,OAAO,MAAM,OAAO,GAC1D,MAAM,IAAI,MAAM,mCAAmC;CACpD,OAAO;EACN;EACA;EACA,OAAO,OAAO,KAAK,iBAAiB,OAAO,KAAK;CACjD;AACD;;AAGA,SAAgB,aACf,iBACA,SACA,SAAwB,aAAa,GACtB;CACf,MAAM,WAAW,kBAAkB,iBAAiB,OAAO;CAC3D,MAAM,WAAW,OAAO,GAAG,SAAS,eAAe,IAChD,OAAO,QAAQ,IACf;CACH,IAAI,OAAO,aAAa,WACvB,MAAM,IAAI,MAAM,uCAAuC;CACxD,OAAO;EAAE,GAAG;EAAU;CAAS;AAChC;AAEA,SAAgB,gBACf,OACiC;CACjC,IAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,KAAK,GACrE,MAAM,IAAI,MAAM,4BAA4B;CAC7C,IAAI,aAAa,OAChB,MAAM,IAAI,MACT,uFACD;CACD,cAAc,OAAO,KAAK;AAC3B;;AAGA,eAAsB,oBACrB,QACA,SACyC;CACzC,gBAAgB,MAAM;CACtB,MAAM,aAAa,OAAO,eACvB,MAAM,OAAO,aAAa,OAAO,IACjC,kBACA,QAAQ,UAAU,WAAW,QAAQ,gBACrC,QAAQ,cACT;CACF,IAAI,eAAe,KAAA,GAAW,OAAO,KAAA;CACrC,IACC,OAAO,eAAe,YACtB,eAAe,QACf,MAAM,QAAQ,UAAU,KACxB,OAAO,WAAW,oBAAoB,YACtC,CAAC,OAAO,MAAM,WAAW,eAAe,KACxC,OAAO,WAAW,YAAY,YAC9B,CAAC,OAAO,MAAM,WAAW,OAAO,KAChC,CAAC,OAAO,MAAM,QAAQ,cAAc,KACpC,OAAO,GAAG,WAAW,SAAS,QAAQ,cAAc,GAEpD,MAAM,IAAI,MACT,kCAAkC,QAAQ,YAAY,sDACvD;CACD,IACC,QAAQ,YACR,WAAW,oBAAoB,QAAQ,SAAS,SAEhD,MAAM,IAAI,MACT,0BAA0B,QAAQ,YAAY,uCAC/C;CACD,MAAM,WAAW,kBAChB,WAAW,iBACX,WAAW,OACZ;CACA,IAAI,WAAW,UAAU,SAAS,OACjC,MAAM,IAAI,MACT,0BAA0B,QAAQ,YAAY,mCAC/C;CAED,OAAO;AACR;;AAGA,SAAgB,2BAA2B,OAAqB;CAC/D,KAAK,MAAM,OAAO;EAAC;EAAkB;EAAiB;CAAiB,GACtE,IAAI,OAAO,OACV,MAAM,IAAI,MACT,GAAG,IAAI,mEACR;AAEH"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
type ReleaseDeltaContext = {
|
|
2
|
+
packageName: string;
|
|
3
|
+
packageDirectory: string;
|
|
4
|
+
currentVersion: string;
|
|
5
|
+
/** The selected historical release, when checking compatibility. */
|
|
6
|
+
baseline?: {
|
|
7
|
+
version: string;
|
|
8
|
+
ref?: string;
|
|
9
|
+
commit?: string;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
type VersionTransition = {
|
|
13
|
+
previousVersion: string;
|
|
14
|
+
version: string;
|
|
15
|
+
delta: `major` | `premajor` | `minor` | `preminor` | `patch` | `prepatch` | `prerelease` | `release` | `none`;
|
|
16
|
+
};
|
|
17
|
+
type ReleaseDeltaResolver = (context: ReleaseDeltaContext) => VersionTransition | undefined | Promise<VersionTransition | undefined>;
|
|
18
|
+
type VersionDelta = VersionTransition & {
|
|
19
|
+
breaking: boolean;
|
|
20
|
+
};
|
|
21
|
+
type SemverPolicyOptions = {
|
|
22
|
+
beforeV1?: `major` | `minor` | `patch`;
|
|
23
|
+
};
|
|
24
|
+
type ReleasePolicyFunction = (delta: VersionTransition) => boolean;
|
|
25
|
+
type ReleasePolicy = ReleasePolicyFunction;
|
|
26
|
+
declare const releaseSchema: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
27
|
+
resolveDelta?: ReleaseDeltaResolver;
|
|
28
|
+
policy?: ReleasePolicyFunction;
|
|
29
|
+
}, {}>;
|
|
30
|
+
type ReleaseConfig = typeof releaseSchema.infer;
|
|
31
|
+
/** Choose which version increments permit breaking changes. Prerelease-only increments do not. */
|
|
32
|
+
declare function semverPolicy({ beforeV1 }?: SemverPolicyOptions): ReleasePolicyFunction;
|
|
33
|
+
/** Describe a version transition without deciding whether it permits breakage. */
|
|
34
|
+
declare function versionTransition(previousVersion: string, version: string): VersionTransition;
|
|
35
|
+
/** Pure comparison; equal and older versions never permit breaking changes. */
|
|
36
|
+
declare function versionDelta(previousVersion: string, version: string, policy?: ReleasePolicy): VersionDelta;
|
|
37
|
+
/** Resolve and validate release evidence; policy is applied separately. */
|
|
38
|
+
declare function resolveReleaseDelta(config: ReleaseConfig, context: ReleaseDeltaContext): Promise<VersionTransition | undefined>;
|
|
39
|
+
export { ReleasePolicyFunction as a, VersionTransition as c, versionDelta as d, versionTransition as f, ReleasePolicy as i, resolveReleaseDelta as l, ReleaseDeltaContext as n, SemverPolicyOptions as o, ReleaseDeltaResolver as r, VersionDelta as s, ReleaseConfig as t, semverPolicy as u };
|
|
40
|
+
//# sourceMappingURL=release-D47BqRuB.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"release-D47BqRuB.d.ts","names":[],"sources":["../src/release.ts"],"mappings":"A,KAIY;EACX;EACA;EACA;;EAEA;IAAa;IAAiB;IAAc;;;KAEjC;EACX;EACA;EACA;;KAWW,wBACX,SAAS,wBACL,gCAAgC,QAAQ;KACjC,eAAe;EAAsB;;KACrC;EAAwB;;KACxB,yBAAyB,OAAO;KAChC,gBAAgB;cACf,6DAAa;;;;KAKd,uBAAuB,cAAc;;iBAGjC,eACf,aACE,sBAA2B;;iBAgBd,kBACf,yBACA,kBACE;;iBAWa,aACf,yBACA,iBACA,SAAQ,gBACN;;iBAuBmB,oBACrB,QAAQ,eACR,SAAS,sBACP,QAAQ"}
|
package/npm.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Use `breakage npm` to test a packed package against the peer versions you declare. This is an optional npm-specific feature. The ordinary public check and its four adoption conditions remain the same; keep that check required alongside peer compatibility.
|
|
4
4
|
|
|
5
|
-
Consider an integration with `"atom.io": "^1 || ^2"`. Its current workspace tests may pass against an edited core, even if only the integration will ship. Representative environments let it prove compatibility with actual released versions. An explicitly registered candidate environment can additionally prove conditional compatibility with a core release your release plan
|
|
5
|
+
Consider an integration with `"atom.io": "^1 || ^2"`. Its current workspace tests may pass against an edited core, even if only the integration will ship. Representative environments let it prove compatibility with actual released versions. An explicitly registered candidate environment can additionally prove conditional compatibility with a core release your release plan identifies.
|
|
6
6
|
|
|
7
7
|
The npm commands require Node 22.18 or newer (including native erasable TypeScript for `.ts` configuration), Git, and your installation/test tools. They run on the filesystem, inside complete representative directories. They do not publish packages.
|
|
8
8
|
|
|
@@ -30,7 +30,7 @@ In the integration package, create complete environments for the peer formulatio
|
|
|
30
30
|
}
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
Pin every dependency in an environment manifest to an exact version: `"host": "1.0.0"` and `"host": "2.1.4"` are valid; `"host": "^1"` and `"host": "^1 || ^2"` are setup errors. This applies to all entries in `dependencies`, `devDependencies`, `optionalDependencies`, and `peerDependencies`, including test runners and helpers. Exact prerelease and build versions are allowed. Ranges, wildcards, tags, aliases, workspace protocols, and file or URL specifiers are rejected before installation. The integration's own `peerDependencies` still express its supported eras with ranges. Enroll coordinated local releases through `breakage.candidates`; their
|
|
33
|
+
Pin every dependency in an environment manifest to an exact version: `"host": "1.0.0"` and `"host": "2.1.4"` are valid; `"host": "^1"` and `"host": "^1 || ^2"` are setup errors. This applies to all entries in `dependencies`, `devDependencies`, `optionalDependencies`, and `peerDependencies`, including test runners and helpers. Exact prerelease and build versions are allowed. Ranges, wildcards, tags, aliases, workspace protocols, and file or URL specifiers are rejected before installation. The integration's own `peerDependencies` still express its supported eras with ranges. Enroll coordinated local releases through `breakage.candidates`; their resolved intended versions supply the exact identities, and any accompanying dependency pins must match those versions.
|
|
34
34
|
|
|
35
35
|
Choose real published versions appropriate for your package. Install once and commit this directory's manifest and lock file. Add any test runner and helpers it needs to its own development dependencies with exact pins. Give each additional formulation its own directory and lock file. Every required peer of the integration must be present; several coupled peers share one environment. Include all intended representatives in the glob, including multiple representatives of the same era.
|
|
36
36
|
|
|
@@ -89,7 +89,7 @@ export default {
|
|
|
89
89
|
}
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
Every file glob must select at least one regular file. Remove the fixture glob if you have no fixtures. `packageDirectory` defaults to the config file's directory; all npm paths resolve from that package directory. Glob discovery uses paths as labels and installed versions as evidence. Register local peer directories in `localPeers`; each declared local peer must have a suite, and its
|
|
92
|
+
Every file glob must select at least one regular file. Remove the fixture glob if you have no fixtures. `packageDirectory` defaults to the config file's directory; all npm paths resolve from that package directory. Glob discovery uses paths as labels and installed versions as evidence. Register local peer directories in `localPeers`; each declared local peer must have a suite, and its planned upcoming releases must be supported and exercised by a coordinated representative. External peer suites are an explicit opt-in. No matrix is inferred from registry metadata.
|
|
93
93
|
|
|
94
94
|
The previous manifest comes from the newest matching release tag. An existing core `baselineFile` may pin a fetched release. For a package's first publication, explicitly use `baseline: { firstRelease: true }` and remove that exception after publishing; missing baseline evidence never silently passes.
|
|
95
95
|
|
|
@@ -103,9 +103,9 @@ breakage npm test --representative peer-environments/core-1
|
|
|
103
103
|
breakage npm check --config ./breakage.config.ts --json
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
All matching representatives run. Every explicit `||` era needs coverage. Preview versions cover only an exact preview alternative, not stable ranges or other previews. Build metadata does not affect range matching, including exact preview selection; installed versions must still match their complete environment pins. Released representatives outside declared support warn; excluding a planned local release fails the release check. Focused requests can intentionally explore versions outside declared support. An audit reports declarations and release-policy findings without claiming assertions passed. Exit statuses are 0 for successful requested work, 1 for compatibility or
|
|
106
|
+
All matching representatives run. Every explicit `||` era needs coverage. Preview versions cover only an exact preview alternative, not stable ranges or other previews. Build metadata does not affect range matching, including exact preview selection; installed versions must still match their complete environment pins. Released representatives outside declared support warn; excluding a planned local release fails the release check. Focused requests can intentionally explore versions outside declared support. An audit reports declarations and release-policy findings without claiming assertions passed. Exit statuses are 0 for successful requested work, 1 for compatibility or unapproved support failures, and 2 for setup or coverage errors.
|
|
107
107
|
|
|
108
|
-
Focused peer/range requests validate manifest pins and skip versions outside the requested range before acquiring stages or preparing dependencies. Coordinated candidates are selected by their
|
|
108
|
+
Focused peer/range requests validate manifest pins and skip versions outside the requested range before acquiring stages or preparing dependencies. Coordinated candidates are selected by their resolved intended versions, and exact-preview selection follows the same rules as coverage. Excluded environments need no lockfile or available stage. Disjoint requests for the same peer can run independently in one checkout without parsing lock files. Requests that select the same environment still contend for its stage, including requests for different peers in a coupled environment. Selected installations retain all frozen-input, identity, and compatibility checks.
|
|
109
109
|
|
|
110
110
|
## Coordinated releases
|
|
111
111
|
|
|
@@ -122,54 +122,76 @@ Create a separate representative that explicitly names candidate peers. Leave an
|
|
|
122
122
|
}
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
-
|
|
125
|
+
Set `release.resolveDelta` to a synchronous/asynchronous resolver receiving `packageName`, `packageDirectory`, and `currentVersion`. For Changesets, use the optional helper:
|
|
126
126
|
|
|
127
127
|
```ts
|
|
128
|
-
import {
|
|
129
|
-
import
|
|
128
|
+
import { changesetsReleaseDelta } from "breakage/changesets"
|
|
129
|
+
import { semverPolicy } from "breakage"
|
|
130
130
|
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
})
|
|
134
|
-
const plan = JSON.parse(
|
|
135
|
-
await readFile(new URL("./release-plan.json", import.meta.url), "utf8"),
|
|
136
|
-
)
|
|
137
|
-
const version = plan[packageName]
|
|
138
|
-
return version ? { status: "release", version } : { status: "none" }
|
|
131
|
+
const release = {
|
|
132
|
+
resolveDelta: changesetsReleaseDelta(),
|
|
133
|
+
policy: semverPolicy({ beforeV1: "minor" }),
|
|
139
134
|
}
|
|
140
135
|
```
|
|
141
136
|
|
|
142
|
-
Missing intent blocks that candidate environment. Inspection errors and invalid evidence are setup errors, not absent intent.
|
|
137
|
+
Assign this object to `npm.release`, or share it at the top level with `defineConfig`. A custom resolver returns `{ previousVersion, version, delta }` or `undefined`; errors must throw. Missing intent blocks that candidate environment. Inspection errors and invalid evidence are setup errors, not absent intent. Both transition versions must be exact, the target must be at least the current workspace version, and the delta must match the versions. Candidate identity uses the transition's `version`; no breaking-change policy is applied to candidate enrollment. The candidate archive may have its workspace version or the intended version. Breakage reports both versions. If they differ, it substitutes the intended version in the installed peer's disposable manifest using a fresh file and atomic replacement, preserving shared cache files, other environments, the archive, and the workspace. Archives already carrying their intended version need no substitution.
|
|
143
138
|
|
|
144
|
-
That substitution simulates release metadata; it does not rebuild the package or change version constants embedded in executable code. Your build owns those constants. To exercise the final version metadata directly, supply a peer archive already built with the
|
|
139
|
+
That substitution simulates release metadata; it does not rebuild the package or change version constants embedded in executable code. Your build owns those constants. To exercise the final version metadata directly, supply a peer archive already built with the resolved intended version.
|
|
145
140
|
|
|
146
|
-
Both `npm check` and its coverage-only audit inspect each currently declared `localPeers` package's release intent, even if you omitted its candidate environment. A
|
|
141
|
+
Both `npm check` and its coverage-only audit inspect each currently declared `localPeers` package's release intent, even if you omitted its candidate environment. A resolved intended version must be included in the integration's peer range and covered by a coordinated representative. Missing either fails with exit 2; an installed peer at the same version does not substitute for testing the local candidate. Optional local peers have the same obligation when declared. Resolver errors and invalid evidence fail setup. Resolvers are evaluated once per package and baseline context per invocation, and must return that package's own release intent.
|
|
147
142
|
|
|
148
|
-
For a core still labeled `0.53.3` with a planned `0.54.0` release, an integration declaring only `^0.53.3` fails. Adding `|| ^0.54.0` is recommended when retaining the older era. Admission uses version membership: `0.54.0`, `*`, and other ranges containing that version qualify too. Exact-preview enrollment, coverage of other declared eras, and
|
|
143
|
+
For a core still labeled `0.53.3` with a planned `0.54.0` release, an integration declaring only `^0.53.3` fails. Adding `|| ^0.54.0` is recommended when retaining the older era. Admission uses version membership: `0.54.0`, `*`, and other ranges containing that version qualify too. Exact-preview enrollment, coverage of other declared eras, and breaking release evidence for dropping old support still apply. Your delta resolver supplies the intended transition before a versioning PR is merged; Breakage does not infer it from an open PR.
|
|
149
144
|
|
|
150
145
|
Candidate results are conditional on publication. They can cover a new era, but cannot excuse an incompatible released version that the integration still advertises. Unreleased local edits with no release intent create no new coordination obligation. Registering a currently declared local peer means the integration must accommodate its planned releases. Registrations retained only for historical resolution do not impose this obligation after the peer declaration is removed. The repository owns publication order.
|
|
151
146
|
|
|
152
|
-
Every coordinated candidate must also accept the representative's peer versions in its own normalized `peerDependencies`. For example, a candidate declaring `minimatch: ^8` cannot be approved alongside Minimatch 9, even if the integration declares `^9` and its assertions pass. Both full checks and coverage audits reject that conflict before candidate installation with exit 2, identifying the candidate, required range, and observed version. The representative must supply required peers needed only by a coordinated candidate too; these additional identities do not require separate integration suites. Absent optional peers are allowed, but present optional peers must satisfy their ranges. Another coordinated peer contributes its
|
|
147
|
+
Every coordinated candidate must also accept the representative's peer versions in its own normalized `peerDependencies`. For example, a candidate declaring `minimatch: ^8` cannot be approved alongside Minimatch 9, even if the integration declares `^9` and its assertions pass. Both full checks and coverage audits reject that conflict before candidate installation with exit 2, identifying the candidate, required range, and observed version. The representative must supply required peers needed only by a coordinated candidate too; these additional identities do not require separate integration suites. Absent optional peers are allowed, but present optional peers must satisfy their ranges. Another coordinated peer contributes its resolved intended version; a peer on the integration itself uses the integration's current archive version.
|
|
153
148
|
|
|
154
149
|
Focused `npm test` commands remain available for exploration before advertising an upcoming version. They warn about coordinated candidates' incompatible peer ranges and still execute, while missing required peers remain setup errors. They check release intent for the selected candidate environments but do not audit every planned local release; require the full check or coverage audit as part of the release gate.
|
|
155
150
|
|
|
156
|
-
##
|
|
151
|
+
## Shared release configuration
|
|
157
152
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
To accept intentional support changes, provide both release intent for the integration and an explicit breaking-release policy:
|
|
153
|
+
Use `defineConfig` to share a release resolver and policy with public checks:
|
|
161
154
|
|
|
162
155
|
```ts
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
156
|
+
import { defineConfig, semverPolicy } from "breakage"
|
|
157
|
+
import { changesetsReleaseDelta } from "breakage/changesets"
|
|
158
|
+
|
|
159
|
+
export default defineConfig({
|
|
160
|
+
testPattern: "tests/public/**/*",
|
|
161
|
+
test: "npm run test:public",
|
|
162
|
+
release: {
|
|
163
|
+
resolveDelta: changesetsReleaseDelta(),
|
|
164
|
+
policy: semverPolicy({ beforeV1: "minor" }),
|
|
165
|
+
},
|
|
166
|
+
npm: {
|
|
167
|
+
artifact: "artifacts/integration.tgz",
|
|
168
|
+
representatives: "peer-environments/*",
|
|
169
|
+
lockfile: "package-lock.json",
|
|
170
|
+
prepareCommand: "npm ci --ignore-scripts --workspaces=false",
|
|
171
|
+
suites: {
|
|
172
|
+
"atom.io": {
|
|
173
|
+
files: ["contracts/core/**/*.ts"],
|
|
174
|
+
command: "node --test contracts/core/*.test.ts",
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
baseline: { tagPattern: "refs/tags/my-integration@" },
|
|
178
|
+
},
|
|
179
|
+
})
|
|
170
180
|
```
|
|
171
181
|
|
|
172
|
-
|
|
182
|
+
`defineConfig` inherits the top-level `release` into `npm.release`; an explicit `npm.release` overrides it. Programmatic callers can pass the resulting `config.npm` to `npmCheck` or `npmTest`. Both npm and public CLI commands use the same config loader. npm paths remain relative to the config file's directory, or to `npm.packageDirectory` resolved from it.
|
|
183
|
+
|
|
184
|
+
`npm.release.resolveDelta` replaces `releaseIntent` and returns `{ previousVersion, version, delta }` or `undefined`. Resolution is cached per package and baseline context for each npm invocation. Support checks supply `baseline: { version }` from the integration's historical manifest; Breakage rejects a transition whose `previousVersion` disagrees with that baseline. Local-peer candidate checks have no historical baseline and use the transition's target version for candidate identity and coordination obligations. `changesetsReleaseDelta()` is a factory that returns the resolver, comparing the supplied baseline (or current manifest) with the planned version before Changesets versioning and the manifest version afterward. Because the manifest fallback also supplies a target for an unchanged registered local peer, using it opts those peers into candidate coverage too. A custom resolver can return `undefined` for packages that should have no local release intent. See the [release configuration guide](README.md#typescript-configuration-and-release-intent) for optional Changesets dependencies and standalone helpers.
|
|
185
|
+
|
|
186
|
+
`npm.release.policy` replaces `certifyBreaking`. The default requires a major increment; `semverPolicy({ beforeV1: "minor" })` permits pre-1.0 minor increments too. Missing evidence or a compatible version delta leaves a support drop unapproved. Invalid evidence and resolver/policy errors fail setup. Compatibility failures against still-declared peers remain failures even when the integration has a breaking release. The old `releaseIntent` and `certifyBreaking` hooks are removed and fail with migration guidance. Policies must be functions and receive `{ previousVersion, version, delta }`.
|
|
187
|
+
|
|
188
|
+
`prepareCommand`, `installCommand`, and each suite's `command` are nonempty shell strings. Preparation runs in the representative directory; installation and suites run in its disposable `test/` directory. Breakage captures stdout and stderr for reporting. All nonzero preparation/installation results fail setup; suite exit 1 denotes incompatibility and other nonzero exits denote setup failure. Release-delta resolvers and policies are functions; the optional matrix's archive preparation accepts a command or function.
|
|
189
|
+
|
|
190
|
+
## Removing support
|
|
191
|
+
|
|
192
|
+
`npm check` compares semantic version sets with the previous release. Narrowing a supported range, adding a new required peer, or making an optional peer required produces an end-of-support finding. Equivalent range rewrites do not. Removing a peer requirement entirely relaxes consumer constraints. Preview support is compared too. Optional-peer absence testing is deferred; making an existing optional peer required is still detected as an installation-contract change.
|
|
193
|
+
|
|
194
|
+
The shared `release` model accepts intentional support changes when the integration has a breaking version delta against its previous release. Reports use support status `breaking-release` and include `{ previousVersion, version, delta, breaking }` in `support.release`. Missing intent or a compatible delta leaves support changes unapproved. Resolver or policy exceptions fail setup. Public checks use the same release model; compatibility failures against still-declared peers always remain failures.
|
|
173
195
|
|
|
174
196
|
## Installation, isolation, and recovery
|
|
175
197
|
|
|
@@ -189,26 +211,158 @@ Tests, candidate packages, and their runtime dependencies must resolve the same
|
|
|
189
211
|
|
|
190
212
|
## CI and caching
|
|
191
213
|
|
|
192
|
-
Keep the existing public check as `test:breakage` and add the npm check to that release gate, or use `test:breakage:npm` for a separate required job.
|
|
214
|
+
Keep the existing public check as `test:breakage` and add the npm check to that release gate, or use `test:breakage:npm` for a separate required job. For a local package, `npm check` still performs the complete check without a matrix plan. For a repository with several packages or distributed runners, use the optional plan → execute → verify protocol below. CI owns scheduling and artifact transport; Breakage owns discovery, audits, candidate identity, and receipt verification.
|
|
215
|
+
|
|
216
|
+
### Configure and plan a matrix
|
|
217
|
+
|
|
218
|
+
Keep each package's existing `npm` configuration, representative manifests/lock files, and contracts. Add this to the root `breakage.config.ts`:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
import { defineConfig } from "breakage"
|
|
222
|
+
|
|
223
|
+
export default defineConfig({
|
|
224
|
+
npmMatrix: {
|
|
225
|
+
configs: ["packages/*/breakage.config.ts"],
|
|
226
|
+
prepare: "pnpm exec turbo run pack:breakage",
|
|
227
|
+
label: ({ packageName, description, representative }) =>
|
|
228
|
+
`${packageName}: ${description ?? representative}`,
|
|
229
|
+
},
|
|
230
|
+
})
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
`configs` is a root-relative glob or list of globs. Only matched configs with an `npm` section participate; a package is discovered once even when patterns overlap. Package names must be unique. Root matrix configuration does not implicitly supply release hooks to package configs: share `release` through a TypeScript module and `defineConfig` as usual. Each required peer needs a relationship suite, each declared era needs coverage, and every selected representative must have executable audited suites. Keep exploratory environments outside the matrix's configured representative globs.
|
|
234
|
+
|
|
235
|
+
`prepare` runs once after the coverage/support audits. Use your existing build/pack task to create the exact `artifact` paths in the package configs, including registered local peers. It must not alter tracked sources, manifests, lock files, contracts, or release evidence. Tool choice and dependency build order remain yours. Alternatively, provide an async function receiving `{ rootDirectory, archives }`; each deduplicated archive request contains `{ packageName, directory, artifact }`, with both paths relative to the root. Its return value is ignored, and throwing fails planning. `label` is optional and receives package name, representative path, optional manifest description, and audited peers; the default is `packageName: representative`.
|
|
236
|
+
|
|
237
|
+
Use a clean Git checkout and ignore generated archives, `.breakage/npm-plan/`, `.breakage/receipts/`, and each representative's `test/` directory. Commit the intended source/config changes before planning. All package inputs and configured archive paths must stay within the checkout. The root must be the Git checkout root; the default is the current directory. Planning requires baseline tags or pinned baseline files and the same Changesets/tool dependencies as the package checks. With `changesetsReleaseDelta()`, both pending changesets and versioned manifests supply the existing release evidence; no matrix-specific release script is needed.
|
|
238
|
+
|
|
239
|
+
```sh
|
|
240
|
+
breakage npm plan --config breakage.config.ts --out .breakage/npm-plan
|
|
241
|
+
# stdout is one JSON object: {"include":[{"id":"...","label":"...",...}]}
|
|
242
|
+
# Copy an id from that output; execute every id before verifying.
|
|
243
|
+
breakage npm execute <id> --plan .breakage/npm-plan/plan.json --receipt .breakage/receipts/<id>.json
|
|
244
|
+
breakage npm verify --plan .breakage/npm-plan/plan.json --receipts '.breakage/receipts/*.json'
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
`plan` writes `plan.json` and content-addressed archives into the bundle directory, defaulting to `.breakage/npm-plan`. `--root` changes the checkout root for all three commands; config, output, plan, and receipt paths resolve from it. Quoting the receipt glob lets Breakage collect all matching files, including duplicates from different runners. Empty discovery, uncovered eras, unapproved support changes, and preparation failures are errors. A failed re-plan removes the previous `plan.json` in that output directory. After cancellation, discard the incomplete bundle and inspect/remove its `.lock` directory before retrying.
|
|
248
|
+
|
|
249
|
+
The plan has `formatVersion: 1`, a fresh run identity, source commit, configuration and input fingerprints, release evidence, coverage/support audits, expected environments/suites/peer identities, and archive SHA-256 digests. Its `id` binds the complete payload. Execution consumes the bundled archives directly; workers do not repack or restore archives over workspace outputs. Each receipt has `formatVersion: 1`, `planId`, environment `id`, `exitCode`, and a report or setup error. Environment IDs remain stable for a config/representative path, while plan IDs change on every planning run.
|
|
250
|
+
|
|
251
|
+
Execution rechecks the source, configuration, representative discovery, contract/manifests/lockfile inputs, release evidence, and archive digests before and after running the shared npm test engine with exactly the planned suites. A representative can contain a peer outside its declared support range while covering another peer; matrix execution omits that unsupported relationship's suite, matching the audit. Full peer identity and configuration validation still apply. Explicit `npm test --representative` exploration continues to run every configured suite whose peer is present. A valid plan produces a failure receipt on ordinary setup or compatibility failure. An invalid/unreadable plan or killed process may produce no receipt; that cannot satisfy verification. Assertions retain exit 1, setup failures exit 2, and successful execution exits 0. Planning and aggregate verification return 0 on success and 2 on any failure.
|
|
252
|
+
|
|
253
|
+
### Use the same protocol from TypeScript
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
import { readFile } from "node:fs/promises"
|
|
257
|
+
import { npmPlan, npmMatrix, npmExecute, npmVerify } from "breakage"
|
|
258
|
+
import config from "./breakage.config.ts"
|
|
259
|
+
|
|
260
|
+
const location = { rootDirectory: process.cwd() }
|
|
261
|
+
if (!config.npmMatrix) throw new Error("Configure npmMatrix first.")
|
|
262
|
+
const plan = await npmPlan(config.npmMatrix, location)
|
|
263
|
+
const receipts = []
|
|
264
|
+
for (const { id } of npmMatrix(plan).include) {
|
|
265
|
+
receipts.push(await npmExecute(plan, id, location))
|
|
266
|
+
}
|
|
267
|
+
const result = await npmVerify(plan, receipts, location)
|
|
268
|
+
process.exitCode = result.exitCode
|
|
269
|
+
|
|
270
|
+
// Another runner can load the transported bundle without depending on its old path.
|
|
271
|
+
const transportedPlan: unknown = JSON.parse(
|
|
272
|
+
await readFile(".breakage/npm-plan/plan.json", "utf8"),
|
|
273
|
+
)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
The exported types are `NpmMatrixConfig`, `NpmPrepareContext`, `NpmArchiveRequest`, `NpmMatrixLabelContext`, `NpmMatrixLocation`, `NpmPlan`, `NpmReceipt`, and `NpmVerification`. `npmVerify` validates the checkout and bundle as well as receipts. `verifyNpmReceipts(plan, receipts)` provides pure structural/audit/receipt verification for custom orchestrators that independently validate source/config/archive identity. It does not inspect the filesystem. Receipt order and runner-local installed paths do not affect either verifier.
|
|
277
|
+
|
|
278
|
+
### Distribute the matrix in CI
|
|
279
|
+
|
|
280
|
+
This GitHub Actions example assumes your existing `./.github/actions/setup` installs pinned Node/package-manager versions and frozen workspace dependencies. Keep the plan bundle and receipts specific to the current workflow run; fetch release history in every checkout. The verify job must require successful planning and execution jobs as well as complete receipts, so cancellation or skipped jobs cannot reuse old success. Keep the ordinary public check required separately.
|
|
193
281
|
|
|
194
282
|
```yaml
|
|
283
|
+
name: Peer compatibility
|
|
284
|
+
on: [pull_request]
|
|
285
|
+
permissions:
|
|
286
|
+
contents: read
|
|
195
287
|
jobs:
|
|
196
|
-
|
|
288
|
+
plan:
|
|
289
|
+
runs-on: ubuntu-latest
|
|
290
|
+
outputs:
|
|
291
|
+
matrix: ${{ steps.plan.outputs.matrix }}
|
|
197
292
|
steps:
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
293
|
+
- uses: actions/checkout@v4
|
|
294
|
+
with:
|
|
295
|
+
fetch-depth: 0
|
|
296
|
+
- uses: ./.github/actions/setup
|
|
297
|
+
- id: plan
|
|
298
|
+
run: |
|
|
299
|
+
matrix=$(pnpm exec breakage npm plan)
|
|
300
|
+
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"
|
|
301
|
+
- uses: actions/upload-artifact@v4
|
|
302
|
+
with:
|
|
303
|
+
name: peer-plan
|
|
304
|
+
path: .breakage/npm-plan/
|
|
305
|
+
include-hidden-files: true
|
|
306
|
+
if-no-files-found: error
|
|
307
|
+
execute:
|
|
308
|
+
needs: plan
|
|
309
|
+
runs-on: ubuntu-latest
|
|
310
|
+
name: ${{ matrix.label }}
|
|
201
311
|
strategy:
|
|
202
312
|
fail-fast: false
|
|
203
|
-
matrix:
|
|
204
|
-
|
|
313
|
+
matrix: ${{ fromJSON(needs.plan.outputs.matrix) }}
|
|
314
|
+
steps:
|
|
315
|
+
- uses: actions/checkout@v4
|
|
316
|
+
with:
|
|
317
|
+
fetch-depth: 0
|
|
318
|
+
- uses: ./.github/actions/setup
|
|
319
|
+
- uses: actions/download-artifact@v4
|
|
320
|
+
with:
|
|
321
|
+
name: peer-plan
|
|
322
|
+
path: .breakage/npm-plan/
|
|
323
|
+
- env:
|
|
324
|
+
ENVIRONMENT_ID: ${{ matrix.id }}
|
|
325
|
+
run: pnpm exec breakage npm execute "$ENVIRONMENT_ID" --plan .breakage/npm-plan/plan.json --receipt ".breakage/receipts/$ENVIRONMENT_ID.json"
|
|
326
|
+
- uses: actions/upload-artifact@v4
|
|
327
|
+
if: always()
|
|
328
|
+
with:
|
|
329
|
+
name: peer-receipt-${{ matrix.id }}
|
|
330
|
+
path: .breakage/receipts/
|
|
331
|
+
include-hidden-files: true
|
|
332
|
+
if-no-files-found: error
|
|
333
|
+
verify:
|
|
334
|
+
name: Peer compatibility
|
|
335
|
+
if: always()
|
|
336
|
+
needs: [plan, execute]
|
|
337
|
+
runs-on: ubuntu-latest
|
|
205
338
|
steps:
|
|
206
|
-
|
|
207
|
-
|
|
339
|
+
- env:
|
|
340
|
+
PLAN_RESULT: ${{ needs.plan.result }}
|
|
341
|
+
EXECUTION_RESULT: ${{ needs.execute.result }}
|
|
342
|
+
run: test "$PLAN_RESULT" = success && test "$EXECUTION_RESULT" = success
|
|
343
|
+
- uses: actions/checkout@v4
|
|
344
|
+
with:
|
|
345
|
+
fetch-depth: 0
|
|
346
|
+
- uses: ./.github/actions/setup
|
|
347
|
+
- uses: actions/download-artifact@v4
|
|
348
|
+
with:
|
|
349
|
+
name: peer-plan
|
|
350
|
+
path: .breakage/npm-plan/
|
|
351
|
+
- uses: actions/download-artifact@v4
|
|
352
|
+
with:
|
|
353
|
+
pattern: peer-receipt-*
|
|
354
|
+
path: .breakage/receipts/
|
|
355
|
+
- run: pnpm exec breakage npm verify --plan .breakage/npm-plan/plan.json --receipts '.breakage/receipts/**/*.json'
|
|
208
356
|
```
|
|
209
357
|
|
|
210
|
-
|
|
358
|
+
Keep downloaded receipt artifacts in separate subdirectories: merging them could overwrite duplicate filenames before the verifier sees them. Require the stable aggregate `Peer compatibility` job. Pin action references according to your repository policy. Other CI systems can transport the same JSON and archive bundle, schedule `npmMatrix(plan).include`, and run the same commands.
|
|
359
|
+
|
|
360
|
+
### Remove repository-specific orchestration
|
|
361
|
+
|
|
362
|
+
For atom.io-style scripts, replace discovery/coverage commands with `npm plan`, archive collection and copying with its bundle, per-environment wrappers with `npm execute`, and custom receipt validation with `npm verify`. Delete the custom plan/receipt types, hashing, suite enumeration, and aggregate verifier tests once the repository uses the protocol. Keep package configs/shared release helpers, authored peer environments/contracts, the build/pack task, tool setup, and CI artifact transport. No workspace manager, task runner, Changesets versioning, or CI provider is invoked implicitly.
|
|
363
|
+
|
|
364
|
+
Verification rejects missing, duplicate, unexpected, malformed, stale, failed, or skipped receipts; wrong package/peer identities; audit reports substituted for execution; and missing, duplicate, unexecuted, or failing suites. It validates coverage/support audits as well as executions. The protocol checks consistency within trusted CI, not cryptographic attestation of an untrusted worker: a worker able to fabricate both plans and receipts can fabricate success. Commands, hooks, environment variables, and external services remain repository-owned inputs; use identical toolchains and deterministic hooks across jobs.
|
|
211
365
|
|
|
212
|
-
Start uncached. A correct cache key includes candidate/peer archives, suite files, representative manifests and lock files, observed installation identities, configuration and imported helpers, baseline, release-plan inputs, and command/environment/toolchain inputs. Unknown dynamic input means the check stays uncached. There is no new npm prelude
|
|
366
|
+
Start uncached. A correct cache key includes candidate/peer archives, suite files, representative manifests and lock files, observed installation identities, configuration and imported helpers, baseline, release-plan inputs, and command/environment/toolchain inputs. Unknown dynamic input means the check stays uncached. Do not reuse execution receipts across planning runs. There is no new npm prelude, and the matrix protocol is optional.
|
|
213
367
|
|
|
214
368
|
Programmatic consumers can import `npmCheck`, `npmTest`, `formatNpmReport`, and their exported types from `breakage`. Both check functions return a structured `NpmReport` with `exitCode`, coverage, support findings, and independent representative/suite results; `mode` distinguishes audit from execution.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "breakage",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Run yesterday’s tests against tomorrow’s release.",
|
|
5
5
|
"homepage": "https://github.com/jeremybanka/breakage#readme",
|
|
6
6
|
"bugs": "https://github.com/jeremybanka/breakage/issues",
|
|
@@ -15,6 +15,17 @@
|
|
|
15
15
|
"email": "hello@jeremybanka.com"
|
|
16
16
|
},
|
|
17
17
|
"type": "module",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/breakage.d.ts",
|
|
21
|
+
"default": "./dist/breakage.js"
|
|
22
|
+
},
|
|
23
|
+
"./changesets": {
|
|
24
|
+
"types": "./dist/changesets.d.ts",
|
|
25
|
+
"default": "./dist/changesets.js"
|
|
26
|
+
},
|
|
27
|
+
"./*": "./*"
|
|
28
|
+
},
|
|
18
29
|
"main": "dist/breakage.js",
|
|
19
30
|
"types": "dist/breakage.d.ts",
|
|
20
31
|
"bin": {
|
|
@@ -26,26 +37,54 @@
|
|
|
26
37
|
"src"
|
|
27
38
|
],
|
|
28
39
|
"dependencies": {
|
|
29
|
-
"arktype": "2.2.
|
|
30
|
-
"comline": "0.9.
|
|
40
|
+
"arktype": "2.2.9",
|
|
41
|
+
"comline": "0.9.3",
|
|
31
42
|
"minimatch": "10.2.6",
|
|
32
43
|
"semver": "7.8.5",
|
|
33
44
|
"simple-git": "4.0.2",
|
|
34
45
|
"takua": "0.2.3"
|
|
35
46
|
},
|
|
36
47
|
"devDependencies": {
|
|
37
|
-
"@
|
|
48
|
+
"@changesets/assemble-release-plan": "7.0.0",
|
|
49
|
+
"@changesets/config": "4.0.1",
|
|
50
|
+
"@changesets/pre": "3.0.0",
|
|
51
|
+
"@changesets/read": "1.0.1",
|
|
52
|
+
"@manypkg/get-packages": "3.1.0",
|
|
53
|
+
"@types/bun": "npm:bun-types@1.4.4",
|
|
38
54
|
"@types/semver": "7.8.0",
|
|
39
55
|
"typescript": "7.0.2"
|
|
40
56
|
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"@changesets/assemble-release-plan": "^7.0.0",
|
|
59
|
+
"@changesets/config": "^4.0.1",
|
|
60
|
+
"@changesets/pre": "^3.0.0",
|
|
61
|
+
"@changesets/read": "^1.0.1",
|
|
62
|
+
"@manypkg/get-packages": "^3.1.0"
|
|
63
|
+
},
|
|
64
|
+
"peerDependenciesMeta": {
|
|
65
|
+
"@changesets/assemble-release-plan": {
|
|
66
|
+
"optional": true
|
|
67
|
+
},
|
|
68
|
+
"@changesets/config": {
|
|
69
|
+
"optional": true
|
|
70
|
+
},
|
|
71
|
+
"@changesets/pre": {
|
|
72
|
+
"optional": true
|
|
73
|
+
},
|
|
74
|
+
"@changesets/read": {
|
|
75
|
+
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"@manypkg/get-packages": {
|
|
78
|
+
"optional": true
|
|
79
|
+
}
|
|
80
|
+
},
|
|
41
81
|
"publishConfig": {
|
|
42
82
|
"access": "public"
|
|
43
83
|
},
|
|
44
84
|
"scripts": {
|
|
45
|
-
"build": "vp pack
|
|
85
|
+
"build": "vp pack",
|
|
46
86
|
"check": "vp run -w check",
|
|
47
87
|
"postversion": "dprint fmt package.json",
|
|
48
|
-
"schema": "bun src/breakage.x.ts --outdir=dist -- schema",
|
|
49
88
|
"test": "vp run test:run",
|
|
50
89
|
"test:public": "BREAKAGE_CLI_ENTRYPOINT=./src/breakage.x.ts bun test -- ./tests/public",
|
|
51
90
|
"test:watch": "vp run test:run --watch"
|