fg-devkit 1.12.14 → 1.14.3

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
@@ -102,7 +102,7 @@ Check vulnerabilities for all policy packages together (including transitive dep
102
102
  pnpm exec fgd policy-vulns
103
103
  ```
104
104
 
105
- Check vulnerabilities for all dependencies in the current repo:
105
+ Check vulnerabilities for all dependencies in the current repo (and nested packages with their own `pnpm-lock.yaml`):
106
106
 
107
107
  ```bash
108
108
  pnpm exec fgd repo-vulns
@@ -126,7 +126,8 @@ If npm audit is rate-limited (429), slow the scan down:
126
126
  pnpm exec fgd fleet-vulns --delay-ms=8000
127
127
  ```
128
128
 
129
- Upgrade policy versions interactively:
129
+ Upgrade policy versions interactively and auto-commit
130
+ (`dep(package): bump to version`, or `dep(): bump N packages` with a body listing each bump):
130
131
 
131
132
  ```bash
132
133
  pnpm exec fgd policy-upgrade
@@ -138,13 +139,15 @@ Sync dependency policy, run pnpm install, and auto-commit (`chore(repo): sync de
138
139
  pnpm exec fgd sync-deps-commit
139
140
  ```
140
141
 
141
- Upgrade one package.json dependency and auto-commit (`dep(package): bump to version`):
142
+ Upgrade one or more package.json dependencies and auto-commit
143
+ (`dep(package): bump to version`, or `dep(): bump N packages` with a body listing each bump):
142
144
 
143
145
  ```bash
144
146
  pnpm exec fgd policy-upgrade-commit
145
147
  ```
146
148
 
147
- Fix one vulnerability override interactively and auto-commit (`vuln(package): override version`):
149
+ Fix one or more vulnerability overrides interactively and auto-commit
150
+ (`vuln(package): override version`, or `vuln(): override N packages` with a body listing each change):
148
151
 
149
152
  ```bash
150
153
  pnpm exec fgd audit-override-commit
@@ -5,7 +5,7 @@
5
5
  [changelog]
6
6
  body = """
7
7
  {% if version %}\
8
- ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d %H:%M") }}
8
+ ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
9
9
  {% else %}\
10
10
  ## [unreleased]
11
11
  {% endif %}\
@@ -5,7 +5,7 @@
5
5
  [changelog]
6
6
  body = """
7
7
  {% if version %}\
8
- ## Version {{ version | trim_start_matches(pat="v") }} - {{ timestamp | date(format="%Y-%m-%d %H:%M") }}
8
+ ## Version {{ version | trim_start_matches(pat="v") }} - {{ timestamp | date(format="%Y-%m-%d") }}
9
9
  {% else %}\
10
10
  ## [unreleased]
11
11
  {% endif %}\
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "toolchain": {
3
- "packageManager": "pnpm@11.20.0",
3
+ "packageManager": "pnpm@11.21.0",
4
4
  "node": {
5
5
  "nvmrc": "22",
6
6
  "engines": ">=22.13.0",
@@ -10,16 +10,17 @@
10
10
  },
11
11
  "managedDependencies": {
12
12
  "dependencies": {
13
+ "@e965/xlsx": "0.20.3",
13
14
  "@element-plus/icons-vue": "2.3.2",
14
15
  "@lexical/html": "0.49.0",
15
16
  "@lexical/list": "0.49.0",
16
17
  "@lexical/rich-text": "0.49.0",
18
+ "@lexical/link": "0.49.0",
17
19
  "@piwikpro/vue-piwik-pro": "1.7.1",
18
- "@sentry/browser": "10.69.0",
20
+ "@sentry/browser": "10.70.0",
19
21
  "@sentry/tracing": "7.120.4",
20
- "@sentry/vue": "10.69.0",
21
- "@sentry/wasm": "10.69.0",
22
- "@tailwindcss/postcss": "4.3.3",
22
+ "@sentry/vue": "10.70.0",
23
+ "@sentry/wasm": "10.70.0",
23
24
  "@tanstack/vue-query": "5.101.4",
24
25
  "@types/leaflet": "1.9.22",
25
26
  "@unhead/vue": "2.0.5",
@@ -31,7 +32,7 @@
31
32
  "chart.js": "4.5.1",
32
33
  "content-disposition-header": "0.6.0",
33
34
  "date-fns": "4.4.0",
34
- "element-plus": "2.14.3",
35
+ "element-plus": "2.14.4",
35
36
  "fds-vue-core": "8.4.3",
36
37
  "imask": "7.6.1",
37
38
  "leaflet": "1.9.4",
@@ -60,11 +61,11 @@
60
61
  "@eslint/js": "9.39.1",
61
62
  "@intlify/unplugin-vue-i18n": "11.2.4",
62
63
  "@playwright/test": "1.62.1",
63
- "@storybook/addon-a11y": "10.5.6",
64
- "@storybook/addon-docs": "10.5.6",
65
- "@storybook/addon-vitest": "10.5.6",
66
- "@storybook/vue3": "10.5.6",
67
- "@storybook/vue3-vite": "10.5.6",
64
+ "@storybook/addon-a11y": "10.5.7",
65
+ "@storybook/addon-docs": "10.5.7",
66
+ "@storybook/addon-vitest": "10.5.7",
67
+ "@storybook/vue3": "10.5.7",
68
+ "@storybook/vue3-vite": "10.5.7",
68
69
  "@tanstack/vue-query-devtools": "6.1.38",
69
70
  "@tsconfig/node22": "22.0.5",
70
71
  "@types/lodash-es": "4.17.12",
@@ -76,13 +77,12 @@
76
77
  "@vue/test-utils": "2.4.11",
77
78
  "arg": "5.0.2",
78
79
  "babel-eslint": "10.1.0",
79
- "eslint-plugin-storybook": "10.5.6",
80
+ "eslint-plugin-storybook": "10.5.7",
80
81
  "globals": "15.11.0",
81
82
  "jsdom": "26.1.0",
82
- "postcss": "8.5.26",
83
83
  "sass": "1.102.0",
84
- "storybook": "10.5.6",
85
- "tsx": "4.23.9",
84
+ "storybook": "10.5.7",
85
+ "tsx": "4.23.12",
86
86
  "vega": "6.3.1",
87
87
  "vite-plugin-dts": "4.5.4",
88
88
  "vite-plugin-vue-devtools": "8.2.1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fg-devkit",
3
- "version": "1.12.14",
3
+ "version": "1.14.3",
4
4
  "description": "Shared configs, scripts, and DX tooling for Vue / TypeScript frontend repos",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -107,13 +107,13 @@
107
107
  "@inquirer/input": "5.1.2",
108
108
  "@inquirer/select": "5.2.1",
109
109
  "@intlify/eslint-plugin-vue-i18n": "4.5.1",
110
- "@typescript-eslint/eslint-plugin": "8.66.0",
111
- "@typescript-eslint/parser": "8.66.0",
112
- "@vitest/eslint-plugin": "1.6.26",
110
+ "@typescript-eslint/eslint-plugin": "8.67.0",
111
+ "@typescript-eslint/parser": "8.67.0",
112
+ "@vitest/eslint-plugin": "1.6.27",
113
113
  "@vue/eslint-config-prettier": "10.2.0",
114
114
  "@vue/eslint-config-typescript": "14.9.0",
115
115
  "@vue/tsconfig": "0.9.1",
116
- "eslint": "10.8.0",
116
+ "eslint": "10.8.1",
117
117
  "eslint-plugin-prettier": "5.5.6",
118
118
  "eslint-plugin-vue": "10.10.0",
119
119
  "fallow": "2.101.0",
@@ -121,13 +121,19 @@
121
121
  "lefthook": "2.1.10",
122
122
  "npm-check-updates": "22.2.7",
123
123
  "prettier": "3.9.6",
124
- "tsx": "4.23.9",
124
+ "tsx": "4.23.12",
125
125
  "typescript": "5.9.3",
126
126
  "vue-eslint-parser": "10.4.1",
127
127
  "vue-tsc": "3.3.9"
128
128
  },
129
129
  "devDependencies": {
130
- "cz-git": "1.13.1",
131
- "czg": "1.13.1"
130
+ "cz-git": "1.13.2",
131
+ "czg": "1.13.2"
132
+ },
133
+ "scripts": {
134
+ "maintainer-ui": "pnpm --dir electron/maintainer-ui start",
135
+ "maintainer-ui:install": "pnpm --dir electron/maintainer-ui install",
136
+ "fgd-ui": "pnpm --dir electron/maintainer-ui start",
137
+ "fgd-ui:install": "pnpm --dir electron/maintainer-ui install"
132
138
  }
133
139
  }
@@ -1,22 +1,18 @@
1
1
  import type { ChangelogRelease } from './types'
2
2
 
3
+ export const toChangelogDateOnly = (date: string): string => {
4
+ const dateMatch = date.match(/^(\d{4}-\d{2}-\d{2})(?: \d{2}:\d{2})?$/)
5
+ return dateMatch?.[1] ?? date
6
+ }
7
+
3
8
  export const formatChangelogDate = (date: string): string => {
4
- const dateTimeMatch = date.match(/^(\d{4}-\d{2}-\d{2})(?: (\d{2}:\d{2}))?$/)
5
- if (!dateTimeMatch?.[1]) {
9
+ const datePart = toChangelogDateOnly(date)
10
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(datePart)) {
6
11
  return date
7
12
  }
8
13
 
9
- const datePart = dateTimeMatch[1]
10
- const timePart = dateTimeMatch[2]
11
- const parsed = timePart ? new Date(`${datePart}T${timePart}:00`) : new Date(`${datePart}T12:00:00`)
12
- const formattedDate = parsed.toLocaleDateString('sv-SE', { dateStyle: 'long' })
13
-
14
- if (!timePart) {
15
- return formattedDate
16
- }
17
-
18
- const formattedTime = parsed.toLocaleTimeString('sv-SE', { hour: '2-digit', minute: '2-digit' })
19
- return `${formattedDate} ${formattedTime}`
14
+ const parsed = new Date(`${datePart}T12:00:00`)
15
+ return parsed.toLocaleDateString('sv-SE', { dateStyle: 'long' })
20
16
  }
21
17
 
22
18
  const CONVENTIONAL_COMMIT_RE = /^(feat|fix|perf|refactor|chore|dep|vuln)(\([^)]+\))?:\s/
@@ -1,8 +1,11 @@
1
- import { sortUserReleaseSections } from './format'
1
+ import { sortUserReleaseSections, toChangelogDateOnly } from './format'
2
2
  import type { ChangelogRelease } from './types'
3
3
 
4
4
  export const serializeUserChangelogRelease = (release: ChangelogRelease): string => {
5
- const lines: string[] = [`## Version ${release.version} - ${release.date}`, '']
5
+ const lines: string[] = [
6
+ `## Version ${release.version} - ${toChangelogDateOnly(release.date)}`,
7
+ '',
8
+ ]
6
9
 
7
10
  for (const section of sortUserReleaseSections(release.sections)) {
8
11
  if (section.items.length === 0) {
@@ -3,7 +3,6 @@ import path from 'node:path'
3
3
  import { fileURLToPath } from 'node:url'
4
4
  import { parseUserChangelog } from './parseChangelog.ts'
5
5
  import { serializeUserChangelogRelease } from './serializeUserChangelog.ts'
6
- import type { ChangelogRelease } from './types.ts'
7
6
 
8
7
  const rootDir = process.env.FG_DEVKIT_CWD
9
8
  ? path.resolve(process.env.FG_DEVKIT_CWD)
@@ -14,36 +13,6 @@ const contentPath = path.join(rootDir, 'CHANGELOG.user.md')
14
13
  const readMarkdown = (filePath: string): string =>
15
14
  existsSync(filePath) ? readFileSync(filePath, 'utf-8') : ''
16
15
 
17
- const updateVersionTimestamps = (
18
- content: string,
19
- draftReleases: ChangelogRelease[],
20
- ): { content: string; versions: string[] } => {
21
- const versions: string[] = []
22
- let result = content
23
-
24
- for (const release of draftReleases) {
25
- const dateTimeMatch = release.date.match(/^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2})$/)
26
- if (!dateTimeMatch) {
27
- continue
28
- }
29
-
30
- const [, datePart, timePart] = dateTimeMatch
31
- const headingWithoutTime = `## Version ${release.version} - ${datePart}`
32
- const headingWithTime = `## Version ${release.version} - ${datePart} ${timePart}`
33
-
34
- if (result.includes(headingWithTime)) {
35
- continue
36
- }
37
-
38
- if (result.includes(headingWithoutTime)) {
39
- result = result.replaceAll(headingWithoutTime, headingWithTime)
40
- versions.push(release.version)
41
- }
42
- }
43
-
44
- return { content: result, versions }
45
- }
46
-
47
16
  const draft = parseUserChangelog(readMarkdown(draftPath))
48
17
  const existingContent = readMarkdown(contentPath).trimEnd()
49
18
  const existing = existingContent.length > 0 ? parseUserChangelog(existingContent) : []
@@ -51,34 +20,17 @@ const existing = existingContent.length > 0 ? parseUserChangelog(existingContent
51
20
  const existingVersions = new Set(existing.map((release) => release.version))
52
21
  const newReleases = draft.filter((release) => !existingVersions.has(release.version))
53
22
 
54
- let mergedContent = existingContent
55
-
56
- if (newReleases.length > 0) {
57
- const newBlocks = newReleases.map((release) => serializeUserChangelogRelease(release)).join('\n\n')
58
- mergedContent =
59
- existingContent.length > 0 ? `${newBlocks}\n\n${existingContent}` : newBlocks
60
- }
61
-
62
- const { content: contentWithTimestamps, versions: updatedTimestamps } = updateVersionTimestamps(
63
- mergedContent,
64
- draft,
65
- )
66
-
67
- if (newReleases.length === 0 && updatedTimestamps.length === 0) {
23
+ if (newReleases.length === 0) {
68
24
  process.stdout.write('No user changelog changes to sync.\n')
69
25
  process.exit(0)
70
26
  }
71
27
 
72
- writeFileSync(contentPath, `${contentWithTimestamps.trimEnd()}\n`)
28
+ const newBlocks = newReleases.map((release) => serializeUserChangelogRelease(release)).join('\n\n')
29
+ const mergedContent =
30
+ existingContent.length > 0 ? `${newBlocks}\n\n${existingContent}` : newBlocks
73
31
 
74
- if (newReleases.length > 0) {
75
- process.stdout.write(
76
- `Added user changelog version(s): ${newReleases.map((release) => release.version).join(', ')}\n`,
77
- )
78
- }
32
+ writeFileSync(contentPath, `${mergedContent.trimEnd()}\n`)
79
33
 
80
- if (updatedTimestamps.length > 0) {
81
- process.stdout.write(
82
- `Updated timestamps in CHANGELOG.user.md for version(s): ${updatedTimestamps.join(', ')}\n`,
83
- )
84
- }
34
+ process.stdout.write(
35
+ `Added user changelog version(s): ${newReleases.map((release) => release.version).join(', ')}\n`,
36
+ )
@@ -15,7 +15,6 @@
15
15
  "poeditor:upload": "pnpm exec fg-devkit poeditor-upload"
16
16
  },
17
17
  "dependencies": {
18
- "@tailwindcss/postcss": "4.3.3",
19
18
  "@tailwindcss/vite": "4.3.3",
20
19
  "@tanstack/vue-query": "5.101.4",
21
20
  "axios": "1.19.0",
@@ -34,12 +33,12 @@
34
33
  "@tanstack/vue-query-devtools": "6.1.38",
35
34
  "@types/node": "22.16.5",
36
35
  "@vitejs/plugin-vue": "6.0.8",
37
- "fg-devkit": "1.12.14",
36
+ "fg-devkit": "1.14.3",
38
37
  "tailwindcss": "4.3.3",
39
- "tsx": "4.23.9",
38
+ "tsx": "4.23.12",
40
39
  "vite": "7.3.5"
41
40
  },
42
- "packageManager": "pnpm@11.20.0",
41
+ "packageManager": "pnpm@11.21.0",
43
42
  "engines": {
44
43
  "node": ">=22.13.0"
45
44
  }
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Fix one vulnerability via `pnpm audit --fix override -i`, install, then auto-commit.
3
+ * Fix one or more vulnerabilities via `pnpm audit --fix override -i`, install, then auto-commit.
4
4
  *
5
5
  * Usage:
6
6
  * pnpm exec fg-devkit audit-override-commit [pnpm-audit-args...]
@@ -150,14 +150,11 @@ function main() {
150
150
  console.log('[audit-override-commit] No override changes detected.')
151
151
  process.exit(0)
152
152
  }
153
- if (changed.length > 1) {
154
- console.log('[audit-override-commit] Multiple override changes detected:')
155
- for (const item of changed) {
156
- const fromLabel = item.from ?? '(missing)'
157
- console.log(` - ${item.name}: ${fromLabel} -> ${item.to}`)
158
- }
159
- console.log('[audit-override-commit] Auto-commit supports exactly one override change. Commit manually.')
160
- process.exit(1)
153
+
154
+ console.log('[audit-override-commit] Override changes detected:')
155
+ for (const item of changed) {
156
+ const fromLabel = item.from ?? '(missing)'
157
+ console.log(` - ${item.name}: ${fromLabel} -> ${item.to}`)
161
158
  }
162
159
 
163
160
  const installExitCode = runPnpm(['install'])
@@ -165,16 +162,30 @@ function main() {
165
162
  process.exit(installExitCode)
166
163
  }
167
164
 
168
- const [only] = changed
169
165
  const stageExitCode = stageExistingFiles(['package.json', 'pnpm-lock.yaml', 'pnpm-workspace.yaml'])
170
166
  if (stageExitCode !== 0) {
171
167
  process.exit(stageExitCode)
172
168
  }
173
169
 
174
- const commitMessage = `vuln(${only.name}): override ${only.to}`
175
- console.log(`[audit-override-commit] Staged override for ${only.name} -> ${only.to}`)
176
- console.log(`[audit-override-commit] Auto-committing: ${commitMessage}`)
177
- process.exit(runGit(['commit', '-m', commitMessage]))
170
+ const { subject, body } = buildCommitMessage(changed)
171
+ console.log(`[audit-override-commit] Auto-committing: ${subject}`)
172
+ if (body) {
173
+ console.log(body)
174
+ }
175
+ const commitArgs = body ? ['commit', '-m', subject, '-m', body] : ['commit', '-m', subject]
176
+ process.exit(runGit(commitArgs))
177
+ }
178
+
179
+ function buildCommitMessage(changed) {
180
+ if (changed.length === 1) {
181
+ const only = changed[0]
182
+ return { subject: `vuln(${only.name}): override ${only.to}`, body: '' }
183
+ }
184
+
185
+ const body = changed
186
+ .map((item) => `- ${item.name}: ${item.from ?? '(missing)'} -> ${item.to}`)
187
+ .join('\n')
188
+ return { subject: `vuln(): override ${changed.length} packages`, body }
178
189
  }
179
190
 
180
191
  main()
@@ -503,7 +503,7 @@ Commands:
503
503
  sync-deps-commit [policy-file] Sync dependency policy, pnpm install, then auto-commit
504
504
  policy-outdated [policy-file] Show outdated policy packages via pnpm outdated
505
505
  policy-vulns [policy-file] Check all policy packages together (including transitives) via pnpm audit
506
- repo-vulns Check all repo dependencies for vulnerabilities via pnpm audit
506
+ repo-vulns Check repo + nested lockfile packages via pnpm audit
507
507
  fleet-vulns [projects-root] [--summary-only] [--delay-ms=<ms>]
508
508
  Run pnpm audit across Vue repos (full output + combined summary)
509
509
  fleet-bump-fg-devkit [projects-root] [--dry-run]
@@ -535,11 +535,11 @@ Commands:
535
535
  Maintainer: discard tracked git changes in chosen repositories
536
536
  Options: --dry-run, --fg-devkit-only, --pick, --all, --only=a,b
537
537
  policy-upgrade [policy-file] [ncu-args...]
538
- Upgrade policy packages via npm-check-updates (default interactive)
538
+ Upgrade policy packages via ncu (interactive) and auto-commit
539
539
  policy-upgrade-commit [ncu-args...]
540
- Upgrade one consumer package.json package and auto-commit
540
+ Upgrade one or more consumer package.json packages and auto-commit
541
541
  audit-override-commit [pnpm-audit-args...]
542
- Fix one vulnerability override via pnpm audit and auto-commit
542
+ Fix one or more vulnerability overrides via pnpm audit and auto-commit
543
543
  sync-project-configs VS Code + tooling sync (overwrites config files)
544
544
  sync-tooling-configs Tooling sync only (overwrites config files)
545
545
  sync-vscode-settings Copy shared VS Code settings into .vscode/settings.json
@@ -785,8 +785,8 @@ async function runInteractiveMenu() {
785
785
  choices: [
786
786
  { name: 'Check', value: 'check' },
787
787
  { name: 'Sync and auto-commit', value: 'sync-commit' },
788
- { name: 'Upgrade one package.json package and auto-commit', value: 'upgrade-commit' },
789
- { name: 'Fix one vulnerability override and auto-commit', value: 'audit-override-commit' },
788
+ { name: 'Upgrade package.json packages and auto-commit', value: 'upgrade-commit' },
789
+ { name: 'Fix vulnerability overrides and auto-commit', value: 'audit-override-commit' },
790
790
  { name: 'Cancel', value: 'cancel' },
791
791
  ],
792
792
  default: 'check',
@@ -857,7 +857,7 @@ async function runInteractiveMenu() {
857
857
  message: 'Maintainer',
858
858
  choices: [
859
859
  { name: 'Show outdated dependency policy packages', value: 'm1' },
860
- { name: 'Upgrade dependency policy (interactive)', value: 'm2' },
860
+ { name: 'Upgrade dependency policy (interactive) and auto-commit', value: 'm2' },
861
861
  { name: 'Refresh dependency policy from repos (and scaffold/vue package.json)', value: 'm3' },
862
862
  { name: 'Check policy vulnerabilities (audit)', value: 'm4' },
863
863
  { name: 'Check fg-devkit repo vulnerabilities', value: 'm5' },
@@ -3,47 +3,86 @@
3
3
  * Bumps fg-devkit to latest in Vue repositories, syncs dependency policy, and runs pnpm install.
4
4
  *
5
5
  * Usage:
6
- * pnpm exec fg-devkit fleet-bump-fg-devkit [projects-root] [--dry-run]
6
+ * pnpm exec fg-devkit fleet-bump-fg-devkit [projects-root] [options]
7
+ *
8
+ * Options:
9
+ * --dry-run Print planned bumps without changing files or installing
10
+ * --all Run on all matching Vue repos (default when not picking)
11
+ * --fg-devkit-only No-op for this command (fg-devkit is not a consumer of itself)
12
+ * --pick Interactive checkbox (TTY); prefer --only= from automation/UI
13
+ * --only=a,b Run only on matching repos (basename or path under projects root)
7
14
  */
8
15
 
16
+ import fs from 'node:fs'
9
17
  import path from 'node:path'
10
18
  import { spawnSync } from 'node:child_process'
11
19
  import { fileURLToPath } from 'node:url'
12
20
 
13
21
  import { collectFleetVueRepos } from './fleet-vue-repos.mjs'
22
+ import { parseFleetSelectionFlags, resolveFleetRepos } from './fleet-repo-picker.mjs'
14
23
 
15
24
  const scriptDir = path.dirname(fileURLToPath(import.meta.url))
16
25
  const cliArgs = process.argv.slice(2)
17
26
  const dryRun = cliArgs.includes('--dry-run')
18
- const projectsRootArg = cliArgs.find((a) => !a.startsWith('-'))
19
- const projectsRoot = projectsRootArg ? path.resolve(process.cwd(), projectsRootArg) : path.resolve(scriptDir, '../..')
27
+ const fgDevkitOnly = cliArgs.includes('--fg-devkit-only')
28
+ const { pick, all, only, filteredArgs: positionalArgs } = parseFleetSelectionFlags(cliArgs)
29
+ const projectsRootArg = positionalArgs.find((a) => !a.startsWith('-'))
30
+ const projectsRoot = projectsRootArg
31
+ ? path.resolve(process.cwd(), projectsRootArg)
32
+ : path.resolve(scriptDir, '../..')
33
+
34
+ const LOG_PREFIX = '[fleet-bump-fg-devkit]'
35
+
36
+ if (fgDevkitOnly) {
37
+ console.log(
38
+ `${LOG_PREFIX} Nothing to do for fg-devkit only — bump targets Vue consumer repos that depend on fg-devkit.`,
39
+ )
40
+ process.exit(0)
41
+ }
42
+
43
+ if (!fs.existsSync(projectsRoot)) {
44
+ console.error(`${LOG_PREFIX} projects root not found: ${projectsRoot}`)
45
+ process.exit(1)
46
+ }
47
+
48
+ const allRepos = collectFleetVueRepos(projectsRoot)
49
+ if (allRepos.length === 0) {
50
+ console.log(`${LOG_PREFIX} No Vue repositories with fg-devkit found under ${projectsRoot}`)
51
+ process.exit(0)
52
+ }
53
+
54
+ const uniqueRepos = await resolveFleetRepos(allRepos, projectsRoot, {
55
+ pick,
56
+ only,
57
+ all: all || (!pick && !only),
58
+ message: 'Select Vue repositories to bump fg-devkit',
59
+ })
20
60
 
21
- const uniqueRepos = collectFleetVueRepos(projectsRoot)
22
61
  if (uniqueRepos.length === 0) {
23
- console.log(`[fleet-bump-fg-devkit] No Vue repositories with fg-devkit found under ${projectsRoot}`)
62
+ console.log(`${LOG_PREFIX} No repositories selected.`)
24
63
  process.exit(0)
25
64
  }
26
65
 
27
- console.log(`[fleet-bump-fg-devkit] projectsRoot=${projectsRoot}`)
28
- console.log(`[fleet-bump-fg-devkit] repositories=${uniqueRepos.length}`)
66
+ console.log(`${LOG_PREFIX} projectsRoot=${projectsRoot}`)
67
+ console.log(`${LOG_PREFIX} repositories=${uniqueRepos.length}`)
29
68
 
30
69
  let failures = 0
31
70
  let updated = 0
32
71
 
33
72
  for (const repoDir of uniqueRepos) {
34
- const repoName = path.basename(repoDir)
73
+ const repoName = path.relative(projectsRoot, repoDir).split(path.sep).join('/') || path.basename(repoDir)
35
74
  const bumpArgs = ['up', 'fg-devkit@latest']
36
75
  const syncDepsArgs = ['exec', 'fg-devkit', 'sync-deps']
37
76
  const installArgs = ['install']
38
77
 
39
78
  if (dryRun) {
40
- console.log(`[fleet-bump-fg-devkit] DRY RUN ${repoName}: pnpm ${bumpArgs.join(' ')}`)
41
- console.log(`[fleet-bump-fg-devkit] DRY RUN ${repoName}: pnpm ${syncDepsArgs.join(' ')}`)
42
- console.log(`[fleet-bump-fg-devkit] DRY RUN ${repoName}: pnpm ${installArgs.join(' ')}`)
79
+ console.log(`${LOG_PREFIX} DRY RUN ${repoName}: pnpm ${bumpArgs.join(' ')}`)
80
+ console.log(`${LOG_PREFIX} DRY RUN ${repoName}: pnpm ${syncDepsArgs.join(' ')}`)
81
+ console.log(`${LOG_PREFIX} DRY RUN ${repoName}: pnpm ${installArgs.join(' ')}`)
43
82
  continue
44
83
  }
45
84
 
46
- console.log(`\n[fleet-bump-fg-devkit] Updating ${repoName}`)
85
+ console.log(`\n${LOG_PREFIX} Updating ${repoName}`)
47
86
  const bump = spawnSync('pnpm', bumpArgs, {
48
87
  cwd: repoDir,
49
88
  stdio: 'inherit',
@@ -56,14 +95,14 @@ for (const repoDir of uniqueRepos) {
56
95
 
57
96
  if (bump.error) {
58
97
  failures += 1
59
- console.error(`[fleet-bump-fg-devkit] FAIL ${repoName}: ${bump.error.message}`)
98
+ console.error(`${LOG_PREFIX} FAIL ${repoName}: ${bump.error.message}`)
60
99
  continue
61
100
  }
62
101
 
63
102
  const bumpStatus = bump.status ?? 1
64
103
  if (bumpStatus !== 0) {
65
104
  failures += 1
66
- console.error(`[fleet-bump-fg-devkit] FAIL ${repoName}: pnpm up exited with code ${bumpStatus}`)
105
+ console.error(`${LOG_PREFIX} FAIL ${repoName}: pnpm up exited with code ${bumpStatus}`)
67
106
  continue
68
107
  }
69
108
 
@@ -79,14 +118,14 @@ for (const repoDir of uniqueRepos) {
79
118
 
80
119
  if (syncDeps.error) {
81
120
  failures += 1
82
- console.error(`[fleet-bump-fg-devkit] FAIL ${repoName}: ${syncDeps.error.message}`)
121
+ console.error(`${LOG_PREFIX} FAIL ${repoName}: ${syncDeps.error.message}`)
83
122
  continue
84
123
  }
85
124
 
86
125
  const syncDepsStatus = syncDeps.status ?? 1
87
126
  if (syncDepsStatus !== 0) {
88
127
  failures += 1
89
- console.error(`[fleet-bump-fg-devkit] FAIL ${repoName}: sync-deps exited with code ${syncDepsStatus}`)
128
+ console.error(`${LOG_PREFIX} FAIL ${repoName}: sync-deps exited with code ${syncDepsStatus}`)
90
129
  continue
91
130
  }
92
131
 
@@ -102,20 +141,20 @@ for (const repoDir of uniqueRepos) {
102
141
 
103
142
  if (install.error) {
104
143
  failures += 1
105
- console.error(`[fleet-bump-fg-devkit] FAIL ${repoName}: ${install.error.message}`)
144
+ console.error(`${LOG_PREFIX} FAIL ${repoName}: ${install.error.message}`)
106
145
  continue
107
146
  }
108
147
 
109
148
  const status = install.status ?? 1
110
149
  if (status !== 0) {
111
150
  failures += 1
112
- console.error(`[fleet-bump-fg-devkit] FAIL ${repoName}: pnpm install exited with code ${status}`)
151
+ console.error(`${LOG_PREFIX} FAIL ${repoName}: pnpm install exited with code ${status}`)
113
152
  continue
114
153
  }
115
154
 
116
155
  updated += 1
117
- console.log(`[fleet-bump-fg-devkit] OK ${repoName}`)
156
+ console.log(`${LOG_PREFIX} OK ${repoName}`)
118
157
  }
119
158
 
120
- console.log(`\n[fleet-bump-fg-devkit] Done: updated=${updated}, failed=${failures}`)
159
+ console.log(`\n${LOG_PREFIX} Done: updated=${updated}, failed=${failures}`)
121
160
  process.exit(failures > 0 ? 1 : 0)
@@ -4,7 +4,7 @@
4
4
  * pnpm audit output and a combined summary.
5
5
  *
6
6
  * Usage:
7
- * pnpm exec fg-devkit fleet-vulns [projects-root] [--summary-only] [--delay-ms=<ms>]
7
+ * pnpm exec fg-devkit fleet-vulns [projects-root] [--summary-only] [--delay-ms=<ms>] [--dry-run] [--only=a,b]
8
8
  */
9
9
 
10
10
  import fs from 'node:fs'
@@ -12,10 +12,13 @@ import path from 'node:path'
12
12
  import { spawnSync } from 'node:child_process'
13
13
  import { fileURLToPath } from 'node:url'
14
14
  import { isFgDevkitRepoDir } from './fleet-project-repos.mjs'
15
+ import { filterReposByOnly, parseFleetOnlyArg } from './fleet-repo-picker.mjs'
15
16
 
16
17
  const scriptDir = path.dirname(fileURLToPath(import.meta.url))
17
18
  const cliArgs = process.argv.slice(2)
18
19
  const summaryOnly = cliArgs.includes('--summary-only')
20
+ const dryRun = cliArgs.includes('--dry-run')
21
+ const only = parseFleetOnlyArg(cliArgs)
19
22
  const projectsRootArg = cliArgs.find((a) => !a.startsWith('-'))
20
23
  const projectsRoot = projectsRootArg ? path.resolve(process.cwd(), projectsRootArg) : path.resolve(scriptDir, '../..')
21
24
  const delayArg = cliArgs.find((a) => a.startsWith('--delay-ms='))
@@ -63,7 +66,16 @@ for (const packageJsonPath of packageJsonFiles) {
63
66
  }
64
67
  }
65
68
 
66
- const uniqueRepos = [...new Set(repos)].sort((a, b) => a.localeCompare(b))
69
+ const uniqueReposAll = [...new Set(repos)].sort((a, b) => a.localeCompare(b))
70
+ let uniqueRepos = uniqueReposAll
71
+ if (only?.length) {
72
+ uniqueRepos = filterReposByOnly(uniqueReposAll, projectsRoot, only)
73
+ if (uniqueRepos.length === 0) {
74
+ console.error(`[fleet-vulns] No repositories matched --only=${only.join(',')}`)
75
+ process.exit(1)
76
+ }
77
+ }
78
+
67
79
  if (uniqueRepos.length === 0) {
68
80
  console.log(`[fleet-vulns] No pnpm repositories found under ${projectsRoot}`)
69
81
  process.exit(0)
@@ -72,6 +84,16 @@ if (uniqueRepos.length === 0) {
72
84
  console.log(`[fleet-vulns] projectsRoot=${projectsRoot}`)
73
85
  console.log(`[fleet-vulns] repositories=${uniqueRepos.length}`)
74
86
 
87
+ if (dryRun) {
88
+ console.log('[fleet-vulns] DRY RUN — would audit:')
89
+ for (const repoDir of uniqueRepos) {
90
+ const rel = path.relative(projectsRoot, repoDir).split(path.sep).join('/') || path.basename(repoDir)
91
+ const hasLock = fs.existsSync(path.join(repoDir, 'pnpm-lock.yaml'))
92
+ console.log(` - ${rel}${hasLock ? '' : ' (missing pnpm-lock.yaml → skip)'}`)
93
+ }
94
+ process.exit(0)
95
+ }
96
+
75
97
  const results = []
76
98
  let hasFailures = false
77
99
  let hasVulnerabilities = false
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Upgrade one package in the consumer package.json, then auto-commit.
3
+ * Upgrade one or more packages in the consumer package.json, then auto-commit.
4
4
  *
5
5
  * Usage:
6
6
  * pnpm exec fg-devkit policy-upgrade-commit [ncu-args...]
@@ -139,25 +139,34 @@ function main() {
139
139
  console.log('[policy-upgrade-commit] No package version changes detected in package.json.')
140
140
  process.exit(0)
141
141
  }
142
- if (changed.length > 1) {
143
- console.log('[policy-upgrade-commit] Multiple package changes detected in package.json:')
144
- for (const item of changed) {
145
- console.log(` - ${item.name}: ${item.from} -> ${item.to}`)
146
- }
147
- console.log('[policy-upgrade-commit] Auto-commit supports exactly one changed package. Commit manually.')
148
- process.exit(1)
142
+
143
+ console.log('[policy-upgrade-commit] Package version changes detected in package.json:')
144
+ for (const item of changed) {
145
+ console.log(` - ${item.name}: ${item.from} -> ${item.to}`)
149
146
  }
150
147
 
151
- const [only] = changed
152
148
  const stageExitCode = stageExistingFiles(['package.json', 'pnpm-lock.yaml', 'pnpm-workspace.yaml'])
153
149
  if (stageExitCode !== 0) {
154
150
  process.exit(stageExitCode)
155
151
  }
156
152
 
157
- const commitMessage = `dep(${only.name}): bump to ${only.to}`
158
- console.log(`[policy-upgrade-commit] Staged changes for ${only.name} -> ${only.to}`)
159
- console.log(`[policy-upgrade-commit] Auto-committing: ${commitMessage}`)
160
- process.exit(runGit(['commit', '-m', commitMessage]))
153
+ const { subject, body } = buildCommitMessage(changed)
154
+ console.log(`[policy-upgrade-commit] Auto-committing: ${subject}`)
155
+ if (body) {
156
+ console.log(body)
157
+ }
158
+ const commitArgs = body ? ['commit', '-m', subject, '-m', body] : ['commit', '-m', subject]
159
+ process.exit(runGit(commitArgs))
160
+ }
161
+
162
+ function buildCommitMessage(changed) {
163
+ if (changed.length === 1) {
164
+ const only = changed[0]
165
+ return { subject: `dep(${only.name}): bump to ${only.to}`, body: '' }
166
+ }
167
+
168
+ const body = changed.map((item) => `- ${item.name}: ${item.from} -> ${item.to}`).join('\n')
169
+ return { subject: `dep(): bump ${changed.length} packages`, body }
161
170
  }
162
171
 
163
172
  main()
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
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.
3
+ * Upgrades dependency policy package versions through npm-check-updates, then auto-commits.
4
+ * It projects the policy into a temporary package.json, runs ncu, writes selected versions
5
+ * back to the policy file, syncs scaffold, and commits.
6
6
  *
7
7
  * Usage:
8
8
  * pnpm exec fg-devkit policy-upgrade [policy-file] [ncu-args...]
@@ -22,6 +22,7 @@ import { syncScaffoldVueDepsFromPolicy } from './sync-scaffold-vue-deps.mjs'
22
22
 
23
23
  const require = createRequire(import.meta.url)
24
24
  const scriptDir = path.dirname(fileURLToPath(import.meta.url))
25
+ const fgDevkitRoot = path.resolve(scriptDir, '..')
25
26
 
26
27
  if (process.argv.includes('--help') || process.argv.includes('-h')) {
27
28
  console.log(`Usage:
@@ -42,7 +43,60 @@ function readJson(p) {
42
43
  return JSON.parse(fs.readFileSync(p, 'utf8'))
43
44
  }
44
45
 
46
+ function collectPolicyVersions(policy) {
47
+ return {
48
+ ...(policy.managedDependencies?.dependencies ?? {}),
49
+ ...(policy.managedDependencies?.devDependencies ?? {}),
50
+ ...(policy.managedAnywhere ?? {}),
51
+ }
52
+ }
53
+
54
+ function collectVersionChanges(before, after) {
55
+ const changed = []
56
+ for (const [name, newVersion] of Object.entries(after)) {
57
+ const previousVersion = before[name]
58
+ if (previousVersion !== undefined && previousVersion !== newVersion) {
59
+ changed.push({ name, from: previousVersion, to: newVersion })
60
+ }
61
+ }
62
+ return changed
63
+ }
64
+
65
+ function buildCommitMessage(changed) {
66
+ if (changed.length === 1) {
67
+ const only = changed[0]
68
+ return { subject: `dep(${only.name}): bump to ${only.to}`, body: '' }
69
+ }
70
+
71
+ const body = changed.map((item) => `- ${item.name}: ${item.from} -> ${item.to}`).join('\n')
72
+ return { subject: `dep(): bump ${changed.length} packages`, body }
73
+ }
74
+
75
+ function runGit(args) {
76
+ const result = spawnSync('git', args, {
77
+ stdio: 'inherit',
78
+ cwd: fgDevkitRoot,
79
+ env: process.env,
80
+ })
81
+ if (result.error) {
82
+ console.error('[policy-upgrade]', result.error.message)
83
+ return 1
84
+ }
85
+ return result.status ?? 1
86
+ }
87
+
88
+ function stageExistingFiles(files) {
89
+ const existingFiles = files.filter((filePath) => fs.existsSync(path.join(fgDevkitRoot, filePath)))
90
+ if (existingFiles.length === 0) return 0
91
+ return runGit(['add', '--', ...existingFiles])
92
+ }
93
+
94
+ function toRepoRelative(absolutePath) {
95
+ return path.relative(fgDevkitRoot, absolutePath)
96
+ }
97
+
45
98
  const policy = readJson(policyPath)
99
+ const beforeVersions = collectPolicyVersions(policy)
46
100
 
47
101
  const depNames = new Set(Object.keys(policy.managedDependencies?.dependencies ?? {}))
48
102
  const devNames = new Set(Object.keys(policy.managedDependencies?.devDependencies ?? {}))
@@ -115,6 +169,34 @@ for (const [name, version] of Object.entries(updatedDevDeps)) {
115
169
  }
116
170
 
117
171
  fs.writeFileSync(policyPath, `${JSON.stringify(policy, null, 2)}\n`, 'utf8')
118
- const fgDevkitRoot = path.resolve(scriptDir, '..')
119
172
  syncScaffoldVueDepsFromPolicy({ fgDevkitRoot, policyPath })
120
- console.log('[policy-upgrade] Updated dependency policy from selected ncu changes.')
173
+
174
+ const afterPolicy = readJson(policyPath)
175
+ const changed = collectVersionChanges(beforeVersions, collectPolicyVersions(afterPolicy))
176
+
177
+ if (changed.length === 0) {
178
+ console.log('[policy-upgrade] No package version changes detected in dependency policy.')
179
+ process.exit(0)
180
+ }
181
+
182
+ console.log('[policy-upgrade] Package version changes detected in dependency policy:')
183
+ for (const item of changed) {
184
+ console.log(` - ${item.name}: ${item.from} -> ${item.to}`)
185
+ }
186
+
187
+ const stageExitCode = stageExistingFiles([
188
+ toRepoRelative(policyPath),
189
+ 'scaffold/vue/package.json',
190
+ 'scaffold/vue/Dockerfile.local',
191
+ ])
192
+ if (stageExitCode !== 0) {
193
+ process.exit(stageExitCode)
194
+ }
195
+
196
+ const { subject, body } = buildCommitMessage(changed)
197
+ console.log(`[policy-upgrade] Auto-committing: ${subject}`)
198
+ if (body) {
199
+ console.log(body)
200
+ }
201
+ const commitArgs = body ? ['commit', '-m', subject, '-m', body] : ['commit', '-m', subject]
202
+ process.exit(runGit(commitArgs))
@@ -1,26 +1,83 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * Checks vulnerabilities for the full dependency graph in the current repository
4
- * by running `pnpm audit` in the working directory.
4
+ * by running `pnpm audit` in the working directory and any nested packages that
5
+ * have their own pnpm-lock.yaml (e.g. electron/maintainer-ui in fg-devkit).
5
6
  *
6
7
  * Usage:
7
8
  * pnpm exec fg-devkit repo-vulns
8
9
  */
9
10
 
11
+ import fs from 'node:fs'
12
+ import path from 'node:path'
10
13
  import { spawnSync } from 'node:child_process'
11
14
 
12
- console.log(`[repo-vulns] cwd=${process.cwd()}`)
13
- const audit = spawnSync('pnpm', ['audit'], {
14
- cwd: process.cwd(),
15
- stdio: 'inherit',
16
- env: process.env,
17
- })
18
- if (audit.error) {
19
- console.error('[repo-vulns]', audit.error.message)
20
- process.exit(1)
15
+ const root = process.cwd()
16
+
17
+ const SKIP_DIR_NAMES = new Set([
18
+ 'node_modules',
19
+ '.git',
20
+ '.next',
21
+ 'dist',
22
+ 'build',
23
+ 'coverage',
24
+ 'storybook-static',
25
+ ])
26
+
27
+ /**
28
+ * @param {string} dir
29
+ * @param {string[]} [acc]
30
+ * @returns {string[]}
31
+ */
32
+ function findNestedLockfileDirs(dir, acc = []) {
33
+ for (const item of fs.readdirSync(dir, { withFileTypes: true })) {
34
+ if (!item.isDirectory()) continue
35
+ if (SKIP_DIR_NAMES.has(item.name) || item.name.startsWith('.')) continue
36
+ const child = path.join(dir, item.name)
37
+ if (
38
+ fs.existsSync(path.join(child, 'pnpm-lock.yaml')) &&
39
+ fs.existsSync(path.join(child, 'package.json'))
40
+ ) {
41
+ acc.push(child)
42
+ }
43
+ findNestedLockfileDirs(child, acc)
44
+ }
45
+ return acc
21
46
  }
22
- // pnpm audit returns exit code 1 when vulnerabilities are found.
23
- if ((audit.status ?? 1) === 1) {
24
- process.exit(0)
47
+
48
+ /**
49
+ * @param {string} dir
50
+ * @param {string} label
51
+ * @returns {number}
52
+ */
53
+ function auditDir(dir, label) {
54
+ console.log(`\n[repo-vulns] Auditing ${label}`)
55
+ console.log(`[repo-vulns] cwd=${dir}`)
56
+ const audit = spawnSync('pnpm', ['audit'], {
57
+ cwd: dir,
58
+ stdio: 'inherit',
59
+ env: process.env,
60
+ })
61
+ if (audit.error) {
62
+ console.error('[repo-vulns]', audit.error.message)
63
+ return 1
64
+ }
65
+ return audit.status ?? 1
25
66
  }
26
- process.exit(audit.status ?? 1)
67
+
68
+ const nested = findNestedLockfileDirs(root).sort((a, b) => a.localeCompare(b))
69
+ const targets = [root, ...nested]
70
+
71
+ console.log(`[repo-vulns] packages=${targets.length}`)
72
+
73
+ let worstFailure = 0
74
+
75
+ for (const dir of targets) {
76
+ const label = dir === root ? '.' : path.relative(root, dir).split(path.sep).join('/')
77
+ const code = auditDir(dir, label)
78
+ // pnpm audit returns exit code 1 when vulnerabilities are found — treat as ok.
79
+ if (code === 0 || code === 1) continue
80
+ worstFailure = code || 1
81
+ }
82
+
83
+ process.exit(worstFailure)
@@ -2,7 +2,7 @@
2
2
  * Resolves which dependency policy JSON file check/sync scripts should load.
3
3
  *
4
4
  * Used by check-dependency-policy.mjs and sync-dependency-policy.mjs so both tools share one rule:
5
- * optional argv[2] override, otherwise the packaged default next to this package.
5
+ * optional argv[2] override (ignored when it looks like a CLI flag), otherwise the packaged default.
6
6
  */
7
7
 
8
8
  import fs from 'node:fs'
@@ -17,8 +17,10 @@ const DEFAULT_POLICY = 'dependency-policy.vue-workspace.json'
17
17
  * - Relative path: if it exists under cwd, use it; else fg-devkit/configs/<arg>
18
18
  */
19
19
  export function resolveDependencyPolicyPath(scriptDir, cwd, argv) {
20
+ // Optional policy path is argv[2] only when it is not an option flag
21
+ // (e.g. `policy-upgrade --interactive` must use the default policy file).
20
22
  const arg = argv[2]
21
- if (!arg) {
23
+ if (!arg || arg.startsWith('-')) {
22
24
  return path.resolve(scriptDir, '../configs', DEFAULT_POLICY)
23
25
  }
24
26
  if (path.isAbsolute(arg)) {