fg-devkit 1.7.3 → 1.7.5

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
@@ -45,6 +45,24 @@ pnpm exec fgd typecheck
45
45
  pnpm exec fgd typecheck-watch
46
46
  ```
47
47
 
48
+ ## Bundled Fallow (dead code analysis)
49
+
50
+ `fg-devkit` also ships Fallow for dead code analysis in consuming repos.
51
+
52
+ Run dead code analysis via CLI:
53
+
54
+ ```bash
55
+ pnpm exec fgd dead-code
56
+ ```
57
+
58
+ Or run Fallow directly with custom args:
59
+
60
+ ```bash
61
+ pnpm exec fgd fallow dead-code --json
62
+ ```
63
+
64
+ When you run `fgd sync-tooling-configs` (or `fgd sync-project-configs`), fg-devkit also writes a shared `.fallowrc.json` to the consuming repo.
65
+
48
66
  To remove redundant tooling dependencies from a consuming repo:
49
67
 
50
68
  ```bash
@@ -136,6 +154,33 @@ export default {
136
154
  ...basePrettierConfig,
137
155
  }
138
156
  ```
157
+
158
+ ## Local development with pnpm link
159
+
160
+ Use this flow to test local `fg-devkit` changes in a consuming repo.
161
+
162
+ In this repo (`fg-devkit`):
163
+
164
+ ```bash
165
+ pnpm install
166
+ pnpm link --global
167
+ ```
168
+
169
+ In the consuming repo:
170
+
171
+ ```bash
172
+ pnpm link --global fg-devkit
173
+ pnpm install
174
+ pnpm exec fgd --help
175
+ ```
176
+
177
+ To remove the global link in a consuming repo:
178
+
179
+ ```bash
180
+ pnpm unlink fg-devkit
181
+ pnpm install
182
+ ```
183
+
139
184
  ## Install CLI in this repo
140
185
 
141
186
  1) Add this to your shell config (`~/.zshrc`) and update `FGD_DEVKIT_PATH`:
@@ -0,0 +1,13 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/fallow-rs/fallow/main/schema.json",
3
+ "ignorePatterns": [
4
+ "**/*.d.ts",
5
+ "**/*.generated.*",
6
+ "dist/**",
7
+ "build/**",
8
+ "coverage/**",
9
+ "src/style.css",
10
+ "index.html"
11
+ ],
12
+ "publicPackages": ["fds-vue-core"]
13
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fg-devkit",
3
- "version": "1.7.3",
3
+ "version": "1.7.5",
4
4
  "description": "Shared configs, scripts, and DX tooling for Vue / TypeScript frontend repos",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -72,6 +72,8 @@
72
72
  }
73
73
  },
74
74
  "dependencies": {
75
+ "@inquirer/confirm": "^6.0.12",
76
+ "@inquirer/select": "^5.1.4",
75
77
  "@intlify/eslint-plugin-vue-i18n": "4.3.0",
76
78
  "@typescript-eslint/eslint-plugin": "8.58.2",
77
79
  "@typescript-eslint/parser": "8.58.2",
@@ -83,6 +85,7 @@
83
85
  "eslint": "10.2.1",
84
86
  "eslint-plugin-prettier": "5.5.5",
85
87
  "eslint-plugin-vue": "10.8.0",
88
+ "fallow": "^2.52.2",
86
89
  "lefthook": "2.1.6",
87
90
  "npm-check-updates": "21.0.3",
88
91
  "prettier": "3.8.3",
@@ -34,7 +34,7 @@
34
34
  "@tanstack/vue-query-devtools": "6.1.24",
35
35
  "@types/node": "22.16.5",
36
36
  "@vitejs/plugin-vue": "6.0.6",
37
- "fg-devkit": "1.7.3",
37
+ "fg-devkit": "1.7.5",
38
38
  "start-server-and-test": "2.1.3",
39
39
  "tailwindcss": "4.2.4",
40
40
  "tsx": "4.21.0",
@@ -28,6 +28,8 @@
28
28
  * pnpm exec fg-devkit format-check [prettier-args...] (same as format plus --check)
29
29
  * pnpm exec fg-devkit typecheck [vue-tsc-args...] (run vue-tsc with fg-devkit bundled package)
30
30
  * pnpm exec fg-devkit typecheck-watch [vue-tsc-args...] (same as typecheck plus --watch)
31
+ * pnpm exec fg-devkit fallow [fallow-args...] (run Fallow with fg-devkit bundled package)
32
+ * pnpm exec fg-devkit dead-code [fallow-args...] (same as: fallow dead-code [args...])
31
33
  * pnpm exec fg-devkit poeditor-download [args...] (download translations using poeditor/.poeditor.json + env token)
32
34
  * pnpm exec fg-devkit poeditor-upload [args...] (upload translations using poeditor/.poeditor.json + env token)
33
35
  * Interactive menu (no subcommand):
@@ -40,10 +42,8 @@
40
42
 
41
43
  import { spawnSync } from 'node:child_process'
42
44
  import fs from 'node:fs'
43
- import readline from 'node:readline/promises'
44
45
  import path from 'node:path'
45
46
  import { fileURLToPath } from 'node:url'
46
- import { stdin as input, stdout as output } from 'node:process'
47
47
  import { createRequire } from 'node:module'
48
48
 
49
49
  const scriptDir = path.dirname(fileURLToPath(import.meta.url))
@@ -269,30 +269,6 @@ function runMaintainerAuditAll() {
269
269
  return runNode('repo-vulns.mjs')
270
270
  }
271
271
 
272
- /** Prompt release type in CLI before invoking release.sh to avoid a "stuck" terminal feeling. */
273
- async function promptReleaseType(rl) {
274
- console.log('\nSelect release type:')
275
- console.log(' 1) patch')
276
- console.log(' 2) minor')
277
- console.log(' 3) major')
278
- console.log(' 0) Cancel\n')
279
- const choice = (await rl.question('Release type [1-3] (default: 1): ')).trim() || '1'
280
- if (choice === '0') {
281
- return null
282
- }
283
- if (choice === '1') {
284
- return 'patch'
285
- }
286
- if (choice === '2') {
287
- return 'minor'
288
- }
289
- if (choice === '3') {
290
- return 'major'
291
- }
292
- console.error('[fg-devkit] Invalid release type selection.')
293
- return null
294
- }
295
-
296
272
  /**
297
273
  * Run Commitizen from fg-devkit's bundled dependency set.
298
274
  * Always forces fg-devkit's bundled cz adapter so it stays interactive.
@@ -340,6 +316,18 @@ bootstrap(
340
316
 
341
317
  /** Run ESLint from fg-devkit's bundled dependency set. */
342
318
  function runBundledEslint(args = []) {
319
+ const hasExplicitConfig = args.some((arg) => arg === '--config' || arg === '-c' || arg.startsWith('--config='))
320
+ const hasLocalFlatConfig =
321
+ fs.existsSync(path.join(process.cwd(), 'eslint.config.js')) ||
322
+ fs.existsSync(path.join(process.cwd(), 'eslint.config.mjs')) ||
323
+ fs.existsSync(path.join(process.cwd(), 'eslint.config.cjs'))
324
+
325
+ if (!hasExplicitConfig && !hasLocalFlatConfig) {
326
+ console.error('[fg-devkit] Missing eslint.config.js|mjs|cjs in current directory.')
327
+ console.error('[fg-devkit] Run "fgd sync-project-configs" or "fgd sync-tooling-configs" and retry.')
328
+ return 1
329
+ }
330
+
343
331
  let eslintBin
344
332
  try {
345
333
  const eslintPkgPath = require.resolve('eslint/package.json')
@@ -405,6 +393,65 @@ function runBundledTypeScript(args = []) {
405
393
  return r.status ?? 1
406
394
  }
407
395
 
396
+ /** Run Fallow from fg-devkit's bundled dependency set. */
397
+ function runBundledFallow(args = []) {
398
+ let fallowBin
399
+ try {
400
+ const fallowPkgJson = require.resolve('fallow/package.json', {
401
+ paths: [fgRoot, process.cwd()],
402
+ })
403
+ const fallowPkgRoot = path.dirname(fallowPkgJson)
404
+ fallowBin = path.join(fallowPkgRoot, 'bin', 'fallow')
405
+ if (!fs.existsSync(fallowBin)) {
406
+ throw new Error('fallow bin not found')
407
+ }
408
+ } catch {
409
+ console.error('[fg-devkit] Could not resolve bundled fallow.')
410
+ console.error('[fg-devkit] Run pnpm install in fg-devkit and in the linked consumer, then retry.')
411
+ return 1
412
+ }
413
+
414
+ const r = spawnSync(process.execPath, [fallowBin, ...args], {
415
+ stdio: 'inherit',
416
+ cwd: process.cwd(),
417
+ env: process.env,
418
+ })
419
+ if (r.error) {
420
+ console.error('[fg-devkit] Could not run bundled fallow.')
421
+ console.error('[fg-devkit]', r.error.message)
422
+ return 1
423
+ }
424
+ return r.status ?? 1
425
+ }
426
+
427
+ function syncFileFromTemplate(relativeSourcePath, targetPath) {
428
+ const sourcePath = path.join(fgRoot, relativeSourcePath)
429
+ if (!fs.existsSync(sourcePath)) {
430
+ console.error(`[fg-devkit] Missing template: ${sourcePath}`)
431
+ return 1
432
+ }
433
+ try {
434
+ fs.copyFileSync(sourcePath, path.join(process.cwd(), targetPath))
435
+ console.log(`[fg-devkit] Synced ${targetPath}`)
436
+ return 0
437
+ } catch (err) {
438
+ console.error(`[fg-devkit] Failed to sync ${targetPath}: ${err.message}`)
439
+ return 1
440
+ }
441
+ }
442
+
443
+ function runSyncConfigTarget(target) {
444
+ if (target === 'vscode') return runNode('sync-vscode-settings.mjs')
445
+ if (target === 'eslint') return syncFileFromTemplate('configs/eslint.sync-stub.js', 'eslint.config.js')
446
+ if (target === 'prettier') return syncFileFromTemplate('configs/prettier.sync-stub.js', 'prettier.config.js')
447
+ if (target === 'tsconfig') return syncFileFromTemplate('configs/tsconfig.app.sync.json', 'tsconfig.json')
448
+ if (target === 'fallow') return syncFileFromTemplate('configs/fallowrc.sync-stub.json', '.fallowrc.json')
449
+ if (target === 'cursor') return runNode('sync-core-coding-standards.mjs')
450
+ if (target === 'all') return runNode('sync-project-configs.mjs')
451
+ console.error(`[fg-devkit] Unknown config sync target: ${target}`)
452
+ return 1
453
+ }
454
+
408
455
  /** Map subcommand name → script file (extra argv after subcommand is forwarded). */
409
456
  const DIRECT_COMMANDS = {
410
457
  'check-deps': 'check-dependency-policy.mjs',
@@ -461,6 +508,8 @@ Commands:
461
508
  format-check [prettier-args...] Run Prettier with --check using fg-devkit bundled package
462
509
  typecheck [vue-tsc-args...] Run vue-tsc using fg-devkit bundled package
463
510
  typecheck-watch [vue-tsc-args...] Run vue-tsc with --watch using fg-devkit bundled package
511
+ fallow [fallow-args...] Run Fallow using fg-devkit bundled package
512
+ dead-code [fallow-args...] Run Fallow dead code analysis (shortcut for: fallow dead-code)
464
513
  poeditor-download [args...] Download locale files via PoEditor (reads poeditor/.poeditor.json by default)
465
514
  poeditor-upload [args...] Upload locale file via PoEditor (reads poeditor/.poeditor.json by default)
466
515
  scaffold-vue <target-dir> [--name …] [--slug …] [--force] …
@@ -468,119 +517,12 @@ Commands:
468
517
  sync-scaffold-vue-deps [policy-file]
469
518
  Align scaffold/vue/package.json versions to the dependency policy
470
519
 
471
- With no command, opens an interactive menu.
520
+ With no command, opens an interactive Inquirer menu.
472
521
 
473
522
  Options:
474
523
  -h, --help Show this help`)
475
524
  }
476
525
 
477
- /** Numbered actions shown in the interactive menu (keys must match user input). */
478
- const MENU = [
479
- {
480
- key: '1',
481
- label: 'Check dependency policy',
482
- run: () => runNode('check-dependency-policy.mjs'),
483
- },
484
- {
485
- key: '2',
486
- label: 'Sync dependency policy',
487
- run: () => runNode('sync-dependency-policy.mjs'),
488
- },
489
- {
490
- key: '3',
491
- label: 'Sync configs (vscode, eslint, prettier, tsconfig)',
492
- run: () => runNode('sync-project-configs.mjs'),
493
- },
494
- {
495
- key: '4',
496
- label: 'Create release',
497
- run: () => runReleaseScript(),
498
- },
499
- {
500
- key: '5',
501
- label: 'Commit (Commitizen)',
502
- run: () => runCommitizen(),
503
- },
504
- {
505
- key: '6',
506
- label: 'Commit vulnerability (Commitizen)',
507
- run: () => runCommitizen([], { FG_DEVKIT_CZ_DEFAULT_TYPE: 'vuln' }),
508
- },
509
- {
510
- key: '7',
511
- label: 'Remove unnecessary deps in package.json',
512
- run: () => runNode('prune-consumer-deps.mjs'),
513
- },
514
- {
515
- key: '8',
516
- label: 'Full consumer setup (7 -> scripts -> lefthook -> 3 -> 2 -> pnpm install)',
517
- run: () => runFullConsumerSetup(),
518
- },
519
- {
520
- key: '9',
521
- label: 'Setup lefthook (migrate from husky/lint-staged)',
522
- run: () => runSetupLefthook(),
523
- },
524
- {
525
- key: '10',
526
- label: 'PoEditor: Download translations',
527
- run: () => runNode('poeditor-download.mjs'),
528
- },
529
- {
530
- key: '11',
531
- label: 'PoEditor: Upload translations',
532
- run: () => runNode('poeditor-upload.mjs'),
533
- },
534
- {
535
- key: '12',
536
- label: 'Sync .cursor core coding standards',
537
- run: () => runNode('sync-core-coding-standards.mjs'),
538
- },
539
- ]
540
-
541
- const MAINTAINER_MENU = [
542
- {
543
- key: 'm1',
544
- label: 'Maintainer: Show outdated dependency policy packages',
545
- run: () => runNode('policy-outdated.mjs'),
546
- },
547
- {
548
- key: 'm2',
549
- label: 'Maintainer: Upgrade dependency policy (interactive)',
550
- run: () => runNode('policy-upgrade.mjs'),
551
- },
552
- {
553
- key: 'm3',
554
- label: 'Maintainer: Refresh dependency policy from repos (and scaffold/vue package.json)',
555
- run: () => runNode('refresh-dependency-policy-from-repos.mjs'),
556
- },
557
- {
558
- key: 'm4',
559
- label: 'Maintainer: Upgrade one policy package, stage, and auto-commit',
560
- run: () => runMaintainerPolicyUpgradeAndCommit(),
561
- },
562
- {
563
- key: 'm5',
564
- label: 'Maintainer: Check policy vulnerabilities (audit)',
565
- run: () => runNode('policy-vulns.mjs'),
566
- },
567
- {
568
- key: 'm6',
569
- label: 'Maintainer: Check fg-devkit repo vulnerabilities',
570
- run: () => runMaintainerAuditAll(),
571
- },
572
- {
573
- key: 'm7',
574
- label: 'Maintainer: Check vulnerabilities across all Vue repos',
575
- run: () => runNode('fleet-vulns.mjs'),
576
- },
577
- {
578
- key: 'm8',
579
- label: 'Maintainer: Bump fg-devkit in all Vue repos, run sync-deps, and pnpm install',
580
- run: () => runNode('fleet-bump-fg-devkit.mjs'),
581
- },
582
- ]
583
-
584
526
  function isFgDevkitMaintainerContext() {
585
527
  if (path.resolve(process.cwd()) !== fgRoot) {
586
528
  return false
@@ -598,53 +540,252 @@ function isFgDevkitMaintainerContext() {
598
540
  }
599
541
 
600
542
  async function runInteractiveMenu() {
601
- const rl = readline.createInterface({ input, output })
602
- const isMaintainer = isFgDevkitMaintainerContext()
603
- const menu = isMaintainer ? [...MENU, ...MAINTAINER_MENU] : MENU
604
-
605
- console.log(`\nfg-devkit — choose a task (cwd: ${process.cwd()})\n`)
606
-
607
- for (const item of menu) {
608
- console.log(` ${item.key}) ${item.label}`)
543
+ let selectPrompt
544
+ try {
545
+ const inquirerSelect = await import('@inquirer/select')
546
+ selectPrompt = inquirerSelect.default
547
+ } catch {
548
+ console.error('[fg-devkit] Interactive menu requires @inquirer/select but it is not available.')
549
+ console.error('[fg-devkit] Run pnpm install in your project and try again.')
550
+ return 1
609
551
  }
610
- console.log(' 0) Exit\n')
611
-
612
- const choice = (await rl.question('Number: ')).trim()
613
552
 
614
- if (choice === '0' || choice === '') {
615
- rl.close()
553
+ const runSelectedAction = async (action) => {
554
+ const out = action()
555
+ if (typeof out === 'object' && out !== null && typeof out.then === 'function') {
556
+ return await out
557
+ }
558
+ if (typeof out === 'number') {
559
+ return out
560
+ }
616
561
  return 0
617
562
  }
618
563
 
619
- const item = menu.find((m) => m.key === choice)
620
- if (!item) {
621
- console.error('[fg-devkit] Unknown option. Run again and choose a number from the list.')
622
- rl.close()
623
- return 1
564
+ const maintainerOnly = () => {
565
+ if (!isFgDevkitMaintainerContext()) {
566
+ console.error('[fg-devkit] Maintainer actions are only available in the fg-devkit repository root.')
567
+ return 1
568
+ }
569
+ return 0
624
570
  }
625
571
 
626
- let out
627
- if (item.key === '4') {
628
- const bumpType = await promptReleaseType(rl)
629
- if (!bumpType) {
630
- rl.close()
631
- return 0
572
+ const groups = [
573
+ {
574
+ name: '1) Create commit',
575
+ value: 'commit',
576
+ },
577
+ {
578
+ name: '2) Create release',
579
+ value: 'release',
580
+ },
581
+ {
582
+ name: '3) Lint and typecheck',
583
+ value: 'lint-typecheck',
584
+ },
585
+ {
586
+ name: '4) Poeditor',
587
+ value: 'poeditor',
588
+ },
589
+ {
590
+ name: '5) Dependecy policy',
591
+ value: 'dependency-policy',
592
+ },
593
+ {
594
+ name: '6) Sync configs',
595
+ value: 'sync-configs',
596
+ },
597
+ {
598
+ name: '7) Setup',
599
+ value: 'setup',
600
+ },
601
+ {
602
+ name: '8) Fallow analysis',
603
+ value: 'fallow-analysis',
604
+ },
605
+ {
606
+ name: '9) Maintainer',
607
+ value: 'maintainer',
608
+ },
609
+ {
610
+ name: '10) Exit',
611
+ value: 'exit',
612
+ },
613
+ ]
614
+
615
+ while (true) {
616
+ const group = await selectPrompt({
617
+ message: `fg-devkit — choose a task (cwd: ${process.cwd()})`,
618
+ choices: groups,
619
+ loop: false,
620
+ })
621
+ if (group === 'exit') return 0
622
+
623
+ if (group === 'commit') {
624
+ const choice = await selectPrompt({
625
+ message: 'Create commit',
626
+ choices: [
627
+ { name: 'Commit', value: 'commit' },
628
+ { name: 'Commit vulnerability', value: 'commit-vuln' },
629
+ { name: 'Cancel', value: 'cancel' },
630
+ ],
631
+ default: 'commit',
632
+ loop: false,
633
+ })
634
+ if (choice === 'cancel') continue
635
+ return runSelectedAction(() => (choice === 'commit' ? runCommitizen() : runNode('vuln-commit.mjs')))
632
636
  }
633
- // Close menu readline before spawning interactive child processes.
634
- rl.close()
635
- out = runReleaseScript([bumpType])
636
- } else {
637
- // Close menu readline before spawning interactive child processes.
638
- rl.close()
639
- out = item.run()
640
- }
641
637
 
642
- if (typeof out === 'object' && out !== null && typeof out.then === 'function') {
643
- return await out
644
- }
645
- if (typeof out === 'number') {
646
- return out
638
+ if (group === 'release') {
639
+ const releaseChoice = await selectPrompt({
640
+ message: 'Create release',
641
+ choices: [
642
+ { name: 'Patch', value: 'patch' },
643
+ { name: 'Minor', value: 'minor' },
644
+ { name: 'Major', value: 'major' },
645
+ { name: 'Cancel', value: 'cancel' },
646
+ ],
647
+ default: 'patch',
648
+ loop: false,
649
+ })
650
+ if (releaseChoice === 'cancel') continue
651
+ return runSelectedAction(() => runReleaseScript([releaseChoice]))
652
+ }
653
+
654
+ if (group === 'lint-typecheck') {
655
+ const choice = await selectPrompt({
656
+ message: 'Lint and typecheck',
657
+ choices: [
658
+ { name: 'Lint and typecheck', value: 'both' },
659
+ { name: 'Lint', value: 'lint' },
660
+ { name: 'Typecheck', value: 'typecheck' },
661
+ { name: 'Cancel', value: 'cancel' },
662
+ ],
663
+ default: 'both',
664
+ loop: false,
665
+ })
666
+ if (choice === 'cancel') continue
667
+ if (choice === 'lint') return runSelectedAction(() => runBundledEslint(['.']))
668
+ if (choice === 'typecheck') {
669
+ return runSelectedAction(() => runBundledTypeScript(['--noEmit', '-p', 'tsconfig.json']))
670
+ }
671
+ const lintCode = runBundledEslint(['.'])
672
+ if (lintCode !== 0) return lintCode
673
+ return runSelectedAction(() => runBundledTypeScript(['--noEmit', '-p', 'tsconfig.json']))
674
+ }
675
+
676
+ if (group === 'poeditor') {
677
+ const choice = await selectPrompt({
678
+ message: 'Poeditor',
679
+ choices: [
680
+ { name: 'Download', value: 'download' },
681
+ { name: 'Upload', value: 'upload' },
682
+ { name: 'Cancel', value: 'cancel' },
683
+ ],
684
+ default: 'download',
685
+ loop: false,
686
+ })
687
+ if (choice === 'cancel') continue
688
+ return runSelectedAction(() => runNode(choice === 'download' ? 'poeditor-download.mjs' : 'poeditor-upload.mjs'))
689
+ }
690
+
691
+ if (group === 'dependency-policy') {
692
+ const choice = await selectPrompt({
693
+ message: 'Dependecy policy',
694
+ choices: [
695
+ { name: 'Check', value: 'check' },
696
+ { name: 'Sync', value: 'sync' },
697
+ { name: 'Cancel', value: 'cancel' },
698
+ ],
699
+ default: 'check',
700
+ loop: false,
701
+ })
702
+ if (choice === 'cancel') continue
703
+ return runSelectedAction(() =>
704
+ runNode(choice === 'check' ? 'check-dependency-policy.mjs' : 'sync-dependency-policy.mjs'),
705
+ )
706
+ }
707
+
708
+ if (group === 'sync-configs') {
709
+ const choice = await selectPrompt({
710
+ message: 'Sync configs',
711
+ choices: [
712
+ { name: 'vscode', value: 'vscode' },
713
+ { name: 'eslint', value: 'eslint' },
714
+ { name: 'prettier', value: 'prettier' },
715
+ { name: 'tsconfig', value: 'tsconfig' },
716
+ { name: 'Fallow', value: 'fallow' },
717
+ { name: 'Cursor', value: 'cursor' },
718
+ { name: 'All', value: 'all' },
719
+ { name: 'Cancel', value: 'cancel' },
720
+ ],
721
+ default: 'all',
722
+ loop: false,
723
+ })
724
+ if (choice === 'cancel') continue
725
+ return runSelectedAction(() => runSyncConfigTarget(choice))
726
+ }
727
+
728
+ if (group === 'setup') {
729
+ const choice = await selectPrompt({
730
+ message: 'Setup',
731
+ choices: [
732
+ { name: 'Lefthook', value: 'lefthook' },
733
+ { name: 'Full consumer setup', value: 'full-consumer-setup' },
734
+ { name: 'Cancel', value: 'cancel' },
735
+ ],
736
+ default: 'lefthook',
737
+ loop: false,
738
+ })
739
+ if (choice === 'cancel') continue
740
+ return runSelectedAction(() => (choice === 'lefthook' ? runSetupLefthook() : runFullConsumerSetup()))
741
+ }
742
+
743
+ if (group === 'fallow-analysis') {
744
+ const choice = await selectPrompt({
745
+ message: 'Fallow analysis',
746
+ choices: [
747
+ { name: 'dead code', value: 'dead-code' },
748
+ { name: 'Cancel', value: 'cancel' },
749
+ ],
750
+ default: 'dead-code',
751
+ loop: false,
752
+ })
753
+ if (choice === 'cancel') continue
754
+ return runSelectedAction(() => runBundledFallow([choice]))
755
+ }
756
+
757
+ if (group === 'maintainer') {
758
+ const choice = await selectPrompt({
759
+ message: 'Maintainer',
760
+ choices: [
761
+ { name: 'Show outdated dependency policy packages', value: 'm1' },
762
+ { name: 'Upgrade dependency policy (interactive)', value: 'm2' },
763
+ { name: 'Refresh dependency policy from repos (and scaffold/vue package.json)', value: 'm3' },
764
+ { name: 'Upgrade one policy package, stage, and auto-commit', value: 'm4' },
765
+ { name: 'Check policy vulnerabilities (audit)', value: 'm5' },
766
+ { name: 'Check fg-devkit repo vulnerabilities', value: 'm6' },
767
+ { name: 'Check vulnerabilities across all Vue repos', value: 'm7' },
768
+ { name: 'Bump fg-devkit in all Vue repos, run sync-deps, and pnpm install', value: 'm8' },
769
+ { name: 'Cancel', value: 'cancel' },
770
+ ],
771
+ default: 'm1',
772
+ loop: false,
773
+ })
774
+ if (choice === 'cancel') continue
775
+ const maintainerCode = maintainerOnly()
776
+ if (maintainerCode !== 0) return maintainerCode
777
+ if (choice === 'm1') return runSelectedAction(() => runNode('policy-outdated.mjs'))
778
+ if (choice === 'm2') return runSelectedAction(() => runNode('policy-upgrade.mjs'))
779
+ if (choice === 'm3') return runSelectedAction(() => runNode('refresh-dependency-policy-from-repos.mjs'))
780
+ if (choice === 'm4') return runSelectedAction(() => runMaintainerPolicyUpgradeAndCommit())
781
+ if (choice === 'm5') return runSelectedAction(() => runNode('policy-vulns.mjs'))
782
+ if (choice === 'm6') return runSelectedAction(() => runMaintainerAuditAll())
783
+ if (choice === 'm7') return runSelectedAction(() => runNode('fleet-vulns.mjs'))
784
+ if (choice === 'm8') return runSelectedAction(() => runNode('fleet-bump-fg-devkit.mjs'))
785
+ return 1
786
+ }
647
787
  }
788
+
648
789
  return 0
649
790
  }
650
791
 
@@ -705,6 +846,14 @@ async function main() {
705
846
  process.exitCode = runBundledTypeScript(rest.length > 0 ? ['--watch', ...rest] : ['--noEmit', '--watch', '-p', 'tsconfig.json'])
706
847
  return
707
848
  }
849
+ if (sub === 'fallow') {
850
+ process.exitCode = runBundledFallow(rest)
851
+ return
852
+ }
853
+ if (sub === 'dead-code') {
854
+ process.exitCode = runBundledFallow(['dead-code', ...rest])
855
+ return
856
+ }
708
857
  const script = DIRECT_COMMANDS[sub]
709
858
  if (!script) {
710
859
  console.error(`[fg-devkit] Unknown command: ${sub}`)
@@ -4,6 +4,7 @@
4
4
  * - prettier.config.js — overwritten from fg-devkit prettier sync template
5
5
  * - eslint.config.js — overwritten from fg-devkit eslint sync template
6
6
  * - tsconfig.json — overwritten from fg-devkit tsconfig app sync template
7
+ * - .fallowrc.json — overwritten from fg-devkit fallow sync template
7
8
  *
8
9
  * This command is intentionally deterministic: each run rewrites files to the shared templates.
9
10
  *
@@ -46,10 +47,11 @@ if (!fgRoot) {
46
47
  const prettierSrc = path.join(fgRoot, 'configs', 'prettier.sync-stub.js')
47
48
  const eslintStubSrc = path.join(fgRoot, 'configs', 'eslint.sync-stub.js')
48
49
  const tsconfigSrc = path.join(fgRoot, 'configs', 'tsconfig.app.sync.json')
50
+ const fallowConfigSrc = path.join(fgRoot, 'configs', 'fallowrc.sync-stub.json')
49
51
 
50
- if (!fs.existsSync(prettierSrc) || !fs.existsSync(eslintStubSrc) || !fs.existsSync(tsconfigSrc)) {
52
+ if (!fs.existsSync(prettierSrc) || !fs.existsSync(eslintStubSrc) || !fs.existsSync(tsconfigSrc) || !fs.existsSync(fallowConfigSrc)) {
51
53
  console.error(
52
- '[sync-tooling] Missing prettier.sync-stub.js, eslint.sync-stub.js, or tsconfig.app.sync.json inside fg-devkit package.',
54
+ '[sync-tooling] Missing prettier.sync-stub.js, eslint.sync-stub.js, tsconfig.app.sync.json, or fallowrc.sync-stub.json inside fg-devkit package.',
53
55
  )
54
56
  process.exit(1)
55
57
  }
@@ -58,7 +60,8 @@ try {
58
60
  fs.copyFileSync(prettierSrc, path.join(projectRoot, 'prettier.config.js'))
59
61
  fs.copyFileSync(eslintStubSrc, path.join(projectRoot, 'eslint.config.js'))
60
62
  fs.copyFileSync(tsconfigSrc, path.join(projectRoot, 'tsconfig.json'))
61
- console.log('[sync-tooling] Updated prettier.config.js, eslint.config.js, and tsconfig.json.')
63
+ fs.copyFileSync(fallowConfigSrc, path.join(projectRoot, '.fallowrc.json'))
64
+ console.log('[sync-tooling] Updated prettier.config.js, eslint.config.js, tsconfig.json, and .fallowrc.json.')
62
65
  } catch (err) {
63
66
  console.error(`[sync-tooling] ${err.message}`)
64
67
  process.exit(1)