fg-devkit 1.22.0 → 1.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +30 -7
  2. package/configs/dependency-policy.vue-workspace.json +28 -28
  3. package/package.json +12 -85
  4. package/scaffold/vue/package.json +13 -13
  5. package/scripts/audit-override-commit.mjs +167 -24
  6. package/scripts/bump-node-toolchain.mjs +8 -31
  7. package/scripts/bump-pnpm-toolchain.mjs +8 -29
  8. package/scripts/check-dependency-policy.mjs +10 -35
  9. package/scripts/fg-devkit.mjs +290 -337
  10. package/scripts/fleet-bump-fg-devkit.mjs +4 -5
  11. package/scripts/fleet-git-commit-push-scoped.mjs +6 -26
  12. package/scripts/fleet-git-discard-scoped.mjs +5 -25
  13. package/scripts/fleet-git-pull-scoped.mjs +5 -25
  14. package/scripts/fleet-vulns.mjs +4 -9
  15. package/scripts/generate-release-notes.mjs +1 -1
  16. package/scripts/{audit-fix-plan.mjs → lib/audit-fix-plan.mjs} +77 -4
  17. package/scripts/{audit-fix-plan.test.mjs → lib/audit-fix-plan.test.mjs} +50 -2
  18. package/scripts/lib/common.mjs +95 -0
  19. package/scripts/{dependency-policy-toolchain.mjs → lib/dependency-policy-toolchain.mjs} +3 -35
  20. package/scripts/{git-commit-push-fleet-common.mjs → lib/git-commit-push-fleet-common.mjs} +1 -1
  21. package/scripts/{git-discard-fleet-common.mjs → lib/git-discard-fleet-common.mjs} +1 -1
  22. package/scripts/{git-pull-fleet-common.mjs → lib/git-pull-fleet-common.mjs} +1 -1
  23. package/scripts/{override-changes.mjs → lib/override-changes.mjs} +20 -0
  24. package/scripts/{piwik-dimensions.test.mjs → lib/piwik-dimensions.test.mjs} +0 -5
  25. package/scripts/{release-notes-common.mjs → lib/release-notes-common.mjs} +5 -7
  26. package/scripts/{resolve-dependency-policy-path.mjs → lib/resolve-dependency-policy-path.mjs} +4 -3
  27. package/scripts/piwik.mjs +6 -6
  28. package/scripts/poeditor-download.mjs +1 -1
  29. package/scripts/poeditor-upload.mjs +1 -1
  30. package/scripts/policy-outdated.mjs +3 -8
  31. package/scripts/policy-upgrade-commit.mjs +5 -26
  32. package/scripts/policy-upgrade.mjs +6 -30
  33. package/scripts/policy-vulns.mjs +3 -8
  34. package/scripts/prune-overrides.mjs +5 -16
  35. package/scripts/prune-release-age-exclude.mjs +1 -1
  36. package/scripts/refresh-dependency-policy-from-repos.mjs +5 -7
  37. package/scripts/release.sh +2 -1
  38. package/scripts/scaffold-vue.mjs +1 -3
  39. package/scripts/setup-ai-rules.mjs +135 -0
  40. package/scripts/sync-consumer-lefthook.mjs +7 -24
  41. package/scripts/sync-dependency-check-suppressions-commit.mjs +7 -46
  42. package/scripts/sync-dependency-policy-commit.mjs +6 -38
  43. package/scripts/sync-dependency-policy.mjs +9 -35
  44. package/scripts/sync-node-toolchain.mjs +5 -8
  45. package/scripts/sync-pnpm-toolchain.mjs +5 -8
  46. package/scripts/sync-scaffold-vue-deps.mjs +6 -7
  47. package/scripts/sync-tooling-configs.mjs +8 -23
  48. package/scripts/sync-vite-plugin-vue-inspector.mjs +7 -39
  49. package/scripts/verify-release-notes.mjs +1 -1
  50. package/.cursor/rules/core-coding-standards.mdc +0 -291
  51. package/scaffold/vue/.gitignore +0 -13
  52. package/scripts/cz/.cz-config.cjs +0 -29
  53. package/scripts/fleet-git-commit-push.mjs +0 -75
  54. package/scripts/fleet-git-discard.mjs +0 -66
  55. package/scripts/fleet-git-pull.mjs +0 -65
  56. package/scripts/sync-core-coding-standards.mjs +0 -77
  57. package/scripts/sync-peers-from-dev.mjs +0 -31
  58. package/scripts/vuln-commit.mjs +0 -142
  59. /package/configs/{fallowrc.sync-stub.json → fallowrc.json} +0 -0
  60. /package/scripts/{dependency-policy-pins.mjs → lib/dependency-policy-pins.mjs} +0 -0
  61. /package/scripts/{fleet-project-repos.mjs → lib/fleet-project-repos.mjs} +0 -0
  62. /package/scripts/{fleet-repo-picker.mjs → lib/fleet-repo-picker.mjs} +0 -0
  63. /package/scripts/{fleet-vue-repos.mjs → lib/fleet-vue-repos.mjs} +0 -0
  64. /package/scripts/{git-fleet-common.mjs → lib/git-fleet-common.mjs} +0 -0
  65. /package/scripts/{node-toolchain-sync.mjs → lib/node-toolchain-sync.mjs} +0 -0
  66. /package/scripts/{piwik-common.mjs → lib/piwik-common.mjs} +0 -0
  67. /package/scripts/{piwik-dimensions.mjs → lib/piwik-dimensions.mjs} +0 -0
  68. /package/scripts/{pnpm-toolchain-sync.mjs → lib/pnpm-toolchain-sync.mjs} +0 -0
  69. /package/scripts/{poeditor-common.mjs → lib/poeditor-common.mjs} +0 -0
@@ -23,7 +23,6 @@
23
23
  * pnpm exec fg-devkit sync-tooling-configs (tooling sync only; overwrites config files)
24
24
  * pnpm exec fg-devkit sync-dependency-check-suppressions-commit [--all]
25
25
  * pnpm exec fg-devkit sync-vscode-settings
26
- * pnpm exec fg-devkit sync-core-coding-standards
27
26
  * pnpm exec fg-devkit sync-consumer-scripts
28
27
  * pnpm exec fg-devkit setup-lefthook
29
28
  * pnpm exec fg-devkit full-consumer-setup
@@ -31,8 +30,7 @@
31
30
  * pnpm exec fg-devkit generate-release-notes [--tag=vX.Y.Z]
32
31
  * pnpm exec fg-devkit verify-release-notes
33
32
  * pnpm exec fg-devkit sync-release-notes-config
34
- * pnpm exec fg-devkit commit [cz-args...] (czg/cz-git with fg-devkit bundled config)
35
- * pnpm exec fg-devkit commit-vuln (staged package.json version bumps → vuln() commit)
33
+ * pnpm exec fg-devkit commit [git-commit-args...] (conventional commit prompts, then git commit)
36
34
  * pnpm exec fg-devkit lint [eslint-args...] (run ESLint with fg-devkit bundled packages)
37
35
  * pnpm exec fg-devkit lint-fix [eslint-args...] (same as lint plus --fix)
38
36
  * pnpm exec fg-devkit format [prettier-args...] (run Prettier with fg-devkit bundled package)
@@ -57,10 +55,11 @@ import fs from 'node:fs'
57
55
  import path from 'node:path'
58
56
  import { fileURLToPath } from 'node:url'
59
57
  import { createRequire } from 'node:module'
58
+ import { styleText } from 'node:util'
59
+ import { fgDevkitRoot } from './lib/common.mjs'
60
60
 
61
61
  const scriptDir = path.dirname(fileURLToPath(import.meta.url))
62
62
  const require = createRequire(import.meta.url)
63
- const fgRoot = path.resolve(scriptDir, '..')
64
63
 
65
64
  /** Run another script in this package via Node; inherits stdio and returns the child exit code. */
66
65
  function runNode(scriptRelative, args = [], options = {}) {
@@ -81,6 +80,18 @@ function runNode(scriptRelative, args = [], options = {}) {
81
80
  return r.status ?? 1
82
81
  }
83
82
 
83
+ /**
84
+ * Menu label with dimmed tags: `Upgrade packages [interactive, auto-commit]`.
85
+ * interactive: the action stops and asks for input while it runs. autoCommit: it commits on its own.
86
+ */
87
+ function tagged(name, { interactive = false, autoCommit = false }) {
88
+ const tags = [interactive && 'interactive', autoCommit && 'auto-commit'].filter(Boolean)
89
+ return `${name} ${styleText('dim', `[${tags.join(', ')}]`)}`
90
+ }
91
+
92
+ const autoCommit = (name) => tagged(name, { autoCommit: true })
93
+ const interactive = (name) => tagged(name, { interactive: true })
94
+
84
95
  /**
85
96
  * Run release.sh (pnpm version bump, git push, gh release). Uses consumer cwd.
86
97
  * Args: [release|candidate] [patch|minor|major] or [patch|minor|major] [release|candidate].
@@ -149,10 +160,11 @@ function runSetupLefthook() {
149
160
  /**
150
161
  * Full consumer setup flow:
151
162
  * - Sync required consumer scripts for fgd commands
152
- * - Setup lefthook and migrate from husky/lint-staged
163
+ * - Setup lefthook
153
164
  * - Step 3: sync project configs
154
165
  * - Step 2: sync dependency policy
155
166
  * - Then run pnpm install
167
+ * - Set up the fg-ai-rules Claude Code plugins (a failure only warns, e.g. without Claude Code)
156
168
  */
157
169
  function runFullConsumerSetup() {
158
170
  console.log('[fg-devkit] Running full consumer sync and setup')
@@ -172,7 +184,13 @@ function runFullConsumerSetup() {
172
184
  const step2 = runNode('sync-dependency-policy.mjs')
173
185
  if (step2 !== 0) return step2
174
186
 
175
- return runPnpmInstall()
187
+ const installStep = runPnpmInstall()
188
+ if (installStep !== 0) return installStep
189
+
190
+ if (runNode('setup-ai-rules.mjs') !== 0) {
191
+ console.warn('[fg-devkit] AI rules were not set up. Run "fgd setup-ai-rules" when Claude Code is available.')
192
+ }
193
+ return 0
176
194
  }
177
195
 
178
196
  /** Maintainer flow: run full repo vulnerability audit in fg-devkit root. */
@@ -184,7 +202,7 @@ function runMaintainerAuditAll() {
184
202
  * Prompt for a conventional commit message with fg-devkit's legacy question flow,
185
203
  * then run `git commit` directly.
186
204
  */
187
- async function runCzg(args = [], extraEnv = {}) {
205
+ async function runCommit(args = [], extraEnv = {}) {
188
206
  let selectPrompt
189
207
  let inputPrompt
190
208
  try {
@@ -395,12 +413,18 @@ function runBundledTypeScript(args = []) {
395
413
  return r.status ?? 1
396
414
  }
397
415
 
398
- /** Run Fallow from fg-devkit's bundled dependency set. */
416
+ const FALLOW_CONFIG_FILES = ['.fallowrc.json', '.fallowrc.jsonc', 'fallow.toml', '.fallow.toml']
417
+
418
+ /**
419
+ * Run Fallow from fg-devkit's bundled dependency set. Uses fg-devkit's configs/fallowrc.json
420
+ * unless the repo has its own Fallow config or the caller passes -c/--config, so config
421
+ * fixes reach every repo on the next fg-devkit bump.
422
+ */
399
423
  function runBundledFallow(args = []) {
400
424
  let fallowBin
401
425
  try {
402
426
  const fallowPkgJson = require.resolve('fallow/package.json', {
403
- paths: [fgRoot, process.cwd()],
427
+ paths: [fgDevkitRoot, process.cwd()],
404
428
  })
405
429
  const fallowPkgRoot = path.dirname(fallowPkgJson)
406
430
  fallowBin = path.join(fallowPkgRoot, 'bin', 'fallow')
@@ -413,7 +437,12 @@ function runBundledFallow(args = []) {
413
437
  return 1
414
438
  }
415
439
 
416
- const r = spawnSync(process.execPath, [fallowBin, ...args], {
440
+ const hasConfig =
441
+ args.some((arg) => arg === '-c' || arg === '--config' || arg.startsWith('--config=')) ||
442
+ FALLOW_CONFIG_FILES.some((name) => fs.existsSync(path.join(process.cwd(), name)))
443
+ const configArgs = hasConfig ? [] : ['--config', path.join(fgDevkitRoot, 'configs', 'fallowrc.json')]
444
+
445
+ const r = spawnSync(process.execPath, [fallowBin, ...args, ...configArgs], {
417
446
  stdio: 'inherit',
418
447
  cwd: process.cwd(),
419
448
  env: process.env,
@@ -427,7 +456,7 @@ function runBundledFallow(args = []) {
427
456
  }
428
457
 
429
458
  function syncFileFromTemplate(relativeSourcePath, targetPath) {
430
- const sourcePath = path.join(fgRoot, relativeSourcePath)
459
+ const sourcePath = path.join(fgDevkitRoot, relativeSourcePath)
431
460
  if (!fs.existsSync(sourcePath)) {
432
461
  console.error(`[fg-devkit] Missing template: ${sourcePath}`)
433
462
  return 1
@@ -447,9 +476,7 @@ function runSyncConfigTarget(target) {
447
476
  if (target === 'eslint') return syncFileFromTemplate('configs/eslint.sync-stub.js', 'eslint.config.js')
448
477
  if (target === 'prettier') return syncFileFromTemplate('configs/prettier.sync-stub.js', 'prettier.config.js')
449
478
  if (target === 'tsconfig') return syncFileFromTemplate('configs/tsconfig.app.sync.json', 'tsconfig.json')
450
- if (target === 'fallow') return syncFileFromTemplate('configs/fallowrc.sync-stub.json', '.fallowrc.json')
451
479
  if (target === 'dependency-check') return runNode('sync-dependency-check-suppressions-commit.mjs')
452
- if (target === 'cursor') return runNode('sync-core-coding-standards.mjs')
453
480
  if (target === 'vue-inspector') return runNode('sync-vite-plugin-vue-inspector.mjs')
454
481
  if (target === 'all') return runNode('sync-project-configs.mjs')
455
482
  console.error(`[fg-devkit] Unknown config sync target: ${target}`)
@@ -470,11 +497,8 @@ const DIRECT_COMMANDS = {
470
497
  'sync-node-toolchain': 'sync-node-toolchain.mjs',
471
498
  'sync-pnpm-toolchain': 'sync-pnpm-toolchain.mjs',
472
499
  'bump-node-toolchain': 'bump-node-toolchain.mjs',
473
- 'fleet-git-pull': 'fleet-git-pull.mjs',
474
500
  'fleet-git-pull-scoped': 'fleet-git-pull-scoped.mjs',
475
- 'fleet-git-commit-push': 'fleet-git-commit-push.mjs',
476
501
  'fleet-git-commit-push-scoped': 'fleet-git-commit-push-scoped.mjs',
477
- 'fleet-git-discard': 'fleet-git-discard.mjs',
478
502
  'fleet-git-discard-scoped': 'fleet-git-discard-scoped.mjs',
479
503
  'policy-upgrade': 'policy-upgrade.mjs',
480
504
  'policy-upgrade-commit': 'policy-upgrade-commit.mjs',
@@ -485,9 +509,9 @@ const DIRECT_COMMANDS = {
485
509
  'sync-tooling-configs': 'sync-tooling-configs.mjs',
486
510
  'sync-dependency-check-suppressions-commit': 'sync-dependency-check-suppressions-commit.mjs',
487
511
  'sync-vscode-settings': 'sync-vscode-settings.mjs',
488
- 'sync-core-coding-standards': 'sync-core-coding-standards.mjs',
489
512
  'sync-vite-plugin-vue-inspector': 'sync-vite-plugin-vue-inspector.mjs',
490
513
  'sync-consumer-scripts': 'sync-consumer-scripts.mjs',
514
+ 'setup-ai-rules': 'setup-ai-rules.mjs',
491
515
  'sync-release-notes-config': 'sync-release-notes-config.mjs',
492
516
  'generate-release-notes': 'generate-release-notes.mjs',
493
517
  'verify-release-notes': 'verify-release-notes.mjs',
@@ -524,18 +548,12 @@ Commands:
524
548
  bump-node-toolchain [version] [projects-root] [options]
525
549
  Maintainer: set policy.node and sync Node toolchain across fleet
526
550
  Options: --dry-run, --skip-fleet, --no-engines, --engines=RANGE, --pick, --all, --only=a,b
527
- fleet-git-pull [projects-root] [--dry-run]
528
- Maintainer: git pull (current branch) for all git repos under projects root
529
551
  fleet-git-pull-scoped [projects-root] [options]
530
552
  Maintainer: git pull (current branch) for chosen repositories
531
553
  Options: --dry-run, --fg-devkit-only, --pick, --all, --only=a,b
532
- fleet-git-commit-push --message="..." [projects-root] [--dry-run]
533
- Maintainer: stage, commit, and push all git repos under projects root
534
554
  fleet-git-commit-push-scoped --message="..." [projects-root] [options]
535
555
  Maintainer: stage, commit, and push chosen repositories
536
556
  Options: --dry-run, --fg-devkit-only, --pick, --all, --only=a,b
537
- fleet-git-discard [projects-root] [--dry-run]
538
- Maintainer: discard tracked git changes in all git repos under projects root
539
557
  fleet-git-discard-scoped [projects-root] [options]
540
558
  Maintainer: discard tracked git changes in chosen repositories
541
559
  Options: --dry-run, --fg-devkit-only, --pick, --all, --only=a,b
@@ -546,6 +564,7 @@ Commands:
546
564
  --prune-overrides also drops unused workspace overrides in a second commit
547
565
  audit-override-commit [--no-verify] [--no-commit]
548
566
  Fix pnpm audit findings: refresh lockfile first, override only when a range blocks the fix, verify, auto-commit
567
+ Asks before forcing the rest; --allow-major, --allow-young, --bump-direct say yes without asking
549
568
  prune-overrides [--fix] [--commit]
550
569
  Report unused workspace overrides; --fix removes them; --commit also auto-commits
551
570
  prune-release-age-exclude [--fix] [--commit]
@@ -555,11 +574,11 @@ Commands:
555
574
  sync-dependency-check-suppressions-commit [--all]
556
575
  Write the dependency-check suppressions that match pnpm audit findings and auto-commit (--all: copy every entry)
557
576
  sync-vscode-settings Copy shared VS Code settings into .vscode/settings.json
558
- sync-core-coding-standards Copy .cursor core coding standards with consumer package name
559
577
  sync-vite-plugin-vue-inspector Add/pin vite-plugin-vue-inspector, wire vite.config, install, commit
560
578
  sync-consumer-scripts Enforce standard fgd scripts in consumer package.json
561
- setup-lefthook Setup lefthook and migrate from husky/lint-staged
562
- full-consumer-setup Run: scripts -> lefthook -> step 3 -> step 2 -> pnpm install
579
+ setup-lefthook Write lefthook.yml and install the git hooks
580
+ setup-ai-rules Enable the fg-ai-rules Claude Code plugins in this repo and install them
581
+ full-consumer-setup Run: scripts -> lefthook -> step 3 -> step 2 -> pnpm install -> AI rules
563
582
  create-release [release|candidate] [patch|minor|major] [--skip-release-notes]
564
583
  Stable release or candidate (X.Y.Z-rc.N); omit = interactive menu
565
584
  (alias: createRelease)
@@ -567,8 +586,7 @@ Commands:
567
586
  Generate CHANGELOG.md and user changelog via git-cliff
568
587
  verify-release-notes Interactive review step before committing release notes
569
588
  sync-release-notes-config Add CHANGELOG.user.md and generate:release-notes script when missing
570
- commit [args...] czg/cz-git with fg-devkit bundled config
571
- commit-vuln Commit staged dependency bumps as vuln() (alias: vuln-commit)
589
+ commit [args...] Conventional commit prompts, then git commit [args...]
572
590
  lint [eslint-args...] Run ESLint using fg-devkit bundled ESLint stack
573
591
  lint-fix [eslint-args...] Run ESLint with --fix using fg-devkit bundled ESLint stack
574
592
  format [prettier-args...] Run Prettier using fg-devkit bundled package
@@ -592,7 +610,7 @@ Options:
592
610
  }
593
611
 
594
612
  function isFgDevkitMaintainerContext() {
595
- if (path.resolve(process.cwd()) !== fgRoot) {
613
+ if (path.resolve(process.cwd()) !== fgDevkitRoot) {
596
614
  return false
597
615
  }
598
616
  const pkgPath = path.join(process.cwd(), 'package.json')
@@ -644,52 +662,38 @@ async function runInteractiveMenu() {
644
662
  const isMaintainer = isFgDevkitMaintainerContext()
645
663
  const groups = [
646
664
  {
647
- name: '1) Create commit',
665
+ name: interactive('Commit'),
648
666
  value: 'commit',
667
+ description: 'Write a conventional commit message step by step and commit the staged changes',
649
668
  },
669
+ { name: 'Release', value: 'release', description: 'Bump the version, tag it and publish a GitHub release' },
670
+ { name: 'Lint & typecheck', value: 'lint-typecheck', description: 'Run ESLint and TypeScript' },
671
+ { name: 'Translations', value: 'poeditor', description: 'Download or upload translations in POEditor' },
650
672
  {
651
- name: '2) Create release',
652
- value: 'release',
653
- },
654
- {
655
- name: '3) Lint and typecheck',
656
- value: 'lint-typecheck',
657
- },
658
- {
659
- name: '4) Poeditor',
660
- value: 'poeditor',
661
- },
662
- {
663
- name: '5) Dependecy policy',
673
+ name: 'Dependency policy',
664
674
  value: 'dependency-policy',
675
+ description: 'Keep package.json in line with the shared policy and fix vulnerabilities',
665
676
  },
666
677
  {
667
- name: '6) Sync configs',
678
+ name: 'Sync configs',
668
679
  value: 'sync-configs',
680
+ description: 'Overwrite shared config files with the versions in fg-devkit',
669
681
  },
682
+ { name: 'Setup', value: 'setup', description: 'Git hooks and first-time setup of a repo' },
670
683
  {
671
- name: '7) Setup',
672
- value: 'setup',
673
- },
674
- {
675
- name: '8) Fallow analysis',
676
- value: 'fallow-analysis',
677
- },
678
- {
679
- name: '9) Piwik',
680
- value: 'piwik',
684
+ name: 'Dead code (Fallow)',
685
+ value: 'fallow-dead-code',
686
+ description: 'Find unused files, exports and dependencies',
681
687
  },
688
+ { name: 'Piwik', value: 'piwik', description: 'Manage Piwik PRO custom dimensions' },
682
689
  ]
683
- if (isMaintainer) {
684
- groups.push({
685
- name: '10) Maintainer',
690
+ if (isMaintainer) groups.push({
691
+ name: 'Maintainer',
686
692
  value: 'maintainer',
693
+ description: 'Dependency policy, toolchain and git across all repos',
687
694
  })
688
- }
689
- groups.push({
690
- name: isMaintainer ? '11) Exit' : '10) Exit',
691
- value: 'exit',
692
- })
695
+ groups.push({ name: 'Exit', value: 'exit' })
696
+ for (const [index, group] of groups.entries()) group.name = `${index + 1}) ${group.name}`
693
697
 
694
698
  while (true) {
695
699
  const group = await selectPrompt({
@@ -700,26 +704,15 @@ async function runInteractiveMenu() {
700
704
  if (group === 'exit') return 0
701
705
 
702
706
  if (group === 'commit') {
703
- const choice = await selectPrompt({
704
- message: 'Create commit',
705
- choices: [
706
- { name: 'Commit', value: 'commit' },
707
- { name: 'Commit vulnerability', value: 'commit-vuln' },
708
- { name: 'Cancel', value: 'cancel' },
709
- ],
710
- default: 'commit',
711
- loop: false,
712
- })
713
- if (choice === 'cancel') continue
714
- return runSelectedAction(() => (choice === 'commit' ? runCzg() : runNode('vuln-commit.mjs')))
707
+ return runSelectedAction(() => runCommit())
715
708
  }
716
709
 
717
710
  if (group === 'release') {
718
711
  const releaseKind = await selectPrompt({
719
- message: 'Create release',
712
+ message: 'Release',
720
713
  choices: [
721
- { name: 'Release', value: 'release' },
722
- { name: 'Candidate', value: 'candidate' },
714
+ { name: 'Release', value: 'release', description: 'Tag the next version and publish a GitHub release' },
715
+ { name: 'Release candidate', value: 'candidate', description: 'Tag a prerelease such as 2.8.0-rc.1' },
723
716
  { name: 'Cancel', value: 'cancel' },
724
717
  ],
725
718
  default: 'release',
@@ -728,10 +721,14 @@ async function runInteractiveMenu() {
728
721
  if (releaseKind === 'cancel') continue
729
722
 
730
723
  const releaseMode = await selectPrompt({
731
- message: 'Create release',
724
+ message: 'Release notes',
732
725
  choices: [
733
- { name: 'Release', value: 'without-notes' },
734
- { name: 'Release + release notes', value: 'with-notes' },
726
+ { name: 'Without notes', value: 'without-notes', description: 'GitHub generates the release notes' },
727
+ {
728
+ name: interactive('With notes'),
729
+ value: 'with-notes',
730
+ description: 'Generate CHANGELOG.md and CHANGELOG.user.md with git-cliff and review them before release',
731
+ },
735
732
  { name: 'Cancel', value: 'cancel' },
736
733
  ],
737
734
  default: 'without-notes',
@@ -740,11 +737,11 @@ async function runInteractiveMenu() {
740
737
  if (releaseMode === 'cancel') continue
741
738
 
742
739
  const bumpChoice = await selectPrompt({
743
- message: releaseKind === 'candidate' ? 'Candidate' : 'Release',
740
+ message: releaseKind === 'candidate' ? 'Release candidate' : 'Release',
744
741
  choices: [
745
- { name: 'Patch', value: 'patch' },
746
- { name: 'Minor', value: 'minor' },
747
- { name: 'Major', value: 'major' },
742
+ { name: 'Patch', value: 'patch', description: 'Bug fixes only: 1.2.3 → 1.2.4' },
743
+ { name: 'Minor', value: 'minor', description: 'New features, backwards compatible: 1.2.3 → 1.3.0' },
744
+ { name: 'Major', value: 'major', description: 'Breaking changes: 1.2.3 → 2.0.0' },
748
745
  { name: 'Cancel', value: 'cancel' },
749
746
  ],
750
747
  default: 'patch',
@@ -764,11 +761,11 @@ async function runInteractiveMenu() {
764
761
 
765
762
  if (group === 'lint-typecheck') {
766
763
  const choice = await selectPrompt({
767
- message: 'Lint and typecheck',
764
+ message: 'Lint & typecheck',
768
765
  choices: [
769
- { name: 'Lint and typecheck', value: 'both' },
770
- { name: 'Lint', value: 'lint' },
771
- { name: 'Typecheck', value: 'typecheck' },
766
+ { name: 'Lint & typecheck', value: 'both', description: 'ESLint on the whole repo, then tsc --noEmit' },
767
+ { name: 'Lint', value: 'lint', description: 'ESLint on the whole repo' },
768
+ { name: 'Typecheck', value: 'typecheck', description: 'tsc --noEmit with tsconfig.json' },
772
769
  { name: 'Cancel', value: 'cancel' },
773
770
  ],
774
771
  default: 'both',
@@ -786,10 +783,10 @@ async function runInteractiveMenu() {
786
783
 
787
784
  if (group === 'poeditor') {
788
785
  const choice = await selectPrompt({
789
- message: 'Poeditor',
786
+ message: 'Translations',
790
787
  choices: [
791
- { name: 'Download', value: 'download' },
792
- { name: 'Upload', value: 'upload' },
788
+ { name: 'Download', value: 'download', description: 'Download translations from POEditor into the repo' },
789
+ { name: 'Upload', value: 'upload', description: 'Upload the repo\'s translations to POEditor' },
793
790
  { name: 'Cancel', value: 'cancel' },
794
791
  ],
795
792
  default: 'download',
@@ -801,12 +798,13 @@ async function runInteractiveMenu() {
801
798
  }
802
799
 
803
800
  const uploadMode = await selectPrompt({
804
- message: 'Upload mode',
801
+ message: 'Upload',
805
802
  choices: [
806
- { name: 'Upload (keep terms missing from file)', value: 'upload' },
803
+ { name: 'Upload', value: 'upload', description: 'Terms missing from the file stay in POEditor' },
807
804
  {
808
- name: 'Upload + sync terms (delete terms missing from file)',
805
+ name: 'Upload and delete missing terms',
809
806
  value: 'upload-sync',
807
+ description: 'Terms missing from the file are deleted in POEditor, with all their translations',
810
808
  },
811
809
  { name: 'Cancel', value: 'cancel' },
812
810
  ],
@@ -830,14 +828,38 @@ async function runInteractiveMenu() {
830
828
  const choice = await selectPrompt({
831
829
  message: 'Piwik PRO custom dimensions',
832
830
  choices: [
833
- { name: 'List dimensions in Piwik', value: 'list' },
834
- { name: 'List local dimension map', value: 'list-local' },
835
- { name: 'Compare local map with Piwik', value: 'diff' },
836
- { name: 'Add missing dimensions', value: 'add' },
837
- { name: 'Update dimension names', value: 'update' },
838
- { name: 'Sync from one side to another', value: 'sync' },
839
- { name: 'Compare environments', value: 'compare' },
840
- { name: 'Migrate between environments', value: 'migrate' },
831
+ { name: 'List in Piwik', value: 'list', description: 'Dimensions on a site from src/config/env.ts' },
832
+ {
833
+ name: 'List local',
834
+ value: 'list-local',
835
+ description: 'Slot map in dimensions.ts or piwikDimensions.ts. Does not call Piwik',
836
+ },
837
+ {
838
+ name: 'Compare local with Piwik',
839
+ value: 'diff',
840
+ description: 'Show where the local slot map and Piwik differ',
841
+ },
842
+ {
843
+ name: 'Add missing dimensions',
844
+ value: 'add',
845
+ description: 'Create local dimensions that are missing in Piwik, one slot at a time',
846
+ },
847
+ {
848
+ name: 'Update dimension names',
849
+ value: 'update',
850
+ description: 'Rename Piwik dimensions to the local names',
851
+ },
852
+ {
853
+ name: 'Sync',
854
+ value: 'sync',
855
+ description: 'Copy dimensions between the local map and a site, or between two sites',
856
+ },
857
+ { name: 'Compare environments', value: 'compare', description: 'Compare the dimensions on two Piwik sites' },
858
+ {
859
+ name: 'Migrate between environments',
860
+ value: 'migrate',
861
+ description: 'Copy dimensions from one Piwik site to another',
862
+ },
841
863
  { name: 'Cancel', value: 'cancel' },
842
864
  ],
843
865
  default: 'diff',
@@ -848,7 +870,7 @@ async function runInteractiveMenu() {
848
870
  if (choice === 'list-local') return runSelectedAction(() => runNode('piwik.mjs', ['list', '--local']))
849
871
  if (choice === 'diff') return runSelectedAction(() => runNode('piwik.mjs', ['diff']))
850
872
  if (choice === 'compare' || choice === 'migrate') {
851
- const { defaultEnvironmentPair, listPiwikEnvironments } = await import('./piwik-common.mjs')
873
+ const { defaultEnvironmentPair, listPiwikEnvironments } = await import('./lib/piwik-common.mjs')
852
874
  const environments = listPiwikEnvironments(process.cwd())
853
875
  if (environments.length < 2) {
854
876
  console.log('[piwik] Need two Piwik sites in src/config/env.ts.')
@@ -874,8 +896,8 @@ async function runInteractiveMenu() {
874
896
  const writeMode = await selectPrompt({
875
897
  message: `Write dimensions from ${from} to ${to}?`,
876
898
  choices: [
877
- { name: 'Dry run', value: 'dry-run' },
878
- { name: 'Apply', value: 'apply' },
899
+ { name: 'Dry run', value: 'dry-run', description: 'Show what would change without writing anything' },
900
+ { name: 'Apply', value: 'apply', description: 'Write the changes' },
879
901
  { name: 'Cancel', value: 'cancel' },
880
902
  ],
881
903
  default: 'dry-run',
@@ -895,10 +917,10 @@ async function runInteractiveMenu() {
895
917
  return runSelectedAction(() => runNode('piwik.mjs', migrateArgs))
896
918
  }
897
919
  if (choice === 'sync') {
898
- const { listPiwikEnvironments } = await import('./piwik-common.mjs')
920
+ const { listPiwikEnvironments } = await import('./lib/piwik-common.mjs')
899
921
  const environments = listPiwikEnvironments(process.cwd())
900
922
  const endpoints = [
901
- { name: 'Local map', value: 'local' },
923
+ { name: 'Local map', value: 'local', description: 'dimensions.ts or piwikDimensions.ts in this repo' },
902
924
  ...environments.map((site) => ({ name: site.environment, value: site.environment })),
903
925
  ]
904
926
  const from = await selectPrompt({
@@ -922,8 +944,8 @@ async function runInteractiveMenu() {
922
944
  const writeMode = await selectPrompt({
923
945
  message: from === 'local' ? `Write the local map up to ${to}?` : to === 'local' ? `Write tracking ids from ${from} into the local map?` : `Write dimensions from ${from} to ${to}?`,
924
946
  choices: [
925
- { name: 'Dry run', value: 'dry-run' },
926
- { name: 'Apply', value: 'apply' },
947
+ { name: 'Dry run', value: 'dry-run', description: 'Show what would change without writing anything' },
948
+ { name: 'Apply', value: 'apply', description: 'Write the changes' },
927
949
  { name: 'Cancel', value: 'cancel' },
928
950
  ],
929
951
  default: 'dry-run',
@@ -950,8 +972,8 @@ async function runInteractiveMenu() {
950
972
  const writeMode = await selectPrompt({
951
973
  message: 'Write to Piwik?',
952
974
  choices: [
953
- { name: 'Dry run', value: 'dry-run' },
954
- { name: 'Apply', value: 'apply' },
975
+ { name: 'Dry run', value: 'dry-run', description: 'Show what would change without writing anything' },
976
+ { name: 'Apply', value: 'apply', description: 'Write the changes' },
955
977
  { name: 'Cancel', value: 'cancel' },
956
978
  ],
957
979
  default: 'dry-run',
@@ -973,20 +995,38 @@ async function runInteractiveMenu() {
973
995
 
974
996
  if (group === 'dependency-policy') {
975
997
  const choice = await selectPrompt({
976
- message: 'Dependecy policy',
998
+ message: 'Dependency policy',
977
999
  choices: [
978
- { name: 'Check', value: 'check' },
979
- { name: 'Sync and auto-commit', value: 'sync-commit' },
980
- { name: 'Upgrade package.json packages and auto-commit', value: 'upgrade-commit' },
981
- { name: 'Upgrade package.json packages, prune overrides, and auto-commit', value: 'upgrade-commit-prune' },
982
1000
  {
983
- name: 'Fix vulnerabilities (refresh, override, prune) and auto-commit',
1001
+ name: 'Check',
1002
+ value: 'check',
1003
+ description: 'Check package.json against the dependency policy without changing anything',
1004
+ },
1005
+ {
1006
+ name: autoCommit('Sync'),
1007
+ value: 'sync-commit',
1008
+ description: 'Set package.json versions to the dependency policy and run pnpm install',
1009
+ },
1010
+ {
1011
+ name: tagged('Upgrade packages', { interactive: true, autoCommit: true }),
1012
+ value: 'upgrade-commit-prune',
1013
+ description: 'Pick packages to upgrade with ncu, then prune unused overrides in a second commit',
1014
+ },
1015
+ {
1016
+ name: tagged('Fix vulnerabilities', { interactive: true, autoCommit: true }),
984
1017
  value: 'audit-override-commit',
1018
+ description:
1019
+ 'Refresh the lockfile, override where ranges block, prune, verify. Asks before forcing a new major, a young release or a direct bump',
985
1020
  },
986
- { name: 'Prune unused vulnerability overrides and auto-commit', value: 'prune-overrides' },
987
1021
  {
988
- name: 'Prune unneeded minimumReleaseAgeExclude entries and auto-commit',
1022
+ name: autoCommit('Prune overrides'),
1023
+ value: 'prune-overrides',
1024
+ description: 'Remove workspace overrides that no longer change pnpm audit',
1025
+ },
1026
+ {
1027
+ name: autoCommit('Prune minimumReleaseAgeExclude'),
989
1028
  value: 'prune-release-age-exclude',
1029
+ description: 'Remove pinned versions that are old enough or no longer in the lockfile',
990
1030
  },
991
1031
  { name: 'Cancel', value: 'cancel' },
992
1032
  ],
@@ -998,9 +1038,6 @@ async function runInteractiveMenu() {
998
1038
  if (choice === 'sync-commit') {
999
1039
  return runSelectedAction(() => runNode('sync-dependency-policy-commit.mjs'))
1000
1040
  }
1001
- if (choice === 'upgrade-commit') {
1002
- return runSelectedAction(() => runNode('policy-upgrade-commit.mjs'))
1003
- }
1004
1041
  if (choice === 'upgrade-commit-prune') {
1005
1042
  return runSelectedAction(() => runNode('policy-upgrade-commit.mjs', ['--prune-overrides']))
1006
1043
  }
@@ -1020,15 +1057,21 @@ async function runInteractiveMenu() {
1020
1057
  const choice = await selectPrompt({
1021
1058
  message: 'Sync configs',
1022
1059
  choices: [
1023
- { name: 'vscode', value: 'vscode' },
1024
- { name: 'eslint', value: 'eslint' },
1025
- { name: 'prettier', value: 'prettier' },
1026
- { name: 'tsconfig', value: 'tsconfig' },
1027
- { name: 'Fallow', value: 'fallow' },
1028
- { name: 'Dependency-Check suppressions (sync + commit)', value: 'dependency-check' },
1029
- { name: 'Cursor', value: 'cursor' },
1030
- { name: 'Vue inspector (vite-plugin-vue-inspector)', value: 'vue-inspector' },
1031
- { name: 'All', value: 'all' },
1060
+ { name: 'VS Code', value: 'vscode', description: 'Overwrite .vscode/settings.json with the shared settings' },
1061
+ { name: 'ESLint', value: 'eslint', description: 'Overwrite eslint.config.js with the shared config' },
1062
+ { name: 'Prettier', value: 'prettier', description: 'Overwrite prettier.config.js with the shared config' },
1063
+ { name: 'tsconfig', value: 'tsconfig', description: 'Overwrite tsconfig.json with the shared config' },
1064
+ {
1065
+ name: autoCommit('Dependency-Check suppressions'),
1066
+ value: 'dependency-check',
1067
+ description: 'Write the suppressions that match pnpm audit findings',
1068
+ },
1069
+ {
1070
+ name: autoCommit('Vue inspector'),
1071
+ value: 'vue-inspector',
1072
+ description: 'Add vite-plugin-vue-inspector, wire it in vite.config and run pnpm install',
1073
+ },
1074
+ { name: 'All configs', value: 'all', description: 'VS Code, ESLint, Prettier and tsconfig' },
1032
1075
  { name: 'Cancel', value: 'cancel' },
1033
1076
  ],
1034
1077
  default: 'all',
@@ -1042,236 +1085,153 @@ async function runInteractiveMenu() {
1042
1085
  const choice = await selectPrompt({
1043
1086
  message: 'Setup',
1044
1087
  choices: [
1045
- { name: 'Lefthook', value: 'lefthook' },
1046
- { name: 'Full consumer setup', value: 'full-consumer-setup' },
1088
+ {
1089
+ name: 'Lefthook',
1090
+ value: 'lefthook',
1091
+ description: 'Write lefthook.yml and install the git hooks',
1092
+ },
1093
+ {
1094
+ name: 'AI rules',
1095
+ value: 'ai-rules',
1096
+ description: 'Enable the fg-ai-rules plugins in .claude/settings.json and install them for Claude Code',
1097
+ },
1098
+ {
1099
+ name: 'Full setup',
1100
+ value: 'full-consumer-setup',
1101
+ description: 'Sync scripts, Lefthook, configs and dependency policy, pnpm install, then set up AI rules',
1102
+ },
1047
1103
  { name: 'Cancel', value: 'cancel' },
1048
1104
  ],
1049
1105
  default: 'lefthook',
1050
1106
  loop: false,
1051
1107
  })
1052
1108
  if (choice === 'cancel') continue
1109
+ if (choice === 'ai-rules') return runSelectedAction(() => runNode('setup-ai-rules.mjs'))
1053
1110
  return runSelectedAction(() => (choice === 'lefthook' ? runSetupLefthook() : runFullConsumerSetup()))
1054
1111
  }
1055
1112
 
1056
- if (group === 'fallow-analysis') {
1057
- const choice = await selectPrompt({
1058
- message: 'Fallow analysis',
1059
- choices: [
1060
- { name: 'dead code', value: 'dead-code' },
1061
- { name: 'Cancel', value: 'cancel' },
1062
- ],
1063
- default: 'dead-code',
1064
- loop: false,
1065
- })
1066
- if (choice === 'cancel') continue
1067
- return runSelectedAction(() => runBundledFallow([choice]))
1113
+ if (group === 'fallow-dead-code') {
1114
+ return runSelectedAction(() => runBundledFallow(['dead-code']))
1068
1115
  }
1069
1116
 
1070
1117
  if (group === 'maintainer') {
1071
1118
  const choice = await selectPrompt({
1072
1119
  message: 'Maintainer',
1073
1120
  choices: [
1074
- { name: 'Show outdated dependency policy packages', value: 'm1' },
1075
- { name: 'Upgrade dependency policy (interactive) and auto-commit', value: 'm2' },
1076
- { name: 'Refresh dependency policy from repos (and scaffold/vue package.json)', value: 'm3' },
1077
- { name: 'Check policy vulnerabilities (audit)', value: 'm4' },
1078
- { name: 'Check fg-devkit repo vulnerabilities', value: 'm5' },
1079
- { name: 'Check vulnerabilities across all Vue repos', value: 'm6' },
1080
- { name: 'Bump fg-devkit in all Vue repos, run sync-deps, and pnpm install', value: 'm7' },
1081
- { name: 'Bump pnpm toolchain (latest)', value: 'm8' },
1082
- { name: 'Bump Node toolchain (fleet sync)', value: 'm9' },
1083
- { name: 'Git pull all repositories', value: 'm10' },
1084
- { name: 'Git pull (choose repositories)', value: 'm11' },
1085
- { name: 'Stage, commit and push all repositories', value: 'm12' },
1086
- { name: 'Stage, commit and push (choose repositories)', value: 'm13' },
1087
- { name: 'Discard tracked changes in all repositories', value: 'm14' },
1088
- { name: 'Discard tracked changes (choose repositories)', value: 'm15' },
1121
+ {
1122
+ name: 'Outdated policy packages',
1123
+ value: 'policy-outdated',
1124
+ description: 'Policy packages that have newer versions on npm',
1125
+ },
1126
+ {
1127
+ name: tagged('Upgrade policy', { interactive: true, autoCommit: true }),
1128
+ value: 'policy-upgrade',
1129
+ description: 'Pick policy versions to upgrade with ncu',
1130
+ },
1131
+ {
1132
+ name: 'Refresh policy from repos',
1133
+ value: 'refresh-policy',
1134
+ description: 'Rebuild the policy from the packages used in Vue repos, and update scaffold/vue/package.json',
1135
+ },
1136
+ { name: 'Policy vulnerabilities', value: 'policy-vulns', description: 'pnpm audit of the versions in the policy' },
1137
+ { name: 'Vulnerabilities', value: 'vulns', description: 'pnpm audit across the Vue repos or in fg-devkit' },
1138
+ {
1139
+ name: interactive('Bump fg-devkit in Vue repos'),
1140
+ value: 'bump-fg-devkit',
1141
+ description: 'Bump fg-devkit to latest, sync the dependency policy and run pnpm install in each repo',
1142
+ },
1143
+ { name: 'Bump pnpm', value: 'bump-pnpm', description: 'Latest pnpm in the policy, scaffold and repos' },
1144
+ { name: 'Bump Node', value: 'bump-node', description: 'Sync the Node version from the policy to the repos' },
1145
+ { name: 'Git pull', value: 'git-pull', description: 'Pull the current branch in each repo' },
1146
+ {
1147
+ name: interactive('Commit & push'),
1148
+ value: 'git-commit-push',
1149
+ description: 'Stage all changes, commit and push in each repo',
1150
+ },
1151
+ { name: 'Discard changes', value: 'git-discard', description: 'Throw away uncommitted changes to tracked files in each repo' },
1089
1152
  { name: 'Cancel', value: 'cancel' },
1090
1153
  ],
1091
- default: 'm1',
1154
+ default: 'policy-outdated',
1092
1155
  loop: false,
1093
1156
  })
1094
1157
  if (choice === 'cancel') continue
1095
1158
  const maintainerCode = maintainerOnly()
1096
1159
  if (maintainerCode !== 0) return maintainerCode
1097
- if (choice === 'm1') return runSelectedAction(() => runNode('policy-outdated.mjs'))
1098
- if (choice === 'm2') return runSelectedAction(() => runNode('policy-upgrade.mjs'))
1099
- if (choice === 'm3') return runSelectedAction(() => runNode('refresh-dependency-policy-from-repos.mjs'))
1100
- if (choice === 'm4') return runSelectedAction(() => runNode('policy-vulns.mjs'))
1101
- if (choice === 'm5') return runSelectedAction(() => runMaintainerAuditAll())
1102
- if (choice === 'm6') return runSelectedAction(() => runNode('fleet-vulns.mjs'))
1103
- if (choice === 'm7') return runSelectedAction(() => runNode('fleet-bump-fg-devkit.mjs'))
1104
- if (choice === 'm8') {
1105
- const scope = await selectPrompt({
1106
- message: 'Bump pnpm toolchain',
1107
- choices: [
1108
- { name: 'fg-devkit only (policy, scaffold, install)', value: 'fg-devkit' },
1109
- { name: 'fg-devkit + pick pnpm repos under projects root', value: 'fleet' },
1110
- { name: 'Dry run + pick pnpm repos', value: 'dry-run-fleet' },
1111
- { name: 'Cancel', value: 'cancel' },
1112
- ],
1113
- default: 'fg-devkit',
1114
- loop: false,
1115
- })
1116
- if (scope === 'cancel') continue
1117
- const bumpArgs = []
1118
- if (scope === 'fg-devkit') {
1119
- bumpArgs.push('--skip-fleet')
1120
- }
1121
- if (scope === 'fleet') {
1122
- bumpArgs.push('--pick')
1123
- }
1124
- if (scope === 'dry-run-fleet') {
1125
- bumpArgs.push('--dry-run', '--pick')
1126
- }
1127
- return runSelectedAction(() => runNode('bump-pnpm-toolchain.mjs', bumpArgs))
1128
- }
1129
- if (choice === 'm9') {
1130
- const scope = await selectPrompt({
1131
- message: 'Bump Node toolchain',
1132
- choices: [
1133
- { name: 'fg-devkit only (policy + local files)', value: 'fg-devkit' },
1134
- { name: 'fg-devkit + pick projects under projects root', value: 'fleet' },
1135
- { name: 'Dry run + pick projects', value: 'dry-run-fleet' },
1136
- { name: 'Cancel', value: 'cancel' },
1137
- ],
1138
- default: 'fleet',
1139
- loop: false,
1140
- })
1141
- if (scope === 'cancel') continue
1142
- const bumpArgs = []
1143
- if (scope === 'fg-devkit') {
1144
- bumpArgs.push('--skip-fleet')
1145
- }
1146
- if (scope === 'fleet') {
1147
- bumpArgs.push('--pick')
1148
- }
1149
- if (scope === 'dry-run-fleet') {
1150
- bumpArgs.push('--dry-run', '--pick')
1151
- }
1152
- return runSelectedAction(() => runNode('bump-node-toolchain.mjs', bumpArgs))
1153
- }
1154
- if (choice === 'm10') {
1155
- const scope = await selectPrompt({
1156
- message: 'Git pull all repositories',
1157
- choices: [
1158
- { name: 'Pull all git repos under projects root', value: 'pull' },
1159
- { name: 'Dry run (all repos)', value: 'dry-run' },
1160
- { name: 'Cancel', value: 'cancel' },
1161
- ],
1162
- default: 'pull',
1163
- loop: false,
1164
- })
1165
- if (scope === 'cancel') continue
1166
- const pullArgs = scope === 'dry-run' ? ['--dry-run'] : []
1167
- return runSelectedAction(() => runNode('fleet-git-pull.mjs', pullArgs))
1168
- }
1169
- if (choice === 'm11') {
1170
- const scope = await selectPrompt({
1171
- message: 'Git pull (choose repositories)',
1172
- choices: [
1173
- { name: 'fg-devkit only', value: 'fg-devkit' },
1174
- { name: 'Choose repositories under projects root', value: 'pick' },
1175
- { name: 'Dry run + choose repositories', value: 'dry-run-pick' },
1176
- { name: 'Cancel', value: 'cancel' },
1177
- ],
1178
- default: 'pick',
1179
- loop: false,
1180
- })
1181
- if (scope === 'cancel') continue
1182
- const pullArgs = []
1183
- if (scope === 'fg-devkit') {
1184
- pullArgs.push('--fg-devkit-only')
1185
- }
1186
- if (scope === 'pick') {
1187
- pullArgs.push('--pick')
1188
- }
1189
- if (scope === 'dry-run-pick') {
1190
- pullArgs.push('--dry-run', '--pick')
1191
- }
1192
- return runSelectedAction(() => runNode('fleet-git-pull-scoped.mjs', pullArgs))
1193
- }
1194
- if (choice === 'm12') {
1195
- const scope = await selectPrompt({
1196
- message: 'Stage, commit and push all repositories',
1197
- choices: [
1198
- { name: 'Stage, commit and push all repos', value: 'run' },
1199
- { name: 'Dry run (all repos)', value: 'dry-run' },
1200
- { name: 'Cancel', value: 'cancel' },
1201
- ],
1202
- default: 'run',
1203
- loop: false,
1204
- })
1205
- if (scope === 'cancel') continue
1206
- const commitArgs = scope === 'dry-run' ? ['--dry-run'] : []
1207
- return runSelectedAction(() => runNode('fleet-git-commit-push.mjs', commitArgs))
1160
+ if (choice === 'policy-outdated') return runSelectedAction(() => runNode('policy-outdated.mjs'))
1161
+ if (choice === 'policy-upgrade') return runSelectedAction(() => runNode('policy-upgrade.mjs'))
1162
+ if (choice === 'refresh-policy') {
1163
+ return runSelectedAction(() => runNode('refresh-dependency-policy-from-repos.mjs'))
1208
1164
  }
1209
- if (choice === 'm13') {
1165
+ if (choice === 'policy-vulns') return runSelectedAction(() => runNode('policy-vulns.mjs'))
1166
+ if (choice === 'bump-fg-devkit') return runSelectedAction(() => runNode('fleet-bump-fg-devkit.mjs'))
1167
+ if (choice === 'vulns') {
1210
1168
  const scope = await selectPrompt({
1211
- message: 'Stage, commit and push (choose repositories)',
1169
+ message: 'Vulnerabilities',
1212
1170
  choices: [
1213
- { name: 'fg-devkit only', value: 'fg-devkit' },
1214
- { name: 'Choose repositories under projects root', value: 'pick' },
1215
- { name: 'Dry run + choose repositories', value: 'dry-run-pick' },
1171
+ {
1172
+ name: 'All Vue repos',
1173
+ value: 'all',
1174
+ description: 'pnpm audit in every repo under the projects root, with a summary',
1175
+ },
1176
+ {
1177
+ name: 'fg-devkit only',
1178
+ value: 'fg-devkit',
1179
+ description: 'pnpm audit in fg-devkit and its nested packages',
1180
+ },
1216
1181
  { name: 'Cancel', value: 'cancel' },
1217
1182
  ],
1218
- default: 'pick',
1183
+ default: 'all',
1219
1184
  loop: false,
1220
1185
  })
1221
1186
  if (scope === 'cancel') continue
1222
- const commitArgs = []
1223
- if (scope === 'fg-devkit') {
1224
- commitArgs.push('--fg-devkit-only')
1225
- }
1226
- if (scope === 'pick') {
1227
- commitArgs.push('--pick')
1228
- }
1229
- if (scope === 'dry-run-pick') {
1230
- commitArgs.push('--dry-run', '--pick')
1231
- }
1232
- return runSelectedAction(() => runNode('fleet-git-commit-push-scoped.mjs', commitArgs))
1187
+ return runSelectedAction(() =>
1188
+ scope === 'fg-devkit' ? runMaintainerAuditAll() : runNode('fleet-vulns.mjs'),
1189
+ )
1233
1190
  }
1234
- if (choice === 'm14') {
1235
- const scope = await selectPrompt({
1236
- message: 'Discard tracked changes in all repositories',
1237
- choices: [
1238
- { name: 'Discard tracked changes in all repos', value: 'run' },
1239
- { name: 'Dry run (all repos)', value: 'dry-run' },
1240
- { name: 'Cancel', value: 'cancel' },
1241
- ],
1242
- default: 'run',
1243
- loop: false,
1244
- })
1245
- if (scope === 'cancel') continue
1246
- const discardArgs = scope === 'dry-run' ? ['--dry-run'] : []
1247
- return runSelectedAction(() => runNode('fleet-git-discard.mjs', discardArgs))
1191
+
1192
+ // Toolchain bumps always update fg-devkit itself; the git actions treat it as one repo among the rest.
1193
+ const fleetActions = {
1194
+ 'bump-pnpm': { message: 'Bump pnpm', script: 'bump-pnpm-toolchain.mjs', toolchain: true, default: 'fg-devkit' },
1195
+ 'bump-node': { message: 'Bump Node', script: 'bump-node-toolchain.mjs', toolchain: true, default: 'pick' },
1196
+ 'git-pull': { message: 'Git pull', script: 'fleet-git-pull-scoped.mjs', default: 'pick' },
1197
+ 'git-commit-push': { message: 'Commit & push', script: 'fleet-git-commit-push-scoped.mjs', default: 'pick' },
1198
+ 'git-discard': { message: 'Discard changes', script: 'fleet-git-discard-scoped.mjs', default: 'pick' },
1248
1199
  }
1249
- if (choice === 'm15') {
1250
- const scope = await selectPrompt({
1251
- message: 'Discard tracked changes (choose repositories)',
1252
- choices: [
1253
- { name: 'fg-devkit only', value: 'fg-devkit' },
1254
- { name: 'Choose repositories under projects root', value: 'pick' },
1255
- { name: 'Dry run + choose repositories', value: 'dry-run-pick' },
1256
- { name: 'Cancel', value: 'cancel' },
1257
- ],
1258
- default: 'pick',
1259
- loop: false,
1260
- })
1261
- if (scope === 'cancel') continue
1262
- const discardArgs = []
1263
- if (scope === 'fg-devkit') {
1264
- discardArgs.push('--fg-devkit-only')
1265
- }
1266
- if (scope === 'pick') {
1267
- discardArgs.push('--pick')
1268
- }
1269
- if (scope === 'dry-run-pick') {
1270
- discardArgs.push('--dry-run', '--pick')
1271
- }
1272
- return runSelectedAction(() => runNode('fleet-git-discard-scoped.mjs', discardArgs))
1200
+ const action = fleetActions[choice]
1201
+ const scope = await selectPrompt({
1202
+ message: action.message,
1203
+ choices: [
1204
+ {
1205
+ name: action.toolchain ? 'fg-devkit + all repos' : 'All repos',
1206
+ value: 'all',
1207
+ description: 'Every repo under the projects root',
1208
+ },
1209
+ {
1210
+ name: interactive(action.toolchain ? 'fg-devkit + choose repos' : 'Choose repos'),
1211
+ value: 'pick',
1212
+ description: 'Pick repos in a checklist',
1213
+ },
1214
+ { name: 'fg-devkit only', value: 'fg-devkit', description: 'Only this repo' },
1215
+ { name: 'Dry run, all repos', value: 'dry-run-all', description: 'Show what would happen, change nothing' },
1216
+ {
1217
+ name: interactive('Dry run, choose repos'),
1218
+ value: 'dry-run-pick',
1219
+ description: 'Show what would happen, change nothing',
1220
+ },
1221
+ { name: 'Cancel', value: 'cancel' },
1222
+ ],
1223
+ default: action.default,
1224
+ loop: false,
1225
+ })
1226
+ if (scope === 'cancel') continue
1227
+ const scopeArgs = {
1228
+ all: ['--all'],
1229
+ pick: ['--pick'],
1230
+ 'fg-devkit': [action.toolchain ? '--skip-fleet' : '--fg-devkit-only'],
1231
+ 'dry-run-all': ['--dry-run', '--all'],
1232
+ 'dry-run-pick': ['--dry-run', '--pick'],
1273
1233
  }
1274
- return 1
1234
+ return runSelectedAction(() => runNode(action.script, scopeArgs[scope]))
1275
1235
  }
1276
1236
  }
1277
1237
 
@@ -1300,11 +1260,7 @@ async function main() {
1300
1260
  return
1301
1261
  }
1302
1262
  if (sub === 'commit') {
1303
- process.exitCode = await runCzg(rest)
1304
- return
1305
- }
1306
- if (sub === 'commit-vuln' || sub === 'vuln-commit' || sub === 'commit:vuln') {
1307
- process.exitCode = runNode('vuln-commit.mjs', rest)
1263
+ process.exitCode = await runCommit(rest)
1308
1264
  return
1309
1265
  }
1310
1266
  if (sub === 'scaffold-vue') {
@@ -1361,11 +1317,8 @@ async function main() {
1361
1317
  return
1362
1318
  }
1363
1319
  if (
1364
- (sub === 'fleet-git-pull' ||
1365
- sub === 'fleet-git-pull-scoped' ||
1366
- sub === 'fleet-git-commit-push' ||
1320
+ (sub === 'fleet-git-pull-scoped' ||
1367
1321
  sub === 'fleet-git-commit-push-scoped' ||
1368
- sub === 'fleet-git-discard' ||
1369
1322
  sub === 'fleet-git-discard-scoped') &&
1370
1323
  !isFgDevkitMaintainerContext()
1371
1324
  ) {