fg-devkit 1.3.4 → 1.4.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 CHANGED
@@ -70,6 +70,20 @@ 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
+ Upgrade policy versions interactively:
82
+
83
+ ```bash
84
+ pnpm exec fgd policy-upgrade
85
+ ```
86
+
73
87
  ## tsconfig in consuming repos
74
88
 
75
89
  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.2",
14
- "@tanstack/vue-query": "5.96.2",
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.17.23",
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.1",
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.13",
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.29",
64
+ "@vue/compiler-dom": "3.5.32",
65
65
  "@vue/eslint-config-standard": "8.0.1",
66
- "@vue/server-renderer": "3.5.29",
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,25 +71,25 @@
71
71
  "eslint-plugin-storybook": "10.1.10",
72
72
  "globals": "15.11.0",
73
73
  "jsdom": "26.1.0",
74
- "postcss": "8.5.6",
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.0.7",
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.2",
88
- "@vitejs/plugin-vue": "6.0.5",
89
- "axios": "1.15.0",
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.2",
92
+ "tailwindcss": "4.2.3",
93
93
  "vite": "7.3.2",
94
94
  "vue": "3.5.32"
95
95
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fg-devkit",
3
- "version": "1.3.4",
3
+ "version": "1.4.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,8 @@
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-upgrade.mjs": "./scripts/policy-upgrade.mjs",
31
33
  "./scripts/check-dependency-policy.mjs": "./scripts/check-dependency-policy.mjs",
32
34
  "./scripts/sync-dependency-policy.mjs": "./scripts/sync-dependency-policy.mjs",
33
35
  "./scripts/resolve-dependency-policy-path.mjs": "./scripts/resolve-dependency-policy-path.mjs",
@@ -70,6 +72,7 @@
70
72
  "eslint-plugin-prettier": "5.5.5",
71
73
  "eslint-plugin-vue": "10.8.0",
72
74
  "lefthook": "2.1.6",
75
+ "npm-check-updates": "21.0.3",
73
76
  "prettier": "3.8.3",
74
77
  "typescript": "5.9.3",
75
78
  "vue-eslint-parser": "10.4.0",
@@ -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
  {
@@ -5,6 +5,9 @@
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 policy-upgrade [policy-file] [ncu-args...]
8
11
  * pnpm exec fg-devkit sync-project-configs (VS Code + tooling sync; overwrites config files)
9
12
  * pnpm exec fg-devkit sync-tooling-configs (tooling sync only; overwrites config files)
10
13
  * pnpm exec fg-devkit sync-vscode-settings
@@ -38,6 +41,7 @@ import { createRequire } from 'node:module'
38
41
 
39
42
  const scriptDir = path.dirname(fileURLToPath(import.meta.url))
40
43
  const require = createRequire(import.meta.url)
44
+ const fgRoot = path.resolve(scriptDir, '..')
41
45
 
42
46
  /** Run another script in this package via Node; inherits stdio and returns the child exit code. */
43
47
  function runNode(scriptRelative, args = [], options = {}) {
@@ -94,6 +98,24 @@ function runPnpmInstall() {
94
98
  return r.status ?? 1
95
99
  }
96
100
 
101
+ /** Run pnpm audit in current repo and return normalized status info. */
102
+ function runPnpmAuditCurrentRepo() {
103
+ const r = spawnSync('pnpm', ['audit'], {
104
+ stdio: 'inherit',
105
+ cwd: process.cwd(),
106
+ env: process.env,
107
+ })
108
+ if (r.error) {
109
+ console.error('[fg-devkit]', r.error.message)
110
+ return { ok: false, hasVulns: false, exitCode: 1 }
111
+ }
112
+ const code = r.status ?? 1
113
+ if (code === 1) {
114
+ return { ok: true, hasVulns: true, exitCode: 0 }
115
+ }
116
+ return { ok: code === 0, hasVulns: false, exitCode: code }
117
+ }
118
+
97
119
  /** Run Lefthook from fg-devkit bundled dependency set. */
98
120
  function runBundledLefthook(args = []) {
99
121
  let lefthookBin
@@ -153,6 +175,124 @@ function runFullConsumerSetup() {
153
175
  return runPnpmInstall()
154
176
  }
155
177
 
178
+ function stageFiles(paths = []) {
179
+ const existing = paths.filter((p) => fs.existsSync(path.join(process.cwd(), p)))
180
+ if (existing.length === 0) return 0
181
+ const r = spawnSync('git', ['add', '--', ...existing], {
182
+ stdio: 'inherit',
183
+ cwd: process.cwd(),
184
+ env: process.env,
185
+ })
186
+ if (r.error) {
187
+ console.error('[fg-devkit]', r.error.message)
188
+ return 1
189
+ }
190
+ return r.status ?? 1
191
+ }
192
+
193
+ function commitStaged(message) {
194
+ const r = spawnSync('git', ['commit', '-m', message], {
195
+ stdio: 'inherit',
196
+ cwd: process.cwd(),
197
+ env: process.env,
198
+ })
199
+ if (r.error) {
200
+ console.error('[fg-devkit]', r.error.message)
201
+ return 1
202
+ }
203
+ return r.status ?? 1
204
+ }
205
+
206
+ function collectPolicyVersionChanges(beforePolicy, afterPolicy) {
207
+ const before = {
208
+ ...(beforePolicy.managedDependencies?.dependencies ?? {}),
209
+ ...(beforePolicy.managedDependencies?.devDependencies ?? {}),
210
+ ...(beforePolicy.managedAnywhere ?? {}),
211
+ }
212
+ const after = {
213
+ ...(afterPolicy.managedDependencies?.dependencies ?? {}),
214
+ ...(afterPolicy.managedDependencies?.devDependencies ?? {}),
215
+ ...(afterPolicy.managedAnywhere ?? {}),
216
+ }
217
+ const changed = []
218
+ for (const [name, newVersion] of Object.entries(after)) {
219
+ const prev = before[name]
220
+ if (prev !== undefined && prev !== newVersion) {
221
+ changed.push({ name, from: prev, to: newVersion })
222
+ }
223
+ }
224
+ return changed
225
+ }
226
+
227
+ /** Maintainer flow: run policy-upgrade, stage policy file, then auto-commit dep(package): bump to version. */
228
+ function runMaintainerPolicyUpgradeAndCommit() {
229
+ const policyPath = path.join(process.cwd(), 'configs', 'dependency-policy.vue-workspace.json')
230
+ if (!fs.existsSync(policyPath)) {
231
+ console.error('[fg-devkit] Expected policy at configs/dependency-policy.vue-workspace.json')
232
+ return 1
233
+ }
234
+
235
+ let beforePolicy
236
+ try {
237
+ beforePolicy = JSON.parse(fs.readFileSync(policyPath, 'utf8'))
238
+ } catch (err) {
239
+ console.error(`[fg-devkit] Failed to read policy before upgrade: ${err.message}`)
240
+ return 1
241
+ }
242
+
243
+ const upgradeCode = runNode('policy-upgrade.mjs')
244
+ if (upgradeCode !== 0) return upgradeCode
245
+
246
+ let afterPolicy
247
+ try {
248
+ afterPolicy = JSON.parse(fs.readFileSync(policyPath, 'utf8'))
249
+ } catch (err) {
250
+ console.error(`[fg-devkit] Failed to read policy after upgrade: ${err.message}`)
251
+ return 1
252
+ }
253
+
254
+ const changed = collectPolicyVersionChanges(beforePolicy, afterPolicy)
255
+ if (changed.length === 0) {
256
+ console.log('[fg-devkit] No policy package version changes detected; skipping stage and commit flow.')
257
+ return 0
258
+ }
259
+ if (changed.length > 1) {
260
+ console.log('[fg-devkit] Multiple package changes detected in policy-upgrade:')
261
+ for (const item of changed) {
262
+ console.log(` - ${item.name}: ${item.from} -> ${item.to}`)
263
+ }
264
+ console.log('[fg-devkit] Please commit manually (or rerun and select a single package).')
265
+ return 1
266
+ }
267
+
268
+ const [only] = changed
269
+ const stageCode = stageFiles(['configs/dependency-policy.vue-workspace.json'])
270
+ if (stageCode !== 0) return stageCode
271
+
272
+ const commitMessage = `dep(${only.name}): bump to ${only.to}`
273
+ console.log(`[fg-devkit] Staged policy change for ${only.name} -> ${only.to}`)
274
+ console.log(`[fg-devkit] Auto-committing: ${commitMessage}`)
275
+ return commitStaged(commitMessage)
276
+ }
277
+
278
+ /** Maintainer flow: run both policy vuln check and fg-devkit root audit. */
279
+ function runMaintainerAuditAll() {
280
+ console.log('[fg-devkit] Running policy vulnerability scan...')
281
+ const policyCode = runNode('policy-vulns.mjs')
282
+ if (policyCode !== 0) return policyCode
283
+
284
+ console.log('[fg-devkit] Running fg-devkit repository audit...')
285
+ const repoAudit = runPnpmAuditCurrentRepo()
286
+ if (!repoAudit.ok) return repoAudit.exitCode
287
+
288
+ if (repoAudit.hasVulns) {
289
+ console.log('[fg-devkit] Completed: vulnerabilities were reported in fg-devkit repository audit.')
290
+ } else {
291
+ console.log('[fg-devkit] Completed: no vulnerabilities reported in fg-devkit repository audit.')
292
+ }
293
+ return 0
294
+ }
295
+
156
296
  /** Prompt release type in CLI before invoking release.sh to avoid a "stuck" terminal feeling. */
157
297
  async function promptReleaseType(rl) {
158
298
  console.log('\nSelect release type:')
@@ -293,6 +433,9 @@ function runBundledTypeScript(args = []) {
293
433
  const DIRECT_COMMANDS = {
294
434
  'check-deps': 'check-dependency-policy.mjs',
295
435
  'sync-deps': 'sync-dependency-policy.mjs',
436
+ 'policy-outdated': 'policy-outdated.mjs',
437
+ 'policy-vulns': 'policy-vulns.mjs',
438
+ 'policy-upgrade': 'policy-upgrade.mjs',
296
439
  'sync-project-configs': 'sync-project-configs.mjs',
297
440
  'sync-tooling-configs': 'sync-tooling-configs.mjs',
298
441
  'sync-vscode-settings': 'sync-vscode-settings.mjs',
@@ -308,6 +451,10 @@ function printHelp() {
308
451
  Commands:
309
452
  check-deps [policy-file] Validate package.json + pins against policy
310
453
  sync-deps [policy-file] Align declared versions to policy (existing keys only)
454
+ policy-outdated [policy-file] Show outdated policy packages via pnpm outdated
455
+ policy-vulns [policy-file] Check policy packages for vulnerabilities via pnpm audit
456
+ policy-upgrade [policy-file] [ncu-args...]
457
+ Upgrade policy packages via npm-check-updates (default interactive)
311
458
  sync-project-configs VS Code + tooling sync (overwrites config files)
312
459
  sync-tooling-configs Tooling sync only (overwrites config files)
313
460
  sync-vscode-settings Copy shared VS Code settings into .vscode/settings.json
@@ -381,12 +528,63 @@ const MENU = [
381
528
  },
382
529
  ]
383
530
 
531
+ const MAINTAINER_MENU = [
532
+ {
533
+ key: 'm1',
534
+ label: 'Maintainer: Show outdated dependency policy packages',
535
+ run: () => runNode('policy-outdated.mjs'),
536
+ },
537
+ {
538
+ key: 'm2',
539
+ label: 'Maintainer: Upgrade dependency policy (interactive)',
540
+ run: () => runNode('policy-upgrade.mjs'),
541
+ },
542
+ {
543
+ key: 'm3',
544
+ label: 'Maintainer: Refresh dependency policy from repos',
545
+ run: () => runNode('refresh-dependency-policy-from-repos.mjs'),
546
+ },
547
+ {
548
+ key: 'm4',
549
+ label: 'Maintainer: Upgrade one policy package, stage, and auto-commit',
550
+ run: () => runMaintainerPolicyUpgradeAndCommit(),
551
+ },
552
+ {
553
+ key: 'm5',
554
+ label: 'Maintainer: Check policy vulnerabilities (audit)',
555
+ run: () => runNode('policy-vulns.mjs'),
556
+ },
557
+ {
558
+ key: 'm6',
559
+ label: 'Maintainer: Check vulnerabilities (policy + fg-devkit repo)',
560
+ run: () => runMaintainerAuditAll(),
561
+ },
562
+ ]
563
+
564
+ function isFgDevkitMaintainerContext() {
565
+ if (path.resolve(process.cwd()) !== fgRoot) {
566
+ return false
567
+ }
568
+ const pkgPath = path.join(process.cwd(), 'package.json')
569
+ if (!fs.existsSync(pkgPath)) {
570
+ return false
571
+ }
572
+ try {
573
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
574
+ return pkg?.name === 'fg-devkit'
575
+ } catch {
576
+ return false
577
+ }
578
+ }
579
+
384
580
  async function runInteractiveMenu() {
385
581
  const rl = readline.createInterface({ input, output })
582
+ const isMaintainer = isFgDevkitMaintainerContext()
583
+ const menu = isMaintainer ? [...MENU, ...MAINTAINER_MENU] : MENU
386
584
 
387
585
  console.log(`\nfg-devkit — choose a task (cwd: ${process.cwd()})\n`)
388
586
 
389
- for (const item of MENU) {
587
+ for (const item of menu) {
390
588
  console.log(` ${item.key}) ${item.label}`)
391
589
  }
392
590
  console.log(' 0) Exit\n')
@@ -398,7 +596,7 @@ async function runInteractiveMenu() {
398
596
  return 0
399
597
  }
400
598
 
401
- const item = MENU.find((m) => m.key === choice)
599
+ const item = menu.find((m) => m.key === choice)
402
600
  if (!item) {
403
601
  console.error('[fg-devkit] Unknown option. Run again and choose a number from the list.')
404
602
  rl.close()
@@ -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,78 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Checks vulnerabilities for dependency policy entries by projecting policy data
4
+ * into a 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
+ 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-vulns-'))
34
+ const pkgPath = path.join(tmpDir, 'package.json')
35
+ fs.writeFileSync(
36
+ pkgPath,
37
+ `${JSON.stringify(
38
+ {
39
+ name: 'fgd-policy-vulns-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-vulns] policy=${policyPath}`)
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
+ // pnpm audit returns exit code 1 when vulnerabilities are found.
75
+ if ((audit.status ?? 1) === 1) {
76
+ process.exit(0)
77
+ }
78
+ process.exit(audit.status ?? 1)