fg-devkit 1.21.5 → 1.23.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 +16 -7
- package/configs/dependency-policy.vue-workspace.json +27 -27
- package/configs/eslint.config.base.d.ts +1 -0
- package/configs/eslint.config.base.js +3 -0
- package/configs/eslint.sync-stub.js +1 -0
- package/package.json +11 -81
- package/scaffold/vue/package.json +12 -12
- package/scripts/audit-override-commit.mjs +167 -24
- package/scripts/bump-node-toolchain.mjs +8 -31
- package/scripts/bump-pnpm-toolchain.mjs +8 -29
- package/scripts/check-dependency-policy.mjs +10 -35
- package/scripts/fg-devkit.mjs +274 -331
- package/scripts/fleet-bump-fg-devkit.mjs +4 -5
- package/scripts/fleet-git-commit-push-scoped.mjs +6 -26
- package/scripts/fleet-git-discard-scoped.mjs +5 -25
- package/scripts/fleet-git-pull-scoped.mjs +5 -25
- package/scripts/fleet-vulns.mjs +4 -9
- package/scripts/generate-release-notes.mjs +1 -1
- package/scripts/{audit-fix-plan.mjs → lib/audit-fix-plan.mjs} +77 -4
- package/scripts/{audit-fix-plan.test.mjs → lib/audit-fix-plan.test.mjs} +50 -2
- package/scripts/lib/common.mjs +95 -0
- package/scripts/{dependency-policy-toolchain.mjs → lib/dependency-policy-toolchain.mjs} +3 -35
- package/scripts/{git-commit-push-fleet-common.mjs → lib/git-commit-push-fleet-common.mjs} +1 -1
- package/scripts/{git-discard-fleet-common.mjs → lib/git-discard-fleet-common.mjs} +1 -1
- package/scripts/{git-pull-fleet-common.mjs → lib/git-pull-fleet-common.mjs} +1 -1
- package/scripts/{override-changes.mjs → lib/override-changes.mjs} +20 -0
- package/scripts/{piwik-dimensions.test.mjs → lib/piwik-dimensions.test.mjs} +0 -5
- package/scripts/{release-notes-common.mjs → lib/release-notes-common.mjs} +5 -7
- package/scripts/{resolve-dependency-policy-path.mjs → lib/resolve-dependency-policy-path.mjs} +4 -3
- package/scripts/piwik.mjs +6 -6
- package/scripts/poeditor-download.mjs +1 -1
- package/scripts/poeditor-upload.mjs +1 -1
- package/scripts/policy-outdated.mjs +3 -8
- package/scripts/policy-upgrade-commit.mjs +5 -26
- package/scripts/policy-upgrade.mjs +6 -30
- package/scripts/policy-vulns.mjs +3 -8
- package/scripts/prune-overrides.mjs +5 -16
- package/scripts/prune-release-age-exclude.mjs +1 -1
- package/scripts/refresh-dependency-policy-from-repos.mjs +5 -7
- package/scripts/release.sh +2 -1
- package/scripts/scaffold-vue.mjs +1 -3
- package/scripts/sync-consumer-lefthook.mjs +7 -24
- package/scripts/sync-core-coding-standards.mjs +2 -17
- package/scripts/sync-dependency-check-suppressions-commit.mjs +7 -46
- package/scripts/sync-dependency-policy-commit.mjs +6 -38
- package/scripts/sync-dependency-policy.mjs +9 -35
- package/scripts/sync-node-toolchain.mjs +5 -7
- package/scripts/sync-pnpm-toolchain.mjs +5 -7
- package/scripts/sync-scaffold-vue-deps.mjs +6 -7
- package/scripts/sync-tooling-configs.mjs +8 -23
- package/scripts/sync-vite-plugin-vue-inspector.mjs +7 -39
- package/scripts/verify-release-notes.mjs +1 -1
- package/scaffold/vue/.gitignore +0 -13
- package/scripts/cz/.cz-config.cjs +0 -29
- package/scripts/fleet-git-commit-push.mjs +0 -75
- package/scripts/fleet-git-discard.mjs +0 -66
- package/scripts/fleet-git-pull.mjs +0 -65
- package/scripts/sync-peers-from-dev.mjs +0 -31
- package/scripts/vuln-commit.mjs +0 -142
- /package/configs/{fallowrc.sync-stub.json → fallowrc.json} +0 -0
- /package/scripts/{dependency-policy-pins.mjs → lib/dependency-policy-pins.mjs} +0 -0
- /package/scripts/{fleet-project-repos.mjs → lib/fleet-project-repos.mjs} +0 -0
- /package/scripts/{fleet-repo-picker.mjs → lib/fleet-repo-picker.mjs} +0 -0
- /package/scripts/{fleet-vue-repos.mjs → lib/fleet-vue-repos.mjs} +0 -0
- /package/scripts/{git-fleet-common.mjs → lib/git-fleet-common.mjs} +0 -0
- /package/scripts/{node-toolchain-sync.mjs → lib/node-toolchain-sync.mjs} +0 -0
- /package/scripts/{piwik-common.mjs → lib/piwik-common.mjs} +0 -0
- /package/scripts/{piwik-dimensions.mjs → lib/piwik-dimensions.mjs} +0 -0
- /package/scripts/{pnpm-toolchain-sync.mjs → lib/pnpm-toolchain-sync.mjs} +0 -0
- /package/scripts/{poeditor-common.mjs → lib/poeditor-common.mjs} +0 -0
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
* 3. Audit again. Remaining findings with a patched version that a parent's range blocks
|
|
9
9
|
* get a tight override (`name@>=installed <patched`: lowest patched version) in
|
|
10
10
|
* pnpm-workspace.yaml. Findings with no patch, a patch in another major, a patch
|
|
11
|
-
* younger than minimumReleaseAge, or on a direct dependency are
|
|
11
|
+
* younger than minimumReleaseAge, or on a direct dependency are not fixed automatically:
|
|
12
|
+
* in a terminal you are asked per package whether to force it (override across the
|
|
13
|
+
* major, allow the young release via minimumReleaseAgeExclude, bump the direct
|
|
14
|
+
* dependency in package.json). Without a terminal they are reported unless the matching
|
|
15
|
+
* --allow-major / --allow-young / --bump-direct flag is passed.
|
|
12
16
|
* 4. Prune overrides that no longer change the audit (prune-overrides --fix).
|
|
13
17
|
* 5. Prune minimumReleaseAgeExclude entries that are no longer needed
|
|
14
18
|
* (prune-release-age-exclude --fix). Runs even when the audit is clean.
|
|
@@ -16,11 +20,12 @@
|
|
|
16
20
|
* shared dependency-check-suppressions.xml, write the repo's copy with only those
|
|
17
21
|
* entries, so OWASP Dependency-Check accepts exactly the risks this repo has.
|
|
18
22
|
* 7. Install and run the repo's checks (lint-check, lint:ts, test, test:unit, build).
|
|
19
|
-
* 8. Commit the refresh, the overrides, the release-age excludes and
|
|
20
|
-
* file separately.
|
|
23
|
+
* 8. Commit the refresh, the direct bumps, the overrides, the release-age excludes and
|
|
24
|
+
* the suppressions file separately.
|
|
21
25
|
*
|
|
22
26
|
* Usage:
|
|
23
27
|
* pnpm exec fg-devkit audit-override-commit [--no-verify] [--no-commit]
|
|
28
|
+
* [--allow-major] [--allow-young] [--bump-direct]
|
|
24
29
|
*/
|
|
25
30
|
|
|
26
31
|
import fs from 'node:fs'
|
|
@@ -31,23 +36,27 @@ import { parseArgs } from 'node:util'
|
|
|
31
36
|
import {
|
|
32
37
|
DEFAULT_MINIMUM_RELEASE_AGE_MINUTES,
|
|
33
38
|
activeSuppressedGhsas,
|
|
39
|
+
addReleaseAgeExcludes,
|
|
34
40
|
buildRefreshCommitMessage,
|
|
41
|
+
bumpDependencySpec,
|
|
35
42
|
filterSuppressions,
|
|
36
43
|
diffLockfileVersions,
|
|
37
44
|
isReleaseAgeExcluded,
|
|
45
|
+
isSameReleaseLine,
|
|
38
46
|
normalizeAdvisories,
|
|
39
47
|
parseAuditJson,
|
|
40
48
|
parseReleaseAgeExcludeEntry,
|
|
41
49
|
planOverrides,
|
|
42
50
|
readWorkspaceList,
|
|
43
51
|
upsertWorkspaceOverrides,
|
|
44
|
-
} from './audit-fix-plan.mjs'
|
|
52
|
+
} from './lib/audit-fix-plan.mjs'
|
|
45
53
|
import {
|
|
46
54
|
buildOverrideCommitMessage,
|
|
47
55
|
collectOverrideChanges,
|
|
48
56
|
collectOverrides,
|
|
49
57
|
formatOverrideValue,
|
|
50
|
-
} from './override-changes.mjs'
|
|
58
|
+
} from './lib/override-changes.mjs'
|
|
59
|
+
import { fgDevkitRoot } from './lib/common.mjs'
|
|
51
60
|
|
|
52
61
|
const TAG = '[audit-override-commit]'
|
|
53
62
|
const cwd = process.cwd()
|
|
@@ -62,6 +71,9 @@ const { values: args } = parseArgs({
|
|
|
62
71
|
options: {
|
|
63
72
|
'no-verify': { type: 'boolean', default: false },
|
|
64
73
|
'no-commit': { type: 'boolean', default: false },
|
|
74
|
+
'allow-major': { type: 'boolean', default: false },
|
|
75
|
+
'allow-young': { type: 'boolean', default: false },
|
|
76
|
+
'bump-direct': { type: 'boolean', default: false },
|
|
65
77
|
help: { type: 'boolean', short: 'h', default: false },
|
|
66
78
|
},
|
|
67
79
|
strict: true,
|
|
@@ -69,14 +81,21 @@ const { values: args } = parseArgs({
|
|
|
69
81
|
|
|
70
82
|
if (args.help) {
|
|
71
83
|
console.log(`Usage: pnpm exec fg-devkit audit-override-commit [--no-verify] [--no-commit]
|
|
84
|
+
[--allow-major] [--allow-young] [--bump-direct]
|
|
72
85
|
|
|
73
86
|
Refreshes vulnerable packages in the lockfile within their existing ranges, adds a
|
|
74
87
|
tight override only where a parent's range blocks the patched version, prunes unused
|
|
75
88
|
overrides, syncs dependency-check-suppressions.xml when a remaining finding is
|
|
76
89
|
suppressed there, runs the repo's checks, and commits each kind of change separately.
|
|
77
90
|
|
|
78
|
-
|
|
79
|
-
|
|
91
|
+
Findings that need a decision are asked about one package at a time in a terminal.
|
|
92
|
+
Without a terminal they are only reported, unless a flag below says yes for them.
|
|
93
|
+
|
|
94
|
+
--no-verify Skip lint/typecheck/test/build (git hooks still run on commit)
|
|
95
|
+
--no-commit Leave the changes in the working tree
|
|
96
|
+
--allow-major Override even when the patch is only in a new major
|
|
97
|
+
--allow-young Allow patches younger than minimumReleaseAge (adds minimumReleaseAgeExclude)
|
|
98
|
+
--bump-direct Bump vulnerable direct dependencies in package.json`)
|
|
80
99
|
process.exit(0)
|
|
81
100
|
}
|
|
82
101
|
|
|
@@ -151,11 +170,11 @@ function publishTimes(name) {
|
|
|
151
170
|
}
|
|
152
171
|
}
|
|
153
172
|
|
|
173
|
+
const DEPENDENCY_SECTIONS = ['dependencies', 'devDependencies', 'optionalDependencies']
|
|
174
|
+
|
|
154
175
|
function directDependencies() {
|
|
155
176
|
const pkg = JSON.parse(readFile('package.json'))
|
|
156
|
-
return new Set(
|
|
157
|
-
['dependencies', 'devDependencies', 'optionalDependencies'].flatMap((section) => Object.keys(pkg[section] ?? {})),
|
|
158
|
-
)
|
|
177
|
+
return new Set(DEPENDENCY_SECTIONS.flatMap((section) => Object.keys(pkg[section] ?? {})))
|
|
159
178
|
}
|
|
160
179
|
|
|
161
180
|
// ---------------------------------------------------------------------------
|
|
@@ -215,7 +234,7 @@ function refreshLockfile(advisories) {
|
|
|
215
234
|
runOrFail('pnpm', ['update', ...recursive, ...names, '--lockfile-only'])
|
|
216
235
|
}
|
|
217
236
|
|
|
218
|
-
/** Split planned
|
|
237
|
+
/** Split planned changes (`{ name, value }`) into allowed ones and ones held back by minimumReleaseAge. */
|
|
219
238
|
function applyReleaseAgeGate(overrides) {
|
|
220
239
|
const config = readPnpmConfig()
|
|
221
240
|
const minutes = Number(config.minimumReleaseAge ?? DEFAULT_MINIMUM_RELEASE_AGE_MINUTES)
|
|
@@ -241,6 +260,92 @@ function applyReleaseAgeGate(overrides) {
|
|
|
241
260
|
return { allowed, heldBack }
|
|
242
261
|
}
|
|
243
262
|
|
|
263
|
+
/** Yes when the flag is passed, else ask in a terminal, else no. */
|
|
264
|
+
let confirmPrompt
|
|
265
|
+
async function decide(message, flag) {
|
|
266
|
+
if (args[flag]) return true
|
|
267
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return false
|
|
268
|
+
confirmPrompt ??= (await import('@inquirer/confirm')).default
|
|
269
|
+
return confirmPrompt({ message, default: false })
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** First few advisory ids, so prompts stay on one line. */
|
|
273
|
+
const ghsaList = (item) =>
|
|
274
|
+
item.ghsas.length > 3 ? `${item.ghsas.slice(0, 3).join(', ')} +${item.ghsas.length - 3} more` : item.ghsas.join(', ')
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Ask about the findings the automatic pass will not fix. Returns what to force:
|
|
278
|
+
* overrides across a major, bumps of direct dependencies, and which of the young
|
|
279
|
+
* releases to allow via minimumReleaseAgeExclude.
|
|
280
|
+
*/
|
|
281
|
+
async function chooseForcedFixes(plan) {
|
|
282
|
+
const blocked = [...plan.crossMajor, ...plan.direct.filter((item) => item.to)]
|
|
283
|
+
if (blocked.length === 0) return { overrides: [], bumps: [] }
|
|
284
|
+
if (process.stdin.isTTY) console.log(`\n${TAG} Remaining findings that need a decision:`)
|
|
285
|
+
|
|
286
|
+
const overrides = []
|
|
287
|
+
for (const item of plan.crossMajor) {
|
|
288
|
+
const message = `${item.name} ${item.from} → ${item.to} (new major, ${ghsaList(item)}). Override?`
|
|
289
|
+
if (await decide(message, 'allow-major')) {
|
|
290
|
+
overrides.push({ ...item, key: `${item.name}@>=${item.from} <${item.to}`, value: item.to, forced: true })
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const pkg = JSON.parse(readFile('package.json'))
|
|
295
|
+
const bumps = []
|
|
296
|
+
for (const item of plan.direct) {
|
|
297
|
+
if (!item.to) continue
|
|
298
|
+
const sections = DEPENDENCY_SECTIONS.filter((section) => pkg[section]?.[item.name])
|
|
299
|
+
const specs = sections.map((section) => bumpDependencySpec(pkg[section][item.name], item.to))
|
|
300
|
+
if (sections.length === 0 || specs.includes(null)) continue
|
|
301
|
+
const major = item.versions.some((version) => !isSameReleaseLine(version, item.to)) ? 'new major, ' : ''
|
|
302
|
+
const message = `${item.name} ${item.versions.join(', ')} → ${item.to} (direct dependency, ${major}${ghsaList(item)}). Bump in package.json?`
|
|
303
|
+
if (await decide(message, 'bump-direct')) bumps.push({ ...item, value: item.to, kind: 'bump' })
|
|
304
|
+
}
|
|
305
|
+
return { overrides, bumps }
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Ask, per release held back by minimumReleaseAge, whether to allow it anyway. */
|
|
309
|
+
async function chooseYoungReleases(heldBack) {
|
|
310
|
+
const allowed = []
|
|
311
|
+
const declined = []
|
|
312
|
+
for (const item of heldBack) {
|
|
313
|
+
const message = `${item.name} ${item.value} is younger than minimumReleaseAge (~${item.waitHours}h left, ${ghsaList(item)}). Allow it via minimumReleaseAgeExclude?`
|
|
314
|
+
if (await decide(message, 'allow-young')) allowed.push({ ...item, excluded: true })
|
|
315
|
+
else declined.push(item)
|
|
316
|
+
}
|
|
317
|
+
return { allowed, declined }
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function writeReleaseAgeExcludes(items) {
|
|
321
|
+
if (items.length === 0) return
|
|
322
|
+
const file = path.join(cwd, 'pnpm-workspace.yaml')
|
|
323
|
+
const before = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : ''
|
|
324
|
+
fs.writeFileSync(file, addReleaseAgeExcludes(before, items.map(({ name, value }) => ({ name, version: value }))))
|
|
325
|
+
for (const item of items) console.log(`${TAG} minimumReleaseAgeExclude += ${item.name}@${item.value}`)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function writeDirectBumps(bumps) {
|
|
329
|
+
const file = path.join(cwd, 'package.json')
|
|
330
|
+
const pkg = JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
331
|
+
for (const bump of bumps) {
|
|
332
|
+
for (const section of DEPENDENCY_SECTIONS) {
|
|
333
|
+
if (!pkg[section]?.[bump.name]) continue
|
|
334
|
+
pkg[section][bump.name] = bumpDependencySpec(pkg[section][bump.name], bump.value)
|
|
335
|
+
}
|
|
336
|
+
console.log(`${TAG} Bump ${bump.name} -> ${bump.value} (${ghsaList(bump)})`)
|
|
337
|
+
}
|
|
338
|
+
const indent = fs.readFileSync(file, 'utf8').match(/^\{\n([ \t]+)/)?.[1] ?? ' '
|
|
339
|
+
fs.writeFileSync(file, `${JSON.stringify(pkg, null, indent)}\n`)
|
|
340
|
+
runOrFail('pnpm', ['install', '--lockfile-only', '--ignore-scripts', '--no-frozen-lockfile'])
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function buildBumpCommitMessage(bumps) {
|
|
344
|
+
const describe = (bump) => `- ${bump.name}: ${bump.versions.join(', ')} -> ${bump.value}`
|
|
345
|
+
if (bumps.length === 1) return { subject: `vuln(${bumps[0].name}): bump to ${bumps[0].value}`, body: describe(bumps[0]) }
|
|
346
|
+
return { subject: `vuln(): bump ${bumps.length} dependencies`, body: bumps.map(describe).join('\n') }
|
|
347
|
+
}
|
|
348
|
+
|
|
244
349
|
function writeOverrides(overrides) {
|
|
245
350
|
const file = path.join(cwd, 'pnpm-workspace.yaml')
|
|
246
351
|
const before = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : ''
|
|
@@ -300,7 +405,7 @@ function pruneOverrides() {
|
|
|
300
405
|
* shared file that match a finding still reported here. Untouched when nothing matches.
|
|
301
406
|
*/
|
|
302
407
|
function syncSuppressions(remaining) {
|
|
303
|
-
const sourcePath = path.
|
|
408
|
+
const sourcePath = path.join(fgDevkitRoot, 'configs', SUPPRESSIONS_FILE)
|
|
304
409
|
if (!fs.existsSync(sourcePath)) return { covered: [], uncovered: remaining }
|
|
305
410
|
const shared = fs.readFileSync(sourcePath, 'utf8')
|
|
306
411
|
const active = activeSuppressedGhsas(shared)
|
|
@@ -337,7 +442,7 @@ function verify() {
|
|
|
337
442
|
}
|
|
338
443
|
}
|
|
339
444
|
|
|
340
|
-
function printSummary({ fixedByRefresh, plan, heldBack, applied, remaining, suppressions, excludeChanges }) {
|
|
445
|
+
function printSummary({ fixedByRefresh, plan, forced, heldBack, applied, remaining, suppressions, excludeChanges }) {
|
|
341
446
|
const suppressed = new Set(suppressions.covered.map((advisory) => advisory.ghsa))
|
|
342
447
|
const unique = (items) => [...new Set(items)]
|
|
343
448
|
const lines = []
|
|
@@ -346,21 +451,40 @@ function printSummary({ fixedByRefresh, plan, heldBack, applied, remaining, supp
|
|
|
346
451
|
lines.push(title, ...items.map((item) => ` - ${item}`))
|
|
347
452
|
}
|
|
348
453
|
section('Fixed by lockfile refresh:', fixedByRefresh)
|
|
454
|
+
const youngNote = (item) => (item.excluded ? ', young release allowed via minimumReleaseAgeExclude' : '')
|
|
349
455
|
section(
|
|
350
456
|
'Overridden (parent range blocked the patch):',
|
|
351
|
-
applied
|
|
457
|
+
applied
|
|
458
|
+
.filter((item) => !item.forced)
|
|
459
|
+
.map((item) => `${item.key} -> ${item.value} (${item.ghsas.join(', ')}${youngNote(item)})`),
|
|
460
|
+
)
|
|
461
|
+
section(
|
|
462
|
+
'Overridden across a major (forced, verify covers it):',
|
|
463
|
+
applied
|
|
464
|
+
.filter((item) => item.forced)
|
|
465
|
+
.map((item) => `${item.key} -> ${item.value} (${item.ghsas.join(', ')}${youngNote(item)})`),
|
|
352
466
|
)
|
|
353
467
|
section(
|
|
354
|
-
'
|
|
468
|
+
'Bumped in package.json:',
|
|
469
|
+
forced.bumps.map(
|
|
470
|
+
(item) => `${item.name} ${item.versions.join(', ')} -> ${item.value} (${item.ghsas.join(', ')}${youngNote(item)})`,
|
|
471
|
+
),
|
|
472
|
+
)
|
|
473
|
+
section(
|
|
474
|
+
'Held back by minimumReleaseAge (re-run later, or pass --allow-young):',
|
|
355
475
|
heldBack.map((item) => `${item.name}@${item.value}, ~${item.waitHours}h left (${item.ghsas.join(', ')})`),
|
|
356
476
|
)
|
|
357
477
|
section(
|
|
358
|
-
'Direct dependency, bump
|
|
359
|
-
plan.direct
|
|
478
|
+
'Direct dependency, not bumped (pass --bump-direct, or use policy-upgrade-commit):',
|
|
479
|
+
plan.direct
|
|
480
|
+
.filter((item) => !forced.bumps.some((bump) => bump.name === item.name))
|
|
481
|
+
.map((item) => `${item.name} ${item.versions.join(', ')} (${item.ghsas.join(', ')})`),
|
|
360
482
|
)
|
|
361
483
|
section(
|
|
362
|
-
'Patch only in a new major, upgrade the parent
|
|
363
|
-
plan.crossMajor
|
|
484
|
+
'Patch only in a new major, not overridden (pass --allow-major, or upgrade the parent):',
|
|
485
|
+
plan.crossMajor
|
|
486
|
+
.filter((item) => !forced.overrides.some((override) => override.name === item.name && override.from === item.from))
|
|
487
|
+
.map((item) => `${item.name} ${item.from} -> ${item.to} (${item.ghsas.join(', ')})`),
|
|
364
488
|
)
|
|
365
489
|
section(
|
|
366
490
|
'No patched version and not suppressed, add a <suppress> entry to fg-devkit configs/dependency-check-suppressions.xml if the risk is accepted:',
|
|
@@ -393,7 +517,7 @@ function printSummary({ fixedByRefresh, plan, heldBack, applied, remaining, supp
|
|
|
393
517
|
// Main
|
|
394
518
|
// ---------------------------------------------------------------------------
|
|
395
519
|
|
|
396
|
-
function main() {
|
|
520
|
+
async function main() {
|
|
397
521
|
if (!readFile('package.json')) throw new Error(`package.json not found in ${cwd}`)
|
|
398
522
|
if (!readFile('pnpm-lock.yaml')) throw new Error(`pnpm-lock.yaml not found in ${cwd}`)
|
|
399
523
|
const shouldCommit = !args['no-commit']
|
|
@@ -408,9 +532,11 @@ function main() {
|
|
|
408
532
|
let refreshChanges = []
|
|
409
533
|
let fixedByRefresh = []
|
|
410
534
|
let plan = { overrides: [], noPatch: [], crossMajor: [], direct: [], unparseable: [] }
|
|
535
|
+
let forced = { overrides: [], bumps: [] }
|
|
411
536
|
let heldBack = []
|
|
412
537
|
let allowed = []
|
|
413
538
|
let remaining = []
|
|
539
|
+
let afterBumps
|
|
414
540
|
|
|
415
541
|
if (initial.length === 0) {
|
|
416
542
|
console.log(`${TAG} pnpm audit is clean.`)
|
|
@@ -429,19 +555,33 @@ function main() {
|
|
|
429
555
|
...new Set(initial.filter((advisory) => !remainingIds.has(advisory.id)).map((a) => `${a.name} (${a.ghsa})`)),
|
|
430
556
|
]
|
|
431
557
|
plan = planOverrides(afterRefresh, { directDependencies: directDependencies() })
|
|
432
|
-
|
|
558
|
+
forced = await chooseForcedFixes(plan)
|
|
559
|
+
const gated = applyReleaseAgeGate([...plan.overrides, ...forced.overrides, ...forced.bumps])
|
|
560
|
+
const young = await chooseYoungReleases(gated.heldBack)
|
|
561
|
+
heldBack = young.declined
|
|
562
|
+
const passed = [...gated.allowed, ...young.allowed]
|
|
563
|
+
forced.bumps = passed.filter((item) => item.kind === 'bump')
|
|
564
|
+
allowed = passed.filter((item) => item.kind !== 'bump')
|
|
565
|
+
|
|
566
|
+
// Direct bumps first, in their own commit.
|
|
567
|
+
if (forced.bumps.length > 0) {
|
|
568
|
+
writeReleaseAgeExcludes(forced.bumps.filter((item) => item.excluded))
|
|
569
|
+
writeDirectBumps(forced.bumps)
|
|
570
|
+
afterBumps = snapshot()
|
|
571
|
+
}
|
|
572
|
+
writeReleaseAgeExcludes(allowed.filter((item) => item.excluded))
|
|
433
573
|
if (allowed.length > 0) writeOverrides(allowed)
|
|
434
574
|
|
|
435
575
|
// 4. Prune overrides.
|
|
436
576
|
pruneOverrides()
|
|
437
|
-
remaining = allowed.length > 0 ? audit() : afterRefresh
|
|
577
|
+
remaining = allowed.length > 0 || forced.bumps.length > 0 ? audit() : afterRefresh
|
|
438
578
|
}
|
|
439
579
|
const afterOverrides = snapshot()
|
|
440
580
|
|
|
441
581
|
// 5. Release-age excludes, 6. suppressions, 7. verify.
|
|
442
582
|
const excludeChanges = pruneReleaseAgeExclude()
|
|
443
583
|
const suppressions = syncSuppressions(remaining)
|
|
444
|
-
printSummary({ fixedByRefresh, plan, heldBack, applied: allowed, remaining, suppressions, excludeChanges })
|
|
584
|
+
printSummary({ fixedByRefresh, plan, forced, heldBack, applied: allowed, remaining, suppressions, excludeChanges })
|
|
445
585
|
|
|
446
586
|
const final = snapshot()
|
|
447
587
|
const nothingChanged = !suppressionsWritten && TRACKED_FILES.every((file) => final[file] === original[file])
|
|
@@ -460,6 +600,9 @@ function main() {
|
|
|
460
600
|
if (refreshChanges.length > 0 && stageContents(refreshed) > 0) {
|
|
461
601
|
commit(buildRefreshCommitMessage(refreshChanges))
|
|
462
602
|
}
|
|
603
|
+
if (afterBumps && stageContents(afterBumps) > 0) {
|
|
604
|
+
commit(buildBumpCommitMessage(forced.bumps))
|
|
605
|
+
}
|
|
463
606
|
for (const item of overrideChanges) {
|
|
464
607
|
console.log(` - ${item.name}: ${item.from ?? '(missing)'} -> ${formatOverrideValue(item.to)}`)
|
|
465
608
|
}
|
|
@@ -487,7 +630,7 @@ function main() {
|
|
|
487
630
|
}
|
|
488
631
|
|
|
489
632
|
try {
|
|
490
|
-
process.exit(main())
|
|
633
|
+
process.exit(await main())
|
|
491
634
|
} catch (error) {
|
|
492
635
|
console.error(`${TAG} ${error instanceof Error ? error.message : error}`)
|
|
493
636
|
console.error(
|
|
@@ -31,22 +31,20 @@
|
|
|
31
31
|
|
|
32
32
|
import fs from 'node:fs'
|
|
33
33
|
import path from 'node:path'
|
|
34
|
-
import { fileURLToPath } from 'node:url'
|
|
35
34
|
|
|
36
|
-
import { collectFleetProjectRepos, toolchainExclude } from './fleet-project-repos.mjs'
|
|
37
|
-
import { parseFleetSelectionFlags, resolveFleetRepos } from './fleet-repo-picker.mjs'
|
|
35
|
+
import { collectFleetProjectRepos, toolchainExclude } from './lib/fleet-project-repos.mjs'
|
|
36
|
+
import { parseFleetSelectionFlags, resolveFleetRepos } from './lib/fleet-repo-picker.mjs'
|
|
38
37
|
import {
|
|
39
38
|
formatNodeNvmrc,
|
|
40
39
|
readFgDevkitNodePolicy,
|
|
41
40
|
resolveExpectedNodePolicy,
|
|
42
|
-
} from './dependency-policy-toolchain.mjs'
|
|
43
|
-
import { syncNodeToolchainInRepo } from './node-toolchain-sync.mjs'
|
|
44
|
-
import { resolveDependencyPolicyPath } from './resolve-dependency-policy-path.mjs'
|
|
41
|
+
} from './lib/dependency-policy-toolchain.mjs'
|
|
42
|
+
import { syncNodeToolchainInRepo } from './lib/node-toolchain-sync.mjs'
|
|
43
|
+
import { resolveDependencyPolicyPath } from './lib/resolve-dependency-policy-path.mjs'
|
|
44
|
+
import { assertFgDevkitMaintainerContext, fgDevkitRoot, readJson } from './lib/common.mjs'
|
|
45
45
|
|
|
46
|
-
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
|
47
|
-
const fgDevkitRoot = path.resolve(scriptDir, '..')
|
|
48
46
|
const fgDevkitPkgPath = path.join(fgDevkitRoot, 'package.json')
|
|
49
|
-
const policyPath = resolveDependencyPolicyPath(
|
|
47
|
+
const policyPath = resolveDependencyPolicyPath(fgDevkitRoot, [])
|
|
50
48
|
|
|
51
49
|
const cliArgs = process.argv.slice(2)
|
|
52
50
|
const dryRun = cliArgs.includes('--dry-run')
|
|
@@ -55,27 +53,6 @@ const noEngines = cliArgs.includes('--no-engines')
|
|
|
55
53
|
const enginesArg = cliArgs.find((a) => a.startsWith('--engines='))?.slice('--engines='.length)
|
|
56
54
|
const { pick, all, only, filteredArgs: positionalArgs } = parseFleetSelectionFlags(cliArgs)
|
|
57
55
|
|
|
58
|
-
function readJson(jsonPath) {
|
|
59
|
-
return JSON.parse(fs.readFileSync(jsonPath, 'utf8'))
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function assertFgDevkitMaintainerContext() {
|
|
63
|
-
if (path.resolve(process.cwd()) !== fgDevkitRoot) {
|
|
64
|
-
console.error('[bump-node-toolchain] Run from the fg-devkit repository root.')
|
|
65
|
-
process.exit(1)
|
|
66
|
-
}
|
|
67
|
-
try {
|
|
68
|
-
const pkg = readJson(fgDevkitPkgPath)
|
|
69
|
-
if (pkg?.name !== 'fg-devkit') {
|
|
70
|
-
console.error('[bump-node-toolchain] package.json name must be fg-devkit.')
|
|
71
|
-
process.exit(1)
|
|
72
|
-
}
|
|
73
|
-
} catch {
|
|
74
|
-
console.error('[bump-node-toolchain] Could not read fg-devkit package.json.')
|
|
75
|
-
process.exit(1)
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
56
|
function runStep(label, fn) {
|
|
80
57
|
console.log(`\n[bump-node-toolchain] ${label}`)
|
|
81
58
|
return fn()
|
|
@@ -185,7 +162,7 @@ function syncFleetRepo(repoDir, projectsRoot, policy) {
|
|
|
185
162
|
}
|
|
186
163
|
|
|
187
164
|
async function main() {
|
|
188
|
-
assertFgDevkitMaintainerContext()
|
|
165
|
+
assertFgDevkitMaintainerContext('[bump-node-toolchain]')
|
|
189
166
|
|
|
190
167
|
const nvmrcArg = positionalArgs.find((a) => /^\d/.test(a))
|
|
191
168
|
const projectsRootArg = positionalArgs.find((a) => a !== nvmrcArg)
|
|
@@ -35,20 +35,20 @@ import path from 'node:path'
|
|
|
35
35
|
import { spawnSync } from 'node:child_process'
|
|
36
36
|
import { fileURLToPath } from 'node:url'
|
|
37
37
|
|
|
38
|
-
import { formatPnpmPackageManager } from './dependency-policy-toolchain.mjs'
|
|
39
|
-
import { collectFleetPnpmRepos } from './fleet-project-repos.mjs'
|
|
40
|
-
import { parseFleetSelectionFlags, resolveFleetRepos } from './fleet-repo-picker.mjs'
|
|
38
|
+
import { formatPnpmPackageManager } from './lib/dependency-policy-toolchain.mjs'
|
|
39
|
+
import { collectFleetPnpmRepos } from './lib/fleet-project-repos.mjs'
|
|
40
|
+
import { parseFleetSelectionFlags, resolveFleetRepos } from './lib/fleet-repo-picker.mjs'
|
|
41
41
|
import {
|
|
42
42
|
isVueFgDevkitConsumerRepo,
|
|
43
43
|
syncPnpmToolchainInRepo,
|
|
44
|
-
} from './pnpm-toolchain-sync.mjs'
|
|
45
|
-
import { resolveDependencyPolicyPath } from './resolve-dependency-policy-path.mjs'
|
|
44
|
+
} from './lib/pnpm-toolchain-sync.mjs'
|
|
45
|
+
import { resolveDependencyPolicyPath } from './lib/resolve-dependency-policy-path.mjs'
|
|
46
46
|
import { syncScaffoldVueDepsFromPolicy } from './sync-scaffold-vue-deps.mjs'
|
|
47
|
+
import { assertFgDevkitMaintainerContext, fgDevkitRoot, readJson } from './lib/common.mjs'
|
|
47
48
|
|
|
48
49
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
|
49
|
-
const fgDevkitRoot = path.resolve(scriptDir, '..')
|
|
50
50
|
const fgDevkitPkgPath = path.join(fgDevkitRoot, 'package.json')
|
|
51
|
-
const policyPath = resolveDependencyPolicyPath(
|
|
51
|
+
const policyPath = resolveDependencyPolicyPath(fgDevkitRoot, [])
|
|
52
52
|
|
|
53
53
|
const cliArgs = process.argv.slice(2)
|
|
54
54
|
const dryRun = cliArgs.includes('--dry-run')
|
|
@@ -57,27 +57,6 @@ const skipInstall = cliArgs.includes('--skip-install')
|
|
|
57
57
|
const { pick, all, only, filteredArgs: positionalArgs } = parseFleetSelectionFlags(cliArgs)
|
|
58
58
|
let projectsRoot = path.resolve(fgDevkitRoot, '..')
|
|
59
59
|
|
|
60
|
-
function readJson(jsonPath) {
|
|
61
|
-
return JSON.parse(fs.readFileSync(jsonPath, 'utf8'))
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function assertFgDevkitMaintainerContext() {
|
|
65
|
-
if (path.resolve(process.cwd()) !== fgDevkitRoot) {
|
|
66
|
-
console.error('[bump-pnpm-toolchain] Run from the fg-devkit repository root.')
|
|
67
|
-
process.exit(1)
|
|
68
|
-
}
|
|
69
|
-
try {
|
|
70
|
-
const pkg = readJson(fgDevkitPkgPath)
|
|
71
|
-
if (pkg?.name !== 'fg-devkit') {
|
|
72
|
-
console.error('[bump-pnpm-toolchain] package.json name must be fg-devkit.')
|
|
73
|
-
process.exit(1)
|
|
74
|
-
}
|
|
75
|
-
} catch {
|
|
76
|
-
console.error('[bump-pnpm-toolchain] Could not read fg-devkit package.json.')
|
|
77
|
-
process.exit(1)
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
60
|
function resolveTargetVersion(versionArg) {
|
|
82
61
|
if (versionArg) {
|
|
83
62
|
return formatPnpmPackageManager(versionArg)
|
|
@@ -218,7 +197,7 @@ function syncFleetRepo(repoDir, repoName, packageManager) {
|
|
|
218
197
|
}
|
|
219
198
|
|
|
220
199
|
async function main() {
|
|
221
|
-
assertFgDevkitMaintainerContext()
|
|
200
|
+
assertFgDevkitMaintainerContext('[bump-pnpm-toolchain]')
|
|
222
201
|
|
|
223
202
|
const versionArg = positionalArgs.find((a) => /^(pnpm@)?\d/.test(a))
|
|
224
203
|
const projectsRootArg = positionalArgs.find((a) => a !== versionArg)
|
|
@@ -15,22 +15,22 @@
|
|
|
15
15
|
|
|
16
16
|
import fs from 'node:fs'
|
|
17
17
|
import path from 'node:path'
|
|
18
|
-
import { fileURLToPath } from 'node:url'
|
|
19
18
|
|
|
20
|
-
import { collectPinViolations, isPinnedDependencyVersion } from './dependency-policy-pins.mjs'
|
|
19
|
+
import { collectPinViolations, isPinnedDependencyVersion } from './lib/dependency-policy-pins.mjs'
|
|
21
20
|
import {
|
|
22
21
|
collectToolchainPolicyViolations,
|
|
23
22
|
resolveExpectedPackageManager,
|
|
24
|
-
} from './dependency-policy-toolchain.mjs'
|
|
25
|
-
import { findPnpmVersionMismatchesInRepo } from './pnpm-toolchain-sync.mjs'
|
|
26
|
-
import { collectNodeToolchainViolations } from './node-toolchain-sync.mjs'
|
|
27
|
-
import { resolveDependencyPolicyPath } from './resolve-dependency-policy-path.mjs'
|
|
23
|
+
} from './lib/dependency-policy-toolchain.mjs'
|
|
24
|
+
import { findPnpmVersionMismatchesInRepo } from './lib/pnpm-toolchain-sync.mjs'
|
|
25
|
+
import { collectNodeToolchainViolations } from './lib/node-toolchain-sync.mjs'
|
|
26
|
+
import { resolveDependencyPolicyPath } from './lib/resolve-dependency-policy-path.mjs'
|
|
27
|
+
import { getRelevantPolicyOverrides } from './lib/override-changes.mjs'
|
|
28
|
+
import { fgDevkitRoot } from './lib/common.mjs'
|
|
28
29
|
|
|
29
30
|
const cwd = process.cwd()
|
|
30
31
|
const pkgPath = path.resolve(cwd, 'package.json')
|
|
31
32
|
const lockfilePath = path.resolve(cwd, 'pnpm-lock.yaml')
|
|
32
|
-
const
|
|
33
|
-
const policyPath = resolveDependencyPolicyPath(scriptDir, cwd, process.argv)
|
|
33
|
+
const policyPath = resolveDependencyPolicyPath(cwd, process.argv)
|
|
34
34
|
|
|
35
35
|
if (!fs.existsSync(pkgPath)) {
|
|
36
36
|
console.error('[check-deps] Could not find package.json in current working directory')
|
|
@@ -50,30 +50,6 @@ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
|
|
|
50
50
|
const policy = JSON.parse(fs.readFileSync(policyPath, 'utf8'))
|
|
51
51
|
const mismatches = []
|
|
52
52
|
|
|
53
|
-
function getOverridePackageName(overrideKey) {
|
|
54
|
-
const match = overrideKey.match(/^(@[^/]+\/[^@]+|[^@]+)@/)
|
|
55
|
-
return match ? match[1] : overrideKey
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function getRelevantPolicyOverrides(policyOverrides) {
|
|
59
|
-
const entries = Object.entries(policyOverrides ?? {})
|
|
60
|
-
if (entries.length === 0) {
|
|
61
|
-
return {}
|
|
62
|
-
}
|
|
63
|
-
if (!fs.existsSync(lockfilePath)) {
|
|
64
|
-
return Object.fromEntries(entries)
|
|
65
|
-
}
|
|
66
|
-
const lockfile = fs.readFileSync(lockfilePath, 'utf8')
|
|
67
|
-
const relevant = {}
|
|
68
|
-
for (const [name, version] of entries) {
|
|
69
|
-
const pkgName = getOverridePackageName(name)
|
|
70
|
-
if (lockfile.includes(`${pkgName}@`) || lockfile.includes(`/${pkgName}/`)) {
|
|
71
|
-
relevant[name] = version
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return relevant
|
|
75
|
-
}
|
|
76
|
-
|
|
77
53
|
/** True if this package+section is governed by the policy (so global pin checks skip it). */
|
|
78
54
|
function isPolicyManagedInSection(section, name) {
|
|
79
55
|
if (policy.managedAnywhere?.[name] !== undefined) {
|
|
@@ -90,7 +66,7 @@ for (const [section, dependencies] of Object.entries(policy.managedDependencies
|
|
|
90
66
|
mismatches.push(...collectPinViolations('[policy] managedAnywhere.', policy.managedAnywhere ?? {}))
|
|
91
67
|
mismatches.push(...collectToolchainPolicyViolations(policy))
|
|
92
68
|
|
|
93
|
-
const expectedPackageManager = resolveExpectedPackageManager(policy,
|
|
69
|
+
const expectedPackageManager = resolveExpectedPackageManager(policy, fgDevkitRoot)
|
|
94
70
|
if (expectedPackageManager !== undefined) {
|
|
95
71
|
const actualPackageManager = pkg.packageManager
|
|
96
72
|
if (actualPackageManager !== expectedPackageManager) {
|
|
@@ -100,7 +76,6 @@ if (expectedPackageManager !== undefined) {
|
|
|
100
76
|
}
|
|
101
77
|
}
|
|
102
78
|
|
|
103
|
-
const fgDevkitRoot = path.resolve(scriptDir, '..')
|
|
104
79
|
mismatches.push(...collectNodeToolchainViolations(cwd, policy, fgDevkitRoot))
|
|
105
80
|
if (expectedPackageManager !== undefined) {
|
|
106
81
|
mismatches.push(...findPnpmVersionMismatchesInRepo(cwd, expectedPackageManager))
|
|
@@ -146,7 +121,7 @@ for (const [name, expected] of Object.entries(policy.managedAnywhere ?? {})) {
|
|
|
146
121
|
|
|
147
122
|
// Optional: policy may define pnpm.overrides entries the consumer must mirror.
|
|
148
123
|
const overrides = pkg.pnpm?.overrides ?? {}
|
|
149
|
-
for (const [name, expected] of Object.entries(getRelevantPolicyOverrides(policy.overrides
|
|
124
|
+
for (const [name, expected] of Object.entries(getRelevantPolicyOverrides(policy.overrides, lockfilePath))) {
|
|
150
125
|
const actual = overrides[name]
|
|
151
126
|
if (actual !== expected) {
|
|
152
127
|
mismatches.push(`pnpm.overrides.${name}: expected ${expected}, got ${actual ?? '(missing)'}`)
|