breakage 0.8.4 → 0.9.0
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 +164 -46
- package/dist/breakage-DIY49fzP.js +1349 -0
- package/dist/breakage-DIY49fzP.js.map +1 -0
- package/dist/breakage.d.ts +179 -11
- package/dist/breakage.d.ts.map +1 -1
- package/dist/breakage.js +3 -1
- package/dist/breakage.x.js +169 -67
- 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 +236 -0
- package/package.json +51 -11
- package/src/breakage.ts +136 -70
- package/src/breakage.x.ts +160 -112
- package/src/changesets.ts +51 -0
- package/src/config.ts +128 -0
- package/src/npm/check.ts +449 -0
- package/src/npm/cli.ts +130 -0
- package/src/npm/config.ts +118 -0
- package/src/npm/environment.ts +661 -0
- package/src/npm/manifest.ts +134 -0
- package/src/npm/release.ts +173 -0
- package/src/npm/types.ts +64 -0
- package/src/release.ts +153 -0
- package/src/task.ts +54 -0
- package/dist/breakage-Dc4IldeW.js +0 -435
- package/dist/breakage-Dc4IldeW.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.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.x.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import path from "node:path"
|
|
4
4
|
import { inspect } from "node:util"
|
|
5
5
|
|
|
6
|
-
import { type } from "arktype"
|
|
7
|
-
import type { OptionsGroup } from "comline"
|
|
8
6
|
import {
|
|
9
7
|
cli,
|
|
10
8
|
completionResponse,
|
|
@@ -15,25 +13,33 @@ import {
|
|
|
15
13
|
optional,
|
|
16
14
|
options,
|
|
17
15
|
parseBooleanOption,
|
|
16
|
+
required,
|
|
18
17
|
} from "comline"
|
|
19
18
|
import logger from "takua"
|
|
20
19
|
|
|
21
|
-
import
|
|
20
|
+
import {
|
|
21
|
+
defaultConfig,
|
|
22
|
+
configPath,
|
|
23
|
+
resolveConfig,
|
|
24
|
+
configSchema,
|
|
25
|
+
configFields,
|
|
26
|
+
} from "./config.ts"
|
|
22
27
|
import { breakage, breakagePrelude } from "./breakage.ts"
|
|
28
|
+
import { errorMessage } from "./task.ts"
|
|
29
|
+
import { npmCheckManual, npmTestManual, runNpmCli } from "./npm/cli.ts"
|
|
30
|
+
|
|
31
|
+
// Either command spelling can override the other spelling in a config file.
|
|
32
|
+
const commandOverrides = new Set<`test` | `testCommand`>()
|
|
23
33
|
|
|
24
34
|
const BREAKAGE_MANUAL = options(
|
|
25
35
|
`Check for breaking changes in a package.`,
|
|
26
|
-
|
|
27
|
-
"tagPattern?": `string`,
|
|
28
|
-
"baselineFile?": `string`,
|
|
29
|
-
testPattern: `string`,
|
|
30
|
-
testCommand: `string`,
|
|
31
|
-
certifyCommand: `string`,
|
|
32
|
-
"baseDirname?": `string`,
|
|
33
|
-
"verbose?": `boolean`,
|
|
34
|
-
}),
|
|
36
|
+
configSchema,
|
|
35
37
|
{
|
|
38
|
+
release: { configOnly: true },
|
|
39
|
+
npm: { configOnly: true },
|
|
40
|
+
out: { configOnly: true },
|
|
36
41
|
tagPattern: {
|
|
42
|
+
schema: configFields.tagPattern,
|
|
37
43
|
aliases: [`tag-pattern`],
|
|
38
44
|
completion: { repeatable: false },
|
|
39
45
|
flag: `g`,
|
|
@@ -42,6 +48,7 @@ const BREAKAGE_MANUAL = options(
|
|
|
42
48
|
example: `--tagPattern="my-library"`,
|
|
43
49
|
},
|
|
44
50
|
baselineFile: {
|
|
51
|
+
schema: configFields.baselineFile,
|
|
45
52
|
aliases: [`baseline-file`],
|
|
46
53
|
completion: { repeatable: false, fileSystem: `files` },
|
|
47
54
|
required: false,
|
|
@@ -49,30 +56,41 @@ const BREAKAGE_MANUAL = options(
|
|
|
49
56
|
example: `--baseline-file=.breakage/baseline.json`,
|
|
50
57
|
},
|
|
51
58
|
testPattern: {
|
|
59
|
+
schema: configFields.testPattern,
|
|
52
60
|
aliases: [`test-pattern`, `pattern`],
|
|
53
61
|
completion: { repeatable: false },
|
|
54
62
|
flag: `p`,
|
|
55
|
-
required:
|
|
63
|
+
required: false,
|
|
56
64
|
description: `The pattern to match test files that test the public API of the library.`,
|
|
57
65
|
example: `--pattern="*__public.test.ts"`,
|
|
58
66
|
},
|
|
59
67
|
testCommand: {
|
|
68
|
+
parse: (value) => {
|
|
69
|
+
commandOverrides.add(`testCommand`)
|
|
70
|
+
return value
|
|
71
|
+
},
|
|
72
|
+
schema: configFields.testCommand,
|
|
60
73
|
aliases: [`test-command`],
|
|
61
74
|
completion: { repeatable: false },
|
|
62
75
|
flag: `t`,
|
|
63
|
-
required:
|
|
76
|
+
required: false,
|
|
64
77
|
description: `Complete bash command that runs the tests for the library's public API.`,
|
|
65
78
|
example: `--testCommand="npm run test"`,
|
|
66
79
|
},
|
|
67
|
-
|
|
68
|
-
|
|
80
|
+
test: {
|
|
81
|
+
parse: (value) => {
|
|
82
|
+
commandOverrides.add(`test`)
|
|
83
|
+
return value
|
|
84
|
+
},
|
|
85
|
+
schema: configFields.test,
|
|
69
86
|
completion: { repeatable: false },
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
example: `--certifyCommand=\tsx scripts/certify-major-version-bump.node`,
|
|
87
|
+
required: false,
|
|
88
|
+
description: `Shell command running the restored public contracts.`,
|
|
89
|
+
example: `--test="npm run test:public"`,
|
|
74
90
|
},
|
|
75
91
|
baseDirname: {
|
|
92
|
+
parse: (value) => path.resolve(value),
|
|
93
|
+
schema: configFields.baseDirname,
|
|
76
94
|
aliases: [`base-dir`, `base-dirname`],
|
|
77
95
|
completion: { repeatable: false, fileSystem: `directories` },
|
|
78
96
|
flag: `d`,
|
|
@@ -81,6 +99,7 @@ const BREAKAGE_MANUAL = options(
|
|
|
81
99
|
example: `--baseDirname=./packages/my-package`,
|
|
82
100
|
},
|
|
83
101
|
verbose: {
|
|
102
|
+
schema: configFields.verbose,
|
|
84
103
|
completion: { repeatable: false },
|
|
85
104
|
flag: `v`,
|
|
86
105
|
required: false,
|
|
@@ -89,20 +108,11 @@ const BREAKAGE_MANUAL = options(
|
|
|
89
108
|
parse: parseBooleanOption,
|
|
90
109
|
},
|
|
91
110
|
},
|
|
92
|
-
)
|
|
111
|
+
)
|
|
93
112
|
|
|
94
113
|
const PRELUDE_MANUAL = options(
|
|
95
114
|
`Resolve and fetch the release baseline for a cacheable compatibility check.`,
|
|
96
|
-
|
|
97
|
-
"tagPattern?": `string`,
|
|
98
|
-
"baseDirname?": `string`,
|
|
99
|
-
"out?": `string`,
|
|
100
|
-
"testPattern?": `string`,
|
|
101
|
-
"testCommand?": `string`,
|
|
102
|
-
"certifyCommand?": `string`,
|
|
103
|
-
"baselineFile?": `string`,
|
|
104
|
-
"verbose?": `boolean`,
|
|
105
|
-
}),
|
|
115
|
+
configSchema,
|
|
106
116
|
{
|
|
107
117
|
...BREAKAGE_MANUAL.optionConfigs,
|
|
108
118
|
testPattern: {
|
|
@@ -113,11 +123,8 @@ const PRELUDE_MANUAL = options(
|
|
|
113
123
|
...BREAKAGE_MANUAL.optionConfigs.testCommand,
|
|
114
124
|
required: false,
|
|
115
125
|
},
|
|
116
|
-
certifyCommand: {
|
|
117
|
-
...BREAKAGE_MANUAL.optionConfigs.certifyCommand,
|
|
118
|
-
required: false,
|
|
119
|
-
},
|
|
120
126
|
out: {
|
|
127
|
+
schema: configFields.out,
|
|
121
128
|
completion: { repeatable: false, fileSystem: `files` },
|
|
122
129
|
required: false,
|
|
123
130
|
description: `Gitignored snapshot path relative to the base directory (default: .breakage/baseline.json).`,
|
|
@@ -126,27 +133,26 @@ const PRELUDE_MANUAL = options(
|
|
|
126
133
|
},
|
|
127
134
|
)
|
|
128
135
|
|
|
129
|
-
|
|
130
|
-
`Create a copy of the schema for configuring breakage.`,
|
|
131
|
-
type({ "outdir?": `string` }),
|
|
132
|
-
{
|
|
133
|
-
outdir: {
|
|
134
|
-
aliases: [`out-dir`],
|
|
135
|
-
completion: { repeatable: false, fileSystem: `directories` },
|
|
136
|
-
flag: `o`,
|
|
137
|
-
required: false,
|
|
138
|
-
description: `Directory to write the schema to.`,
|
|
139
|
-
example: `--outdir=./dist`,
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
)
|
|
143
|
-
|
|
136
|
+
let loadedConfigPath: string | undefined
|
|
144
137
|
const parse = cli(
|
|
145
138
|
{
|
|
146
139
|
cliName: `breakage`,
|
|
140
|
+
discoverConfigPath: (segments: readonly string[]) => {
|
|
141
|
+
if (segments[0] === `help` || segments[0] === `npm`) return undefined
|
|
142
|
+
const explicit = segments[0] === `prelude` ? segments[1] : segments[0]
|
|
143
|
+
loadedConfigPath = configPath(
|
|
144
|
+
explicit ?? defaultConfig(process.cwd()),
|
|
145
|
+
explicit !== undefined,
|
|
146
|
+
)
|
|
147
|
+
return loadedConfigPath
|
|
148
|
+
},
|
|
147
149
|
routes: optional({
|
|
148
150
|
help: null,
|
|
149
|
-
|
|
151
|
+
npm: optional({
|
|
152
|
+
help: null,
|
|
153
|
+
check: null,
|
|
154
|
+
test: optional({ $peer: required({ $range: null }) }),
|
|
155
|
+
}),
|
|
150
156
|
prelude: optional({ $configPath: null }),
|
|
151
157
|
$configPath: null,
|
|
152
158
|
}),
|
|
@@ -154,7 +160,11 @@ const parse = cli(
|
|
|
154
160
|
"": BREAKAGE_MANUAL,
|
|
155
161
|
$configPath: BREAKAGE_MANUAL,
|
|
156
162
|
help: noOptions(`Show usage.`),
|
|
157
|
-
|
|
163
|
+
npm: noOptions(`Show npm compatibility commands.`),
|
|
164
|
+
"npm/help": noOptions(`Show npm compatibility commands.`),
|
|
165
|
+
"npm/check": npmCheckManual,
|
|
166
|
+
"npm/test": npmTestManual,
|
|
167
|
+
"npm/test/$peer/$range": npmTestManual,
|
|
158
168
|
prelude: PRELUDE_MANUAL,
|
|
159
169
|
"prelude/$configPath": PRELUDE_MANUAL,
|
|
160
170
|
},
|
|
@@ -162,15 +172,6 @@ const parse = cli(
|
|
|
162
172
|
$configPath: { fileSystem: `files` },
|
|
163
173
|
"prelude/$configPath": { fileSystem: `files` },
|
|
164
174
|
},
|
|
165
|
-
discoverConfigPath: (args: readonly string[]) => {
|
|
166
|
-
if (args[0] === `help` || args[0] === `schema`) {
|
|
167
|
-
return
|
|
168
|
-
}
|
|
169
|
-
const configPath =
|
|
170
|
-
(args[0] === `prelude` ? args[1] : args[0]) ??
|
|
171
|
-
path.join(process.cwd(), `breakage.config.json`)
|
|
172
|
-
return configPath
|
|
173
|
-
},
|
|
174
175
|
},
|
|
175
176
|
{
|
|
176
177
|
info: (message, ...data) => logger.info(`breakage`, message, data),
|
|
@@ -182,63 +183,110 @@ const completion = await completionResponse(parse.definition, process.argv)
|
|
|
182
183
|
if (completion !== undefined) {
|
|
183
184
|
process.stdout.write(completion)
|
|
184
185
|
} else {
|
|
185
|
-
|
|
186
|
-
|
|
186
|
+
try {
|
|
187
|
+
const { inputs, warnings } = await parse.async(process.argv)
|
|
188
|
+
logWarnings(warnings)
|
|
189
|
+
if (
|
|
190
|
+
process.argv
|
|
191
|
+
.slice(2)
|
|
192
|
+
.some((arg) =>
|
|
193
|
+
/^(?:--certifyCommand|--certify-command|-c)(?:=|$)/.test(arg),
|
|
194
|
+
)
|
|
195
|
+
)
|
|
196
|
+
throw new Error(
|
|
197
|
+
`certifyCommand was removed. Configure release.resolveDelta and release.policy instead.`,
|
|
198
|
+
)
|
|
187
199
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
200
|
+
switch (inputs.case) {
|
|
201
|
+
case `npm`:
|
|
202
|
+
case `npm/help`:
|
|
203
|
+
case `help`:
|
|
204
|
+
console.log(help(parse.definition))
|
|
205
|
+
break
|
|
206
|
+
case `npm/check`:
|
|
207
|
+
process.exitCode = await runNpmCli(`check`, inputs.opts)
|
|
208
|
+
break
|
|
209
|
+
case `npm/test`:
|
|
210
|
+
process.exitCode = await runNpmCli(`test`, inputs.opts)
|
|
211
|
+
break
|
|
212
|
+
case `npm/test/$peer/$range`:
|
|
213
|
+
process.exitCode = await runNpmCli(`test`, inputs.opts, inputs.params)
|
|
214
|
+
break
|
|
215
|
+
case `prelude`:
|
|
216
|
+
case `prelude/$configPath`: {
|
|
217
|
+
const config = resolveConfig(inputs.opts, loadedConfigPath)
|
|
218
|
+
const baseline = await breakagePrelude({
|
|
219
|
+
...config,
|
|
220
|
+
out: config.out ?? `.breakage/baseline.json`,
|
|
221
|
+
})
|
|
222
|
+
process.stdout.write(`Pinned ${baseline.ref} at ${baseline.commit}.\n`)
|
|
223
|
+
break
|
|
197
224
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
225
|
+
case ``:
|
|
226
|
+
case `$configPath`: {
|
|
227
|
+
const merged = resolveConfig(inputs.opts, loadedConfigPath)
|
|
228
|
+
if (
|
|
229
|
+
commandOverrides.has(`testCommand`) &&
|
|
230
|
+
!commandOverrides.has(`test`)
|
|
231
|
+
)
|
|
232
|
+
delete merged.test
|
|
233
|
+
if (
|
|
234
|
+
commandOverrides.has(`test`) &&
|
|
235
|
+
!commandOverrides.has(`testCommand`)
|
|
236
|
+
)
|
|
237
|
+
delete merged.testCommand
|
|
238
|
+
if (!merged.testPattern)
|
|
239
|
+
throw new Error(`required arguments were not provided: testPattern.`)
|
|
240
|
+
const { returnValue } = await encapsulate(
|
|
241
|
+
() =>
|
|
242
|
+
breakage({
|
|
243
|
+
...merged,
|
|
244
|
+
testPattern: merged.testPattern!,
|
|
245
|
+
}),
|
|
246
|
+
{
|
|
247
|
+
console: true,
|
|
248
|
+
stdout: true,
|
|
249
|
+
},
|
|
250
|
+
)
|
|
251
|
+
if (`release` in returnValue && returnValue.release) {
|
|
252
|
+
const { previousVersion, version, delta, breaking } =
|
|
253
|
+
returnValue.release
|
|
254
|
+
process.stdout.write(
|
|
255
|
+
`Release: ${previousVersion} → ${version} (${delta}; ${breaking ? `breaking` : `compatible`}).\n`,
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
if (`testResult` in returnValue && returnValue.breakingChangesFound) {
|
|
259
|
+
process.stdout.write(returnValue.testResult)
|
|
260
|
+
}
|
|
261
|
+
if (`breakingChangesFound` in returnValue) {
|
|
262
|
+
if (returnValue.breakingChangesFound) {
|
|
263
|
+
if (returnValue.release?.breaking) {
|
|
264
|
+
process.stdout.write(
|
|
265
|
+
`👷 Breaking changes are covered by the breaking release.`,
|
|
266
|
+
)
|
|
267
|
+
process.exit(0)
|
|
268
|
+
} else {
|
|
269
|
+
process.stdout.write(
|
|
270
|
+
`❌ Breaking changes require a breaking release.`,
|
|
271
|
+
)
|
|
272
|
+
process.exit(1)
|
|
273
|
+
}
|
|
224
274
|
} else {
|
|
225
|
-
process.stdout.write(
|
|
226
|
-
|
|
227
|
-
)
|
|
228
|
-
process.exit(1)
|
|
275
|
+
process.stdout.write(`✅ No breaking changes were found.`)
|
|
276
|
+
process.exit(0)
|
|
229
277
|
}
|
|
230
278
|
} else {
|
|
231
|
-
process.stdout.write(
|
|
232
|
-
|
|
279
|
+
process.stdout.write(
|
|
280
|
+
`💥 Breakage failed to determine breaking changes.` +
|
|
281
|
+
`\n` +
|
|
282
|
+
inspect(returnValue),
|
|
283
|
+
)
|
|
284
|
+
process.exit(2)
|
|
233
285
|
}
|
|
234
|
-
} else {
|
|
235
|
-
process.stdout.write(
|
|
236
|
-
`💥 Breakage failed to determine breaking changes.` +
|
|
237
|
-
`\n` +
|
|
238
|
-
inspect(returnValue),
|
|
239
|
-
)
|
|
240
|
-
process.exit(2)
|
|
241
286
|
}
|
|
242
287
|
}
|
|
288
|
+
} catch (error) {
|
|
289
|
+
process.stderr.write(`Breakage setup error: ${errorMessage(error)}\n`)
|
|
290
|
+
process.exitCode = 2
|
|
243
291
|
}
|
|
244
292
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { assembleReleasePlan } from "@changesets/assemble-release-plan"
|
|
2
|
+
import { readConfig } from "@changesets/config"
|
|
3
|
+
import { readPreState } from "@changesets/pre"
|
|
4
|
+
import { readChangesets } from "@changesets/read"
|
|
5
|
+
import { getPackages } from "@manypkg/get-packages"
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
versionTransition,
|
|
9
|
+
type ReleaseDeltaContext,
|
|
10
|
+
type VersionTransition,
|
|
11
|
+
} from "./release.ts"
|
|
12
|
+
|
|
13
|
+
/** Read the actual Changesets plan, including dependent bumps and prerelease state. */
|
|
14
|
+
export async function readChangesetsReleasePlan(
|
|
15
|
+
directory: string = process.cwd(),
|
|
16
|
+
): Promise<ReadonlyMap<string, string>> {
|
|
17
|
+
const packages = await getPackages(directory)
|
|
18
|
+
const root = packages.rootDir
|
|
19
|
+
const { config, errors } = await readConfig(root, packages)
|
|
20
|
+
if (errors) throw new Error(errors.join(`\n`))
|
|
21
|
+
const [changesets, preState] = await Promise.all([
|
|
22
|
+
readChangesets(root),
|
|
23
|
+
readPreState(root),
|
|
24
|
+
])
|
|
25
|
+
const plan = assembleReleasePlan(changesets, packages, config, preState)
|
|
26
|
+
return new Map(
|
|
27
|
+
plan.releases.flatMap(({ name, newVersion }) =>
|
|
28
|
+
newVersion === undefined ? [] : [[name, newVersion] as const],
|
|
29
|
+
),
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Compare the selected baseline (or current manifest) with the planned or versioned release. */
|
|
34
|
+
export function changesetsReleaseDelta({
|
|
35
|
+
directory,
|
|
36
|
+
}: { directory?: string } = {}): (
|
|
37
|
+
context: ReleaseDeltaContext,
|
|
38
|
+
) => Promise<VersionTransition> {
|
|
39
|
+
return async ({
|
|
40
|
+
packageName,
|
|
41
|
+
packageDirectory,
|
|
42
|
+
currentVersion,
|
|
43
|
+
baseline,
|
|
44
|
+
}) => {
|
|
45
|
+
const version =
|
|
46
|
+
(await readChangesetsReleasePlan(directory ?? packageDirectory)).get(
|
|
47
|
+
packageName,
|
|
48
|
+
) ?? currentVersion
|
|
49
|
+
return versionTransition(baseline?.version ?? currentVersion, version)
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { existsSync } from "node:fs"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
|
|
4
|
+
import { type } from "arktype"
|
|
5
|
+
import { cli, options, type ConfigInput } from "comline"
|
|
6
|
+
|
|
7
|
+
import { npmConfigSchema } from "./npm/config.ts"
|
|
8
|
+
import {
|
|
9
|
+
releaseSchema,
|
|
10
|
+
validateRelease,
|
|
11
|
+
rejectCertificationOptions,
|
|
12
|
+
} from "./release.ts"
|
|
13
|
+
|
|
14
|
+
// Reuse these field schemas for CLI metadata even though hooks cannot become JSON Schema.
|
|
15
|
+
export const configFields = {
|
|
16
|
+
tagPattern: type(`string`),
|
|
17
|
+
baselineFile: type(`string`),
|
|
18
|
+
testPattern: type(`string`),
|
|
19
|
+
testCommand: type(`string`),
|
|
20
|
+
test: type(`string`),
|
|
21
|
+
baseDirname: type(`string`),
|
|
22
|
+
verbose: type(`boolean`),
|
|
23
|
+
out: type(`string`),
|
|
24
|
+
}
|
|
25
|
+
export const configSchema = type({
|
|
26
|
+
"+": `reject`,
|
|
27
|
+
"tagPattern?": configFields.tagPattern.or(`undefined`),
|
|
28
|
+
"baselineFile?": configFields.baselineFile.or(`undefined`),
|
|
29
|
+
"testPattern?": configFields.testPattern,
|
|
30
|
+
"testCommand?": configFields.testCommand,
|
|
31
|
+
"test?": configFields.test,
|
|
32
|
+
"baseDirname?": configFields.baseDirname,
|
|
33
|
+
"verbose?": configFields.verbose.or(`undefined`),
|
|
34
|
+
"out?": configFields.out,
|
|
35
|
+
"release?": releaseSchema,
|
|
36
|
+
"npm?": npmConfigSchema,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
/** The complete config, with no fields exposed to argv by this loader. */
|
|
40
|
+
export const configOptions = options(`Breakage configuration`, configSchema, {
|
|
41
|
+
tagPattern: { configOnly: true },
|
|
42
|
+
baselineFile: { configOnly: true },
|
|
43
|
+
testPattern: { configOnly: true },
|
|
44
|
+
testCommand: { configOnly: true },
|
|
45
|
+
test: { configOnly: true },
|
|
46
|
+
baseDirname: { configOnly: true },
|
|
47
|
+
verbose: { configOnly: true },
|
|
48
|
+
out: { configOnly: true },
|
|
49
|
+
release: { configOnly: true },
|
|
50
|
+
npm: { configOnly: true },
|
|
51
|
+
})
|
|
52
|
+
export type BreakageConfig = ConfigInput<typeof configOptions>
|
|
53
|
+
|
|
54
|
+
/** Share the root release model with npm checks unless they supply their own. */
|
|
55
|
+
export function defineConfig(config: BreakageConfig): BreakageConfig {
|
|
56
|
+
if (config && typeof config === `object`) {
|
|
57
|
+
rejectCertificationOptions(config)
|
|
58
|
+
if (config.npm) rejectCertificationOptions(config.npm)
|
|
59
|
+
if (config.release !== undefined) validateRelease(config.release)
|
|
60
|
+
if (config.npm?.release !== undefined) validateRelease(config.npm.release)
|
|
61
|
+
}
|
|
62
|
+
configSchema.assert(config)
|
|
63
|
+
return {
|
|
64
|
+
...config,
|
|
65
|
+
...(config.npm
|
|
66
|
+
? {
|
|
67
|
+
npm: {
|
|
68
|
+
...config.npm,
|
|
69
|
+
...(config.release && config.npm.release === undefined
|
|
70
|
+
? { release: config.release }
|
|
71
|
+
: {}),
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
: {}),
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function defaultConfig(directory: string): string {
|
|
79
|
+
const filename = path.join(directory, `breakage.config.ts`)
|
|
80
|
+
if (
|
|
81
|
+
!existsSync(filename) &&
|
|
82
|
+
existsSync(path.join(directory, `breakage.config.json`))
|
|
83
|
+
)
|
|
84
|
+
throw new Error(
|
|
85
|
+
`JSON configuration was removed. Migrate breakage.config.json to breakage.config.ts.`,
|
|
86
|
+
)
|
|
87
|
+
return filename
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Comline ignores absent discovered files; explicit paths must exist. */
|
|
91
|
+
export function configPath(filename: string, required = true): string {
|
|
92
|
+
const absolute = path.resolve(filename)
|
|
93
|
+
if (path.extname(absolute) !== `.ts`)
|
|
94
|
+
throw new Error(
|
|
95
|
+
`Configuration must be a .ts module with a default export; JSON configuration was removed.`,
|
|
96
|
+
)
|
|
97
|
+
if (required && !existsSync(absolute))
|
|
98
|
+
throw new Error(`Configuration file does not exist: ${absolute}`)
|
|
99
|
+
return absolute
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Apply application semantics after Comline validates and merges configuration. */
|
|
103
|
+
export function resolveConfig(
|
|
104
|
+
config: BreakageConfig,
|
|
105
|
+
filename?: string,
|
|
106
|
+
): BreakageConfig {
|
|
107
|
+
return {
|
|
108
|
+
...defineConfig(config),
|
|
109
|
+
baseDirname: path.resolve(
|
|
110
|
+
filename ? path.dirname(filename) : process.cwd(),
|
|
111
|
+
config.baseDirname ?? `.`,
|
|
112
|
+
),
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Use Comline's module loading and full-schema validation for programmatic and npm consumers. */
|
|
117
|
+
export async function loadConfig(filename: string): Promise<BreakageConfig> {
|
|
118
|
+
const absolute = configPath(filename)
|
|
119
|
+
const parse = cli({
|
|
120
|
+
cliName: `breakage`,
|
|
121
|
+
routeOptions: { "": configOptions },
|
|
122
|
+
discoverConfigPath: () => absolute,
|
|
123
|
+
})
|
|
124
|
+
return resolveConfig(
|
|
125
|
+
(await parse.async([process.execPath, `breakage`])).inputs.opts,
|
|
126
|
+
absolute,
|
|
127
|
+
)
|
|
128
|
+
}
|