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
package/src/breakage.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { exec } from "node:child_process"
|
|
2
1
|
import path from "node:path"
|
|
3
2
|
|
|
4
3
|
import { minimatch } from "minimatch"
|
|
@@ -6,14 +5,60 @@ import { simpleGit } from "simple-git"
|
|
|
6
5
|
import type { Chronicle } from "takua"
|
|
7
6
|
import logger from "takua"
|
|
8
7
|
|
|
8
|
+
import { command, errorMessage } from "./task.ts"
|
|
9
|
+
import { readManifest, parseManifest } from "./npm/manifest.ts"
|
|
10
|
+
import {
|
|
11
|
+
resolveReleaseDelta,
|
|
12
|
+
versionDelta,
|
|
13
|
+
validateRelease,
|
|
14
|
+
rejectCertificationOptions,
|
|
15
|
+
} from "./release.ts"
|
|
16
|
+
import type { ReleaseConfig, VersionDelta } from "./release.ts"
|
|
9
17
|
import { readBaseline } from "./baseline.ts"
|
|
10
18
|
import { withDirectoryLock } from "./directory-lock.ts"
|
|
11
19
|
import { latestReleaseTag as selectLatestReleaseTag } from "./release-tag.ts"
|
|
12
20
|
import { withTestFileState } from "./test-file-state.ts"
|
|
13
21
|
|
|
22
|
+
export { defineConfig, loadConfig } from "./config.ts"
|
|
23
|
+
export type { BreakageConfig } from "./config.ts"
|
|
24
|
+
export {
|
|
25
|
+
semverPolicy,
|
|
26
|
+
versionTransition,
|
|
27
|
+
versionDelta,
|
|
28
|
+
resolveReleaseDelta,
|
|
29
|
+
} from "./release.ts"
|
|
30
|
+
export type {
|
|
31
|
+
ReleaseConfig,
|
|
32
|
+
ReleasePolicy,
|
|
33
|
+
ReleasePolicyFunction,
|
|
34
|
+
SemverPolicyOptions,
|
|
35
|
+
VersionTransition,
|
|
36
|
+
ReleaseDeltaResolver,
|
|
37
|
+
ReleaseDeltaContext,
|
|
38
|
+
VersionDelta,
|
|
39
|
+
} from "./release.ts"
|
|
40
|
+
export type { CommandResult } from "./task.ts"
|
|
41
|
+
|
|
14
42
|
export type { BreakageBaseline, BreakagePreludeOptions } from "./baseline.ts"
|
|
15
43
|
export { breakagePrelude } from "./baseline.ts"
|
|
16
44
|
export { formatNpmReport, npmCheck, npmTest } from "./npm/check.ts"
|
|
45
|
+
export {
|
|
46
|
+
npmPlan,
|
|
47
|
+
npmExecute,
|
|
48
|
+
npmVerify,
|
|
49
|
+
npmMatrix,
|
|
50
|
+
verifyNpmReceipts,
|
|
51
|
+
} from "./npm/matrix.ts"
|
|
52
|
+
export type {
|
|
53
|
+
NpmMatrixConfig,
|
|
54
|
+
NpmMatrixLabelContext,
|
|
55
|
+
NpmPrepareContext,
|
|
56
|
+
NpmArchiveRequest,
|
|
57
|
+
NpmPlan,
|
|
58
|
+
NpmReceipt,
|
|
59
|
+
NpmVerification,
|
|
60
|
+
NpmMatrixLocation,
|
|
61
|
+
} from "./npm/matrix.ts"
|
|
17
62
|
export type {
|
|
18
63
|
NpmConfig,
|
|
19
64
|
NpmPeerEvidence,
|
|
@@ -22,9 +67,6 @@ export type {
|
|
|
22
67
|
NpmSuite,
|
|
23
68
|
NpmSuiteResult,
|
|
24
69
|
NpmTestSelection,
|
|
25
|
-
ReleaseContext,
|
|
26
|
-
ReleaseEvidence,
|
|
27
|
-
ReleaseIntent,
|
|
28
70
|
SupportChange,
|
|
29
71
|
} from "./npm/types.ts"
|
|
30
72
|
|
|
@@ -32,14 +74,17 @@ export type BreakageOptions = {
|
|
|
32
74
|
tagPattern?: string | undefined
|
|
33
75
|
baselineFile?: string | undefined
|
|
34
76
|
testPattern: string
|
|
35
|
-
testCommand
|
|
36
|
-
|
|
77
|
+
testCommand?: string
|
|
78
|
+
/** Shell command. Do not combine with testCommand. */
|
|
79
|
+
test?: string
|
|
80
|
+
release?: ReleaseConfig
|
|
37
81
|
baseDirname?: string
|
|
38
82
|
verbose?: boolean | undefined
|
|
39
83
|
}
|
|
40
84
|
|
|
41
85
|
export type BreakageOutcome =
|
|
42
86
|
| { gitWasClean: false }
|
|
87
|
+
| { gitWasClean: true; setupError: string }
|
|
43
88
|
| {
|
|
44
89
|
gitWasClean: true
|
|
45
90
|
lastReleaseFound: false
|
|
@@ -57,6 +102,7 @@ export type BreakageOutcome =
|
|
|
57
102
|
testsWereFound: true
|
|
58
103
|
testsFound: string[]
|
|
59
104
|
breakingChangesFound: false
|
|
105
|
+
release?: VersionDelta
|
|
60
106
|
}
|
|
61
107
|
| {
|
|
62
108
|
gitWasClean: true
|
|
@@ -66,20 +112,35 @@ export type BreakageOutcome =
|
|
|
66
112
|
testsFound: string[]
|
|
67
113
|
breakingChangesFound: true
|
|
68
114
|
testResult: string
|
|
69
|
-
|
|
70
|
-
certificationStdout: string
|
|
71
|
-
certificationStderr: string
|
|
115
|
+
release?: VersionDelta
|
|
72
116
|
}
|
|
73
117
|
|
|
74
|
-
export async function breakage(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
118
|
+
export async function breakage(
|
|
119
|
+
options: BreakageOptions,
|
|
120
|
+
): Promise<BreakageOutcome & { summary: string }> {
|
|
121
|
+
rejectCertificationOptions(options)
|
|
122
|
+
const {
|
|
123
|
+
tagPattern,
|
|
124
|
+
baselineFile,
|
|
125
|
+
testPattern,
|
|
126
|
+
testCommand,
|
|
127
|
+
test,
|
|
128
|
+
release,
|
|
129
|
+
baseDirname = process.cwd(),
|
|
130
|
+
verbose = false,
|
|
131
|
+
} = options
|
|
132
|
+
if (!testPattern || (!test && !testCommand))
|
|
133
|
+
throw new Error(
|
|
134
|
+
`required arguments were not provided: testPattern and test or testCommand.`,
|
|
135
|
+
)
|
|
136
|
+
if (test !== undefined && testCommand !== undefined)
|
|
137
|
+
throw new Error(`Choose test or testCommand, not both.`)
|
|
138
|
+
if (
|
|
139
|
+
typeof (test ?? testCommand) !== `string` ||
|
|
140
|
+
!(test ?? testCommand)!.trim()
|
|
141
|
+
)
|
|
142
|
+
throw new Error(`test must be a nonempty shell command string.`)
|
|
143
|
+
if (release !== undefined) validateRelease(release)
|
|
83
144
|
let mark: Chronicle[`mark`] | undefined
|
|
84
145
|
let logMarks: Chronicle[`logMarks`] | undefined
|
|
85
146
|
if (verbose) {
|
|
@@ -181,6 +242,34 @@ export async function breakage({
|
|
|
181
242
|
)
|
|
182
243
|
mark?.(`filtered to public test files`)
|
|
183
244
|
|
|
245
|
+
let releaseEvidence: VersionDelta | undefined
|
|
246
|
+
if (release) {
|
|
247
|
+
const current = await readManifest(path.join(baseDirname, `package.json`))
|
|
248
|
+
const prefix = await git.revparse([`--show-prefix`])
|
|
249
|
+
const previous = parseManifest(
|
|
250
|
+
JSON.parse(await git.show([`${releaseCommit}:${prefix}package.json`])),
|
|
251
|
+
`baseline`,
|
|
252
|
+
)
|
|
253
|
+
if (previous.name !== current.name)
|
|
254
|
+
throw new Error(`Release baseline belongs to another package.`)
|
|
255
|
+
const transition = await resolveReleaseDelta(release, {
|
|
256
|
+
packageName: current.name,
|
|
257
|
+
packageDirectory: path.resolve(baseDirname),
|
|
258
|
+
currentVersion: current.version,
|
|
259
|
+
baseline: {
|
|
260
|
+
version: previous.version,
|
|
261
|
+
ref: latestReleaseTag,
|
|
262
|
+
commit: releaseCommit,
|
|
263
|
+
},
|
|
264
|
+
})
|
|
265
|
+
if (transition !== undefined)
|
|
266
|
+
releaseEvidence = versionDelta(
|
|
267
|
+
transition.previousVersion,
|
|
268
|
+
transition.version,
|
|
269
|
+
release.policy,
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
|
|
184
273
|
const prepared = await withTestFileState(git, baseDirname, async (state) => {
|
|
185
274
|
if (!(await state.isClean())) {
|
|
186
275
|
return {
|
|
@@ -206,61 +295,41 @@ export async function breakage({
|
|
|
206
295
|
const { restore } = prepared
|
|
207
296
|
|
|
208
297
|
try {
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
mark?.(`${wereCertified ? `passed` : `failed`} certification`)
|
|
245
|
-
resolve({
|
|
246
|
-
summary: wereCertified
|
|
247
|
-
? `Breaking changes were found and certified.`
|
|
248
|
-
: `Breaking changes were found, but not certified.`,
|
|
249
|
-
gitWasClean: true,
|
|
250
|
-
lastReleaseFound: true,
|
|
251
|
-
lastReleaseTag: latestReleaseTag,
|
|
252
|
-
testsWereFound: true,
|
|
253
|
-
testsFound: productionTestFiles,
|
|
254
|
-
breakingChangesFound: true,
|
|
255
|
-
testResult: thrown as string,
|
|
256
|
-
breakingChangesCertified: wereCertified,
|
|
257
|
-
certificationStdout: stdout,
|
|
258
|
-
certificationStderr: stderr,
|
|
259
|
-
})
|
|
260
|
-
},
|
|
261
|
-
)
|
|
262
|
-
})
|
|
263
|
-
return breakingChangesCertified
|
|
298
|
+
const result = await command(test ?? testCommand!, baseDirname)
|
|
299
|
+
if (result.code > 1)
|
|
300
|
+
return {
|
|
301
|
+
gitWasClean: true,
|
|
302
|
+
setupError: result.output,
|
|
303
|
+
summary: `Public test setup failed (${result.code}).`,
|
|
304
|
+
}
|
|
305
|
+
const common = {
|
|
306
|
+
gitWasClean: true as const,
|
|
307
|
+
lastReleaseFound: true as const,
|
|
308
|
+
lastReleaseTag: latestReleaseTag,
|
|
309
|
+
testsWereFound: true as const,
|
|
310
|
+
testsFound: productionTestFiles,
|
|
311
|
+
...(releaseEvidence ? { release: releaseEvidence } : {}),
|
|
312
|
+
}
|
|
313
|
+
if (result.code === 0)
|
|
314
|
+
return {
|
|
315
|
+
...common,
|
|
316
|
+
breakingChangesFound: false,
|
|
317
|
+
summary: `No breaking changes were detected.`,
|
|
318
|
+
}
|
|
319
|
+
return {
|
|
320
|
+
...common,
|
|
321
|
+
breakingChangesFound: true,
|
|
322
|
+
testResult: result.output,
|
|
323
|
+
summary: releaseEvidence?.breaking
|
|
324
|
+
? `Breaking changes are covered by release ${releaseEvidence.previousVersion} → ${releaseEvidence.version}.`
|
|
325
|
+
: `Breaking changes require a breaking release.`,
|
|
326
|
+
}
|
|
327
|
+
} catch (error) {
|
|
328
|
+
return {
|
|
329
|
+
gitWasClean: true,
|
|
330
|
+
setupError: errorMessage(error),
|
|
331
|
+
summary: `Public test setup failed: ${errorMessage(error)}`,
|
|
332
|
+
}
|
|
264
333
|
} finally {
|
|
265
334
|
await restore()
|
|
266
335
|
mark?.(`restored original public tests`)
|
package/src/breakage.x.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import path from "node:path"
|
|
3
4
|
import { inspect } from "node:util"
|
|
4
5
|
|
|
5
|
-
import { type } from "arktype"
|
|
6
|
-
import type { OptionsGroup } from "comline"
|
|
7
6
|
import {
|
|
8
7
|
cli,
|
|
9
8
|
completionResponse,
|
|
@@ -18,28 +17,36 @@ import {
|
|
|
18
17
|
} from "comline"
|
|
19
18
|
import logger from "takua"
|
|
20
19
|
|
|
21
|
-
import type { BreakageOptions } from "./breakage.ts"
|
|
22
|
-
import { breakage, breakagePrelude } from "./breakage.ts"
|
|
23
20
|
import {
|
|
24
21
|
defaultConfig,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
configPath,
|
|
23
|
+
resolveConfig,
|
|
24
|
+
configSchema,
|
|
25
|
+
configFields,
|
|
26
|
+
} from "./config.ts"
|
|
27
|
+
import { breakage, breakagePrelude } from "./breakage.ts"
|
|
28
|
+
import { errorMessage } from "./task.ts"
|
|
29
|
+
import { npmCheckManual, npmTestManual, runNpmCli } from "./npm/cli.ts"
|
|
30
|
+
import {
|
|
31
|
+
npmPlanManual,
|
|
32
|
+
npmExecuteManual,
|
|
33
|
+
npmVerifyManual,
|
|
34
|
+
runNpmMatrixCli,
|
|
35
|
+
} from "./npm/matrix-cli.ts"
|
|
36
|
+
|
|
37
|
+
// Either command spelling can override the other spelling in a config file.
|
|
38
|
+
const commandOverrides = new Set<`test` | `testCommand`>()
|
|
29
39
|
|
|
30
40
|
const BREAKAGE_MANUAL = options(
|
|
31
41
|
`Check for breaking changes in a package.`,
|
|
32
|
-
|
|
33
|
-
"tagPattern?": `string`,
|
|
34
|
-
"baselineFile?": `string`,
|
|
35
|
-
testPattern: `string`,
|
|
36
|
-
testCommand: `string`,
|
|
37
|
-
certifyCommand: `string`,
|
|
38
|
-
"baseDirname?": `string`,
|
|
39
|
-
"verbose?": `boolean`,
|
|
40
|
-
}),
|
|
42
|
+
configSchema,
|
|
41
43
|
{
|
|
44
|
+
release: { configOnly: true },
|
|
45
|
+
npm: { configOnly: true },
|
|
46
|
+
npmMatrix: { configOnly: true },
|
|
47
|
+
out: { configOnly: true },
|
|
42
48
|
tagPattern: {
|
|
49
|
+
schema: configFields.tagPattern,
|
|
43
50
|
aliases: [`tag-pattern`],
|
|
44
51
|
completion: { repeatable: false },
|
|
45
52
|
flag: `g`,
|
|
@@ -48,6 +55,7 @@ const BREAKAGE_MANUAL = options(
|
|
|
48
55
|
example: `--tagPattern="my-library"`,
|
|
49
56
|
},
|
|
50
57
|
baselineFile: {
|
|
58
|
+
schema: configFields.baselineFile,
|
|
51
59
|
aliases: [`baseline-file`],
|
|
52
60
|
completion: { repeatable: false, fileSystem: `files` },
|
|
53
61
|
required: false,
|
|
@@ -55,30 +63,41 @@ const BREAKAGE_MANUAL = options(
|
|
|
55
63
|
example: `--baseline-file=.breakage/baseline.json`,
|
|
56
64
|
},
|
|
57
65
|
testPattern: {
|
|
66
|
+
schema: configFields.testPattern,
|
|
58
67
|
aliases: [`test-pattern`, `pattern`],
|
|
59
68
|
completion: { repeatable: false },
|
|
60
69
|
flag: `p`,
|
|
61
|
-
required:
|
|
70
|
+
required: false,
|
|
62
71
|
description: `The pattern to match test files that test the public API of the library.`,
|
|
63
72
|
example: `--pattern="*__public.test.ts"`,
|
|
64
73
|
},
|
|
65
74
|
testCommand: {
|
|
75
|
+
parse: (value) => {
|
|
76
|
+
commandOverrides.add(`testCommand`)
|
|
77
|
+
return value
|
|
78
|
+
},
|
|
79
|
+
schema: configFields.testCommand,
|
|
66
80
|
aliases: [`test-command`],
|
|
67
81
|
completion: { repeatable: false },
|
|
68
82
|
flag: `t`,
|
|
69
|
-
required:
|
|
83
|
+
required: false,
|
|
70
84
|
description: `Complete bash command that runs the tests for the library's public API.`,
|
|
71
85
|
example: `--testCommand="npm run test"`,
|
|
72
86
|
},
|
|
73
|
-
|
|
74
|
-
|
|
87
|
+
test: {
|
|
88
|
+
parse: (value) => {
|
|
89
|
+
commandOverrides.add(`test`)
|
|
90
|
+
return value
|
|
91
|
+
},
|
|
92
|
+
schema: configFields.test,
|
|
75
93
|
completion: { repeatable: false },
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
example: `--certifyCommand=\tsx scripts/certify-major-version-bump.node`,
|
|
94
|
+
required: false,
|
|
95
|
+
description: `Shell command running the restored public contracts.`,
|
|
96
|
+
example: `--test="npm run test:public"`,
|
|
80
97
|
},
|
|
81
98
|
baseDirname: {
|
|
99
|
+
parse: (value) => path.resolve(value),
|
|
100
|
+
schema: configFields.baseDirname,
|
|
82
101
|
aliases: [`base-dir`, `base-dirname`],
|
|
83
102
|
completion: { repeatable: false, fileSystem: `directories` },
|
|
84
103
|
flag: `d`,
|
|
@@ -87,6 +106,7 @@ const BREAKAGE_MANUAL = options(
|
|
|
87
106
|
example: `--baseDirname=./packages/my-package`,
|
|
88
107
|
},
|
|
89
108
|
verbose: {
|
|
109
|
+
schema: configFields.verbose,
|
|
90
110
|
completion: { repeatable: false },
|
|
91
111
|
flag: `v`,
|
|
92
112
|
required: false,
|
|
@@ -95,20 +115,11 @@ const BREAKAGE_MANUAL = options(
|
|
|
95
115
|
parse: parseBooleanOption,
|
|
96
116
|
},
|
|
97
117
|
},
|
|
98
|
-
)
|
|
118
|
+
)
|
|
99
119
|
|
|
100
120
|
const PRELUDE_MANUAL = options(
|
|
101
121
|
`Resolve and fetch the release baseline for a cacheable compatibility check.`,
|
|
102
|
-
|
|
103
|
-
"tagPattern?": `string`,
|
|
104
|
-
"baseDirname?": `string`,
|
|
105
|
-
"out?": `string`,
|
|
106
|
-
"testPattern?": `string`,
|
|
107
|
-
"testCommand?": `string`,
|
|
108
|
-
"certifyCommand?": `string`,
|
|
109
|
-
"baselineFile?": `string`,
|
|
110
|
-
"verbose?": `boolean`,
|
|
111
|
-
}),
|
|
122
|
+
configSchema,
|
|
112
123
|
{
|
|
113
124
|
...BREAKAGE_MANUAL.optionConfigs,
|
|
114
125
|
testPattern: {
|
|
@@ -119,11 +130,8 @@ const PRELUDE_MANUAL = options(
|
|
|
119
130
|
...BREAKAGE_MANUAL.optionConfigs.testCommand,
|
|
120
131
|
required: false,
|
|
121
132
|
},
|
|
122
|
-
certifyCommand: {
|
|
123
|
-
...BREAKAGE_MANUAL.optionConfigs.certifyCommand,
|
|
124
|
-
required: false,
|
|
125
|
-
},
|
|
126
133
|
out: {
|
|
134
|
+
schema: configFields.out,
|
|
127
135
|
completion: { repeatable: false, fileSystem: `files` },
|
|
128
136
|
required: false,
|
|
129
137
|
description: `Gitignored snapshot path relative to the base directory (default: .breakage/baseline.json).`,
|
|
@@ -132,30 +140,27 @@ const PRELUDE_MANUAL = options(
|
|
|
132
140
|
},
|
|
133
141
|
)
|
|
134
142
|
|
|
135
|
-
|
|
136
|
-
`Create a copy of the schema for configuring breakage.`,
|
|
137
|
-
type({ "outdir?": `string` }),
|
|
138
|
-
{
|
|
139
|
-
outdir: {
|
|
140
|
-
aliases: [`out-dir`],
|
|
141
|
-
completion: { repeatable: false, fileSystem: `directories` },
|
|
142
|
-
flag: `o`,
|
|
143
|
-
required: false,
|
|
144
|
-
description: `Directory to write the schema to.`,
|
|
145
|
-
example: `--outdir=./dist`,
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
)
|
|
149
|
-
|
|
143
|
+
let loadedConfigPath: string | undefined
|
|
150
144
|
const parse = cli(
|
|
151
145
|
{
|
|
152
146
|
cliName: `breakage`,
|
|
147
|
+
discoverConfigPath: (segments: readonly string[]) => {
|
|
148
|
+
if (segments[0] === `help` || segments[0] === `npm`) return undefined
|
|
149
|
+
const explicit = segments[0] === `prelude` ? segments[1] : segments[0]
|
|
150
|
+
loadedConfigPath = configPath(
|
|
151
|
+
explicit ?? defaultConfig(process.cwd()),
|
|
152
|
+
explicit !== undefined,
|
|
153
|
+
)
|
|
154
|
+
return loadedConfigPath
|
|
155
|
+
},
|
|
153
156
|
routes: optional({
|
|
154
157
|
help: null,
|
|
155
|
-
schema: null,
|
|
156
158
|
npm: optional({
|
|
157
159
|
help: null,
|
|
158
160
|
check: null,
|
|
161
|
+
plan: null,
|
|
162
|
+
execute: required({ $id: null }),
|
|
163
|
+
verify: null,
|
|
159
164
|
test: optional({ $peer: required({ $range: null }) }),
|
|
160
165
|
}),
|
|
161
166
|
prelude: optional({ $configPath: null }),
|
|
@@ -165,10 +170,12 @@ const parse = cli(
|
|
|
165
170
|
"": BREAKAGE_MANUAL,
|
|
166
171
|
$configPath: BREAKAGE_MANUAL,
|
|
167
172
|
help: noOptions(`Show usage.`),
|
|
168
|
-
schema: SCHEMA_MANUAL,
|
|
169
173
|
npm: noOptions(`Show npm compatibility commands.`),
|
|
170
174
|
"npm/help": noOptions(`Show npm compatibility commands.`),
|
|
171
175
|
"npm/check": npmCheckManual,
|
|
176
|
+
"npm/plan": npmPlanManual,
|
|
177
|
+
"npm/execute/$id": npmExecuteManual,
|
|
178
|
+
"npm/verify": npmVerifyManual,
|
|
172
179
|
"npm/test": npmTestManual,
|
|
173
180
|
"npm/test/$peer/$range": npmTestManual,
|
|
174
181
|
prelude: PRELUDE_MANUAL,
|
|
@@ -178,15 +185,6 @@ const parse = cli(
|
|
|
178
185
|
$configPath: { fileSystem: `files` },
|
|
179
186
|
"prelude/$configPath": { fileSystem: `files` },
|
|
180
187
|
},
|
|
181
|
-
discoverConfigPath: (args: readonly string[]) => {
|
|
182
|
-
if (args[0] === `help` || args[0] === `schema` || args[0] === `npm`) {
|
|
183
|
-
return
|
|
184
|
-
}
|
|
185
|
-
const configPath =
|
|
186
|
-
(args[0] === `prelude` ? args[1] : args[0]) ??
|
|
187
|
-
defaultConfig(process.cwd())
|
|
188
|
-
return configPath
|
|
189
|
-
},
|
|
190
188
|
},
|
|
191
189
|
{
|
|
192
190
|
info: (message, ...data) => logger.info(`breakage`, message, data),
|
|
@@ -198,74 +196,123 @@ const completion = await completionResponse(parse.definition, process.argv)
|
|
|
198
196
|
if (completion !== undefined) {
|
|
199
197
|
process.stdout.write(completion)
|
|
200
198
|
} else {
|
|
201
|
-
|
|
202
|
-
|
|
199
|
+
try {
|
|
200
|
+
const { inputs, warnings } = await parse.async(process.argv)
|
|
201
|
+
logWarnings(warnings)
|
|
202
|
+
if (
|
|
203
|
+
process.argv
|
|
204
|
+
.slice(2)
|
|
205
|
+
.some((arg) =>
|
|
206
|
+
/^(?:--certifyCommand|--certify-command|-c)(?:=|$)/.test(arg),
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
throw new Error(
|
|
210
|
+
`certifyCommand was removed. Configure release.resolveDelta and release.policy instead.`,
|
|
211
|
+
)
|
|
203
212
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
stdout
|
|
240
|
-
|
|
241
|
-
if (`testResult` in returnValue && returnValue.breakingChangesFound) {
|
|
242
|
-
process.stdout.write(returnValue.testResult)
|
|
213
|
+
switch (inputs.case) {
|
|
214
|
+
case `npm`:
|
|
215
|
+
case `npm/help`:
|
|
216
|
+
case `help`:
|
|
217
|
+
console.log(help(parse.definition))
|
|
218
|
+
break
|
|
219
|
+
case `npm/check`:
|
|
220
|
+
process.exitCode = await runNpmCli(`check`, inputs.opts)
|
|
221
|
+
break
|
|
222
|
+
case `npm/plan`:
|
|
223
|
+
process.exitCode = await runNpmMatrixCli(`plan`, inputs.opts)
|
|
224
|
+
break
|
|
225
|
+
case `npm/execute/$id`:
|
|
226
|
+
process.exitCode = await runNpmMatrixCli(
|
|
227
|
+
`execute`,
|
|
228
|
+
inputs.opts,
|
|
229
|
+
inputs.params.id,
|
|
230
|
+
)
|
|
231
|
+
break
|
|
232
|
+
case `npm/verify`:
|
|
233
|
+
process.exitCode = await runNpmMatrixCli(`verify`, inputs.opts)
|
|
234
|
+
break
|
|
235
|
+
case `npm/test`:
|
|
236
|
+
process.exitCode = await runNpmCli(`test`, inputs.opts)
|
|
237
|
+
break
|
|
238
|
+
case `npm/test/$peer/$range`:
|
|
239
|
+
process.exitCode = await runNpmCli(`test`, inputs.opts, inputs.params)
|
|
240
|
+
break
|
|
241
|
+
case `prelude`:
|
|
242
|
+
case `prelude/$configPath`: {
|
|
243
|
+
const config = resolveConfig(inputs.opts, loadedConfigPath)
|
|
244
|
+
const baseline = await breakagePrelude({
|
|
245
|
+
...config,
|
|
246
|
+
out: config.out ?? `.breakage/baseline.json`,
|
|
247
|
+
})
|
|
248
|
+
process.stdout.write(`Pinned ${baseline.ref} at ${baseline.commit}.\n`)
|
|
249
|
+
break
|
|
243
250
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
+
case ``:
|
|
252
|
+
case `$configPath`: {
|
|
253
|
+
const merged = resolveConfig(inputs.opts, loadedConfigPath)
|
|
254
|
+
if (
|
|
255
|
+
commandOverrides.has(`testCommand`) &&
|
|
256
|
+
!commandOverrides.has(`test`)
|
|
257
|
+
)
|
|
258
|
+
delete merged.test
|
|
259
|
+
if (
|
|
260
|
+
commandOverrides.has(`test`) &&
|
|
261
|
+
!commandOverrides.has(`testCommand`)
|
|
262
|
+
)
|
|
263
|
+
delete merged.testCommand
|
|
264
|
+
if (!merged.testPattern)
|
|
265
|
+
throw new Error(`required arguments were not provided: testPattern.`)
|
|
266
|
+
const { returnValue } = await encapsulate(
|
|
267
|
+
() =>
|
|
268
|
+
breakage({
|
|
269
|
+
...merged,
|
|
270
|
+
testPattern: merged.testPattern!,
|
|
271
|
+
}),
|
|
272
|
+
{
|
|
273
|
+
console: true,
|
|
274
|
+
stdout: true,
|
|
275
|
+
},
|
|
276
|
+
)
|
|
277
|
+
if (`release` in returnValue && returnValue.release) {
|
|
278
|
+
const { previousVersion, version, delta, breaking } =
|
|
279
|
+
returnValue.release
|
|
280
|
+
process.stdout.write(
|
|
281
|
+
`Release: ${previousVersion} → ${version} (${delta}; ${breaking ? `breaking` : `compatible`}).\n`,
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
if (`testResult` in returnValue && returnValue.breakingChangesFound) {
|
|
285
|
+
process.stdout.write(returnValue.testResult)
|
|
286
|
+
}
|
|
287
|
+
if (`breakingChangesFound` in returnValue) {
|
|
288
|
+
if (returnValue.breakingChangesFound) {
|
|
289
|
+
if (returnValue.release?.breaking) {
|
|
290
|
+
process.stdout.write(
|
|
291
|
+
`👷 Breaking changes are covered by the breaking release.`,
|
|
292
|
+
)
|
|
293
|
+
process.exit(0)
|
|
294
|
+
} else {
|
|
295
|
+
process.stdout.write(
|
|
296
|
+
`❌ Breaking changes require a breaking release.`,
|
|
297
|
+
)
|
|
298
|
+
process.exit(1)
|
|
299
|
+
}
|
|
251
300
|
} else {
|
|
252
|
-
process.stdout.write(
|
|
253
|
-
|
|
254
|
-
)
|
|
255
|
-
process.exit(1)
|
|
301
|
+
process.stdout.write(`✅ No breaking changes were found.`)
|
|
302
|
+
process.exit(0)
|
|
256
303
|
}
|
|
257
304
|
} else {
|
|
258
|
-
process.stdout.write(
|
|
259
|
-
|
|
305
|
+
process.stdout.write(
|
|
306
|
+
`💥 Breakage failed to determine breaking changes.` +
|
|
307
|
+
`\n` +
|
|
308
|
+
inspect(returnValue),
|
|
309
|
+
)
|
|
310
|
+
process.exit(2)
|
|
260
311
|
}
|
|
261
|
-
} else {
|
|
262
|
-
process.stdout.write(
|
|
263
|
-
`💥 Breakage failed to determine breaking changes.` +
|
|
264
|
-
`\n` +
|
|
265
|
-
inspect(returnValue),
|
|
266
|
-
)
|
|
267
|
-
process.exit(2)
|
|
268
312
|
}
|
|
269
313
|
}
|
|
314
|
+
} catch (error) {
|
|
315
|
+
process.stderr.write(`Breakage setup error: ${errorMessage(error)}\n`)
|
|
316
|
+
process.exitCode = 2
|
|
270
317
|
}
|
|
271
318
|
}
|