fg-devkit 1.9.1 → 1.12.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 +6 -0
- package/configs/cliff.toml +54 -0
- package/configs/cliff.user.toml +64 -0
- package/configs/dependency-policy.vue-workspace.json +9 -6
- package/package.json +20 -6
- package/release-notes/format.ts +82 -0
- package/release-notes/parseChangelog.ts +85 -0
- package/release-notes/serializeUserChangelog.ts +22 -0
- package/release-notes/sync-user-changelog.ts +84 -0
- package/release-notes/types.ts +10 -0
- package/scaffold/vue/Dockerfile.local +1 -1
- package/scaffold/vue/package.json +5 -5
- package/scripts/audit-override-commit.mjs +166 -0
- package/scripts/bump-pnpm-toolchain.mjs +281 -0
- package/scripts/check-dependency-policy.mjs +16 -0
- package/scripts/dependency-policy-toolchain.mjs +101 -0
- package/scripts/fg-devkit.mjs +71 -4
- package/scripts/fleet-bump-fg-devkit.mjs +3 -52
- package/scripts/fleet-vue-repos.mjs +54 -0
- package/scripts/generate-release-notes.mjs +84 -0
- package/scripts/refresh-dependency-policy-from-repos.mjs +14 -0
- package/scripts/release-notes-common.mjs +114 -0
- package/scripts/release.sh +152 -26
- package/scripts/sync-consumer-scripts.mjs +1 -0
- package/scripts/sync-dependency-policy.mjs +12 -1
- package/scripts/sync-release-notes-config.mjs +104 -0
- package/scripts/sync-scaffold-vue-deps.mjs +35 -2
- package/scripts/verify-release-notes.mjs +158 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Fix one vulnerability via `pnpm audit --fix override -i`, install, then auto-commit.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* pnpm exec fg-devkit audit-override-commit [pnpm-audit-args...]
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from 'node:fs'
|
|
10
|
+
import path from 'node:path'
|
|
11
|
+
import { spawnSync } from 'node:child_process'
|
|
12
|
+
|
|
13
|
+
const cwd = process.cwd()
|
|
14
|
+
const packageJsonPath = path.join(cwd, 'package.json')
|
|
15
|
+
const workspaceYamlPath = path.join(cwd, 'pnpm-workspace.yaml')
|
|
16
|
+
|
|
17
|
+
function getOverridePackageName(overrideKey) {
|
|
18
|
+
const match = overrideKey.match(/^(@[^/]+\/[^@]+|[^@]+)@/)
|
|
19
|
+
return match ? match[1] : overrideKey
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Read flat `overrides:` entries from pnpm-workspace.yaml (no YAML dependency). */
|
|
23
|
+
function readWorkspaceOverrides(yamlPath) {
|
|
24
|
+
if (!fs.existsSync(yamlPath)) return {}
|
|
25
|
+
const overrides = {}
|
|
26
|
+
let inOverrides = false
|
|
27
|
+
for (const line of fs.readFileSync(yamlPath, 'utf8').split('\n')) {
|
|
28
|
+
if (/^overrides:\s*$/.test(line)) {
|
|
29
|
+
inOverrides = true
|
|
30
|
+
continue
|
|
31
|
+
}
|
|
32
|
+
if (!inOverrides) continue
|
|
33
|
+
if (/^\S/.test(line)) break
|
|
34
|
+
const match = line.match(/^ ([^:]+):\s*(.+?)\s*$/)
|
|
35
|
+
if (match) {
|
|
36
|
+
overrides[match[1].trim()] = match[2].trim()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return overrides
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readJson(jsonPath) {
|
|
43
|
+
return JSON.parse(fs.readFileSync(jsonPath, 'utf8'))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function collectOverrides() {
|
|
47
|
+
const overrides = {}
|
|
48
|
+
|
|
49
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
50
|
+
const pkg = readJson(packageJsonPath)
|
|
51
|
+
Object.assign(overrides, pkg.overrides ?? {})
|
|
52
|
+
Object.assign(overrides, pkg.pnpm?.overrides ?? {})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
Object.assign(overrides, readWorkspaceOverrides(workspaceYamlPath))
|
|
56
|
+
return overrides
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function collectOverrideChanges(before, after) {
|
|
60
|
+
const changed = []
|
|
61
|
+
const keys = new Set([...Object.keys(before), ...Object.keys(after)])
|
|
62
|
+
for (const key of keys) {
|
|
63
|
+
const from = before[key]
|
|
64
|
+
const to = after[key]
|
|
65
|
+
if (to !== undefined && from !== to) {
|
|
66
|
+
changed.push({ key, name: getOverridePackageName(key), from, to })
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return changed
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function runPnpm(args) {
|
|
73
|
+
const result = spawnSync('pnpm', args, {
|
|
74
|
+
stdio: 'inherit',
|
|
75
|
+
cwd,
|
|
76
|
+
env: process.env,
|
|
77
|
+
})
|
|
78
|
+
if (result.error) {
|
|
79
|
+
console.error('[audit-override-commit]', result.error.message)
|
|
80
|
+
return 1
|
|
81
|
+
}
|
|
82
|
+
return result.status ?? 1
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function runGit(args) {
|
|
86
|
+
const result = spawnSync('git', args, {
|
|
87
|
+
stdio: 'inherit',
|
|
88
|
+
cwd,
|
|
89
|
+
env: process.env,
|
|
90
|
+
})
|
|
91
|
+
if (result.error) {
|
|
92
|
+
console.error('[audit-override-commit]', result.error.message)
|
|
93
|
+
return 1
|
|
94
|
+
}
|
|
95
|
+
return result.status ?? 1
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function stageExistingFiles(files) {
|
|
99
|
+
const existingFiles = files.filter((filePath) => fs.existsSync(path.join(cwd, filePath)))
|
|
100
|
+
if (existingFiles.length === 0) return 0
|
|
101
|
+
return runGit(['add', '--', ...existingFiles])
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function main() {
|
|
105
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
106
|
+
console.error(`[audit-override-commit] package.json not found at ${packageJsonPath}`)
|
|
107
|
+
process.exit(1)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let beforeOverrides
|
|
111
|
+
try {
|
|
112
|
+
beforeOverrides = collectOverrides()
|
|
113
|
+
} catch (err) {
|
|
114
|
+
console.error(`[audit-override-commit] Failed to read overrides before audit fix: ${err.message}`)
|
|
115
|
+
process.exit(1)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const auditArgs =
|
|
119
|
+
process.argv.length > 2 ? process.argv.slice(2) : ['audit', '--fix', 'override', '-i']
|
|
120
|
+
|
|
121
|
+
const auditExitCode = runPnpm(auditArgs)
|
|
122
|
+
if (auditExitCode !== 0) {
|
|
123
|
+
process.exit(auditExitCode)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let afterOverrides
|
|
127
|
+
try {
|
|
128
|
+
afterOverrides = collectOverrides()
|
|
129
|
+
} catch (err) {
|
|
130
|
+
console.error(`[audit-override-commit] Failed to read overrides after audit fix: ${err.message}`)
|
|
131
|
+
process.exit(1)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const changed = collectOverrideChanges(beforeOverrides, afterOverrides)
|
|
135
|
+
if (changed.length === 0) {
|
|
136
|
+
console.log('[audit-override-commit] No override changes detected.')
|
|
137
|
+
process.exit(0)
|
|
138
|
+
}
|
|
139
|
+
if (changed.length > 1) {
|
|
140
|
+
console.log('[audit-override-commit] Multiple override changes detected:')
|
|
141
|
+
for (const item of changed) {
|
|
142
|
+
const fromLabel = item.from ?? '(missing)'
|
|
143
|
+
console.log(` - ${item.name}: ${fromLabel} -> ${item.to}`)
|
|
144
|
+
}
|
|
145
|
+
console.log('[audit-override-commit] Auto-commit supports exactly one override change. Commit manually.')
|
|
146
|
+
process.exit(1)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const installExitCode = runPnpm(['install'])
|
|
150
|
+
if (installExitCode !== 0) {
|
|
151
|
+
process.exit(installExitCode)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const [only] = changed
|
|
155
|
+
const stageExitCode = stageExistingFiles(['package.json', 'pnpm-lock.yaml', 'pnpm-workspace.yaml'])
|
|
156
|
+
if (stageExitCode !== 0) {
|
|
157
|
+
process.exit(stageExitCode)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const commitMessage = `vuln(${only.name}): override ${only.to}`
|
|
161
|
+
console.log(`[audit-override-commit] Staged override for ${only.name} -> ${only.to}`)
|
|
162
|
+
console.log(`[audit-override-commit] Auto-committing: ${commitMessage}`)
|
|
163
|
+
process.exit(runGit(['commit', '-m', commitMessage]))
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
main()
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Maintainer: bump pnpm to the latest (or given) version in fg-devkit and sync toolchain everywhere.
|
|
4
|
+
*
|
|
5
|
+
* Steps in fg-devkit:
|
|
6
|
+
* 1. Resolve target pnpm version (CLI arg or `npm view pnpm version`)
|
|
7
|
+
* 2. Set package.json packageManager
|
|
8
|
+
* 3. Sync dependency policy toolchain, scaffold/vue, and Dockerfile.local
|
|
9
|
+
* 4. corepack prepare + pnpm install
|
|
10
|
+
*
|
|
11
|
+
* Steps per Vue consumer repo under projects-root (unless --skip-fleet):
|
|
12
|
+
* 1. sync-deps against this checkout's policy file (works before fg-devkit is published)
|
|
13
|
+
* 2. corepack prepare + pnpm install
|
|
14
|
+
* 3. check-deps against the same policy file
|
|
15
|
+
*
|
|
16
|
+
* Usage (from fg-devkit repository root):
|
|
17
|
+
* pnpm exec fg-devkit bump-pnpm-toolchain [version] [projects-root] [options]
|
|
18
|
+
*
|
|
19
|
+
* Options:
|
|
20
|
+
* --dry-run Print planned steps without changing files or running install
|
|
21
|
+
* --skip-fleet Only update fg-devkit (policy, scaffold, local install)
|
|
22
|
+
* --skip-install Skip corepack prepare and pnpm install
|
|
23
|
+
*
|
|
24
|
+
* Examples:
|
|
25
|
+
* pnpm exec fg-devkit bump-pnpm-toolchain
|
|
26
|
+
* pnpm exec fg-devkit bump-pnpm-toolchain 12.0.0
|
|
27
|
+
* pnpm exec fg-devkit bump-pnpm-toolchain --dry-run
|
|
28
|
+
* pnpm exec fg-devkit bump-pnpm-toolchain --skip-fleet
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import fs from 'node:fs'
|
|
32
|
+
import path from 'node:path'
|
|
33
|
+
import { spawnSync } from 'node:child_process'
|
|
34
|
+
import { fileURLToPath } from 'node:url'
|
|
35
|
+
|
|
36
|
+
import { formatPnpmPackageManager } from './dependency-policy-toolchain.mjs'
|
|
37
|
+
import { collectFleetVueRepos } from './fleet-vue-repos.mjs'
|
|
38
|
+
import { resolveDependencyPolicyPath } from './resolve-dependency-policy-path.mjs'
|
|
39
|
+
import { syncScaffoldVueDepsFromPolicy } from './sync-scaffold-vue-deps.mjs'
|
|
40
|
+
|
|
41
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
|
42
|
+
const fgDevkitRoot = path.resolve(scriptDir, '..')
|
|
43
|
+
const fgDevkitPkgPath = path.join(fgDevkitRoot, 'package.json')
|
|
44
|
+
const policyPath = resolveDependencyPolicyPath(scriptDir, fgDevkitRoot, [])
|
|
45
|
+
|
|
46
|
+
const cliArgs = process.argv.slice(2)
|
|
47
|
+
const dryRun = cliArgs.includes('--dry-run')
|
|
48
|
+
const skipFleet = cliArgs.includes('--skip-fleet')
|
|
49
|
+
const skipInstall = cliArgs.includes('--skip-install')
|
|
50
|
+
const positionalArgs = cliArgs.filter((a) => !a.startsWith('-'))
|
|
51
|
+
|
|
52
|
+
function readJson(jsonPath) {
|
|
53
|
+
return JSON.parse(fs.readFileSync(jsonPath, 'utf8'))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function assertFgDevkitMaintainerContext() {
|
|
57
|
+
if (path.resolve(process.cwd()) !== fgDevkitRoot) {
|
|
58
|
+
console.error('[bump-pnpm-toolchain] Run from the fg-devkit repository root.')
|
|
59
|
+
process.exit(1)
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const pkg = readJson(fgDevkitPkgPath)
|
|
63
|
+
if (pkg?.name !== 'fg-devkit') {
|
|
64
|
+
console.error('[bump-pnpm-toolchain] package.json name must be fg-devkit.')
|
|
65
|
+
process.exit(1)
|
|
66
|
+
}
|
|
67
|
+
} catch {
|
|
68
|
+
console.error('[bump-pnpm-toolchain] Could not read fg-devkit package.json.')
|
|
69
|
+
process.exit(1)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function resolveTargetVersion(versionArg) {
|
|
74
|
+
if (versionArg) {
|
|
75
|
+
return formatPnpmPackageManager(versionArg)
|
|
76
|
+
}
|
|
77
|
+
const lookup = spawnSync('npm', ['view', 'pnpm', 'version'], {
|
|
78
|
+
encoding: 'utf8',
|
|
79
|
+
env: process.env,
|
|
80
|
+
})
|
|
81
|
+
if (lookup.error) {
|
|
82
|
+
console.error('[bump-pnpm-toolchain]', lookup.error.message)
|
|
83
|
+
process.exit(1)
|
|
84
|
+
}
|
|
85
|
+
if ((lookup.status ?? 1) !== 0) {
|
|
86
|
+
console.error('[bump-pnpm-toolchain] Failed to resolve latest pnpm version via npm view.')
|
|
87
|
+
process.exit(lookup.status ?? 1)
|
|
88
|
+
}
|
|
89
|
+
const latest = lookup.stdout.trim()
|
|
90
|
+
if (!latest) {
|
|
91
|
+
console.error('[bump-pnpm-toolchain] npm view pnpm version returned empty output.')
|
|
92
|
+
process.exit(1)
|
|
93
|
+
}
|
|
94
|
+
return formatPnpmPackageManager(latest)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function runStep(label, fn) {
|
|
98
|
+
console.log(`\n[bump-pnpm-toolchain] ${label}`)
|
|
99
|
+
return fn()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function runCorepackPrepare(packageManager, cwd) {
|
|
103
|
+
const result = spawnSync('corepack', ['prepare', packageManager, '--activate'], {
|
|
104
|
+
cwd,
|
|
105
|
+
stdio: 'inherit',
|
|
106
|
+
env: process.env,
|
|
107
|
+
})
|
|
108
|
+
if (result.error) {
|
|
109
|
+
console.error('[bump-pnpm-toolchain]', result.error.message)
|
|
110
|
+
return 1
|
|
111
|
+
}
|
|
112
|
+
return result.status ?? 1
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function runPnpmInstall(cwd) {
|
|
116
|
+
const result = spawnSync('pnpm', ['install'], {
|
|
117
|
+
cwd,
|
|
118
|
+
stdio: 'inherit',
|
|
119
|
+
env: process.env,
|
|
120
|
+
})
|
|
121
|
+
if (result.error) {
|
|
122
|
+
console.error('[bump-pnpm-toolchain]', result.error.message)
|
|
123
|
+
return 1
|
|
124
|
+
}
|
|
125
|
+
return result.status ?? 1
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function runNodeScript(scriptName, args, cwd) {
|
|
129
|
+
const scriptPath = path.join(scriptDir, scriptName)
|
|
130
|
+
const result = spawnSync(process.execPath, [scriptPath, ...args], {
|
|
131
|
+
cwd,
|
|
132
|
+
stdio: 'inherit',
|
|
133
|
+
env: process.env,
|
|
134
|
+
})
|
|
135
|
+
if (result.error) {
|
|
136
|
+
console.error('[bump-pnpm-toolchain]', result.error.message)
|
|
137
|
+
return 1
|
|
138
|
+
}
|
|
139
|
+
return result.status ?? 1
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function updateFgDevkitPackageManager(packageManager) {
|
|
143
|
+
const pkg = readJson(fgDevkitPkgPath)
|
|
144
|
+
if (pkg.packageManager === packageManager) {
|
|
145
|
+
console.log(`[bump-pnpm-toolchain] fg-devkit packageManager already ${packageManager}`)
|
|
146
|
+
return false
|
|
147
|
+
}
|
|
148
|
+
pkg.packageManager = packageManager
|
|
149
|
+
fs.writeFileSync(fgDevkitPkgPath, `${JSON.stringify(pkg, null, 2)}\n`)
|
|
150
|
+
console.log(`[bump-pnpm-toolchain] Set fg-devkit packageManager -> ${packageManager}`)
|
|
151
|
+
return true
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function syncFleetRepo(repoDir, repoName, packageManager) {
|
|
155
|
+
if (dryRun) {
|
|
156
|
+
console.log(`[bump-pnpm-toolchain] DRY RUN ${repoName}: sync-deps ${policyPath}`)
|
|
157
|
+
console.log(`[bump-pnpm-toolchain] DRY RUN ${repoName}: corepack prepare ${packageManager}`)
|
|
158
|
+
console.log(`[bump-pnpm-toolchain] DRY RUN ${repoName}: pnpm install`)
|
|
159
|
+
console.log(`[bump-pnpm-toolchain] DRY RUN ${repoName}: check-deps ${policyPath}`)
|
|
160
|
+
return 0
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const syncCode = runNodeScript('sync-dependency-policy.mjs', [policyPath], repoDir)
|
|
164
|
+
if (syncCode !== 0) {
|
|
165
|
+
console.error(`[bump-pnpm-toolchain] FAIL ${repoName}: sync-deps exited with code ${syncCode}`)
|
|
166
|
+
return syncCode
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!skipInstall) {
|
|
170
|
+
const corepackCode = runCorepackPrepare(packageManager, repoDir)
|
|
171
|
+
if (corepackCode !== 0) {
|
|
172
|
+
console.error(`[bump-pnpm-toolchain] FAIL ${repoName}: corepack prepare exited with code ${corepackCode}`)
|
|
173
|
+
return corepackCode
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const installCode = runPnpmInstall(repoDir)
|
|
177
|
+
if (installCode !== 0) {
|
|
178
|
+
console.error(`[bump-pnpm-toolchain] FAIL ${repoName}: pnpm install exited with code ${installCode}`)
|
|
179
|
+
return installCode
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const checkCode = runNodeScript('check-dependency-policy.mjs', [policyPath], repoDir)
|
|
184
|
+
if (checkCode !== 0) {
|
|
185
|
+
console.error(`[bump-pnpm-toolchain] FAIL ${repoName}: check-deps exited with code ${checkCode}`)
|
|
186
|
+
return checkCode
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.log(`[bump-pnpm-toolchain] OK ${repoName}`)
|
|
190
|
+
return 0
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function main() {
|
|
194
|
+
assertFgDevkitMaintainerContext()
|
|
195
|
+
|
|
196
|
+
const versionArg = positionalArgs.find((a) => /^(pnpm@)?\d/.test(a))
|
|
197
|
+
const projectsRootArg = positionalArgs.find((a) => a !== versionArg)
|
|
198
|
+
const projectsRoot = projectsRootArg
|
|
199
|
+
? path.resolve(process.cwd(), projectsRootArg)
|
|
200
|
+
: path.resolve(fgDevkitRoot, '..')
|
|
201
|
+
|
|
202
|
+
const packageManager = resolveTargetVersion(versionArg)
|
|
203
|
+
|
|
204
|
+
console.log(`[bump-pnpm-toolchain] target=${packageManager}`)
|
|
205
|
+
console.log(`[bump-pnpm-toolchain] policy=${policyPath}`)
|
|
206
|
+
if (!skipFleet) {
|
|
207
|
+
console.log(`[bump-pnpm-toolchain] projectsRoot=${projectsRoot}`)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (dryRun) {
|
|
211
|
+
console.log('[bump-pnpm-toolchain] DRY RUN fg-devkit: update packageManager')
|
|
212
|
+
console.log('[bump-pnpm-toolchain] DRY RUN fg-devkit: sync-scaffold-vue-deps')
|
|
213
|
+
if (!skipInstall) {
|
|
214
|
+
console.log(`[bump-pnpm-toolchain] DRY RUN fg-devkit: corepack prepare ${packageManager}`)
|
|
215
|
+
console.log('[bump-pnpm-toolchain] DRY RUN fg-devkit: pnpm install')
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
runStep('Update fg-devkit package.json', () => {
|
|
219
|
+
updateFgDevkitPackageManager(packageManager)
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
runStep('Sync policy, scaffold, and Dockerfile', () => {
|
|
223
|
+
syncScaffoldVueDepsFromPolicy({ fgDevkitRoot, policyPath })
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
if (!skipInstall) {
|
|
227
|
+
const corepackCode = runStep('Activate pnpm via corepack (fg-devkit)', () =>
|
|
228
|
+
runCorepackPrepare(packageManager, fgDevkitRoot),
|
|
229
|
+
)
|
|
230
|
+
if (corepackCode !== 0) {
|
|
231
|
+
process.exit(corepackCode)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const installCode = runStep('pnpm install (fg-devkit)', () => runPnpmInstall(fgDevkitRoot))
|
|
235
|
+
if (installCode !== 0) {
|
|
236
|
+
process.exit(installCode)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (skipFleet) {
|
|
242
|
+
console.log('\n[bump-pnpm-toolchain] Done (--skip-fleet). Commit fg-devkit changes and release when ready.')
|
|
243
|
+
process.exit(0)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (!fs.existsSync(projectsRoot)) {
|
|
247
|
+
console.error(`[bump-pnpm-toolchain] projects root not found: ${projectsRoot}`)
|
|
248
|
+
process.exit(1)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const repos = collectFleetVueRepos(projectsRoot)
|
|
252
|
+
if (repos.length === 0) {
|
|
253
|
+
console.log(`[bump-pnpm-toolchain] No Vue repositories with fg-devkit under ${projectsRoot}`)
|
|
254
|
+
process.exit(0)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
console.log(`\n[bump-pnpm-toolchain] Fleet repositories=${repos.length}`)
|
|
258
|
+
|
|
259
|
+
let failures = 0
|
|
260
|
+
for (const repoDir of repos) {
|
|
261
|
+
const repoName = path.basename(repoDir)
|
|
262
|
+
console.log(`\n[bump-pnpm-toolchain] Fleet: ${repoName}`)
|
|
263
|
+
const code = syncFleetRepo(repoDir, repoName, packageManager)
|
|
264
|
+
if (code !== 0) {
|
|
265
|
+
failures += 1
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
console.log(`\n[bump-pnpm-toolchain] Done: fleet ok=${repos.length - failures}, failed=${failures}`)
|
|
270
|
+
if (failures > 0) {
|
|
271
|
+
process.exit(1)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (!dryRun) {
|
|
275
|
+
console.log(
|
|
276
|
+
'[bump-pnpm-toolchain] Commit changes in fg-devkit and each updated consumer repo, then release fg-devkit.',
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
main()
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* - For each dependency listed in the policy, checks the consumer’s declared version matches.
|
|
7
7
|
* - For managedAnywhere packages, allows either dependencies or devDependencies but requires one consistent version.
|
|
8
8
|
* - Optionally checks policy-defined pnpm overrides against the consumer’s package.json.
|
|
9
|
+
* - When the policy defines toolchain.packageManager, requires the consumer to use the same value.
|
|
9
10
|
* - Enforces pinned semver for any other dependencies/devDependencies/optionalDependencies and consumer pnpm.overrides.
|
|
10
11
|
*
|
|
11
12
|
* Policy path: first CLI argument, or default policy under fg-devkit/configs/ (see resolve-dependency-policy-path.mjs).
|
|
@@ -17,6 +18,10 @@ import path from 'node:path'
|
|
|
17
18
|
import { fileURLToPath } from 'node:url'
|
|
18
19
|
|
|
19
20
|
import { collectPinViolations, isPinnedDependencyVersion } from './dependency-policy-pins.mjs'
|
|
21
|
+
import {
|
|
22
|
+
collectToolchainPolicyViolations,
|
|
23
|
+
resolveExpectedPackageManager,
|
|
24
|
+
} from './dependency-policy-toolchain.mjs'
|
|
20
25
|
import { resolveDependencyPolicyPath } from './resolve-dependency-policy-path.mjs'
|
|
21
26
|
|
|
22
27
|
const cwd = process.cwd()
|
|
@@ -81,6 +86,17 @@ for (const [section, dependencies] of Object.entries(policy.managedDependencies
|
|
|
81
86
|
}
|
|
82
87
|
|
|
83
88
|
mismatches.push(...collectPinViolations('[policy] managedAnywhere.', policy.managedAnywhere ?? {}))
|
|
89
|
+
mismatches.push(...collectToolchainPolicyViolations(policy))
|
|
90
|
+
|
|
91
|
+
const expectedPackageManager = resolveExpectedPackageManager(policy, path.resolve(scriptDir, '..'))
|
|
92
|
+
if (expectedPackageManager !== undefined) {
|
|
93
|
+
const actualPackageManager = pkg.packageManager
|
|
94
|
+
if (actualPackageManager !== expectedPackageManager) {
|
|
95
|
+
mismatches.push(
|
|
96
|
+
`packageManager: expected ${expectedPackageManager}, got ${actualPackageManager ?? '(missing)'}`,
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
84
100
|
|
|
85
101
|
// Intersection: only packages that exist in both policy and consumer are compared.
|
|
86
102
|
for (const [section, dependencies] of Object.entries(policy.managedDependencies ?? {})) {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for fleet-wide pnpm / Corepack toolchain policy.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from 'node:fs'
|
|
6
|
+
import path from 'node:path'
|
|
7
|
+
|
|
8
|
+
const PNPM_PACKAGE_MANAGER_PATTERN = /^pnpm@\d+\.\d+\.\d+(-[\w.]+)?$/i
|
|
9
|
+
|
|
10
|
+
/** @param {unknown} value */
|
|
11
|
+
export function isValidPnpmPackageManager(value) {
|
|
12
|
+
return typeof value === 'string' && PNPM_PACKAGE_MANAGER_PATTERN.test(value)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} fgDevkitRoot
|
|
17
|
+
* @returns {string | undefined}
|
|
18
|
+
*/
|
|
19
|
+
export function readFgDevkitPackageManager(fgDevkitRoot) {
|
|
20
|
+
const pkgPath = path.join(fgDevkitRoot, 'package.json')
|
|
21
|
+
if (!fs.existsSync(pkgPath)) {
|
|
22
|
+
return undefined
|
|
23
|
+
}
|
|
24
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
|
|
25
|
+
return typeof pkg.packageManager === 'string' ? pkg.packageManager : undefined
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {Record<string, unknown>} policy
|
|
30
|
+
* @param {string} fgDevkitRoot
|
|
31
|
+
* @returns {{ policy: Record<string, unknown>; changed: boolean }}
|
|
32
|
+
*/
|
|
33
|
+
export function syncPolicyToolchainFromFgDevkit(policy, fgDevkitRoot) {
|
|
34
|
+
const fromFgDevkit = readFgDevkitPackageManager(fgDevkitRoot)
|
|
35
|
+
if (!fromFgDevkit || !isValidPnpmPackageManager(fromFgDevkit)) {
|
|
36
|
+
return { policy, changed: false }
|
|
37
|
+
}
|
|
38
|
+
const next = { ...policy, toolchain: { ...(policy.toolchain ?? {}) } }
|
|
39
|
+
if (next.toolchain.packageManager === fromFgDevkit) {
|
|
40
|
+
return { policy: next, changed: false }
|
|
41
|
+
}
|
|
42
|
+
next.toolchain.packageManager = fromFgDevkit
|
|
43
|
+
return { policy: next, changed: true }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Expected `packageManager` for consumers: policy.toolchain first, then fg-devkit package.json.
|
|
48
|
+
*
|
|
49
|
+
* @param {Record<string, unknown>} policy
|
|
50
|
+
* @param {string} [fgDevkitRoot]
|
|
51
|
+
* @returns {string | undefined}
|
|
52
|
+
*/
|
|
53
|
+
export function resolveExpectedPackageManager(policy, fgDevkitRoot) {
|
|
54
|
+
const fromPolicy = policy?.toolchain?.packageManager
|
|
55
|
+
if (typeof fromPolicy === 'string') {
|
|
56
|
+
return fromPolicy
|
|
57
|
+
}
|
|
58
|
+
if (fgDevkitRoot) {
|
|
59
|
+
return readFgDevkitPackageManager(fgDevkitRoot)
|
|
60
|
+
}
|
|
61
|
+
return undefined
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {Record<string, unknown>} policy
|
|
66
|
+
* @param {string} [labelPrefix]
|
|
67
|
+
* @returns {string[]}
|
|
68
|
+
*/
|
|
69
|
+
export function collectToolchainPolicyViolations(policy, labelPrefix = '[policy] toolchain.') {
|
|
70
|
+
const packageManager = policy?.toolchain?.packageManager
|
|
71
|
+
if (packageManager === undefined) {
|
|
72
|
+
return []
|
|
73
|
+
}
|
|
74
|
+
if (!isValidPnpmPackageManager(packageManager)) {
|
|
75
|
+
return [
|
|
76
|
+
`${labelPrefix}packageManager: must be pnpm@x.y.z, got ${JSON.stringify(packageManager)}`,
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
return []
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param {string} version Bare semver (e.g. 11.7.0) or packageManager string (pnpm@11.7.0).
|
|
84
|
+
* @returns {string}
|
|
85
|
+
*/
|
|
86
|
+
export function formatPnpmPackageManager(version) {
|
|
87
|
+
const bare = version.trim().replace(/^pnpm@/i, '')
|
|
88
|
+
if (!/^\d+\.\d+\.\d+(-[\w.]+)?$/.test(bare)) {
|
|
89
|
+
throw new Error(`Invalid pnpm version: ${JSON.stringify(version)}`)
|
|
90
|
+
}
|
|
91
|
+
return `pnpm@${bare}`
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @param {string} packageManager e.g. pnpm@11.7.0
|
|
96
|
+
* @returns {string | undefined}
|
|
97
|
+
*/
|
|
98
|
+
export function extractPnpmVersion(packageManager) {
|
|
99
|
+
const match = packageManager.match(/^pnpm@(.+)$/i)
|
|
100
|
+
return match ? match[1] : undefined
|
|
101
|
+
}
|