fg-devkit 1.3.4 → 1.5.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 +44 -0
- package/configs/dependency-policy.vue-workspace.json +19 -13
- package/package.json +7 -1
- package/scripts/check-dependency-policy.mjs +26 -7
- package/scripts/cz/cz-adapter.cjs +9 -0
- package/scripts/fg-devkit.mjs +181 -2
- package/scripts/fleet-vulns.mjs +164 -0
- package/scripts/policy-outdated.mjs +78 -0
- package/scripts/policy-upgrade.mjs +117 -0
- package/scripts/policy-vulns.mjs +74 -0
- package/scripts/refresh-dependency-policy-from-repos.mjs +10 -0
- package/scripts/repo-vulns.mjs +26 -0
- package/scripts/sync-dependency-policy.mjs +27 -2
package/README.md
CHANGED
|
@@ -70,6 +70,50 @@ Run full consumer setup in one command:
|
|
|
70
70
|
pnpm exec fgd full-consumer-setup
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
+
## Dependency policy update workflow
|
|
74
|
+
|
|
75
|
+
Check outdated policy versions:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pnpm exec fgd policy-outdated
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Check vulnerabilities for all policy packages together (including transitive dependencies):
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pnpm exec fgd policy-vulns
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Check vulnerabilities for all dependencies in the current repo:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
pnpm exec fgd repo-vulns
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Check vulnerabilities across all Vue repositories under the projects root:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pnpm exec fgd fleet-vulns
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
For summary-only mode (no full per-repo pnpm audit output):
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pnpm exec fgd fleet-vulns --summary-only
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
If npm audit is rate-limited (429), slow the scan down:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pnpm exec fgd fleet-vulns --delay-ms=8000
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Upgrade policy versions interactively:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
pnpm exec fgd policy-upgrade
|
|
115
|
+
```
|
|
116
|
+
|
|
73
117
|
## tsconfig in consuming repos
|
|
74
118
|
|
|
75
119
|
Use `fgd sync-project-configs` (or `fgd sync-tooling-configs`) to write the local `tsconfig.json` template from `fg-devkit`.
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"@sentry/tracing": "7.37.2",
|
|
11
11
|
"@sentry/vue": "7.37.2",
|
|
12
12
|
"@sentry/wasm": "7.67.0",
|
|
13
|
-
"@tailwindcss/postcss": "4.2.
|
|
14
|
-
"@tanstack/vue-query": "5.
|
|
13
|
+
"@tailwindcss/postcss": "4.2.3",
|
|
14
|
+
"@tanstack/vue-query": "5.99.2",
|
|
15
15
|
"@types/leaflet": "1.5.7",
|
|
16
16
|
"@unhead/vue": "2.0.5",
|
|
17
17
|
"@vue-leaflet/vue-leaflet": "0.10.1",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"imask": "7.6.1",
|
|
28
28
|
"leaflet": "1.8.0",
|
|
29
29
|
"lexical": "0.30.0",
|
|
30
|
-
"lodash": "4.
|
|
30
|
+
"lodash": "4.18.1",
|
|
31
31
|
"lodash-es": "4.18.1",
|
|
32
32
|
"markdown-it": "14.1.1",
|
|
33
33
|
"pinia": "3.0.4",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"randexp": "0.5.3",
|
|
37
37
|
"register-service-worker": "1.7.2",
|
|
38
38
|
"vega-embed": "7.1.0",
|
|
39
|
-
"vega-lite": "6.4.
|
|
39
|
+
"vega-lite": "6.4.2",
|
|
40
40
|
"vue-chartjs": "5.3.3",
|
|
41
41
|
"vue-i18n": "11.3.2",
|
|
42
42
|
"vue-quilly": "1.0.5",
|
|
@@ -56,14 +56,14 @@
|
|
|
56
56
|
"@storybook/addon-vitest": "10.2.12",
|
|
57
57
|
"@storybook/vue3": "10.2.12",
|
|
58
58
|
"@storybook/vue3-vite": "10.2.12",
|
|
59
|
-
"@tanstack/vue-query-devtools": "6.1.
|
|
59
|
+
"@tanstack/vue-query-devtools": "6.1.18",
|
|
60
60
|
"@tsconfig/node22": "22.0.5",
|
|
61
61
|
"@types/lodash-es": "4.17.12",
|
|
62
62
|
"@types/node": "22.16.5",
|
|
63
63
|
"@vitest/browser": "3.2.4",
|
|
64
|
-
"@vue/compiler-dom": "3.5.
|
|
64
|
+
"@vue/compiler-dom": "3.5.32",
|
|
65
65
|
"@vue/eslint-config-standard": "8.0.1",
|
|
66
|
-
"@vue/server-renderer": "3.5.
|
|
66
|
+
"@vue/server-renderer": "3.5.32",
|
|
67
67
|
"@vue/test-utils": "2.4.6",
|
|
68
68
|
"@vuepic/vue-datepicker": "6.0.3",
|
|
69
69
|
"arg": "5.0.2",
|
|
@@ -71,26 +71,32 @@
|
|
|
71
71
|
"eslint-plugin-storybook": "10.1.10",
|
|
72
72
|
"globals": "15.11.0",
|
|
73
73
|
"jsdom": "26.1.0",
|
|
74
|
-
"postcss": "8.5.
|
|
74
|
+
"postcss": "8.5.10",
|
|
75
75
|
"sass": "1.97.3",
|
|
76
76
|
"start-server-and-test": "2.1.3",
|
|
77
77
|
"storybook": "10.2.12",
|
|
78
78
|
"tsx": "4.21.0",
|
|
79
79
|
"vega": "6.2.0",
|
|
80
80
|
"vite-plugin-dts": "4.5.4",
|
|
81
|
-
"vite-plugin-vue-devtools": "8.
|
|
81
|
+
"vite-plugin-vue-devtools": "8.1.1",
|
|
82
82
|
"vitest": "3.2.4",
|
|
83
83
|
"yaml": "2.8.3"
|
|
84
84
|
}
|
|
85
85
|
},
|
|
86
86
|
"managedAnywhere": {
|
|
87
|
-
"@tailwindcss/vite": "4.2.
|
|
88
|
-
"@vitejs/plugin-vue": "6.0.
|
|
89
|
-
"axios": "1.15.
|
|
87
|
+
"@tailwindcss/vite": "4.2.3",
|
|
88
|
+
"@vitejs/plugin-vue": "6.0.6",
|
|
89
|
+
"axios": "1.15.1",
|
|
90
90
|
"dotenv": "16.4.5",
|
|
91
91
|
"qrcode.vue": "3.8.1",
|
|
92
|
-
"tailwindcss": "4.2.
|
|
92
|
+
"tailwindcss": "4.2.3",
|
|
93
93
|
"vite": "7.3.2",
|
|
94
94
|
"vue": "3.5.32"
|
|
95
|
+
},
|
|
96
|
+
"overrides": {
|
|
97
|
+
"lodash@<=4.17.23": ">=4.18.0",
|
|
98
|
+
"lodash@>=4.0.0 <=4.17.22": ">=4.17.23",
|
|
99
|
+
"lodash@>=4.0.0 <=4.17.23": ">=4.18.0",
|
|
100
|
+
"tmp@<=0.2.3": ">=0.2.4"
|
|
95
101
|
}
|
|
96
102
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fg-devkit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Shared configs, scripts, and DX tooling for Vue / TypeScript frontend repos",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -28,6 +28,11 @@
|
|
|
28
28
|
"./scripts/sync-consumer-scripts.mjs": "./scripts/sync-consumer-scripts.mjs",
|
|
29
29
|
"./scripts/sync-consumer-lefthook.mjs": "./scripts/sync-consumer-lefthook.mjs",
|
|
30
30
|
"./scripts/prune-consumer-deps.mjs": "./scripts/prune-consumer-deps.mjs",
|
|
31
|
+
"./scripts/policy-outdated.mjs": "./scripts/policy-outdated.mjs",
|
|
32
|
+
"./scripts/policy-vulns.mjs": "./scripts/policy-vulns.mjs",
|
|
33
|
+
"./scripts/repo-vulns.mjs": "./scripts/repo-vulns.mjs",
|
|
34
|
+
"./scripts/fleet-vulns.mjs": "./scripts/fleet-vulns.mjs",
|
|
35
|
+
"./scripts/policy-upgrade.mjs": "./scripts/policy-upgrade.mjs",
|
|
31
36
|
"./scripts/check-dependency-policy.mjs": "./scripts/check-dependency-policy.mjs",
|
|
32
37
|
"./scripts/sync-dependency-policy.mjs": "./scripts/sync-dependency-policy.mjs",
|
|
33
38
|
"./scripts/resolve-dependency-policy-path.mjs": "./scripts/resolve-dependency-policy-path.mjs",
|
|
@@ -70,6 +75,7 @@
|
|
|
70
75
|
"eslint-plugin-prettier": "5.5.5",
|
|
71
76
|
"eslint-plugin-vue": "10.8.0",
|
|
72
77
|
"lefthook": "2.1.6",
|
|
78
|
+
"npm-check-updates": "21.0.3",
|
|
73
79
|
"prettier": "3.8.3",
|
|
74
80
|
"typescript": "5.9.3",
|
|
75
81
|
"vue-eslint-parser": "10.4.0",
|
|
@@ -21,6 +21,7 @@ import { resolveDependencyPolicyPath } from './resolve-dependency-policy-path.mj
|
|
|
21
21
|
|
|
22
22
|
const cwd = process.cwd()
|
|
23
23
|
const pkgPath = path.resolve(cwd, 'package.json')
|
|
24
|
+
const lockfilePath = path.resolve(cwd, 'pnpm-lock.yaml')
|
|
24
25
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
|
25
26
|
const policyPath = resolveDependencyPolicyPath(scriptDir, cwd, process.argv)
|
|
26
27
|
|
|
@@ -42,6 +43,30 @@ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
|
|
|
42
43
|
const policy = JSON.parse(fs.readFileSync(policyPath, 'utf8'))
|
|
43
44
|
const mismatches = []
|
|
44
45
|
|
|
46
|
+
function getOverridePackageName(overrideKey) {
|
|
47
|
+
const match = overrideKey.match(/^(@[^/]+\/[^@]+|[^@]+)@/)
|
|
48
|
+
return match ? match[1] : overrideKey
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getRelevantPolicyOverrides(policyOverrides) {
|
|
52
|
+
const entries = Object.entries(policyOverrides ?? {})
|
|
53
|
+
if (entries.length === 0) {
|
|
54
|
+
return {}
|
|
55
|
+
}
|
|
56
|
+
if (!fs.existsSync(lockfilePath)) {
|
|
57
|
+
return Object.fromEntries(entries)
|
|
58
|
+
}
|
|
59
|
+
const lockfile = fs.readFileSync(lockfilePath, 'utf8')
|
|
60
|
+
const relevant = {}
|
|
61
|
+
for (const [name, version] of entries) {
|
|
62
|
+
const pkgName = getOverridePackageName(name)
|
|
63
|
+
if (lockfile.includes(`${pkgName}@`) || lockfile.includes(`/${pkgName}/`)) {
|
|
64
|
+
relevant[name] = version
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return relevant
|
|
68
|
+
}
|
|
69
|
+
|
|
45
70
|
/** True if this package+section is governed by the policy (so global pin checks skip it). */
|
|
46
71
|
function isPolicyManagedInSection(section, name) {
|
|
47
72
|
if (policy.managedAnywhere?.[name] !== undefined) {
|
|
@@ -56,7 +81,6 @@ for (const [section, dependencies] of Object.entries(policy.managedDependencies
|
|
|
56
81
|
}
|
|
57
82
|
|
|
58
83
|
mismatches.push(...collectPinViolations('[policy] managedAnywhere.', policy.managedAnywhere ?? {}))
|
|
59
|
-
mismatches.push(...collectPinViolations('[policy] overrides.', policy.overrides ?? {}))
|
|
60
84
|
|
|
61
85
|
// Intersection: only packages that exist in both policy and consumer are compared.
|
|
62
86
|
for (const [section, dependencies] of Object.entries(policy.managedDependencies ?? {})) {
|
|
@@ -98,12 +122,10 @@ for (const [name, expected] of Object.entries(policy.managedAnywhere ?? {})) {
|
|
|
98
122
|
|
|
99
123
|
// Optional: policy may define pnpm.overrides entries the consumer must mirror.
|
|
100
124
|
const overrides = pkg.pnpm?.overrides ?? {}
|
|
101
|
-
for (const [name, expected] of Object.entries(policy.overrides ?? {})) {
|
|
125
|
+
for (const [name, expected] of Object.entries(getRelevantPolicyOverrides(policy.overrides ?? {}))) {
|
|
102
126
|
const actual = overrides[name]
|
|
103
127
|
if (actual !== expected) {
|
|
104
128
|
mismatches.push(`pnpm.overrides.${name}: expected ${expected}, got ${actual ?? '(missing)'}`)
|
|
105
|
-
} else if (actual !== undefined && !isPinnedDependencyVersion(actual)) {
|
|
106
|
-
mismatches.push(`pnpm.overrides.${name}: must be pinned, got ${JSON.stringify(actual)}`)
|
|
107
129
|
}
|
|
108
130
|
}
|
|
109
131
|
|
|
@@ -121,9 +143,6 @@ for (const section of ['dependencies', 'devDependencies', 'optionalDependencies'
|
|
|
121
143
|
}
|
|
122
144
|
}
|
|
123
145
|
|
|
124
|
-
// Consumer pnpm.overrides must be pinned (ranges reported even if not in policy).
|
|
125
|
-
mismatches.push(...collectPinViolations('[pins] pnpm.overrides.', pkg.pnpm?.overrides ?? {}))
|
|
126
|
-
|
|
127
146
|
if (mismatches.length > 0) {
|
|
128
147
|
console.error('[check-deps] Dependency policy mismatches found:')
|
|
129
148
|
for (const mismatch of mismatches) {
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
module.exports = {
|
|
6
6
|
prompter(cz, commit) {
|
|
7
7
|
const defaultType = process.env.FG_DEVKIT_CZ_DEFAULT_TYPE;
|
|
8
|
+
const defaultPackages = process.env.FG_DEVKIT_CZ_DEFAULT_PACKAGES;
|
|
9
|
+
const defaultDepVersion = process.env.FG_DEVKIT_CZ_DEFAULT_DEP_VERSION;
|
|
10
|
+
const defaultDepChangeKind = process.env.FG_DEVKIT_CZ_DEFAULT_DEP_CHANGE_KIND;
|
|
8
11
|
const types = [
|
|
9
12
|
{ value: "feat", name: "feat(scope): Added" },
|
|
10
13
|
{ value: "fix", name: "fix(scope): Fixed" },
|
|
@@ -36,6 +39,7 @@ module.exports = {
|
|
|
36
39
|
name: "packages",
|
|
37
40
|
when: (answers) => ["dep", "vuln"].includes(answers.change_type),
|
|
38
41
|
message: "Packages:",
|
|
42
|
+
default: defaultType === "dep" && defaultPackages ? defaultPackages : undefined,
|
|
39
43
|
},
|
|
40
44
|
{
|
|
41
45
|
type: "list",
|
|
@@ -64,12 +68,17 @@ module.exports = {
|
|
|
64
68
|
{ value: "update", name: "updated package" },
|
|
65
69
|
{ value: "remove", name: "removed package" },
|
|
66
70
|
],
|
|
71
|
+
default:
|
|
72
|
+
defaultType === "dep" && ["new", "update", "remove"].includes(defaultDepChangeKind)
|
|
73
|
+
? defaultDepChangeKind
|
|
74
|
+
: "update",
|
|
67
75
|
},
|
|
68
76
|
{
|
|
69
77
|
type: "input",
|
|
70
78
|
name: "dep_version",
|
|
71
79
|
when: (answers) => answers.change_type === "dep",
|
|
72
80
|
message: "Version (x.y.z):",
|
|
81
|
+
default: defaultType === "dep" && defaultDepVersion ? defaultDepVersion : undefined,
|
|
73
82
|
validate: (value) => (/^\d+\.\d+\.\d+$/.test(value) ? true : "Use format x.y.z"),
|
|
74
83
|
},
|
|
75
84
|
{
|
package/scripts/fg-devkit.mjs
CHANGED
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
* Non-interactive (for scripts and automation):
|
|
6
6
|
* pnpm exec fg-devkit check-deps [policy-file]
|
|
7
7
|
* pnpm exec fg-devkit sync-deps [policy-file]
|
|
8
|
+
* pnpm exec fg-devkit policy-outdated [policy-file]
|
|
9
|
+
* pnpm exec fg-devkit policy-vulns [policy-file]
|
|
10
|
+
* pnpm exec fg-devkit repo-vulns
|
|
11
|
+
* pnpm exec fg-devkit fleet-vulns [projects-root] [--summary-only] [--delay-ms=<ms>]
|
|
12
|
+
* pnpm exec fg-devkit policy-upgrade [policy-file] [ncu-args...]
|
|
8
13
|
* pnpm exec fg-devkit sync-project-configs (VS Code + tooling sync; overwrites config files)
|
|
9
14
|
* pnpm exec fg-devkit sync-tooling-configs (tooling sync only; overwrites config files)
|
|
10
15
|
* pnpm exec fg-devkit sync-vscode-settings
|
|
@@ -38,6 +43,7 @@ import { createRequire } from 'node:module'
|
|
|
38
43
|
|
|
39
44
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
|
40
45
|
const require = createRequire(import.meta.url)
|
|
46
|
+
const fgRoot = path.resolve(scriptDir, '..')
|
|
41
47
|
|
|
42
48
|
/** Run another script in this package via Node; inherits stdio and returns the child exit code. */
|
|
43
49
|
function runNode(scriptRelative, args = [], options = {}) {
|
|
@@ -153,6 +159,111 @@ function runFullConsumerSetup() {
|
|
|
153
159
|
return runPnpmInstall()
|
|
154
160
|
}
|
|
155
161
|
|
|
162
|
+
function stageFiles(paths = []) {
|
|
163
|
+
const existing = paths.filter((p) => fs.existsSync(path.join(process.cwd(), p)))
|
|
164
|
+
if (existing.length === 0) return 0
|
|
165
|
+
const r = spawnSync('git', ['add', '--', ...existing], {
|
|
166
|
+
stdio: 'inherit',
|
|
167
|
+
cwd: process.cwd(),
|
|
168
|
+
env: process.env,
|
|
169
|
+
})
|
|
170
|
+
if (r.error) {
|
|
171
|
+
console.error('[fg-devkit]', r.error.message)
|
|
172
|
+
return 1
|
|
173
|
+
}
|
|
174
|
+
return r.status ?? 1
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function commitStaged(message) {
|
|
178
|
+
const r = spawnSync('git', ['commit', '-m', message], {
|
|
179
|
+
stdio: 'inherit',
|
|
180
|
+
cwd: process.cwd(),
|
|
181
|
+
env: process.env,
|
|
182
|
+
})
|
|
183
|
+
if (r.error) {
|
|
184
|
+
console.error('[fg-devkit]', r.error.message)
|
|
185
|
+
return 1
|
|
186
|
+
}
|
|
187
|
+
return r.status ?? 1
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function collectPolicyVersionChanges(beforePolicy, afterPolicy) {
|
|
191
|
+
const before = {
|
|
192
|
+
...(beforePolicy.managedDependencies?.dependencies ?? {}),
|
|
193
|
+
...(beforePolicy.managedDependencies?.devDependencies ?? {}),
|
|
194
|
+
...(beforePolicy.managedAnywhere ?? {}),
|
|
195
|
+
}
|
|
196
|
+
const after = {
|
|
197
|
+
...(afterPolicy.managedDependencies?.dependencies ?? {}),
|
|
198
|
+
...(afterPolicy.managedDependencies?.devDependencies ?? {}),
|
|
199
|
+
...(afterPolicy.managedAnywhere ?? {}),
|
|
200
|
+
}
|
|
201
|
+
const changed = []
|
|
202
|
+
for (const [name, newVersion] of Object.entries(after)) {
|
|
203
|
+
const prev = before[name]
|
|
204
|
+
if (prev !== undefined && prev !== newVersion) {
|
|
205
|
+
changed.push({ name, from: prev, to: newVersion })
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return changed
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Maintainer flow: run policy-upgrade, stage policy file, then auto-commit dep(package): bump to version. */
|
|
212
|
+
function runMaintainerPolicyUpgradeAndCommit() {
|
|
213
|
+
const policyPath = path.join(process.cwd(), 'configs', 'dependency-policy.vue-workspace.json')
|
|
214
|
+
if (!fs.existsSync(policyPath)) {
|
|
215
|
+
console.error('[fg-devkit] Expected policy at configs/dependency-policy.vue-workspace.json')
|
|
216
|
+
return 1
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
let beforePolicy
|
|
220
|
+
try {
|
|
221
|
+
beforePolicy = JSON.parse(fs.readFileSync(policyPath, 'utf8'))
|
|
222
|
+
} catch (err) {
|
|
223
|
+
console.error(`[fg-devkit] Failed to read policy before upgrade: ${err.message}`)
|
|
224
|
+
return 1
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const upgradeCode = runNode('policy-upgrade.mjs')
|
|
228
|
+
if (upgradeCode !== 0) return upgradeCode
|
|
229
|
+
|
|
230
|
+
let afterPolicy
|
|
231
|
+
try {
|
|
232
|
+
afterPolicy = JSON.parse(fs.readFileSync(policyPath, 'utf8'))
|
|
233
|
+
} catch (err) {
|
|
234
|
+
console.error(`[fg-devkit] Failed to read policy after upgrade: ${err.message}`)
|
|
235
|
+
return 1
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const changed = collectPolicyVersionChanges(beforePolicy, afterPolicy)
|
|
239
|
+
if (changed.length === 0) {
|
|
240
|
+
console.log('[fg-devkit] No policy package version changes detected; skipping stage and commit flow.')
|
|
241
|
+
return 0
|
|
242
|
+
}
|
|
243
|
+
if (changed.length > 1) {
|
|
244
|
+
console.log('[fg-devkit] Multiple package changes detected in policy-upgrade:')
|
|
245
|
+
for (const item of changed) {
|
|
246
|
+
console.log(` - ${item.name}: ${item.from} -> ${item.to}`)
|
|
247
|
+
}
|
|
248
|
+
console.log('[fg-devkit] Please commit manually (or rerun and select a single package).')
|
|
249
|
+
return 1
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const [only] = changed
|
|
253
|
+
const stageCode = stageFiles(['configs/dependency-policy.vue-workspace.json'])
|
|
254
|
+
if (stageCode !== 0) return stageCode
|
|
255
|
+
|
|
256
|
+
const commitMessage = `dep(${only.name}): bump to ${only.to}`
|
|
257
|
+
console.log(`[fg-devkit] Staged policy change for ${only.name} -> ${only.to}`)
|
|
258
|
+
console.log(`[fg-devkit] Auto-committing: ${commitMessage}`)
|
|
259
|
+
return commitStaged(commitMessage)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Maintainer flow: run full repo vulnerability audit in fg-devkit root. */
|
|
263
|
+
function runMaintainerAuditAll() {
|
|
264
|
+
return runNode('repo-vulns.mjs')
|
|
265
|
+
}
|
|
266
|
+
|
|
156
267
|
/** Prompt release type in CLI before invoking release.sh to avoid a "stuck" terminal feeling. */
|
|
157
268
|
async function promptReleaseType(rl) {
|
|
158
269
|
console.log('\nSelect release type:')
|
|
@@ -293,6 +404,11 @@ function runBundledTypeScript(args = []) {
|
|
|
293
404
|
const DIRECT_COMMANDS = {
|
|
294
405
|
'check-deps': 'check-dependency-policy.mjs',
|
|
295
406
|
'sync-deps': 'sync-dependency-policy.mjs',
|
|
407
|
+
'policy-outdated': 'policy-outdated.mjs',
|
|
408
|
+
'policy-vulns': 'policy-vulns.mjs',
|
|
409
|
+
'repo-vulns': 'repo-vulns.mjs',
|
|
410
|
+
'fleet-vulns': 'fleet-vulns.mjs',
|
|
411
|
+
'policy-upgrade': 'policy-upgrade.mjs',
|
|
296
412
|
'sync-project-configs': 'sync-project-configs.mjs',
|
|
297
413
|
'sync-tooling-configs': 'sync-tooling-configs.mjs',
|
|
298
414
|
'sync-vscode-settings': 'sync-vscode-settings.mjs',
|
|
@@ -308,6 +424,13 @@ function printHelp() {
|
|
|
308
424
|
Commands:
|
|
309
425
|
check-deps [policy-file] Validate package.json + pins against policy
|
|
310
426
|
sync-deps [policy-file] Align declared versions to policy (existing keys only)
|
|
427
|
+
policy-outdated [policy-file] Show outdated policy packages via pnpm outdated
|
|
428
|
+
policy-vulns [policy-file] Check all policy packages together (including transitives) via pnpm audit
|
|
429
|
+
repo-vulns Check all repo dependencies for vulnerabilities via pnpm audit
|
|
430
|
+
fleet-vulns [projects-root] [--summary-only] [--delay-ms=<ms>]
|
|
431
|
+
Run pnpm audit across Vue repos (full output + combined summary)
|
|
432
|
+
policy-upgrade [policy-file] [ncu-args...]
|
|
433
|
+
Upgrade policy packages via npm-check-updates (default interactive)
|
|
311
434
|
sync-project-configs VS Code + tooling sync (overwrites config files)
|
|
312
435
|
sync-tooling-configs Tooling sync only (overwrites config files)
|
|
313
436
|
sync-vscode-settings Copy shared VS Code settings into .vscode/settings.json
|
|
@@ -381,12 +504,68 @@ const MENU = [
|
|
|
381
504
|
},
|
|
382
505
|
]
|
|
383
506
|
|
|
507
|
+
const MAINTAINER_MENU = [
|
|
508
|
+
{
|
|
509
|
+
key: 'm1',
|
|
510
|
+
label: 'Maintainer: Show outdated dependency policy packages',
|
|
511
|
+
run: () => runNode('policy-outdated.mjs'),
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
key: 'm2',
|
|
515
|
+
label: 'Maintainer: Upgrade dependency policy (interactive)',
|
|
516
|
+
run: () => runNode('policy-upgrade.mjs'),
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
key: 'm3',
|
|
520
|
+
label: 'Maintainer: Refresh dependency policy from repos',
|
|
521
|
+
run: () => runNode('refresh-dependency-policy-from-repos.mjs'),
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
key: 'm4',
|
|
525
|
+
label: 'Maintainer: Upgrade one policy package, stage, and auto-commit',
|
|
526
|
+
run: () => runMaintainerPolicyUpgradeAndCommit(),
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
key: 'm5',
|
|
530
|
+
label: 'Maintainer: Check policy vulnerabilities (audit)',
|
|
531
|
+
run: () => runNode('policy-vulns.mjs'),
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
key: 'm6',
|
|
535
|
+
label: 'Maintainer: Check fg-devkit repo vulnerabilities',
|
|
536
|
+
run: () => runMaintainerAuditAll(),
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
key: 'm7',
|
|
540
|
+
label: 'Maintainer: Check vulnerabilities across all Vue repos',
|
|
541
|
+
run: () => runNode('fleet-vulns.mjs'),
|
|
542
|
+
},
|
|
543
|
+
]
|
|
544
|
+
|
|
545
|
+
function isFgDevkitMaintainerContext() {
|
|
546
|
+
if (path.resolve(process.cwd()) !== fgRoot) {
|
|
547
|
+
return false
|
|
548
|
+
}
|
|
549
|
+
const pkgPath = path.join(process.cwd(), 'package.json')
|
|
550
|
+
if (!fs.existsSync(pkgPath)) {
|
|
551
|
+
return false
|
|
552
|
+
}
|
|
553
|
+
try {
|
|
554
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
|
|
555
|
+
return pkg?.name === 'fg-devkit'
|
|
556
|
+
} catch {
|
|
557
|
+
return false
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
384
561
|
async function runInteractiveMenu() {
|
|
385
562
|
const rl = readline.createInterface({ input, output })
|
|
563
|
+
const isMaintainer = isFgDevkitMaintainerContext()
|
|
564
|
+
const menu = isMaintainer ? [...MENU, ...MAINTAINER_MENU] : MENU
|
|
386
565
|
|
|
387
566
|
console.log(`\nfg-devkit — choose a task (cwd: ${process.cwd()})\n`)
|
|
388
567
|
|
|
389
|
-
for (const item of
|
|
568
|
+
for (const item of menu) {
|
|
390
569
|
console.log(` ${item.key}) ${item.label}`)
|
|
391
570
|
}
|
|
392
571
|
console.log(' 0) Exit\n')
|
|
@@ -398,7 +577,7 @@ async function runInteractiveMenu() {
|
|
|
398
577
|
return 0
|
|
399
578
|
}
|
|
400
579
|
|
|
401
|
-
const item =
|
|
580
|
+
const item = menu.find((m) => m.key === choice)
|
|
402
581
|
if (!item) {
|
|
403
582
|
console.error('[fg-devkit] Unknown option. Run again and choose a number from the list.')
|
|
404
583
|
rl.close()
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Runs vulnerability audits across Vue repositories and prints both per-repo
|
|
4
|
+
* pnpm audit output and a combined summary.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* pnpm exec fg-devkit fleet-vulns [projects-root] [--summary-only] [--delay-ms=<ms>]
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'node:fs'
|
|
11
|
+
import path from 'node:path'
|
|
12
|
+
import { spawnSync } from 'node:child_process'
|
|
13
|
+
import { fileURLToPath } from 'node:url'
|
|
14
|
+
|
|
15
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
|
16
|
+
const cliArgs = process.argv.slice(2)
|
|
17
|
+
const summaryOnly = cliArgs.includes('--summary-only')
|
|
18
|
+
const projectsRootArg = cliArgs.find((a) => !a.startsWith('-'))
|
|
19
|
+
const projectsRoot = projectsRootArg ? path.resolve(process.cwd(), projectsRootArg) : path.resolve(scriptDir, '../..')
|
|
20
|
+
const delayArg = cliArgs.find((a) => a.startsWith('--delay-ms='))
|
|
21
|
+
const delayMs = Number(delayArg?.split('=')[1] ?? 2500)
|
|
22
|
+
|
|
23
|
+
function sleep(ms) {
|
|
24
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function walk(dir, acc = []) {
|
|
28
|
+
for (const item of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
29
|
+
if (item.name === 'node_modules' || item.name === '.git') continue
|
|
30
|
+
const p = path.join(dir, item.name)
|
|
31
|
+
if (item.isDirectory()) walk(p, acc)
|
|
32
|
+
else if (item.name === 'package.json') acc.push(p)
|
|
33
|
+
}
|
|
34
|
+
return acc
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function readJson(p) {
|
|
38
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isVueProject(pkg) {
|
|
42
|
+
return Boolean(pkg.dependencies?.vue || pkg.devDependencies?.vue)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function countTotal(severityMap = {}) {
|
|
46
|
+
return Object.values(severityMap).reduce((sum, n) => sum + Number(n || 0), 0)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const packageJsonFiles = walk(projectsRoot)
|
|
50
|
+
const repos = []
|
|
51
|
+
for (const packageJsonPath of packageJsonFiles) {
|
|
52
|
+
try {
|
|
53
|
+
const pkg = readJson(packageJsonPath)
|
|
54
|
+
const repoDir = path.dirname(packageJsonPath)
|
|
55
|
+
if (!isVueProject(pkg)) continue
|
|
56
|
+
if (pkg.name === 'fg-devkit') continue
|
|
57
|
+
repos.push(repoDir)
|
|
58
|
+
} catch {
|
|
59
|
+
// Ignore invalid package.json files.
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const uniqueRepos = [...new Set(repos)].sort((a, b) => a.localeCompare(b))
|
|
64
|
+
if (uniqueRepos.length === 0) {
|
|
65
|
+
console.log(`[fleet-vulns] No Vue repositories found under ${projectsRoot}`)
|
|
66
|
+
process.exit(0)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log(`[fleet-vulns] projectsRoot=${projectsRoot}`)
|
|
70
|
+
console.log(`[fleet-vulns] repositories=${uniqueRepos.length}`)
|
|
71
|
+
|
|
72
|
+
const results = []
|
|
73
|
+
let hasFailures = false
|
|
74
|
+
let hasVulnerabilities = false
|
|
75
|
+
|
|
76
|
+
for (const repoDir of uniqueRepos) {
|
|
77
|
+
const repoName = path.basename(repoDir)
|
|
78
|
+
const lockfilePath = path.join(repoDir, 'pnpm-lock.yaml')
|
|
79
|
+
if (!fs.existsSync(lockfilePath)) {
|
|
80
|
+
console.log(`[fleet-vulns] SKIP ${repoName}: missing pnpm-lock.yaml`)
|
|
81
|
+
continue
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
console.log(`\n[fleet-vulns] Auditing ${repoName}`)
|
|
85
|
+
if (!summaryOnly) {
|
|
86
|
+
const prettyAudit = spawnSync('pnpm', ['audit'], {
|
|
87
|
+
cwd: repoDir,
|
|
88
|
+
env: process.env,
|
|
89
|
+
stdio: 'inherit',
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
if (prettyAudit.error) {
|
|
93
|
+
hasFailures = true
|
|
94
|
+
console.error(`[fleet-vulns] FAIL ${repoName}: ${prettyAudit.error.message}`)
|
|
95
|
+
continue
|
|
96
|
+
}
|
|
97
|
+
const prettyCode = prettyAudit.status ?? 1
|
|
98
|
+
if (prettyCode !== 0 && prettyCode !== 1) {
|
|
99
|
+
hasFailures = true
|
|
100
|
+
console.error(`[fleet-vulns] FAIL ${repoName}: pnpm audit exited with code ${prettyCode}`)
|
|
101
|
+
continue
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const audit = spawnSync('pnpm', ['audit', '--json'], {
|
|
106
|
+
cwd: repoDir,
|
|
107
|
+
env: process.env,
|
|
108
|
+
encoding: 'utf8',
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
if (audit.error) {
|
|
112
|
+
hasFailures = true
|
|
113
|
+
console.error(`[fleet-vulns] FAIL ${repoName}: ${audit.error.message}`)
|
|
114
|
+
continue
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let report = null
|
|
118
|
+
try {
|
|
119
|
+
report = JSON.parse(audit.stdout || '{}')
|
|
120
|
+
} catch {
|
|
121
|
+
report = null
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!report || typeof report !== 'object') {
|
|
125
|
+
hasFailures = true
|
|
126
|
+
console.error(`[fleet-vulns] FAIL ${repoName}: could not parse pnpm audit --json output`)
|
|
127
|
+
continue
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const vulnerabilities = report.metadata?.vulnerabilities ?? {}
|
|
131
|
+
const total = countTotal(vulnerabilities)
|
|
132
|
+
if (total > 0) hasVulnerabilities = true
|
|
133
|
+
|
|
134
|
+
results.push({
|
|
135
|
+
repoName,
|
|
136
|
+
vulnerabilities,
|
|
137
|
+
total,
|
|
138
|
+
dependencies: report.metadata?.totalDependencies ?? 0,
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
if (delayMs > 0) {
|
|
142
|
+
await sleep(delayMs)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
console.log('\n[fleet-vulns] Summary')
|
|
147
|
+
for (const r of results) {
|
|
148
|
+
const v = r.vulnerabilities
|
|
149
|
+
console.log(
|
|
150
|
+
`- ${r.repoName}: total=${r.total} (critical=${v.critical ?? 0}, high=${v.high ?? 0}, moderate=${v.moderate ?? 0}, low=${v.low ?? 0}, info=${v.info ?? 0}) deps=${r.dependencies}`,
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (results.length === 0) {
|
|
155
|
+
console.log('[fleet-vulns] No repositories were audited.')
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (hasFailures) {
|
|
159
|
+
process.exit(1)
|
|
160
|
+
}
|
|
161
|
+
if (hasVulnerabilities) {
|
|
162
|
+
process.exit(1)
|
|
163
|
+
}
|
|
164
|
+
process.exit(0)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Shows outdated packages for dependency policy entries by projecting policy data
|
|
4
|
+
* into a temporary package.json and running `pnpm outdated` there.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* pnpm exec fg-devkit policy-outdated [policy-file]
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'node:fs'
|
|
11
|
+
import os from 'node:os'
|
|
12
|
+
import path from 'node:path'
|
|
13
|
+
import { spawnSync } from 'node:child_process'
|
|
14
|
+
import { fileURLToPath } from 'node:url'
|
|
15
|
+
import { resolveDependencyPolicyPath } from './resolve-dependency-policy-path.mjs'
|
|
16
|
+
|
|
17
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
|
18
|
+
const policyPath = resolveDependencyPolicyPath(scriptDir, process.cwd(), process.argv)
|
|
19
|
+
|
|
20
|
+
function readJson(p) {
|
|
21
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const policy = readJson(policyPath)
|
|
25
|
+
const deps = {
|
|
26
|
+
...(policy.managedDependencies?.dependencies ?? {}),
|
|
27
|
+
...(policy.managedAnywhere ?? {}),
|
|
28
|
+
}
|
|
29
|
+
const devDeps = {
|
|
30
|
+
...(policy.managedDependencies?.devDependencies ?? {}),
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'fgd-policy-outdated-'))
|
|
34
|
+
const pkgPath = path.join(tmpDir, 'package.json')
|
|
35
|
+
fs.writeFileSync(
|
|
36
|
+
pkgPath,
|
|
37
|
+
`${JSON.stringify(
|
|
38
|
+
{
|
|
39
|
+
name: 'fgd-policy-outdated-temp',
|
|
40
|
+
private: true,
|
|
41
|
+
version: '0.0.0',
|
|
42
|
+
dependencies: deps,
|
|
43
|
+
devDependencies: devDeps,
|
|
44
|
+
},
|
|
45
|
+
null,
|
|
46
|
+
2,
|
|
47
|
+
)}\n`,
|
|
48
|
+
'utf8',
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
console.log(`[policy-outdated] policy=${policyPath}`)
|
|
52
|
+
const install = spawnSync('pnpm', ['install', '--lockfile-only', '--ignore-scripts'], {
|
|
53
|
+
cwd: tmpDir,
|
|
54
|
+
stdio: 'inherit',
|
|
55
|
+
env: process.env,
|
|
56
|
+
})
|
|
57
|
+
if (install.error) {
|
|
58
|
+
console.error('[policy-outdated]', install.error.message)
|
|
59
|
+
process.exit(1)
|
|
60
|
+
}
|
|
61
|
+
if ((install.status ?? 1) !== 0) {
|
|
62
|
+
process.exit(install.status ?? 1)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const result = spawnSync('pnpm', ['outdated'], {
|
|
66
|
+
cwd: tmpDir,
|
|
67
|
+
stdio: 'inherit',
|
|
68
|
+
env: process.env,
|
|
69
|
+
})
|
|
70
|
+
if (result.error) {
|
|
71
|
+
console.error('[policy-outdated]', result.error.message)
|
|
72
|
+
process.exit(1)
|
|
73
|
+
}
|
|
74
|
+
// pnpm outdated returns exit code 1 when outdated packages are found.
|
|
75
|
+
if ((result.status ?? 1) === 1) {
|
|
76
|
+
process.exit(0)
|
|
77
|
+
}
|
|
78
|
+
process.exit(result.status ?? 1)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Upgrades dependency policy package versions through npm-check-updates.
|
|
4
|
+
* It projects the policy into a temporary package.json, runs ncu, then writes
|
|
5
|
+
* selected versions back to the policy file.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* pnpm exec fg-devkit policy-upgrade [policy-file] [ncu-args...]
|
|
9
|
+
*
|
|
10
|
+
* Defaults when no ncu args are given:
|
|
11
|
+
* --interactive --format group --upgrade --packageManager pnpm --install never
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from 'node:fs'
|
|
15
|
+
import os from 'node:os'
|
|
16
|
+
import path from 'node:path'
|
|
17
|
+
import { spawnSync } from 'node:child_process'
|
|
18
|
+
import { fileURLToPath } from 'node:url'
|
|
19
|
+
import { createRequire } from 'node:module'
|
|
20
|
+
import { resolveDependencyPolicyPath } from './resolve-dependency-policy-path.mjs'
|
|
21
|
+
|
|
22
|
+
const require = createRequire(import.meta.url)
|
|
23
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
|
24
|
+
|
|
25
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
26
|
+
console.log(`Usage:
|
|
27
|
+
pnpm exec fg-devkit policy-upgrade [policy-file] [ncu-args...]
|
|
28
|
+
|
|
29
|
+
Examples:
|
|
30
|
+
pnpm exec fg-devkit policy-upgrade
|
|
31
|
+
pnpm exec fg-devkit policy-upgrade configs/dependency-policy.vue-workspace.json --interactive --format group --upgrade`)
|
|
32
|
+
process.exit(0)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const argPolicy = process.argv[2]
|
|
36
|
+
const hasPolicyArg = Boolean(argPolicy) && !argPolicy.startsWith('-')
|
|
37
|
+
const policyPath = resolveDependencyPolicyPath(scriptDir, process.cwd(), process.argv)
|
|
38
|
+
const ncuArgs = hasPolicyArg ? process.argv.slice(3) : process.argv.slice(2)
|
|
39
|
+
|
|
40
|
+
function readJson(p) {
|
|
41
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const policy = readJson(policyPath)
|
|
45
|
+
|
|
46
|
+
const depNames = new Set(Object.keys(policy.managedDependencies?.dependencies ?? {}))
|
|
47
|
+
const devNames = new Set(Object.keys(policy.managedDependencies?.devDependencies ?? {}))
|
|
48
|
+
const anyNames = new Set(Object.keys(policy.managedAnywhere ?? {}))
|
|
49
|
+
|
|
50
|
+
const deps = {
|
|
51
|
+
...(policy.managedDependencies?.dependencies ?? {}),
|
|
52
|
+
...(policy.managedAnywhere ?? {}),
|
|
53
|
+
}
|
|
54
|
+
const devDeps = {
|
|
55
|
+
...(policy.managedDependencies?.devDependencies ?? {}),
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'fgd-policy-upgrade-'))
|
|
59
|
+
const pkgPath = path.join(tmpDir, 'package.json')
|
|
60
|
+
fs.writeFileSync(
|
|
61
|
+
pkgPath,
|
|
62
|
+
`${JSON.stringify(
|
|
63
|
+
{
|
|
64
|
+
name: 'fgd-policy-upgrade-temp',
|
|
65
|
+
private: true,
|
|
66
|
+
version: '0.0.0',
|
|
67
|
+
dependencies: deps,
|
|
68
|
+
devDependencies: devDeps,
|
|
69
|
+
},
|
|
70
|
+
null,
|
|
71
|
+
2,
|
|
72
|
+
)}\n`,
|
|
73
|
+
'utf8',
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
let ncuBin
|
|
77
|
+
try {
|
|
78
|
+
const ncuPkgPath = require.resolve('npm-check-updates/package.json')
|
|
79
|
+
ncuBin = path.join(path.dirname(ncuPkgPath), 'build', 'cli.js')
|
|
80
|
+
} catch {
|
|
81
|
+
console.error('[policy-upgrade] Could not resolve npm-check-updates binary.')
|
|
82
|
+
process.exit(1)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const effectiveNcuArgs =
|
|
86
|
+
ncuArgs.length > 0
|
|
87
|
+
? ncuArgs
|
|
88
|
+
: ['--interactive', '--format', 'group', '--upgrade', '--packageManager', 'pnpm', '--install', 'never']
|
|
89
|
+
|
|
90
|
+
console.log(`[policy-upgrade] policy=${policyPath}`)
|
|
91
|
+
const run = spawnSync(process.execPath, [ncuBin, ...effectiveNcuArgs], {
|
|
92
|
+
cwd: tmpDir,
|
|
93
|
+
stdio: 'inherit',
|
|
94
|
+
env: process.env,
|
|
95
|
+
})
|
|
96
|
+
if (run.error) {
|
|
97
|
+
console.error('[policy-upgrade]', run.error.message)
|
|
98
|
+
process.exit(1)
|
|
99
|
+
}
|
|
100
|
+
if ((run.status ?? 1) !== 0) {
|
|
101
|
+
process.exit(run.status ?? 1)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const updatedPkg = readJson(pkgPath)
|
|
105
|
+
const updatedDeps = updatedPkg.dependencies ?? {}
|
|
106
|
+
const updatedDevDeps = updatedPkg.devDependencies ?? {}
|
|
107
|
+
|
|
108
|
+
for (const [name, version] of Object.entries(updatedDeps)) {
|
|
109
|
+
if (anyNames.has(name)) policy.managedAnywhere[name] = version
|
|
110
|
+
else if (depNames.has(name)) policy.managedDependencies.dependencies[name] = version
|
|
111
|
+
}
|
|
112
|
+
for (const [name, version] of Object.entries(updatedDevDeps)) {
|
|
113
|
+
if (devNames.has(name)) policy.managedDependencies.devDependencies[name] = version
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
fs.writeFileSync(policyPath, `${JSON.stringify(policy, null, 2)}\n`, 'utf8')
|
|
117
|
+
console.log('[policy-upgrade] Updated dependency policy from selected ncu changes.')
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Checks vulnerabilities for all dependency policy entries together by projecting
|
|
4
|
+
* policy data into one temporary package.json and running `pnpm audit` there.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* pnpm exec fg-devkit policy-vulns [policy-file]
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'node:fs'
|
|
11
|
+
import os from 'node:os'
|
|
12
|
+
import path from 'node:path'
|
|
13
|
+
import { spawnSync } from 'node:child_process'
|
|
14
|
+
import { fileURLToPath } from 'node:url'
|
|
15
|
+
import { resolveDependencyPolicyPath } from './resolve-dependency-policy-path.mjs'
|
|
16
|
+
|
|
17
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
|
18
|
+
const policyPath = resolveDependencyPolicyPath(scriptDir, process.cwd(), process.argv)
|
|
19
|
+
|
|
20
|
+
function readJson(p) {
|
|
21
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const policy = readJson(policyPath)
|
|
25
|
+
console.log(`[policy-vulns] policy=${policyPath}`)
|
|
26
|
+
const deps = {
|
|
27
|
+
...(policy.managedDependencies?.dependencies ?? {}),
|
|
28
|
+
...(policy.managedAnywhere ?? {}),
|
|
29
|
+
}
|
|
30
|
+
const devDeps = {
|
|
31
|
+
...(policy.managedDependencies?.devDependencies ?? {}),
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'fgd-policy-vulns-'))
|
|
35
|
+
const pkgPath = path.join(tmpDir, 'package.json')
|
|
36
|
+
fs.writeFileSync(
|
|
37
|
+
pkgPath,
|
|
38
|
+
`${JSON.stringify(
|
|
39
|
+
{
|
|
40
|
+
name: 'fgd-policy-vulns-temp',
|
|
41
|
+
private: true,
|
|
42
|
+
version: '0.0.0',
|
|
43
|
+
dependencies: deps,
|
|
44
|
+
devDependencies: devDeps,
|
|
45
|
+
},
|
|
46
|
+
null,
|
|
47
|
+
2,
|
|
48
|
+
)}\n`,
|
|
49
|
+
'utf8',
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
const install = spawnSync('pnpm', ['install', '--ignore-scripts'], {
|
|
53
|
+
cwd: tmpDir,
|
|
54
|
+
stdio: 'inherit',
|
|
55
|
+
env: process.env,
|
|
56
|
+
})
|
|
57
|
+
if (install.error) {
|
|
58
|
+
console.error('[policy-vulns]', install.error.message)
|
|
59
|
+
process.exit(1)
|
|
60
|
+
}
|
|
61
|
+
if ((install.status ?? 1) !== 0) {
|
|
62
|
+
process.exit(install.status ?? 1)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const audit = spawnSync('pnpm', ['audit'], {
|
|
66
|
+
cwd: tmpDir,
|
|
67
|
+
stdio: 'inherit',
|
|
68
|
+
env: process.env,
|
|
69
|
+
})
|
|
70
|
+
if (audit.error) {
|
|
71
|
+
console.error('[policy-vulns]', audit.error.message)
|
|
72
|
+
process.exit(1)
|
|
73
|
+
}
|
|
74
|
+
process.exit(audit.status ?? 1)
|
|
@@ -39,6 +39,15 @@ const policyPath = process.argv[3]
|
|
|
39
39
|
? path.resolve(process.cwd(), process.argv[3])
|
|
40
40
|
: path.join(fgDevkitRoot, 'configs/dependency-policy.vue-workspace.json')
|
|
41
41
|
|
|
42
|
+
let existingPolicy = {}
|
|
43
|
+
try {
|
|
44
|
+
if (fs.existsSync(policyPath)) {
|
|
45
|
+
existingPolicy = JSON.parse(fs.readFileSync(policyPath, 'utf8'))
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
existingPolicy = {}
|
|
49
|
+
}
|
|
50
|
+
|
|
42
51
|
/** Relative paths under projectsRoot whose package.json files are ignored (maintainer choice). */
|
|
43
52
|
/** Relative path prefixes under projectsRoot to skip when scanning (add repo names as needed). */
|
|
44
53
|
const SKIP_DIR_PREFIXES = []
|
|
@@ -180,6 +189,7 @@ const policy = {
|
|
|
180
189
|
devDependencies: sortRecord(outDev),
|
|
181
190
|
},
|
|
182
191
|
managedAnywhere: sortRecord(outAny),
|
|
192
|
+
overrides: sortRecord(existingPolicy.overrides ?? {}),
|
|
183
193
|
}
|
|
184
194
|
|
|
185
195
|
const policyErrors = [
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Checks vulnerabilities for the full dependency graph in the current repository
|
|
4
|
+
* by running `pnpm audit` in the working directory.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* pnpm exec fg-devkit repo-vulns
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { spawnSync } from 'node:child_process'
|
|
11
|
+
|
|
12
|
+
console.log(`[repo-vulns] cwd=${process.cwd()}`)
|
|
13
|
+
const audit = spawnSync('pnpm', ['audit'], {
|
|
14
|
+
cwd: process.cwd(),
|
|
15
|
+
stdio: 'inherit',
|
|
16
|
+
env: process.env,
|
|
17
|
+
})
|
|
18
|
+
if (audit.error) {
|
|
19
|
+
console.error('[repo-vulns]', audit.error.message)
|
|
20
|
+
process.exit(1)
|
|
21
|
+
}
|
|
22
|
+
// pnpm audit returns exit code 1 when vulnerabilities are found.
|
|
23
|
+
if ((audit.status ?? 1) === 1) {
|
|
24
|
+
process.exit(0)
|
|
25
|
+
}
|
|
26
|
+
process.exit(audit.status ?? 1)
|
|
@@ -20,6 +20,7 @@ import { resolveDependencyPolicyPath } from './resolve-dependency-policy-path.mj
|
|
|
20
20
|
|
|
21
21
|
const cwd = process.cwd()
|
|
22
22
|
const pkgPath = path.resolve(cwd, 'package.json')
|
|
23
|
+
const lockfilePath = path.resolve(cwd, 'pnpm-lock.yaml')
|
|
23
24
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
|
|
24
25
|
const policyPath = resolveDependencyPolicyPath(scriptDir, cwd, process.argv)
|
|
25
26
|
|
|
@@ -43,7 +44,6 @@ const policyErrors = [
|
|
|
43
44
|
collectPinViolations(`managedDependencies.${section}.`, deps),
|
|
44
45
|
),
|
|
45
46
|
...collectPinViolations('managedAnywhere.', policy.managedAnywhere ?? {}),
|
|
46
|
-
...collectPinViolations('overrides.', policy.overrides ?? {}),
|
|
47
47
|
]
|
|
48
48
|
|
|
49
49
|
if (policyErrors.length > 0) {
|
|
@@ -57,6 +57,31 @@ if (policyErrors.length > 0) {
|
|
|
57
57
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
|
|
58
58
|
const changes = []
|
|
59
59
|
|
|
60
|
+
function getOverridePackageName(overrideKey) {
|
|
61
|
+
const match = overrideKey.match(/^(@[^/]+\/[^@]+|[^@]+)@/)
|
|
62
|
+
return match ? match[1] : overrideKey
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getRelevantPolicyOverrides(policyOverrides) {
|
|
66
|
+
const entries = Object.entries(policyOverrides ?? {})
|
|
67
|
+
if (entries.length === 0) {
|
|
68
|
+
return {}
|
|
69
|
+
}
|
|
70
|
+
if (!fs.existsSync(lockfilePath)) {
|
|
71
|
+
return Object.fromEntries(entries)
|
|
72
|
+
}
|
|
73
|
+
const lockfile = fs.readFileSync(lockfilePath, 'utf8')
|
|
74
|
+
const relevant = {}
|
|
75
|
+
for (const [name, version] of entries) {
|
|
76
|
+
const pkgName = getOverridePackageName(name)
|
|
77
|
+
// Conservative: only apply overrides for packages present in lockfile.
|
|
78
|
+
if (lockfile.includes(`${pkgName}@`) || lockfile.includes(`/${pkgName}/`)) {
|
|
79
|
+
relevant[name] = version
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return relevant
|
|
83
|
+
}
|
|
84
|
+
|
|
60
85
|
// Bump versions for managed dependencies / devDependencies where the key already exists.
|
|
61
86
|
for (const [section, dependencies] of Object.entries(policy.managedDependencies ?? {})) {
|
|
62
87
|
pkg[section] ||= {}
|
|
@@ -104,7 +129,7 @@ pkg.pnpm ||= {}
|
|
|
104
129
|
pkg.pnpm.overrides ||= {}
|
|
105
130
|
|
|
106
131
|
// Apply policy-defined override versions when present in the policy.
|
|
107
|
-
for (const [name, version] of Object.entries(policy.overrides ?? {})) {
|
|
132
|
+
for (const [name, version] of Object.entries(getRelevantPolicyOverrides(policy.overrides ?? {}))) {
|
|
108
133
|
if (pkg.pnpm.overrides[name] !== version) {
|
|
109
134
|
changes.push(`pnpm.overrides.${name}: ${pkg.pnpm.overrides[name] ?? '(missing)'} -> ${version}`)
|
|
110
135
|
pkg.pnpm.overrides[name] = version
|