fg-devkit 1.22.0 → 1.24.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 +30 -7
- package/configs/dependency-policy.vue-workspace.json +28 -28
- package/package.json +12 -85
- package/scaffold/vue/package.json +13 -13
- 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 +290 -337
- 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/setup-ai-rules.mjs +135 -0
- package/scripts/sync-consumer-lefthook.mjs +7 -24
- 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 -8
- package/scripts/sync-pnpm-toolchain.mjs +5 -8
- 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/.cursor/rules/core-coding-standards.mdc +0 -291
- 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-core-coding-standards.mjs +0 -77
- 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
|
@@ -16,12 +16,11 @@
|
|
|
16
16
|
import fs from 'node:fs'
|
|
17
17
|
import path from 'node:path'
|
|
18
18
|
import { spawnSync } from 'node:child_process'
|
|
19
|
-
import { fileURLToPath } from 'node:url'
|
|
20
19
|
|
|
21
|
-
import { collectFleetVueRepos } from './fleet-vue-repos.mjs'
|
|
22
|
-
import { parseFleetSelectionFlags, resolveFleetRepos } from './fleet-repo-picker.mjs'
|
|
20
|
+
import { collectFleetVueRepos } from './lib/fleet-vue-repos.mjs'
|
|
21
|
+
import { parseFleetSelectionFlags, resolveFleetRepos } from './lib/fleet-repo-picker.mjs'
|
|
22
|
+
import { fgDevkitRoot } from './lib/common.mjs'
|
|
23
23
|
|
|
24
|
-
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
|
25
24
|
const cliArgs = process.argv.slice(2)
|
|
26
25
|
const dryRun = cliArgs.includes('--dry-run')
|
|
27
26
|
const fgDevkitOnly = cliArgs.includes('--fg-devkit-only')
|
|
@@ -29,7 +28,7 @@ const { pick, all, only, filteredArgs: positionalArgs } = parseFleetSelectionFla
|
|
|
29
28
|
const projectsRootArg = positionalArgs.find((a) => !a.startsWith('-'))
|
|
30
29
|
const projectsRoot = projectsRootArg
|
|
31
30
|
? path.resolve(process.cwd(), projectsRootArg)
|
|
32
|
-
: path.resolve(
|
|
31
|
+
: path.resolve(fgDevkitRoot, '..')
|
|
33
32
|
|
|
34
33
|
const LOG_PREFIX = '[fleet-bump-fg-devkit]'
|
|
35
34
|
|
|
@@ -15,16 +15,13 @@
|
|
|
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 { collectFleetGitRepos } from './fleet-project-repos.mjs'
|
|
21
|
-
import { parseFleetSelectionFlags, resolveFleetRepos } from './fleet-repo-picker.mjs'
|
|
22
|
-
import { filterCommitPushPositionalArgs, resolveCommitMessage } from './git-fleet-common.mjs'
|
|
23
|
-
import { runFleetGitCommitPush } from './git-commit-push-fleet-common.mjs'
|
|
19
|
+
import { collectFleetGitRepos } from './lib/fleet-project-repos.mjs'
|
|
20
|
+
import { parseFleetSelectionFlags, resolveFleetRepos } from './lib/fleet-repo-picker.mjs'
|
|
21
|
+
import { filterCommitPushPositionalArgs, resolveCommitMessage } from './lib/git-fleet-common.mjs'
|
|
22
|
+
import { runFleetGitCommitPush } from './lib/git-commit-push-fleet-common.mjs'
|
|
23
|
+
import { assertFgDevkitMaintainerContext, fgDevkitRoot } from './lib/common.mjs'
|
|
24
24
|
|
|
25
|
-
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
|
26
|
-
const fgDevkitRoot = path.resolve(scriptDir, '..')
|
|
27
|
-
const fgDevkitPkgPath = path.join(fgDevkitRoot, 'package.json')
|
|
28
25
|
const LOG_PREFIX = '[fleet-git-commit-push-scoped]'
|
|
29
26
|
|
|
30
27
|
const cliArgs = process.argv.slice(2)
|
|
@@ -37,25 +34,8 @@ let projectsRoot = projectsRootArg
|
|
|
37
34
|
? path.resolve(process.cwd(), projectsRootArg)
|
|
38
35
|
: path.resolve(fgDevkitRoot, '..')
|
|
39
36
|
|
|
40
|
-
function assertFgDevkitMaintainerContext() {
|
|
41
|
-
if (path.resolve(process.cwd()) !== fgDevkitRoot) {
|
|
42
|
-
console.error(`${LOG_PREFIX} Run from the fg-devkit repository root.`)
|
|
43
|
-
process.exit(1)
|
|
44
|
-
}
|
|
45
|
-
try {
|
|
46
|
-
const pkg = JSON.parse(fs.readFileSync(fgDevkitPkgPath, 'utf8'))
|
|
47
|
-
if (pkg?.name !== 'fg-devkit') {
|
|
48
|
-
console.error(`${LOG_PREFIX} package.json name must be fg-devkit.`)
|
|
49
|
-
process.exit(1)
|
|
50
|
-
}
|
|
51
|
-
} catch {
|
|
52
|
-
console.error(`${LOG_PREFIX} Could not read fg-devkit package.json.`)
|
|
53
|
-
process.exit(1)
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
37
|
async function main() {
|
|
58
|
-
assertFgDevkitMaintainerContext()
|
|
38
|
+
assertFgDevkitMaintainerContext(LOG_PREFIX)
|
|
59
39
|
|
|
60
40
|
if (!fgDevkitOnly && !fs.existsSync(projectsRoot)) {
|
|
61
41
|
console.error(`${LOG_PREFIX} projects root not found: ${projectsRoot}`)
|
|
@@ -15,15 +15,12 @@
|
|
|
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 { collectFleetGitRepos } from './fleet-project-repos.mjs'
|
|
21
|
-
import { parseFleetSelectionFlags, resolveFleetRepos } from './fleet-repo-picker.mjs'
|
|
22
|
-
import { runFleetGitDiscard } from './git-discard-fleet-common.mjs'
|
|
19
|
+
import { collectFleetGitRepos } from './lib/fleet-project-repos.mjs'
|
|
20
|
+
import { parseFleetSelectionFlags, resolveFleetRepos } from './lib/fleet-repo-picker.mjs'
|
|
21
|
+
import { runFleetGitDiscard } from './lib/git-discard-fleet-common.mjs'
|
|
22
|
+
import { assertFgDevkitMaintainerContext, fgDevkitRoot } from './lib/common.mjs'
|
|
23
23
|
|
|
24
|
-
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
|
25
|
-
const fgDevkitRoot = path.resolve(scriptDir, '..')
|
|
26
|
-
const fgDevkitPkgPath = path.join(fgDevkitRoot, 'package.json')
|
|
27
24
|
const LOG_PREFIX = '[fleet-git-discard-scoped]'
|
|
28
25
|
|
|
29
26
|
const cliArgs = process.argv.slice(2)
|
|
@@ -36,25 +33,8 @@ let projectsRoot = projectsRootArg
|
|
|
36
33
|
? path.resolve(process.cwd(), projectsRootArg)
|
|
37
34
|
: path.resolve(fgDevkitRoot, '..')
|
|
38
35
|
|
|
39
|
-
function assertFgDevkitMaintainerContext() {
|
|
40
|
-
if (path.resolve(process.cwd()) !== fgDevkitRoot) {
|
|
41
|
-
console.error(`${LOG_PREFIX} Run from the fg-devkit repository root.`)
|
|
42
|
-
process.exit(1)
|
|
43
|
-
}
|
|
44
|
-
try {
|
|
45
|
-
const pkg = JSON.parse(fs.readFileSync(fgDevkitPkgPath, 'utf8'))
|
|
46
|
-
if (pkg?.name !== 'fg-devkit') {
|
|
47
|
-
console.error(`${LOG_PREFIX} package.json name must be fg-devkit.`)
|
|
48
|
-
process.exit(1)
|
|
49
|
-
}
|
|
50
|
-
} catch {
|
|
51
|
-
console.error(`${LOG_PREFIX} Could not read fg-devkit package.json.`)
|
|
52
|
-
process.exit(1)
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
36
|
async function main() {
|
|
57
|
-
assertFgDevkitMaintainerContext()
|
|
37
|
+
assertFgDevkitMaintainerContext(LOG_PREFIX)
|
|
58
38
|
|
|
59
39
|
if (!fgDevkitOnly && !fs.existsSync(projectsRoot)) {
|
|
60
40
|
console.error(`${LOG_PREFIX} projects root not found: ${projectsRoot}`)
|
|
@@ -15,15 +15,12 @@
|
|
|
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 { collectFleetGitRepos } from './fleet-project-repos.mjs'
|
|
21
|
-
import { parseFleetSelectionFlags, resolveFleetRepos } from './fleet-repo-picker.mjs'
|
|
22
|
-
import { runFleetGitPull } from './git-pull-fleet-common.mjs'
|
|
19
|
+
import { collectFleetGitRepos } from './lib/fleet-project-repos.mjs'
|
|
20
|
+
import { parseFleetSelectionFlags, resolveFleetRepos } from './lib/fleet-repo-picker.mjs'
|
|
21
|
+
import { runFleetGitPull } from './lib/git-pull-fleet-common.mjs'
|
|
22
|
+
import { assertFgDevkitMaintainerContext, fgDevkitRoot } from './lib/common.mjs'
|
|
23
23
|
|
|
24
|
-
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
|
25
|
-
const fgDevkitRoot = path.resolve(scriptDir, '..')
|
|
26
|
-
const fgDevkitPkgPath = path.join(fgDevkitRoot, 'package.json')
|
|
27
24
|
|
|
28
25
|
const cliArgs = process.argv.slice(2)
|
|
29
26
|
const dryRun = cliArgs.includes('--dry-run')
|
|
@@ -35,25 +32,8 @@ let projectsRoot = projectsRootArg
|
|
|
35
32
|
? path.resolve(process.cwd(), projectsRootArg)
|
|
36
33
|
: path.resolve(fgDevkitRoot, '..')
|
|
37
34
|
|
|
38
|
-
function assertFgDevkitMaintainerContext() {
|
|
39
|
-
if (path.resolve(process.cwd()) !== fgDevkitRoot) {
|
|
40
|
-
console.error('[fleet-git-pull-scoped] Run from the fg-devkit repository root.')
|
|
41
|
-
process.exit(1)
|
|
42
|
-
}
|
|
43
|
-
try {
|
|
44
|
-
const pkg = JSON.parse(fs.readFileSync(fgDevkitPkgPath, 'utf8'))
|
|
45
|
-
if (pkg?.name !== 'fg-devkit') {
|
|
46
|
-
console.error('[fleet-git-pull-scoped] package.json name must be fg-devkit.')
|
|
47
|
-
process.exit(1)
|
|
48
|
-
}
|
|
49
|
-
} catch {
|
|
50
|
-
console.error('[fleet-git-pull-scoped] Could not read fg-devkit package.json.')
|
|
51
|
-
process.exit(1)
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
35
|
async function main() {
|
|
56
|
-
assertFgDevkitMaintainerContext()
|
|
36
|
+
assertFgDevkitMaintainerContext('[fleet-git-pull-scoped]')
|
|
57
37
|
|
|
58
38
|
if (!fgDevkitOnly && !fs.existsSync(projectsRoot)) {
|
|
59
39
|
console.error(`[fleet-git-pull-scoped] projects root not found: ${projectsRoot}`)
|
package/scripts/fleet-vulns.mjs
CHANGED
|
@@ -10,17 +10,16 @@
|
|
|
10
10
|
import fs from 'node:fs'
|
|
11
11
|
import path from 'node:path'
|
|
12
12
|
import { spawnSync } from 'node:child_process'
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
13
|
+
import { isFgDevkitRepoDir } from './lib/fleet-project-repos.mjs'
|
|
14
|
+
import { filterReposByOnly, parseFleetOnlyArg } from './lib/fleet-repo-picker.mjs'
|
|
15
|
+
import { fgDevkitRoot, readJson } from './lib/common.mjs'
|
|
16
16
|
|
|
17
|
-
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
|
18
17
|
const cliArgs = process.argv.slice(2)
|
|
19
18
|
const summaryOnly = cliArgs.includes('--summary-only')
|
|
20
19
|
const dryRun = cliArgs.includes('--dry-run')
|
|
21
20
|
const only = parseFleetOnlyArg(cliArgs)
|
|
22
21
|
const projectsRootArg = cliArgs.find((a) => !a.startsWith('-'))
|
|
23
|
-
const projectsRoot = projectsRootArg ? path.resolve(process.cwd(), projectsRootArg) : path.resolve(
|
|
22
|
+
const projectsRoot = projectsRootArg ? path.resolve(process.cwd(), projectsRootArg) : path.resolve(fgDevkitRoot, '..')
|
|
24
23
|
const delayArg = cliArgs.find((a) => a.startsWith('--delay-ms='))
|
|
25
24
|
const delayMs = Number(delayArg?.split('=')[1] ?? 2500)
|
|
26
25
|
|
|
@@ -38,10 +37,6 @@ function walk(dir, acc = []) {
|
|
|
38
37
|
return acc
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
function readJson(p) {
|
|
42
|
-
return JSON.parse(fs.readFileSync(p, 'utf8'))
|
|
43
|
-
}
|
|
44
|
-
|
|
45
40
|
function isPnpmProject(pkg) {
|
|
46
41
|
return (
|
|
47
42
|
typeof pkg.packageManager === 'string' && pkg.packageManager.toLowerCase().startsWith('pnpm@')
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
resolveCliffConfig,
|
|
14
14
|
resolveGitCliffBin,
|
|
15
15
|
runSyncUserChangelog,
|
|
16
|
-
} from './release-notes-common.mjs'
|
|
16
|
+
} from './lib/release-notes-common.mjs'
|
|
17
17
|
const cwd = process.cwd()
|
|
18
18
|
const tagArg = process.argv.find((arg) => arg.startsWith('--tag='))?.slice('--tag='.length)
|
|
19
19
|
|
|
@@ -156,10 +156,6 @@ export function planOverrides(advisories, { directDependencies = new Set() } = {
|
|
|
156
156
|
|
|
157
157
|
for (const [name, packageAdvisories] of byPackage) {
|
|
158
158
|
const ghsas = [...new Set(packageAdvisories.map((advisory) => advisory.ghsa))]
|
|
159
|
-
if (directDependencies.has(name)) {
|
|
160
|
-
plan.direct.push({ name, ghsas, versions: collectVersions(packageAdvisories) })
|
|
161
|
-
continue
|
|
162
|
-
}
|
|
163
159
|
|
|
164
160
|
// installed version -> highest "lowest patched version" across its advisories
|
|
165
161
|
const targets = new Map()
|
|
@@ -185,6 +181,17 @@ export function planOverrides(advisories, { directDependencies = new Set() } = {
|
|
|
185
181
|
}
|
|
186
182
|
}
|
|
187
183
|
|
|
184
|
+
if (directDependencies.has(name)) {
|
|
185
|
+
// Bumped in package.json, not overridden. `to` is the lowest version that fixes every
|
|
186
|
+
// installed version, or undefined when some advisory cannot be fixed.
|
|
187
|
+
const fixable = blocked.none.size === 0 && blocked.unparseable.size === 0 && targets.size > 0
|
|
188
|
+
const to = fixable
|
|
189
|
+
? [...targets.values()].map((item) => item.target).sort(compareVersions).at(-1)
|
|
190
|
+
: undefined
|
|
191
|
+
plan.direct.push({ name, ghsas, versions: collectVersions(packageAdvisories), to })
|
|
192
|
+
continue
|
|
193
|
+
}
|
|
194
|
+
|
|
188
195
|
if (blocked.none.size > 0) {
|
|
189
196
|
plan.noPatch.push({ name, ghsas: [...blocked.none], versions: collectVersions(packageAdvisories) })
|
|
190
197
|
}
|
|
@@ -568,3 +575,69 @@ export function rewriteWorkspaceList(text, key, replacements) {
|
|
|
568
575
|
}
|
|
569
576
|
return [...output, ...lines.slice(end)].join('\n')
|
|
570
577
|
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Add `name@version` pins to the top-level minimumReleaseAgeExclude list. A version is
|
|
581
|
+
* merged into an existing `name@a || b` entry, skipped when a name rule already covers it,
|
|
582
|
+
* and otherwise appended as a new item. Creates the list when it is missing.
|
|
583
|
+
*
|
|
584
|
+
* @param {string} text
|
|
585
|
+
* @param {{ name: string, version: string }[]} pins
|
|
586
|
+
*/
|
|
587
|
+
export function addReleaseAgeExcludes(text, pins) {
|
|
588
|
+
const key = 'minimumReleaseAgeExclude'
|
|
589
|
+
let result = String(text ?? '')
|
|
590
|
+
const replacements = new Map()
|
|
591
|
+
const appended = []
|
|
592
|
+
const entries = readWorkspaceList(result, key)
|
|
593
|
+
for (const { name, version } of pins) {
|
|
594
|
+
const current = [...entries.map((entry) => replacements.get(entry) ?? entry), ...appended]
|
|
595
|
+
if (isReleaseAgeExcluded(current, name, version)) continue
|
|
596
|
+
const existing = entries.find((entry) => {
|
|
597
|
+
const parsed = parseReleaseAgeExcludeEntry(entry)
|
|
598
|
+
return parsed.name === name && parsed.versions !== null
|
|
599
|
+
})
|
|
600
|
+
if (existing) {
|
|
601
|
+
const merged = replacements.get(existing) ?? existing
|
|
602
|
+
replacements.set(existing, `${merged} || ${version}`)
|
|
603
|
+
continue
|
|
604
|
+
}
|
|
605
|
+
const pending = appended.findIndex((entry) => parseReleaseAgeExcludeEntry(entry).name === name)
|
|
606
|
+
if (pending >= 0) appended[pending] = `${appended[pending]} || ${version}`
|
|
607
|
+
else appended.push(`${name}@${version}`)
|
|
608
|
+
}
|
|
609
|
+
if (replacements.size > 0) result = rewriteWorkspaceList(result, key, replacements)
|
|
610
|
+
if (appended.length === 0) return result
|
|
611
|
+
|
|
612
|
+
const lines = result.split('\n')
|
|
613
|
+
const start = lines.findIndex((line) => line === `${key}:` || line.startsWith(`${key}: #`))
|
|
614
|
+
const rendered = (indent) => appended.map((entry) => `${indent}- ${renderYamlListItem(entry)}`)
|
|
615
|
+
if (start === -1) {
|
|
616
|
+
while (lines.length > 0 && lines.at(-1) === '') lines.pop()
|
|
617
|
+
lines.push(`${key}:`, ...rendered(' '), '')
|
|
618
|
+
return lines.join('\n')
|
|
619
|
+
}
|
|
620
|
+
let last = start
|
|
621
|
+
let indent = ' '
|
|
622
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
623
|
+
if (lines[i].trim() === '' || lines[i].trim().startsWith('#')) continue
|
|
624
|
+
const item = lines[i].match(/^(\s+)-\s/)
|
|
625
|
+
if (!item) break
|
|
626
|
+
indent = item[1]
|
|
627
|
+
last = i
|
|
628
|
+
}
|
|
629
|
+
lines.splice(last + 1, 0, ...rendered(indent))
|
|
630
|
+
return lines.join('\n')
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* New package.json spec that pins `version` with the same prefix as `spec`
|
|
635
|
+
* (`1.2.3`, `^1.2.3`, `~1.2.3`, `=1.2.3`). Returns null for specs that are not a plain
|
|
636
|
+
* version (`workspace:*`, `catalog:`, ranges, URLs), which are left for a human.
|
|
637
|
+
*/
|
|
638
|
+
export function bumpDependencySpec(spec, version) {
|
|
639
|
+
const match = String(spec ?? '')
|
|
640
|
+
.trim()
|
|
641
|
+
.match(/^([\^~=]?)v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/)
|
|
642
|
+
return match ? `${match[1]}${version}` : null
|
|
643
|
+
}
|
|
@@ -3,7 +3,9 @@ import test from 'node:test'
|
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
activeSuppressedGhsas,
|
|
6
|
+
addReleaseAgeExcludes,
|
|
6
7
|
buildRefreshCommitMessage,
|
|
8
|
+
bumpDependencySpec,
|
|
7
9
|
compareVersions,
|
|
8
10
|
diffLockfileVersions,
|
|
9
11
|
filterSuppressions,
|
|
@@ -148,11 +150,57 @@ test('planOverrides reports instead of overriding when it would be unsafe or use
|
|
|
148
150
|
[['old', '2.4.0', '3.0.0']],
|
|
149
151
|
)
|
|
150
152
|
assert.deepEqual(
|
|
151
|
-
plan.direct.map((item) => item.name),
|
|
152
|
-
['eslint'],
|
|
153
|
+
plan.direct.map((item) => [item.name, item.to]),
|
|
154
|
+
[['eslint', '10.0.1']],
|
|
153
155
|
)
|
|
154
156
|
})
|
|
155
157
|
|
|
158
|
+
test('planOverrides leaves the direct target empty when a finding has no patch', () => {
|
|
159
|
+
const plan = planOverrides(
|
|
160
|
+
[advisory({ name: 'vite', ghsa: 'GHSA-none', vulnerable: '<=7.0.0', patched: null, versions: ['7.0.0'] })],
|
|
161
|
+
{ directDependencies: new Set(['vite']) },
|
|
162
|
+
)
|
|
163
|
+
assert.deepEqual(
|
|
164
|
+
plan.direct.map((item) => [item.name, item.to]),
|
|
165
|
+
[['vite', undefined]],
|
|
166
|
+
)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
test('addReleaseAgeExcludes merges, skips covered names and appends', () => {
|
|
170
|
+
const text = ['packages: []', 'minimumReleaseAgeExclude:', ' - fg-devkit', ' - lefthook@2.1.15', ''].join('\n')
|
|
171
|
+
assert.equal(
|
|
172
|
+
addReleaseAgeExcludes(text, [
|
|
173
|
+
{ name: 'fg-devkit', version: '1.0.0' },
|
|
174
|
+
{ name: 'lefthook', version: '2.1.17' },
|
|
175
|
+
{ name: '@scope/pkg', version: '1.2.3' },
|
|
176
|
+
{ name: '@scope/pkg', version: '1.2.4' },
|
|
177
|
+
]),
|
|
178
|
+
[
|
|
179
|
+
'packages: []',
|
|
180
|
+
'minimumReleaseAgeExclude:',
|
|
181
|
+
' - fg-devkit',
|
|
182
|
+
' - lefthook@2.1.15 || 2.1.17',
|
|
183
|
+
" - '@scope/pkg@1.2.3 || 1.2.4'",
|
|
184
|
+
'',
|
|
185
|
+
].join('\n'),
|
|
186
|
+
)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
test('addReleaseAgeExcludes creates the list when it is missing', () => {
|
|
190
|
+
assert.equal(
|
|
191
|
+
addReleaseAgeExcludes('packages: []\n', [{ name: 'undici', version: '6.21.2' }]),
|
|
192
|
+
'packages: []\nminimumReleaseAgeExclude:\n - undici@6.21.2\n',
|
|
193
|
+
)
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
test('bumpDependencySpec keeps the prefix and skips non-version specs', () => {
|
|
197
|
+
assert.equal(bumpDependencySpec('1.7.0', '1.8.2'), '1.8.2')
|
|
198
|
+
assert.equal(bumpDependencySpec('^1.7.0', '1.8.2'), '^1.8.2')
|
|
199
|
+
assert.equal(bumpDependencySpec('~1.7.0', '1.8.2'), '~1.8.2')
|
|
200
|
+
assert.equal(bumpDependencySpec('workspace:*', '1.8.2'), null)
|
|
201
|
+
assert.equal(bumpDependencySpec('>=1.0.0 <2', '1.8.2'), null)
|
|
202
|
+
})
|
|
203
|
+
|
|
156
204
|
test('isReleaseAgeExcluded understands names, patterns and version lists', () => {
|
|
157
205
|
const list = ['fg-devkit', '@fallow-cli/*', 'lefthook@2.1.15 || 2.1.17']
|
|
158
206
|
assert.equal(isReleaseAgeExcluded(list, 'fg-devkit', '1.0.0'), true)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small helpers shared by the fg-devkit scripts: JSON, git and locating fg-devkit itself.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { spawnSync } from 'node:child_process'
|
|
6
|
+
import fs from 'node:fs'
|
|
7
|
+
import path from 'node:path'
|
|
8
|
+
import { fileURLToPath } from 'node:url'
|
|
9
|
+
|
|
10
|
+
/** Root of the fg-devkit package these scripts belong to. */
|
|
11
|
+
export const fgDevkitRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..')
|
|
12
|
+
|
|
13
|
+
export function readJson(filePath) {
|
|
14
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Run git in `cwd`. Output goes to the terminal unless `capture` is set. A failure to start
|
|
19
|
+
* git is logged with `tag` and reported as status 1.
|
|
20
|
+
*
|
|
21
|
+
* @param {string[]} args
|
|
22
|
+
* @param {{ cwd: string, tag: string, capture?: boolean }} options
|
|
23
|
+
* @returns {{ status: number, stdout: string }}
|
|
24
|
+
*/
|
|
25
|
+
export function runGit(args, { cwd, tag, capture = false }) {
|
|
26
|
+
const result = spawnSync('git', args, {
|
|
27
|
+
stdio: capture ? 'pipe' : 'inherit',
|
|
28
|
+
encoding: capture ? 'utf8' : undefined,
|
|
29
|
+
cwd,
|
|
30
|
+
env: process.env,
|
|
31
|
+
})
|
|
32
|
+
if (result.error) {
|
|
33
|
+
console.error(tag, result.error.message)
|
|
34
|
+
return { status: 1, stdout: '' }
|
|
35
|
+
}
|
|
36
|
+
return { status: result.status ?? 1, stdout: result.stdout ?? '' }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* `git add` the files that exist, relative to `cwd`.
|
|
41
|
+
*
|
|
42
|
+
* @param {string[]} files
|
|
43
|
+
* @param {{ cwd: string, tag: string }} options
|
|
44
|
+
* @returns {number} exit status, 0 when there was nothing to stage
|
|
45
|
+
*/
|
|
46
|
+
export function stageExistingFiles(files, options) {
|
|
47
|
+
const existingFiles = files.filter((filePath) => fs.existsSync(path.join(options.cwd, filePath)))
|
|
48
|
+
if (existingFiles.length === 0) return 0
|
|
49
|
+
return runGit(['add', '--', ...existingFiles], options).status
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* True when the file exists and is untracked or differs from HEAD.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} filePath relative to `cwd`
|
|
56
|
+
* @param {{ cwd: string, tag: string }} options
|
|
57
|
+
*/
|
|
58
|
+
export function fileDiffersFromHead(filePath, options) {
|
|
59
|
+
if (!fs.existsSync(path.join(options.cwd, filePath))) return false
|
|
60
|
+
const tracked = runGit(['ls-files', '--error-unmatch', '--', filePath], { ...options, capture: true })
|
|
61
|
+
if (tracked.status !== 0) return true
|
|
62
|
+
return runGit(['diff', '--quiet', 'HEAD', '--', filePath], { ...options, capture: true }).status !== 0
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* fg-devkit on disk for a consumer repo: this package, a direct dependency, or the copy
|
|
67
|
+
* bundled with fds-vue-core. Null when none of them has a package.json.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} projectRoot
|
|
70
|
+
*/
|
|
71
|
+
export function findFgDevkitPackageRoot(projectRoot) {
|
|
72
|
+
const candidates = [
|
|
73
|
+
fgDevkitRoot,
|
|
74
|
+
path.join(projectRoot, 'node_modules', 'fg-devkit'),
|
|
75
|
+
path.join(projectRoot, 'node_modules', 'fds-vue-core', 'node_modules', 'fg-devkit'),
|
|
76
|
+
]
|
|
77
|
+
return candidates.find((candidate) => fs.existsSync(path.join(candidate, 'package.json'))) ?? null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Exit unless the current directory is the fg-devkit repository root. */
|
|
81
|
+
export function assertFgDevkitMaintainerContext(tag) {
|
|
82
|
+
if (path.resolve(process.cwd()) !== fgDevkitRoot) {
|
|
83
|
+
console.error(`${tag} Run from the fg-devkit repository root.`)
|
|
84
|
+
process.exit(1)
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
if (readJson(path.join(fgDevkitRoot, 'package.json'))?.name !== 'fg-devkit') {
|
|
88
|
+
console.error(`${tag} package.json name must be fg-devkit.`)
|
|
89
|
+
process.exit(1)
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
console.error(`${tag} Could not read fg-devkit package.json.`)
|
|
93
|
+
process.exit(1)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -9,7 +9,7 @@ const PNPM_PACKAGE_MANAGER_PATTERN = /^pnpm@\d+\.\d+\.\d+(-[\w.]+)?$/i
|
|
|
9
9
|
const NODE_NVMRC_PATTERN = /^\d+(\.\d+(\.\d+)?)?$/
|
|
10
10
|
|
|
11
11
|
/** @param {unknown} value */
|
|
12
|
-
|
|
12
|
+
function isValidPnpmPackageManager(value) {
|
|
13
13
|
return typeof value === 'string' && PNPM_PACKAGE_MANAGER_PATTERN.test(value)
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -17,7 +17,7 @@ export function isValidPnpmPackageManager(value) {
|
|
|
17
17
|
* @param {string} fgDevkitRoot
|
|
18
18
|
* @returns {string | undefined}
|
|
19
19
|
*/
|
|
20
|
-
|
|
20
|
+
function readFgDevkitPackageManager(fgDevkitRoot) {
|
|
21
21
|
const pkgPath = path.join(fgDevkitRoot, 'package.json')
|
|
22
22
|
if (!fs.existsSync(pkgPath)) {
|
|
23
23
|
return undefined
|
|
@@ -82,7 +82,7 @@ export function collectToolchainPolicyViolations(policy, labelPrefix = '[policy]
|
|
|
82
82
|
/**
|
|
83
83
|
* @param {unknown} value
|
|
84
84
|
*/
|
|
85
|
-
|
|
85
|
+
function isValidNodeNvmrc(value) {
|
|
86
86
|
return typeof value === 'string' && NODE_NVMRC_PATTERN.test(value.trim().replace(/^v/, ''))
|
|
87
87
|
}
|
|
88
88
|
|
|
@@ -189,38 +189,6 @@ export function resolveExpectedNodePolicy(policy, fgDevkitRoot) {
|
|
|
189
189
|
return undefined
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
/**
|
|
193
|
-
* @param {Record<string, unknown>} policy
|
|
194
|
-
* @param {string} fgDevkitRoot
|
|
195
|
-
* @returns {{ policy: Record<string, unknown>; changed: boolean }}
|
|
196
|
-
*/
|
|
197
|
-
export function syncPolicyNodeFromFgDevkit(policy, fgDevkitRoot) {
|
|
198
|
-
const fromFgDevkit = readFgDevkitNodePolicy(fgDevkitRoot)
|
|
199
|
-
if (!fromFgDevkit) {
|
|
200
|
-
return { policy, changed: false }
|
|
201
|
-
}
|
|
202
|
-
const next = {
|
|
203
|
-
...policy,
|
|
204
|
-
toolchain: {
|
|
205
|
-
...(policy.toolchain ?? {}),
|
|
206
|
-
node: {
|
|
207
|
-
nvmrc: fromFgDevkit.nvmrc,
|
|
208
|
-
...(fromFgDevkit.engines ? { engines: fromFgDevkit.engines } : {}),
|
|
209
|
-
jenkinsImage: fromFgDevkit.jenkinsImage,
|
|
210
|
-
dockerTag: fromFgDevkit.dockerTag,
|
|
211
|
-
},
|
|
212
|
-
},
|
|
213
|
-
}
|
|
214
|
-
const current = policy.toolchain?.node
|
|
215
|
-
const changed =
|
|
216
|
-
!current ||
|
|
217
|
-
current.nvmrc !== next.toolchain.node.nvmrc ||
|
|
218
|
-
current.engines !== next.toolchain.node.engines ||
|
|
219
|
-
current.jenkinsImage !== next.toolchain.node.jenkinsImage ||
|
|
220
|
-
current.dockerTag !== next.toolchain.node.dockerTag
|
|
221
|
-
return { policy: changed ? next : policy, changed }
|
|
222
|
-
}
|
|
223
|
-
|
|
224
192
|
/**
|
|
225
193
|
* @param {Record<string, unknown>} policy
|
|
226
194
|
* @param {string} [labelPrefix]
|
|
@@ -119,7 +119,7 @@ export function runFleetGitCommitPush(options) {
|
|
|
119
119
|
console.log(`${logPrefix} repositories=${repos.length}`)
|
|
120
120
|
|
|
121
121
|
if (committed.length > 0) {
|
|
122
|
-
console.log(`${logPrefix} committed and pushed (${committed.length}):`)
|
|
122
|
+
console.log(`${logPrefix} ${dryRun ? 'would commit and push' : 'committed and pushed'} (${committed.length}):`)
|
|
123
123
|
for (const item of committed) {
|
|
124
124
|
console.log(` - ${item.label} (${item.branch})`)
|
|
125
125
|
}
|
|
@@ -72,7 +72,7 @@ export function runFleetGitDiscard(options) {
|
|
|
72
72
|
console.log(`${logPrefix} repositories=${repos.length}`)
|
|
73
73
|
|
|
74
74
|
if (discarded.length > 0) {
|
|
75
|
-
console.log(`${logPrefix} discarded tracked changes (${discarded.length}):`)
|
|
75
|
+
console.log(`${logPrefix} ${dryRun ? 'would discard tracked changes' : 'discarded tracked changes'} (${discarded.length}):`)
|
|
76
76
|
for (const item of discarded) {
|
|
77
77
|
console.log(` - ${item.label} (${item.branch})`)
|
|
78
78
|
}
|
|
@@ -69,7 +69,7 @@ export function runFleetGitPull(options) {
|
|
|
69
69
|
console.log(`${logPrefix} repositories=${repos.length}`)
|
|
70
70
|
|
|
71
71
|
if (pulled.length > 0) {
|
|
72
|
-
console.log(`${logPrefix} pulled (${pulled.length}):`)
|
|
72
|
+
console.log(`${logPrefix} ${dryRun ? 'would pull' : 'pulled'} (${pulled.length}):`)
|
|
73
73
|
for (const item of pulled) {
|
|
74
74
|
console.log(` - ${item.label} (${item.branch})`)
|
|
75
75
|
}
|
|
@@ -17,6 +17,26 @@ export function getOverridePackageName(overrideKey) {
|
|
|
17
17
|
return match ? match[1] : key
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Policy overrides for packages present in the lockfile, so a repo only gets the overrides
|
|
22
|
+
* it can use. All of them when there is no lockfile.
|
|
23
|
+
*
|
|
24
|
+
* @param {Record<string, string> | undefined} policyOverrides
|
|
25
|
+
* @param {string} lockfilePath
|
|
26
|
+
*/
|
|
27
|
+
export function getRelevantPolicyOverrides(policyOverrides, lockfilePath) {
|
|
28
|
+
const entries = Object.entries(policyOverrides ?? {})
|
|
29
|
+
if (entries.length === 0) return {}
|
|
30
|
+
if (!fs.existsSync(lockfilePath)) return Object.fromEntries(entries)
|
|
31
|
+
const lockfile = fs.readFileSync(lockfilePath, 'utf8')
|
|
32
|
+
return Object.fromEntries(
|
|
33
|
+
entries.filter(([name]) => {
|
|
34
|
+
const pkgName = getOverridePackageName(name)
|
|
35
|
+
return lockfile.includes(`${pkgName}@`) || lockfile.includes(`/${pkgName}/`)
|
|
36
|
+
}),
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
20
40
|
function readWorkspaceOverrides(yamlPath) {
|
|
21
41
|
if (!fs.existsSync(yamlPath)) return {}
|
|
22
42
|
const overrides = {}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import assert from 'node:assert/strict'
|
|
2
|
-
import fs from 'node:fs'
|
|
3
|
-
import path from 'node:path'
|
|
4
2
|
import test from 'node:test'
|
|
5
|
-
import { fileURLToPath } from 'node:url'
|
|
6
3
|
|
|
7
4
|
import {
|
|
8
5
|
accountFromPiwikUrl,
|
|
@@ -24,8 +21,6 @@ import {
|
|
|
24
21
|
rewriteDimensionNumbers,
|
|
25
22
|
} from './piwik-dimensions.mjs'
|
|
26
23
|
|
|
27
|
-
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
|
28
|
-
|
|
29
24
|
const bookingSource = `
|
|
30
25
|
export const customDimensions = {
|
|
31
26
|
clientNumber: 1,
|
|
@@ -6,11 +6,9 @@
|
|
|
6
6
|
import { spawnSync } from 'node:child_process'
|
|
7
7
|
import fs from 'node:fs'
|
|
8
8
|
import path from 'node:path'
|
|
9
|
-
import {
|
|
9
|
+
import { fgDevkitRoot } from './common.mjs'
|
|
10
10
|
|
|
11
|
-
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
|
12
11
|
|
|
13
|
-
export const fgRoot = path.resolve(scriptDir, '..')
|
|
14
12
|
|
|
15
13
|
/** True when the consumer repo has opted into git-cliff release notes. */
|
|
16
14
|
export function hasReleaseNotesConfig(cwd = process.cwd()) {
|
|
@@ -28,7 +26,7 @@ export function resolveCliffConfig(name, cwd = process.cwd()) {
|
|
|
28
26
|
return local
|
|
29
27
|
}
|
|
30
28
|
|
|
31
|
-
const bundled = path.join(
|
|
29
|
+
const bundled = path.join(fgDevkitRoot, 'configs', name)
|
|
32
30
|
if (fs.existsSync(bundled)) {
|
|
33
31
|
return bundled
|
|
34
32
|
}
|
|
@@ -89,20 +87,20 @@ function resolvePackageFile(packageName, relativePaths, searchRoots) {
|
|
|
89
87
|
}
|
|
90
88
|
|
|
91
89
|
function dependencySearchRoots() {
|
|
92
|
-
return [
|
|
90
|
+
return [fgDevkitRoot, process.cwd(), path.join(process.cwd(), 'node_modules', 'fg-devkit')]
|
|
93
91
|
}
|
|
94
92
|
|
|
95
93
|
export function resolveGitCliffBin() {
|
|
96
94
|
return resolvePackageFile('git-cliff', ['lib/cli/cli.js'], dependencySearchRoots())
|
|
97
95
|
}
|
|
98
96
|
|
|
99
|
-
|
|
97
|
+
function resolveTsxBin() {
|
|
100
98
|
return resolvePackageFile('tsx', ['dist/cli.mjs', 'cli.mjs'], dependencySearchRoots())
|
|
101
99
|
}
|
|
102
100
|
|
|
103
101
|
export function runSyncUserChangelog(cwd = process.cwd()) {
|
|
104
102
|
const tsxBin = resolveTsxBin()
|
|
105
|
-
const syncScript = path.join(
|
|
103
|
+
const syncScript = path.join(fgDevkitRoot, 'release-notes', 'sync-user-changelog.ts')
|
|
106
104
|
|
|
107
105
|
if (!tsxBin) {
|
|
108
106
|
console.error('[fg-devkit] Could not resolve tsx. Reinstall fg-devkit and retry.')
|