breakage 0.8.3 → 0.8.5
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 +8 -6
- package/dist/breakage-B7dJsgcu.js +1235 -0
- package/dist/breakage-B7dJsgcu.js.map +1 -0
- package/dist/breakage.d.ts +113 -1
- package/dist/breakage.d.ts.map +1 -1
- package/dist/breakage.js +1 -1
- package/dist/breakage.npm.check.schema.json +16 -0
- package/dist/breakage.npm.help.schema.json +6 -0
- package/dist/breakage.npm.schema.json +6 -0
- package/dist/breakage.npm.test.$peer.$range.schema.json +16 -0
- package/dist/breakage.npm.test.schema.json +16 -0
- package/dist/breakage.x.js +109 -5
- package/dist/breakage.x.js.map +1 -1
- package/npm.md +214 -0
- package/package.json +9 -7
- package/src/breakage.ts +14 -0
- package/src/breakage.x.ts +30 -3
- package/src/npm/check.ts +448 -0
- package/src/npm/cli.ts +126 -0
- package/src/npm/command.ts +52 -0
- package/src/npm/config.ts +86 -0
- package/src/npm/environment.ts +661 -0
- package/src/npm/manifest.ts +134 -0
- package/src/npm/release.ts +189 -0
- package/src/npm/types.ts +104 -0
- package/dist/breakage-Dc4IldeW.js +0 -435
- package/dist/breakage-Dc4IldeW.js.map +0 -1
package/dist/breakage.x.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"breakage.x.js","names":["path"],"sources":["../src/breakage.x.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport * as path from \"node:path\"\nimport { inspect } from \"node:util\"\n\nimport { type } from \"arktype\"\nimport type { OptionsGroup } from \"comline\"\nimport {\n\tcli,\n\tcompletionResponse,\n\tencapsulate,\n\thelp,\n\tlogWarnings,\n\tnoOptions,\n\toptional,\n\toptions,\n\tparseBooleanOption,\n} from \"comline\"\nimport logger from \"takua\"\n\nimport type { BreakageOptions } from \"./breakage.ts\"\nimport { breakage, breakagePrelude } from \"./breakage.ts\"\n\nconst BREAKAGE_MANUAL = options(\n\t`Check for breaking changes in a package.`,\n\ttype({\n\t\t\"tagPattern?\": `string`,\n\t\t\"baselineFile?\": `string`,\n\t\ttestPattern: `string`,\n\t\ttestCommand: `string`,\n\t\tcertifyCommand: `string`,\n\t\t\"baseDirname?\": `string`,\n\t\t\"verbose?\": `boolean`,\n\t}),\n\t{\n\t\ttagPattern: {\n\t\t\taliases: [`tag-pattern`],\n\t\t\tcompletion: { repeatable: false },\n\t\t\tflag: `g`,\n\t\t\trequired: false,\n\t\t\tdescription: `RegExp which, if found matched to a git tag, will be considered a release tag for your library.`,\n\t\t\texample: `--tagPattern=\"my-library\"`,\n\t\t},\n\t\tbaselineFile: {\n\t\t\taliases: [`baseline-file`],\n\t\t\tcompletion: { repeatable: false, fileSystem: `files` },\n\t\t\trequired: false,\n\t\t\tdescription: `Use the exact locally fetched commit from a prelude snapshot instead of discovering remote tags.`,\n\t\t\texample: `--baseline-file=.breakage/baseline.json`,\n\t\t},\n\t\ttestPattern: {\n\t\t\taliases: [`test-pattern`, `pattern`],\n\t\t\tcompletion: { repeatable: false },\n\t\t\tflag: `p`,\n\t\t\trequired: true,\n\t\t\tdescription: `The pattern to match test files that test the public API of the library.`,\n\t\t\texample: `--pattern=\"*__public.test.ts\"`,\n\t\t},\n\t\ttestCommand: {\n\t\t\taliases: [`test-command`],\n\t\t\tcompletion: { repeatable: false },\n\t\t\tflag: `t`,\n\t\t\trequired: true,\n\t\t\tdescription: `Complete bash command that runs the tests for the library's public API.`,\n\t\t\texample: `--testCommand=\"npm run test\"`,\n\t\t},\n\t\tcertifyCommand: {\n\t\t\taliases: [`certify-command`],\n\t\t\tcompletion: { repeatable: false },\n\t\t\tflag: `c`,\n\t\t\trequired: true,\n\t\t\tdescription: `Complete bash command that determines whether a major version bump for your package is indicated in the workspace. Exit code 0 indicates that a major version bump is indicated, and exit code 1 indicates that no major version bump is indicated.`,\n\t\t\texample: `--certifyCommand=\\tsx scripts/certify-major-version-bump.node`,\n\t\t},\n\t\tbaseDirname: {\n\t\t\taliases: [`base-dir`, `base-dirname`],\n\t\t\tcompletion: { repeatable: false, fileSystem: `directories` },\n\t\t\tflag: `d`,\n\t\t\trequired: false,\n\t\t\tdescription: `The base directory to run the command from.`,\n\t\t\texample: `--baseDirname=./packages/my-package`,\n\t\t},\n\t\tverbose: {\n\t\t\tcompletion: { repeatable: false },\n\t\t\tflag: `v`,\n\t\t\trequired: false,\n\t\t\tdescription: `Prints out more information about the process.`,\n\t\t\texample: `--verbose`,\n\t\t\tparse: parseBooleanOption,\n\t\t},\n\t},\n) satisfies OptionsGroup<BreakageOptions>\n\nconst PRELUDE_MANUAL = options(\n\t`Resolve and fetch the release baseline for a cacheable compatibility check.`,\n\ttype({\n\t\t\"tagPattern?\": `string`,\n\t\t\"baseDirname?\": `string`,\n\t\t\"out?\": `string`,\n\t\t\"testPattern?\": `string`,\n\t\t\"testCommand?\": `string`,\n\t\t\"certifyCommand?\": `string`,\n\t\t\"baselineFile?\": `string`,\n\t\t\"verbose?\": `boolean`,\n\t}),\n\t{\n\t\t...BREAKAGE_MANUAL.optionConfigs,\n\t\ttestPattern: {\n\t\t\t...BREAKAGE_MANUAL.optionConfigs.testPattern,\n\t\t\trequired: false,\n\t\t},\n\t\ttestCommand: {\n\t\t\t...BREAKAGE_MANUAL.optionConfigs.testCommand,\n\t\t\trequired: false,\n\t\t},\n\t\tcertifyCommand: {\n\t\t\t...BREAKAGE_MANUAL.optionConfigs.certifyCommand,\n\t\t\trequired: false,\n\t\t},\n\t\tout: {\n\t\t\tcompletion: { repeatable: false, fileSystem: `files` },\n\t\t\trequired: false,\n\t\t\tdescription: `Gitignored snapshot path relative to the base directory (default: .breakage/baseline.json).`,\n\t\t\texample: `--out=.breakage/baseline.json`,\n\t\t},\n\t},\n)\n\nconst SCHEMA_MANUAL = options(\n\t`Create a copy of the schema for configuring breakage.`,\n\ttype({ \"outdir?\": `string` }),\n\t{\n\t\toutdir: {\n\t\t\taliases: [`out-dir`],\n\t\t\tcompletion: { repeatable: false, fileSystem: `directories` },\n\t\t\tflag: `o`,\n\t\t\trequired: false,\n\t\t\tdescription: `Directory to write the schema to.`,\n\t\t\texample: `--outdir=./dist`,\n\t\t},\n\t},\n)\n\nconst parse = cli(\n\t{\n\t\tcliName: `breakage`,\n\t\troutes: optional({\n\t\t\thelp: null,\n\t\t\tschema: null,\n\t\t\tprelude: optional({ $configPath: null }),\n\t\t\t$configPath: null,\n\t\t}),\n\t\trouteOptions: {\n\t\t\t\"\": BREAKAGE_MANUAL,\n\t\t\t$configPath: BREAKAGE_MANUAL,\n\t\t\thelp: noOptions(`Show usage.`),\n\t\t\tschema: SCHEMA_MANUAL,\n\t\t\tprelude: PRELUDE_MANUAL,\n\t\t\t\"prelude/$configPath\": PRELUDE_MANUAL,\n\t\t},\n\t\tpositionalCompletions: {\n\t\t\t$configPath: { fileSystem: `files` },\n\t\t\t\"prelude/$configPath\": { fileSystem: `files` },\n\t\t},\n\t\tdiscoverConfigPath: (args: readonly string[]) => {\n\t\t\tif (args[0] === `help` || args[0] === `schema`) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst configPath =\n\t\t\t\t(args[0] === `prelude` ? args[1] : args[0]) ??\n\t\t\t\tpath.join(process.cwd(), `breakage.config.json`)\n\t\t\treturn configPath\n\t\t},\n\t},\n\t{\n\t\tinfo: (message, ...data) => logger.info(`breakage`, message, data),\n\t\terror: (message, ...data) => logger.error(`breakage`, message, data),\n\t},\n)\n\nconst completion = await completionResponse(parse.definition, process.argv)\nif (completion !== undefined) {\n\tprocess.stdout.write(completion)\n} else {\n\tconst { inputs, warnings, writeJsonSchema } = parse(process.argv)\n\tlogWarnings(warnings)\n\n\tswitch (inputs.case) {\n\t\tcase `help`:\n\t\t\tconsole.log(help(parse.definition))\n\t\t\tbreak\n\t\tcase `schema`:\n\t\t\t{\n\t\t\t\tconst { outdir } = inputs.opts\n\t\t\t\twriteJsonSchema(outdir ?? `.`)\n\t\t\t\tprocess.stdout.write(`📝 Wrote json.schema.`)\n\t\t\t}\n\t\t\tbreak\n\t\tcase `prelude`:\n\t\tcase `prelude/$configPath`: {\n\t\t\tconst baseline = await breakagePrelude({\n\t\t\t\t...inputs.opts,\n\t\t\t\tout: inputs.opts.out ?? `.breakage/baseline.json`,\n\t\t\t})\n\t\t\tprocess.stdout.write(`Pinned ${baseline.ref} at ${baseline.commit}.\\n`)\n\t\t\tbreak\n\t\t}\n\t\tcase ``:\n\t\tcase `$configPath`: {\n\t\t\tconst { returnValue } = await encapsulate(() => breakage(inputs.opts), {\n\t\t\t\tconsole: true,\n\t\t\t\tstdout: true,\n\t\t\t})\n\t\t\tif (`testResult` in returnValue && returnValue.breakingChangesFound) {\n\t\t\t\tprocess.stdout.write(returnValue.testResult)\n\t\t\t}\n\t\t\tif (`breakingChangesFound` in returnValue) {\n\t\t\t\tif (returnValue.breakingChangesFound) {\n\t\t\t\t\tif (returnValue.breakingChangesCertified) {\n\t\t\t\t\t\tprocess.stdout.write(\n\t\t\t\t\t\t\t`👷 Breaking changes were found and certified.`,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tprocess.exit(0)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tprocess.stdout.write(\n\t\t\t\t\t\t\t`❌ Breaking changes were found, but not certified.`,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tprocess.exit(1)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tprocess.stdout.write(`✅ No breaking changes were found.`)\n\t\t\t\t\tprocess.exit(0)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tprocess.stdout.write(\n\t\t\t\t\t`💥 Breakage failed to determine breaking changes.` +\n\t\t\t\t\t\t`\\n` +\n\t\t\t\t\t\tinspect(returnValue),\n\t\t\t\t)\n\t\t\t\tprocess.exit(2)\n\t\t\t}\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"breakage.x.js","names":[],"sources":["../src/npm/cli.ts","../src/breakage.x.ts"],"sourcesContent":["import { existsSync } from \"node:fs\"\nimport { readFile } from \"node:fs/promises\"\nimport path from \"node:path\"\nimport { pathToFileURL } from \"node:url\"\n\nimport { type } from \"arktype\"\nimport type { OptionsGroup } from \"comline\"\nimport { options, parseBooleanOption } from \"comline\"\n\nimport { formatNpmReport, npmCheck, npmTest } from \"./check.ts\"\nimport { errorMessage } from \"./command.ts\"\nimport { validateConfig } from \"./config.ts\"\nimport { record } from \"./manifest.ts\"\nimport type { NpmTestSelection } from \"./types.ts\"\n\ntype CommonOptions = { config?: string | undefined; json?: boolean | undefined }\ntype CheckOptions = CommonOptions & { coverageOnly?: boolean | undefined }\ntype TestOptions = CommonOptions & { representative?: string | undefined }\n\nconst common = {\n\tconfig: {\n\t\trequired: false,\n\t\tdescription: `Configuration file (defaults to breakage.config.ts, then .json).`,\n\t\texample: `--config ./breakage.config.ts`,\n\t\tcompletion: { fileSystem: `files` as const, repeatable: false },\n\t},\n\tjson: {\n\t\trequired: false,\n\t\tdescription: `Emit the structured result.`,\n\t\texample: `--json`,\n\t\tparse: parseBooleanOption,\n\t},\n}\n\nexport const npmCheckManual: OptionsGroup<CheckOptions> = options(\n\t`Check npm peer compatibility and changes in declared support.`,\n\ttype({ \"config?\": `string`, \"json?\": `boolean`, \"coverageOnly?\": `boolean` }),\n\t{\n\t\t...common,\n\t\tcoverageOnly: {\n\t\t\taliases: [`coverage-only`],\n\t\t\trequired: false,\n\t\t\tdescription: `Audit coverage and support changes without executing assertions.`,\n\t\t\texample: `--coverage-only`,\n\t\t\tparse: parseBooleanOption,\n\t\t},\n\t},\n)\n\nexport const npmTestManual: OptionsGroup<TestOptions> = options(\n\t`Run current contracts in matching complete peer environments.`,\n\ttype({\n\t\t\"config?\": `string`,\n\t\t\"json?\": `boolean`,\n\t\t\"representative?\": `string`,\n\t}),\n\t{\n\t\t...common,\n\t\trepresentative: {\n\t\t\trequired: false,\n\t\t\tdescription: `Select an exact configured representative path relative to the package.`,\n\t\t\texample: `--representative peer-environments/react-19`,\n\t\t\tcompletion: { fileSystem: `directories`, repeatable: false },\n\t\t},\n\t},\n)\n\nexport function defaultConfig(directory: string): string {\n\treturn path.join(\n\t\tdirectory,\n\t\texistsSync(path.join(directory, `breakage.config.ts`))\n\t\t\t? `breakage.config.ts`\n\t\t\t: `breakage.config.json`,\n\t)\n}\n\nexport async function runNpmCli(\n\taction: `check` | `test`,\n\topts: CheckOptions & TestOptions,\n\tpeerSelection?: { peer: string; range: string },\n): Promise<number> {\n\ttry {\n\t\tconst filename = path.resolve(opts.config ?? defaultConfig(process.cwd()))\n\t\tconst loaded: unknown = filename.endsWith(`.json`)\n\t\t\t? JSON.parse(await readFile(filename, `utf8`))\n\t\t\t: ((await import(pathToFileURL(filename).href)) as { default: unknown })\n\t\t\t\t\t.default\n\t\tconst config = record(loaded) ? loaded.npm : undefined\n\t\tvalidateConfig(config)\n\t\tlet selection: NpmTestSelection | undefined\n\t\tif (action === `test`) {\n\t\t\tif (peerSelection && opts.representative)\n\t\t\t\tthrow new Error(`Choose a peer range or a representative, not both.`)\n\t\t\tselection =\n\t\t\t\tpeerSelection ??\n\t\t\t\t(opts.representative\n\t\t\t\t\t? { representative: opts.representative }\n\t\t\t\t\t: undefined)\n\t\t\tif (!selection)\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Use npm test <peer> <range> or npm test --representative <path>.`,\n\t\t\t\t)\n\t\t}\n\t\tconst options = {\n\t\t\tbaseDirectory: path.dirname(filename),\n\t\t\tcoverageOnly: opts.coverageOnly ?? false,\n\t\t}\n\t\tconst report = selection\n\t\t\t? await npmTest(config, selection, options)\n\t\t\t: await npmCheck(config, options)\n\t\tprocess.stdout.write(\n\t\t\topts.json\n\t\t\t\t? `${JSON.stringify(report, null, 2)}\\n`\n\t\t\t\t: formatNpmReport(report),\n\t\t)\n\t\treturn report.exitCode\n\t} catch (error) {\n\t\tconst message = errorMessage(error)\n\t\tprocess.stdout.write(\n\t\t\topts.json\n\t\t\t\t? `${JSON.stringify({ exitCode: 2, errors: [message] })}\\n`\n\t\t\t\t: `npm setup error: ${message}\\n`,\n\t\t)\n\t\treturn 2\n\t}\n}\n","#!/usr/bin/env node\n\nimport { inspect } from \"node:util\"\n\nimport { type } from \"arktype\"\nimport type { OptionsGroup } from \"comline\"\nimport {\n\tcli,\n\tcompletionResponse,\n\tencapsulate,\n\thelp,\n\tlogWarnings,\n\tnoOptions,\n\toptional,\n\toptions,\n\tparseBooleanOption,\n\trequired,\n} from \"comline\"\nimport logger from \"takua\"\n\nimport type { BreakageOptions } from \"./breakage.ts\"\nimport { breakage, breakagePrelude } from \"./breakage.ts\"\nimport {\n\tdefaultConfig,\n\tnpmCheckManual,\n\tnpmTestManual,\n\trunNpmCli,\n} from \"./npm/cli.ts\"\n\nconst BREAKAGE_MANUAL = options(\n\t`Check for breaking changes in a package.`,\n\ttype({\n\t\t\"tagPattern?\": `string`,\n\t\t\"baselineFile?\": `string`,\n\t\ttestPattern: `string`,\n\t\ttestCommand: `string`,\n\t\tcertifyCommand: `string`,\n\t\t\"baseDirname?\": `string`,\n\t\t\"verbose?\": `boolean`,\n\t}),\n\t{\n\t\ttagPattern: {\n\t\t\taliases: [`tag-pattern`],\n\t\t\tcompletion: { repeatable: false },\n\t\t\tflag: `g`,\n\t\t\trequired: false,\n\t\t\tdescription: `RegExp which, if found matched to a git tag, will be considered a release tag for your library.`,\n\t\t\texample: `--tagPattern=\"my-library\"`,\n\t\t},\n\t\tbaselineFile: {\n\t\t\taliases: [`baseline-file`],\n\t\t\tcompletion: { repeatable: false, fileSystem: `files` },\n\t\t\trequired: false,\n\t\t\tdescription: `Use the exact locally fetched commit from a prelude snapshot instead of discovering remote tags.`,\n\t\t\texample: `--baseline-file=.breakage/baseline.json`,\n\t\t},\n\t\ttestPattern: {\n\t\t\taliases: [`test-pattern`, `pattern`],\n\t\t\tcompletion: { repeatable: false },\n\t\t\tflag: `p`,\n\t\t\trequired: true,\n\t\t\tdescription: `The pattern to match test files that test the public API of the library.`,\n\t\t\texample: `--pattern=\"*__public.test.ts\"`,\n\t\t},\n\t\ttestCommand: {\n\t\t\taliases: [`test-command`],\n\t\t\tcompletion: { repeatable: false },\n\t\t\tflag: `t`,\n\t\t\trequired: true,\n\t\t\tdescription: `Complete bash command that runs the tests for the library's public API.`,\n\t\t\texample: `--testCommand=\"npm run test\"`,\n\t\t},\n\t\tcertifyCommand: {\n\t\t\taliases: [`certify-command`],\n\t\t\tcompletion: { repeatable: false },\n\t\t\tflag: `c`,\n\t\t\trequired: true,\n\t\t\tdescription: `Complete bash command that determines whether a major version bump for your package is indicated in the workspace. Exit code 0 indicates that a major version bump is indicated, and exit code 1 indicates that no major version bump is indicated.`,\n\t\t\texample: `--certifyCommand=\\tsx scripts/certify-major-version-bump.node`,\n\t\t},\n\t\tbaseDirname: {\n\t\t\taliases: [`base-dir`, `base-dirname`],\n\t\t\tcompletion: { repeatable: false, fileSystem: `directories` },\n\t\t\tflag: `d`,\n\t\t\trequired: false,\n\t\t\tdescription: `The base directory to run the command from.`,\n\t\t\texample: `--baseDirname=./packages/my-package`,\n\t\t},\n\t\tverbose: {\n\t\t\tcompletion: { repeatable: false },\n\t\t\tflag: `v`,\n\t\t\trequired: false,\n\t\t\tdescription: `Prints out more information about the process.`,\n\t\t\texample: `--verbose`,\n\t\t\tparse: parseBooleanOption,\n\t\t},\n\t},\n) satisfies OptionsGroup<BreakageOptions>\n\nconst PRELUDE_MANUAL = options(\n\t`Resolve and fetch the release baseline for a cacheable compatibility check.`,\n\ttype({\n\t\t\"tagPattern?\": `string`,\n\t\t\"baseDirname?\": `string`,\n\t\t\"out?\": `string`,\n\t\t\"testPattern?\": `string`,\n\t\t\"testCommand?\": `string`,\n\t\t\"certifyCommand?\": `string`,\n\t\t\"baselineFile?\": `string`,\n\t\t\"verbose?\": `boolean`,\n\t}),\n\t{\n\t\t...BREAKAGE_MANUAL.optionConfigs,\n\t\ttestPattern: {\n\t\t\t...BREAKAGE_MANUAL.optionConfigs.testPattern,\n\t\t\trequired: false,\n\t\t},\n\t\ttestCommand: {\n\t\t\t...BREAKAGE_MANUAL.optionConfigs.testCommand,\n\t\t\trequired: false,\n\t\t},\n\t\tcertifyCommand: {\n\t\t\t...BREAKAGE_MANUAL.optionConfigs.certifyCommand,\n\t\t\trequired: false,\n\t\t},\n\t\tout: {\n\t\t\tcompletion: { repeatable: false, fileSystem: `files` },\n\t\t\trequired: false,\n\t\t\tdescription: `Gitignored snapshot path relative to the base directory (default: .breakage/baseline.json).`,\n\t\t\texample: `--out=.breakage/baseline.json`,\n\t\t},\n\t},\n)\n\nconst SCHEMA_MANUAL = options(\n\t`Create a copy of the schema for configuring breakage.`,\n\ttype({ \"outdir?\": `string` }),\n\t{\n\t\toutdir: {\n\t\t\taliases: [`out-dir`],\n\t\t\tcompletion: { repeatable: false, fileSystem: `directories` },\n\t\t\tflag: `o`,\n\t\t\trequired: false,\n\t\t\tdescription: `Directory to write the schema to.`,\n\t\t\texample: `--outdir=./dist`,\n\t\t},\n\t},\n)\n\nconst parse = cli(\n\t{\n\t\tcliName: `breakage`,\n\t\troutes: optional({\n\t\t\thelp: null,\n\t\t\tschema: null,\n\t\t\tnpm: optional({\n\t\t\t\thelp: null,\n\t\t\t\tcheck: null,\n\t\t\t\ttest: optional({ $peer: required({ $range: null }) }),\n\t\t\t}),\n\t\t\tprelude: optional({ $configPath: null }),\n\t\t\t$configPath: null,\n\t\t}),\n\t\trouteOptions: {\n\t\t\t\"\": BREAKAGE_MANUAL,\n\t\t\t$configPath: BREAKAGE_MANUAL,\n\t\t\thelp: noOptions(`Show usage.`),\n\t\t\tschema: SCHEMA_MANUAL,\n\t\t\tnpm: noOptions(`Show npm compatibility commands.`),\n\t\t\t\"npm/help\": noOptions(`Show npm compatibility commands.`),\n\t\t\t\"npm/check\": npmCheckManual,\n\t\t\t\"npm/test\": npmTestManual,\n\t\t\t\"npm/test/$peer/$range\": npmTestManual,\n\t\t\tprelude: PRELUDE_MANUAL,\n\t\t\t\"prelude/$configPath\": PRELUDE_MANUAL,\n\t\t},\n\t\tpositionalCompletions: {\n\t\t\t$configPath: { fileSystem: `files` },\n\t\t\t\"prelude/$configPath\": { fileSystem: `files` },\n\t\t},\n\t\tdiscoverConfigPath: (args: readonly string[]) => {\n\t\t\tif (args[0] === `help` || args[0] === `schema` || args[0] === `npm`) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst configPath =\n\t\t\t\t(args[0] === `prelude` ? args[1] : args[0]) ??\n\t\t\t\tdefaultConfig(process.cwd())\n\t\t\treturn configPath\n\t\t},\n\t},\n\t{\n\t\tinfo: (message, ...data) => logger.info(`breakage`, message, data),\n\t\terror: (message, ...data) => logger.error(`breakage`, message, data),\n\t},\n)\n\nconst completion = await completionResponse(parse.definition, process.argv)\nif (completion !== undefined) {\n\tprocess.stdout.write(completion)\n} else {\n\tconst { inputs, warnings, writeJsonSchema } = parse(process.argv)\n\tlogWarnings(warnings)\n\n\tswitch (inputs.case) {\n\t\tcase `npm`:\n\t\tcase `npm/help`:\n\t\tcase `help`:\n\t\t\tconsole.log(help(parse.definition))\n\t\t\tbreak\n\t\tcase `npm/check`:\n\t\t\tprocess.exitCode = await runNpmCli(`check`, inputs.opts)\n\t\t\tbreak\n\t\tcase `npm/test`:\n\t\t\tprocess.exitCode = await runNpmCli(`test`, inputs.opts)\n\t\t\tbreak\n\t\tcase `npm/test/$peer/$range`:\n\t\t\tprocess.exitCode = await runNpmCli(`test`, inputs.opts, inputs.params)\n\t\t\tbreak\n\t\tcase `schema`:\n\t\t\t{\n\t\t\t\tconst { outdir } = inputs.opts\n\t\t\t\twriteJsonSchema(outdir ?? `.`)\n\t\t\t\tprocess.stdout.write(`📝 Wrote json.schema.`)\n\t\t\t}\n\t\t\tbreak\n\t\tcase `prelude`:\n\t\tcase `prelude/$configPath`: {\n\t\t\tconst baseline = await breakagePrelude({\n\t\t\t\t...inputs.opts,\n\t\t\t\tout: inputs.opts.out ?? `.breakage/baseline.json`,\n\t\t\t})\n\t\t\tprocess.stdout.write(`Pinned ${baseline.ref} at ${baseline.commit}.\\n`)\n\t\t\tbreak\n\t\t}\n\t\tcase ``:\n\t\tcase `$configPath`: {\n\t\t\tconst { returnValue } = await encapsulate(() => breakage(inputs.opts), {\n\t\t\t\tconsole: true,\n\t\t\t\tstdout: true,\n\t\t\t})\n\t\t\tif (`testResult` in returnValue && returnValue.breakingChangesFound) {\n\t\t\t\tprocess.stdout.write(returnValue.testResult)\n\t\t\t}\n\t\t\tif (`breakingChangesFound` in returnValue) {\n\t\t\t\tif (returnValue.breakingChangesFound) {\n\t\t\t\t\tif (returnValue.breakingChangesCertified) {\n\t\t\t\t\t\tprocess.stdout.write(\n\t\t\t\t\t\t\t`👷 Breaking changes were found and certified.`,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tprocess.exit(0)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tprocess.stdout.write(\n\t\t\t\t\t\t\t`❌ Breaking changes were found, but not certified.`,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tprocess.exit(1)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tprocess.stdout.write(`✅ No breaking changes were found.`)\n\t\t\t\t\tprocess.exit(0)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tprocess.stdout.write(\n\t\t\t\t\t`💥 Breakage failed to determine breaking changes.` +\n\t\t\t\t\t\t`\\n` +\n\t\t\t\t\t\tinspect(returnValue),\n\t\t\t\t)\n\t\t\t\tprocess.exit(2)\n\t\t\t}\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|
package/npm.md
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# npm peer compatibility
|
|
2
|
+
|
|
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
|
+
|
|
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 certifies.
|
|
6
|
+
|
|
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
|
+
|
|
9
|
+
## Declare support deliberately
|
|
10
|
+
|
|
11
|
+
Current candidates must declare explicit peer support eras, for example `"atom.io": "^0.53.3 || ^0.54.0"`. Moving workspace shorthand (`workspace:`, `workspace:*`, `workspace:^`, and `workspace:~`) is rejected, including in coordinated peer candidates. With `workspace:^`, advancing the local core from `0.53.3` to `0.54.0` silently changes the published support range and drops the previous era. Keep that decision explicit and test the versions you promise to support.
|
|
12
|
+
|
|
13
|
+
A fixed declaration such as `workspace:^0.53.3` is accepted and compared as `^0.53.3`, matching its packed form. Ordinary semver ranges and optional peers retain their meaning. Workspace aliases and relative paths are unsupported. Current source manifests are never rewritten by this normalization.
|
|
14
|
+
|
|
15
|
+
Older releases may have used moving shorthand. For the baseline comparison, register that peer's directory in `localPeers`; Breakage reads its version from the same historical commit as the integration manifest. For example, historical `workspace:^` becomes `^0.53.3` when that commit's peer version is `0.53.3`, even if today's checkout or release plan says `0.54.0`. Missing historical evidence is a setup error. The configured directory must exist at the baseline commit; automatic tracking across package moves is unsupported.
|
|
16
|
+
|
|
17
|
+
After removing a peer requirement, you may retain its historical registration even if its directory has been deleted from the current checkout. Breakage still checks existing registered directories for workspace-source leaks. A broken directory link remains an error.
|
|
18
|
+
|
|
19
|
+
## Start with released peers
|
|
20
|
+
|
|
21
|
+
In the integration package, create complete environments for the peer formulations you want to exercise. For example, `peer-environments/core-1/package.json`:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"name": "core-1-contracts",
|
|
26
|
+
"version": "1.0.0",
|
|
27
|
+
"private": true,
|
|
28
|
+
"type": "module",
|
|
29
|
+
"dependencies": { "atom.io": "1.12.0", "react": "19.0.0" }
|
|
30
|
+
}
|
|
31
|
+
```
|
|
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 certified intended versions supply the exact identities, and any accompanying dependency pins must match those versions.
|
|
34
|
+
|
|
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
|
+
|
|
37
|
+
Ignore the disposable execution directories in your repository:
|
|
38
|
+
|
|
39
|
+
```gitignore
|
|
40
|
+
peer-environments/*/test/
|
|
41
|
+
node_modules/
|
|
42
|
+
artifacts/
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Build and pack the integration before checking. `npm pack --ignore-scripts --pack-destination artifacts` creates an archive with a versioned filename; configure `artifact` to that file or have your build copy it to a stable path. Breakage checks the installed archive's name, version, and peer declarations against the current manifest. Repository preparation owns making the archive current, including generated files and runtime version constants.
|
|
46
|
+
|
|
47
|
+
Write current contracts that import the package and its peers by consumer package names. Keep fixtures and explicit runner configuration under the package directory. The command runs in `peer-environments/<name>/test/`, with those selected paths copied intact. Avoid source aliases, workspace-relative imports, and runner configurations that walk back to workspace source. A Node test runner contract might look like:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { test } from "node:test"
|
|
51
|
+
import { strict as assert } from "node:assert"
|
|
52
|
+
import { createIntegration } from "my-integration"
|
|
53
|
+
import { createStore } from "atom.io"
|
|
54
|
+
|
|
55
|
+
test("the integration uses a consumer's store", () => {
|
|
56
|
+
const store = createStore()
|
|
57
|
+
const integration = createIntegration(store)
|
|
58
|
+
assert.equal(integration.store, store)
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Use your actual public API and meaningful assertions. A suite must fail when no tests execute. Exit 1 denotes assertions failing; other nonzero exits denote setup failure. If your runner reports infrastructure errors as 1 too, wrap it to distinguish setup from assertions. Breakage separately classifies failures in preparation, artifact installation, coverage, and resolution as setup failures.
|
|
63
|
+
|
|
64
|
+
Create `breakage.config.ts`:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import type { NpmConfig } from "breakage"
|
|
68
|
+
|
|
69
|
+
export default {
|
|
70
|
+
npm: {
|
|
71
|
+
artifact: "artifacts/integration.tgz",
|
|
72
|
+
representatives: "peer-environments/*",
|
|
73
|
+
lockfile: "package-lock.json",
|
|
74
|
+
prepareCommand: "npm ci --ignore-scripts --workspaces=false",
|
|
75
|
+
suites: {
|
|
76
|
+
"atom.io": {
|
|
77
|
+
files: ["contracts/core/**/*.ts", "fixtures/**/*"],
|
|
78
|
+
command: "node --test contracts/core/*.test.ts",
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
localPeers: {
|
|
82
|
+
"atom.io": {
|
|
83
|
+
directory: "../atom.io",
|
|
84
|
+
artifact: "../atom.io/artifacts/core.tgz",
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
baseline: { tagPattern: "refs/tags/my-integration@" },
|
|
88
|
+
} satisfies NpmConfig,
|
|
89
|
+
}
|
|
90
|
+
```
|
|
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 certified 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
|
+
|
|
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
|
+
|
|
96
|
+
Run the package check or a focused test:
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
breakage npm check
|
|
100
|
+
breakage npm check --coverage-only
|
|
101
|
+
breakage npm test atom.io 1
|
|
102
|
+
breakage npm test --representative peer-environments/core-1
|
|
103
|
+
breakage npm check --config ./breakage.config.ts --json
|
|
104
|
+
```
|
|
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 uncertified support failures, and 2 for setup or coverage errors.
|
|
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 certified 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
|
+
|
|
110
|
+
## Coordinated releases
|
|
111
|
+
|
|
112
|
+
Create a separate representative that explicitly names candidate peers. Leave an unpublished peer out of the frozen dependency list:
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"name": "core-next-contracts",
|
|
117
|
+
"version": "1.0.0",
|
|
118
|
+
"private": true,
|
|
119
|
+
"type": "module",
|
|
120
|
+
"dependencies": { "react": "19.0.0" },
|
|
121
|
+
"breakage": { "candidates": ["atom.io"] }
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Add a typed `releaseIntent` callback to the npm configuration. This example reads a repository-owned JSON release plan; adapt it to Changesets or your release process. The function receives `packageName`, `packageDirectory`, and `currentVersion`, and may return synchronously or asynchronously:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { readFile } from "node:fs/promises"
|
|
129
|
+
import type { NpmConfig } from "breakage"
|
|
130
|
+
|
|
131
|
+
const releaseIntent: NonNullable<NpmConfig["releaseIntent"]> = async ({
|
|
132
|
+
packageName,
|
|
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" }
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Missing intent blocks that candidate environment. Inspection errors and invalid evidence are setup errors, not absent intent. Intended versions must be exact and at least the current workspace version. 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
|
+
|
|
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 certified intended version.
|
|
145
|
+
|
|
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 certified 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. Hook errors and invalid evidence fail setup. Hooks are evaluated once per package per invocation, and must return that package's own release intent.
|
|
147
|
+
|
|
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 certification for dropping old support still apply. Your release hook supplies the intended version before a versioning PR is merged; Breakage does not infer it from an open PR.
|
|
149
|
+
|
|
150
|
+
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
|
+
|
|
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 certified intended version; a peer on the integration itself uses the integration's current archive version.
|
|
153
|
+
|
|
154
|
+
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
|
+
|
|
156
|
+
## Removing support
|
|
157
|
+
|
|
158
|
+
`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.
|
|
159
|
+
|
|
160
|
+
To accept intentional support changes, provide both release intent for the integration and an explicit breaking-release policy:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
const certifyBreaking: NonNullable<NpmConfig["certifyBreaking"]> = ({
|
|
164
|
+
previousVersion,
|
|
165
|
+
release,
|
|
166
|
+
}) =>
|
|
167
|
+
previousVersion.startsWith("0.")
|
|
168
|
+
? release.delta === "minor" || release.delta === "major"
|
|
169
|
+
: release.delta === "major"
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
This callback also receives `changes`, with each peer's previous and current declaration. It must return a boolean; throwing fails setup. Public breakage is still handled by the ordinary public command's certification. Compatibility failures against still-declared peers cannot be certified away.
|
|
173
|
+
|
|
174
|
+
## Installation, isolation, and recovery
|
|
175
|
+
|
|
176
|
+
`prepareCommand` runs in each representative and must install from its frozen lock file. For another package manager, set `lockfile` and this command accordingly, keeping the environment independent of workspace package substitution. Breakage requires unchanged manifest/lock bytes and checks direct installed identities. An existing installation that changes peer versions during frozen preparation is reported as stale; rerun after reviewing the updated installation. The configured package manager establishes lock consistency; Breakage does not reimplement its lock format.
|
|
177
|
+
|
|
178
|
+
Installed peers may come from `dependencies`, `devDependencies`, or `optionalDependencies`. An optional dependency overrides a required dependency of the same name. When present, it supplies peer evidence and runs its selected relationship suite normally. When genuinely absent, it supplies no evidence: a required peer still fails, while an optional peer may be omitted. Broken installed links and missing manifests remain errors. Optional-peer absence coverage is not required.
|
|
179
|
+
|
|
180
|
+
A representative must not declare conflicting development and runtime requirements for the same dependency. For example, `dependencies.host: "1.0.0"` together with `devDependencies.host: "2.0.0"` is a setup error before installation. Remove the duplicate or give both declarations the same specifier. The same rule applies when the runtime declaration is optional.
|
|
181
|
+
|
|
182
|
+
Runtime dependencies must resolve through the representative or its test installation. Explicit package-store links remain supported, including dependencies alongside the linked package in its nearest `node_modules` namespace. Finding a dependency only in an unrelated ancestor installation is a setup error, even if it is optional and would make the assertions pass. Breakage checks this before and after execution.
|
|
183
|
+
|
|
184
|
+
The ignored `test/` directory is also the ownership lock. An existing directory is rejected without deleting it. Independent environments execute concurrently, while two runs cannot own the same environment. On normal completion or failure, Breakage removes only the stage it created. After a killed process, confirm its commands have stopped, inspect the remaining stage, and remove it before retrying.
|
|
185
|
+
|
|
186
|
+
The default candidate install runs npm with `--prefix . --workspaces=false --ignore-scripts --legacy-peer-deps --package-lock=false --no-audit --no-fund`. It installs the archive's runtime dependencies without automatically installing peers. `installCommand` may replace this with a repository-specific equivalent operating on the generated consumer manifest. It must leave the candidates physically inside the stage. A native dependency that needs a lifecycle build requires an intentional command override.
|
|
187
|
+
|
|
188
|
+
Tests, candidate packages, and their runtime dependencies must resolve the same physical configured peer instances. Breakage rejects shadow copies and workspace-source peer links, including links to configured local packages. pnpm-style store links into dependency installations are permitted. These checks establish installed package identity, not registry integrity attestation. Package-manager layout, runner aliases, and module loaders remain relevant: preserve consumer resolution and test your actual runner setup.
|
|
189
|
+
|
|
190
|
+
## CI and caching
|
|
191
|
+
|
|
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. Preparation must build and pack every participating candidate. A developer-owned matrix can run one configured representative per job, plus an audit job:
|
|
193
|
+
|
|
194
|
+
```yaml
|
|
195
|
+
jobs:
|
|
196
|
+
npm-audit:
|
|
197
|
+
steps:
|
|
198
|
+
# Checkout, install tools/dependencies, and prepare release evidence first.
|
|
199
|
+
- run: breakage npm check --coverage-only
|
|
200
|
+
npm-contracts:
|
|
201
|
+
strategy:
|
|
202
|
+
fail-fast: false
|
|
203
|
+
matrix:
|
|
204
|
+
representative: [peer-environments/core-1, peer-environments/core-2]
|
|
205
|
+
steps:
|
|
206
|
+
# Checkout, install tools/dependencies, and build/pack candidates first.
|
|
207
|
+
- run: breakage npm test --representative "${{ matrix.representative }}"
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
This is a job excerpt: add your runner, checkout, setup, and working directory. Require the audit, all matrix jobs, and the public check before release. Updating the authored representative list requires updating the required matrix. Breakage neither generates workflows nor aggregates cross-job receipts.
|
|
211
|
+
|
|
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 or mandatory execution plan.
|
|
213
|
+
|
|
214
|
+
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.8.
|
|
3
|
+
"version": "0.8.5",
|
|
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",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"breakage": "./dist/breakage.x.js"
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
|
+
"npm.md",
|
|
24
25
|
"dist",
|
|
25
26
|
"src"
|
|
26
27
|
],
|
|
@@ -28,24 +29,25 @@
|
|
|
28
29
|
"arktype": "2.2.8",
|
|
29
30
|
"comline": "0.9.0",
|
|
30
31
|
"minimatch": "10.2.6",
|
|
32
|
+
"semver": "7.8.5",
|
|
31
33
|
"simple-git": "4.0.2",
|
|
32
34
|
"takua": "0.2.3"
|
|
33
35
|
},
|
|
34
36
|
"devDependencies": {
|
|
35
37
|
"@types/bun": "npm:bun-types@1.4.3",
|
|
38
|
+
"@types/semver": "7.8.0",
|
|
36
39
|
"typescript": "7.0.2"
|
|
37
40
|
},
|
|
38
41
|
"publishConfig": {
|
|
39
42
|
"access": "public"
|
|
40
43
|
},
|
|
41
44
|
"scripts": {
|
|
42
|
-
"build": "vp pack &&
|
|
43
|
-
"check": "
|
|
44
|
-
"check:tsc": "tsc --noEmit",
|
|
45
|
+
"build": "vp pack && vp run schema",
|
|
46
|
+
"check": "vp run -w check",
|
|
45
47
|
"postversion": "dprint fmt package.json",
|
|
46
48
|
"schema": "bun src/breakage.x.ts --outdir=dist -- schema",
|
|
47
|
-
"
|
|
48
|
-
"test": "bun test -- ./
|
|
49
|
-
"test:watch": "
|
|
49
|
+
"test": "vp run test:run",
|
|
50
|
+
"test:public": "BREAKAGE_CLI_ENTRYPOINT=./src/breakage.x.ts bun test -- ./tests/public",
|
|
51
|
+
"test:watch": "vp run test:run --watch"
|
|
50
52
|
}
|
|
51
53
|
}
|
package/src/breakage.ts
CHANGED
|
@@ -13,6 +13,20 @@ import { withTestFileState } from "./test-file-state.ts"
|
|
|
13
13
|
|
|
14
14
|
export type { BreakageBaseline, BreakagePreludeOptions } from "./baseline.ts"
|
|
15
15
|
export { breakagePrelude } from "./baseline.ts"
|
|
16
|
+
export { formatNpmReport, npmCheck, npmTest } from "./npm/check.ts"
|
|
17
|
+
export type {
|
|
18
|
+
NpmConfig,
|
|
19
|
+
NpmPeerEvidence,
|
|
20
|
+
NpmReport,
|
|
21
|
+
NpmRepresentativeResult,
|
|
22
|
+
NpmSuite,
|
|
23
|
+
NpmSuiteResult,
|
|
24
|
+
NpmTestSelection,
|
|
25
|
+
ReleaseContext,
|
|
26
|
+
ReleaseEvidence,
|
|
27
|
+
ReleaseIntent,
|
|
28
|
+
SupportChange,
|
|
29
|
+
} from "./npm/types.ts"
|
|
16
30
|
|
|
17
31
|
export type BreakageOptions = {
|
|
18
32
|
tagPattern?: string | undefined
|
package/src/breakage.x.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import * as path from "node:path"
|
|
4
3
|
import { inspect } from "node:util"
|
|
5
4
|
|
|
6
5
|
import { type } from "arktype"
|
|
@@ -15,11 +14,18 @@ import {
|
|
|
15
14
|
optional,
|
|
16
15
|
options,
|
|
17
16
|
parseBooleanOption,
|
|
17
|
+
required,
|
|
18
18
|
} from "comline"
|
|
19
19
|
import logger from "takua"
|
|
20
20
|
|
|
21
21
|
import type { BreakageOptions } from "./breakage.ts"
|
|
22
22
|
import { breakage, breakagePrelude } from "./breakage.ts"
|
|
23
|
+
import {
|
|
24
|
+
defaultConfig,
|
|
25
|
+
npmCheckManual,
|
|
26
|
+
npmTestManual,
|
|
27
|
+
runNpmCli,
|
|
28
|
+
} from "./npm/cli.ts"
|
|
23
29
|
|
|
24
30
|
const BREAKAGE_MANUAL = options(
|
|
25
31
|
`Check for breaking changes in a package.`,
|
|
@@ -147,6 +153,11 @@ const parse = cli(
|
|
|
147
153
|
routes: optional({
|
|
148
154
|
help: null,
|
|
149
155
|
schema: null,
|
|
156
|
+
npm: optional({
|
|
157
|
+
help: null,
|
|
158
|
+
check: null,
|
|
159
|
+
test: optional({ $peer: required({ $range: null }) }),
|
|
160
|
+
}),
|
|
150
161
|
prelude: optional({ $configPath: null }),
|
|
151
162
|
$configPath: null,
|
|
152
163
|
}),
|
|
@@ -155,6 +166,11 @@ const parse = cli(
|
|
|
155
166
|
$configPath: BREAKAGE_MANUAL,
|
|
156
167
|
help: noOptions(`Show usage.`),
|
|
157
168
|
schema: SCHEMA_MANUAL,
|
|
169
|
+
npm: noOptions(`Show npm compatibility commands.`),
|
|
170
|
+
"npm/help": noOptions(`Show npm compatibility commands.`),
|
|
171
|
+
"npm/check": npmCheckManual,
|
|
172
|
+
"npm/test": npmTestManual,
|
|
173
|
+
"npm/test/$peer/$range": npmTestManual,
|
|
158
174
|
prelude: PRELUDE_MANUAL,
|
|
159
175
|
"prelude/$configPath": PRELUDE_MANUAL,
|
|
160
176
|
},
|
|
@@ -163,12 +179,12 @@ const parse = cli(
|
|
|
163
179
|
"prelude/$configPath": { fileSystem: `files` },
|
|
164
180
|
},
|
|
165
181
|
discoverConfigPath: (args: readonly string[]) => {
|
|
166
|
-
if (args[0] === `help` || args[0] === `schema`) {
|
|
182
|
+
if (args[0] === `help` || args[0] === `schema` || args[0] === `npm`) {
|
|
167
183
|
return
|
|
168
184
|
}
|
|
169
185
|
const configPath =
|
|
170
186
|
(args[0] === `prelude` ? args[1] : args[0]) ??
|
|
171
|
-
|
|
187
|
+
defaultConfig(process.cwd())
|
|
172
188
|
return configPath
|
|
173
189
|
},
|
|
174
190
|
},
|
|
@@ -186,9 +202,20 @@ if (completion !== undefined) {
|
|
|
186
202
|
logWarnings(warnings)
|
|
187
203
|
|
|
188
204
|
switch (inputs.case) {
|
|
205
|
+
case `npm`:
|
|
206
|
+
case `npm/help`:
|
|
189
207
|
case `help`:
|
|
190
208
|
console.log(help(parse.definition))
|
|
191
209
|
break
|
|
210
|
+
case `npm/check`:
|
|
211
|
+
process.exitCode = await runNpmCli(`check`, inputs.opts)
|
|
212
|
+
break
|
|
213
|
+
case `npm/test`:
|
|
214
|
+
process.exitCode = await runNpmCli(`test`, inputs.opts)
|
|
215
|
+
break
|
|
216
|
+
case `npm/test/$peer/$range`:
|
|
217
|
+
process.exitCode = await runNpmCli(`test`, inputs.opts, inputs.params)
|
|
218
|
+
break
|
|
192
219
|
case `schema`:
|
|
193
220
|
{
|
|
194
221
|
const { outdir } = inputs.opts
|