fg-devkit 1.12.6 → 1.12.8

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.
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Maintainer: discard tracked git changes in chosen repositories.
4
+ *
5
+ * Usage (from fg-devkit repository root):
6
+ * pnpm exec fg-devkit fleet-git-discard-scoped [projects-root] [options]
7
+ *
8
+ * Options:
9
+ * --dry-run Print planned discards without changing files
10
+ * --fg-devkit-only Only process the fg-devkit repository
11
+ * --pick Interactive checkbox to choose repositories (default when TTY)
12
+ * --all Run on all git repositories under projects root without prompting
13
+ * --only=a,b Run only on matching repos (basename or path under projects root)
14
+ */
15
+
16
+ import fs from 'node:fs'
17
+ import path from 'node:path'
18
+ import { fileURLToPath } from 'node:url'
19
+
20
+ import { collectFleetGitRepos } from './fleet-project-repos.mjs'
21
+ import { parseFleetSelectionFlags, resolveFleetRepos } from './fleet-repo-picker.mjs'
22
+ import { runFleetGitDiscard } from './git-discard-fleet-common.mjs'
23
+
24
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url))
25
+ const fgDevkitRoot = path.resolve(scriptDir, '..')
26
+ const fgDevkitPkgPath = path.join(fgDevkitRoot, 'package.json')
27
+ const LOG_PREFIX = '[fleet-git-discard-scoped]'
28
+
29
+ const cliArgs = process.argv.slice(2)
30
+ const dryRun = cliArgs.includes('--dry-run')
31
+ const fgDevkitOnly = cliArgs.includes('--fg-devkit-only')
32
+ const { pick, all, only, filteredArgs: positionalArgs } = parseFleetSelectionFlags(cliArgs)
33
+
34
+ const projectsRootArg = positionalArgs[0]
35
+ let projectsRoot = projectsRootArg
36
+ ? path.resolve(process.cwd(), projectsRootArg)
37
+ : path.resolve(fgDevkitRoot, '..')
38
+
39
+ function assertFgDevkitMaintainerContext() {
40
+ if (path.resolve(process.cwd()) !== fgDevkitRoot) {
41
+ console.error(`${LOG_PREFIX} Run from the fg-devkit repository root.`)
42
+ process.exit(1)
43
+ }
44
+ try {
45
+ const pkg = JSON.parse(fs.readFileSync(fgDevkitPkgPath, 'utf8'))
46
+ if (pkg?.name !== 'fg-devkit') {
47
+ console.error(`${LOG_PREFIX} package.json name must be fg-devkit.`)
48
+ process.exit(1)
49
+ }
50
+ } catch {
51
+ console.error(`${LOG_PREFIX} Could not read fg-devkit package.json.`)
52
+ process.exit(1)
53
+ }
54
+ }
55
+
56
+ async function main() {
57
+ assertFgDevkitMaintainerContext()
58
+
59
+ if (!fgDevkitOnly && !fs.existsSync(projectsRoot)) {
60
+ console.error(`${LOG_PREFIX} projects root not found: ${projectsRoot}`)
61
+ process.exit(1)
62
+ }
63
+
64
+ let repos
65
+ if (fgDevkitOnly) {
66
+ repos = [fgDevkitRoot]
67
+ projectsRoot = path.dirname(fgDevkitRoot)
68
+ } else {
69
+ const allRepos = collectFleetGitRepos(projectsRoot)
70
+ repos = await resolveFleetRepos(allRepos, projectsRoot, {
71
+ pick,
72
+ all,
73
+ only,
74
+ message: 'Select repositories to discard tracked changes',
75
+ })
76
+ }
77
+
78
+ console.log(`${LOG_PREFIX} projectsRoot=${projectsRoot}`)
79
+ console.log(`${LOG_PREFIX} repositories=${repos.length}`)
80
+
81
+ const exitCode = runFleetGitDiscard({
82
+ repos,
83
+ projectsRoot,
84
+ dryRun,
85
+ logPrefix: LOG_PREFIX,
86
+ })
87
+ process.exit(exitCode)
88
+ }
89
+
90
+ main().catch((err) => {
91
+ console.error(LOG_PREFIX, err)
92
+ process.exit(1)
93
+ })
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Maintainer: discard tracked git changes in every repository under projects root.
4
+ *
5
+ * Usage (from fg-devkit repository root):
6
+ * pnpm exec fg-devkit fleet-git-discard [projects-root] [--dry-run]
7
+ */
8
+
9
+ import fs from 'node:fs'
10
+ import path from 'node:path'
11
+ import { fileURLToPath } from 'node:url'
12
+
13
+ import { collectFleetGitRepos } from './fleet-project-repos.mjs'
14
+ import { runFleetGitDiscard } from './git-discard-fleet-common.mjs'
15
+
16
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url))
17
+ const fgDevkitRoot = path.resolve(scriptDir, '..')
18
+ const fgDevkitPkgPath = path.join(fgDevkitRoot, 'package.json')
19
+ const LOG_PREFIX = '[fleet-git-discard]'
20
+
21
+ const cliArgs = process.argv.slice(2)
22
+ const dryRun = cliArgs.includes('--dry-run')
23
+ const projectsRootArg = cliArgs.find((a) => !a.startsWith('-'))
24
+ const projectsRoot = projectsRootArg
25
+ ? path.resolve(process.cwd(), projectsRootArg)
26
+ : path.resolve(fgDevkitRoot, '..')
27
+
28
+ function assertFgDevkitMaintainerContext() {
29
+ if (path.resolve(process.cwd()) !== fgDevkitRoot) {
30
+ console.error(`${LOG_PREFIX} Run from the fg-devkit repository root.`)
31
+ process.exit(1)
32
+ }
33
+ try {
34
+ const pkg = JSON.parse(fs.readFileSync(fgDevkitPkgPath, 'utf8'))
35
+ if (pkg?.name !== 'fg-devkit') {
36
+ console.error(`${LOG_PREFIX} package.json name must be fg-devkit.`)
37
+ process.exit(1)
38
+ }
39
+ } catch {
40
+ console.error(`${LOG_PREFIX} Could not read fg-devkit package.json.`)
41
+ process.exit(1)
42
+ }
43
+ }
44
+
45
+ function main() {
46
+ assertFgDevkitMaintainerContext()
47
+
48
+ if (!fs.existsSync(projectsRoot)) {
49
+ console.error(`${LOG_PREFIX} projects root not found: ${projectsRoot}`)
50
+ process.exit(1)
51
+ }
52
+
53
+ const repos = collectFleetGitRepos(projectsRoot)
54
+ console.log(`${LOG_PREFIX} projectsRoot=${projectsRoot}`)
55
+ console.log(`${LOG_PREFIX} repositories=${repos.length}`)
56
+
57
+ const exitCode = runFleetGitDiscard({
58
+ repos,
59
+ projectsRoot,
60
+ dryRun,
61
+ logPrefix: LOG_PREFIX,
62
+ })
63
+ process.exit(exitCode)
64
+ }
65
+
66
+ main()
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Maintainer: git pull on the current branch for a chosen set of repositories.
4
+ *
5
+ * Usage (from fg-devkit repository root):
6
+ * pnpm exec fg-devkit fleet-git-pull-scoped [projects-root] [options]
7
+ *
8
+ * Options:
9
+ * --dry-run Print planned pulls without running git pull
10
+ * --fg-devkit-only Only pull the fg-devkit repository
11
+ * --pick Interactive checkbox to choose repositories (default when TTY)
12
+ * --all Run on all git repositories under projects root without prompting
13
+ * --only=a,b Run only on matching repos (basename or path under projects root)
14
+ */
15
+
16
+ import fs from 'node:fs'
17
+ import path from 'node:path'
18
+ import { fileURLToPath } from 'node:url'
19
+
20
+ import { collectFleetGitRepos } from './fleet-project-repos.mjs'
21
+ import { parseFleetSelectionFlags, resolveFleetRepos } from './fleet-repo-picker.mjs'
22
+ import { runFleetGitPull } from './git-pull-fleet-common.mjs'
23
+
24
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url))
25
+ const fgDevkitRoot = path.resolve(scriptDir, '..')
26
+ const fgDevkitPkgPath = path.join(fgDevkitRoot, 'package.json')
27
+
28
+ const cliArgs = process.argv.slice(2)
29
+ const dryRun = cliArgs.includes('--dry-run')
30
+ const fgDevkitOnly = cliArgs.includes('--fg-devkit-only')
31
+ const { pick, all, only, filteredArgs: positionalArgs } = parseFleetSelectionFlags(cliArgs)
32
+
33
+ const projectsRootArg = positionalArgs[0]
34
+ let projectsRoot = projectsRootArg
35
+ ? path.resolve(process.cwd(), projectsRootArg)
36
+ : path.resolve(fgDevkitRoot, '..')
37
+
38
+ function assertFgDevkitMaintainerContext() {
39
+ if (path.resolve(process.cwd()) !== fgDevkitRoot) {
40
+ console.error('[fleet-git-pull-scoped] Run from the fg-devkit repository root.')
41
+ process.exit(1)
42
+ }
43
+ try {
44
+ const pkg = JSON.parse(fs.readFileSync(fgDevkitPkgPath, 'utf8'))
45
+ if (pkg?.name !== 'fg-devkit') {
46
+ console.error('[fleet-git-pull-scoped] package.json name must be fg-devkit.')
47
+ process.exit(1)
48
+ }
49
+ } catch {
50
+ console.error('[fleet-git-pull-scoped] Could not read fg-devkit package.json.')
51
+ process.exit(1)
52
+ }
53
+ }
54
+
55
+ async function main() {
56
+ assertFgDevkitMaintainerContext()
57
+
58
+ if (!fgDevkitOnly && !fs.existsSync(projectsRoot)) {
59
+ console.error(`[fleet-git-pull-scoped] projects root not found: ${projectsRoot}`)
60
+ process.exit(1)
61
+ }
62
+
63
+ let repos
64
+ if (fgDevkitOnly) {
65
+ repos = [fgDevkitRoot]
66
+ projectsRoot = path.dirname(fgDevkitRoot)
67
+ } else {
68
+ const allRepos = collectFleetGitRepos(projectsRoot)
69
+ repos = await resolveFleetRepos(allRepos, projectsRoot, {
70
+ pick,
71
+ all,
72
+ only,
73
+ message: 'Select repositories to git pull (current branch)',
74
+ })
75
+ }
76
+
77
+ console.log(`[fleet-git-pull-scoped] projectsRoot=${projectsRoot}`)
78
+ console.log(`[fleet-git-pull-scoped] repositories=${repos.length}`)
79
+
80
+ const exitCode = runFleetGitPull({
81
+ repos,
82
+ projectsRoot,
83
+ dryRun,
84
+ logPrefix: '[fleet-git-pull-scoped]',
85
+ })
86
+ process.exit(exitCode)
87
+ }
88
+
89
+ main().catch((err) => {
90
+ console.error('[fleet-git-pull-scoped]', err)
91
+ process.exit(1)
92
+ })
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Maintainer: git pull on the current branch for every git repository under projects root.
4
+ *
5
+ * Usage (from fg-devkit repository root):
6
+ * pnpm exec fg-devkit fleet-git-pull [projects-root] [--dry-run]
7
+ */
8
+
9
+ import fs from 'node:fs'
10
+ import path from 'node:path'
11
+ import { fileURLToPath } from 'node:url'
12
+
13
+ import { collectFleetGitRepos } from './fleet-project-repos.mjs'
14
+ import { runFleetGitPull } from './git-pull-fleet-common.mjs'
15
+
16
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url))
17
+ const fgDevkitRoot = path.resolve(scriptDir, '..')
18
+ const fgDevkitPkgPath = path.join(fgDevkitRoot, 'package.json')
19
+
20
+ const cliArgs = process.argv.slice(2)
21
+ const dryRun = cliArgs.includes('--dry-run')
22
+ const projectsRootArg = cliArgs.find((a) => !a.startsWith('-'))
23
+ const projectsRoot = projectsRootArg
24
+ ? path.resolve(process.cwd(), projectsRootArg)
25
+ : path.resolve(fgDevkitRoot, '..')
26
+
27
+ function assertFgDevkitMaintainerContext() {
28
+ if (path.resolve(process.cwd()) !== fgDevkitRoot) {
29
+ console.error('[fleet-git-pull] Run from the fg-devkit repository root.')
30
+ process.exit(1)
31
+ }
32
+ try {
33
+ const pkg = JSON.parse(fs.readFileSync(fgDevkitPkgPath, 'utf8'))
34
+ if (pkg?.name !== 'fg-devkit') {
35
+ console.error('[fleet-git-pull] package.json name must be fg-devkit.')
36
+ process.exit(1)
37
+ }
38
+ } catch {
39
+ console.error('[fleet-git-pull] Could not read fg-devkit package.json.')
40
+ process.exit(1)
41
+ }
42
+ }
43
+
44
+ function main() {
45
+ assertFgDevkitMaintainerContext()
46
+
47
+ if (!fs.existsSync(projectsRoot)) {
48
+ console.error(`[fleet-git-pull] projects root not found: ${projectsRoot}`)
49
+ process.exit(1)
50
+ }
51
+
52
+ const repos = collectFleetGitRepos(projectsRoot)
53
+ console.log(`[fleet-git-pull] projectsRoot=${projectsRoot}`)
54
+ console.log(`[fleet-git-pull] repositories=${repos.length}`)
55
+
56
+ const exitCode = runFleetGitPull({
57
+ repos,
58
+ projectsRoot,
59
+ dryRun,
60
+ logPrefix: '[fleet-git-pull]',
61
+ })
62
+ process.exit(exitCode)
63
+ }
64
+
65
+ main()
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Discover project repository roots under a projects directory.
3
+ */
4
+
5
+ import fs from 'node:fs'
6
+ import path from 'node:path'
7
+
8
+ /** Repos excluded from all fleet-wide repository scans. */
9
+ export const DEFAULT_FLEET_REPO_EXCLUDE = new Set(['fg-devkit'])
10
+
11
+ /**
12
+ * @param {string} repoDir
13
+ * @returns {boolean}
14
+ */
15
+ export function isFgDevkitRepoDir(repoDir) {
16
+ if (path.basename(repoDir) === 'fg-devkit') {
17
+ return true
18
+ }
19
+ try {
20
+ const pkg = JSON.parse(fs.readFileSync(path.join(repoDir, 'package.json'), 'utf8'))
21
+ return pkg?.name === 'fg-devkit'
22
+ } catch {
23
+ return false
24
+ }
25
+ }
26
+
27
+ /**
28
+ * @param {string} projectsRoot
29
+ * @param {{ exclude?: Set<string> | string[] }} [options]
30
+ * @returns {string[]} Absolute repo directory paths, sorted.
31
+ */
32
+ export function collectFleetProjectRepos(projectsRoot, options = {}) {
33
+ const exclude = new Set([...DEFAULT_FLEET_REPO_EXCLUDE, ...(options.exclude ?? [])])
34
+
35
+ if (!fs.existsSync(projectsRoot)) {
36
+ return []
37
+ }
38
+
39
+ const repos = []
40
+ for (const item of fs.readdirSync(projectsRoot, { withFileTypes: true })) {
41
+ if (!item.isDirectory()) continue
42
+ if (item.name === 'node_modules' || item.name.startsWith('.')) continue
43
+ if (exclude.has(item.name)) continue
44
+
45
+ const repoDir = path.join(projectsRoot, item.name)
46
+ if (isFgDevkitRepoDir(repoDir)) continue
47
+ const hasPackageJson = fs.existsSync(path.join(repoDir, 'package.json'))
48
+ const hasJenkinsfile = fs.existsSync(path.join(repoDir, 'Jenkinsfile'))
49
+ if (!hasPackageJson && !hasJenkinsfile) continue
50
+
51
+ repos.push(repoDir)
52
+ }
53
+
54
+ return repos.sort((a, b) => a.localeCompare(b))
55
+ }
56
+
57
+ /**
58
+ * Top-level git repositories directly under projectsRoot (directory contains `.git`).
59
+ *
60
+ * @param {string} projectsRoot
61
+ * @returns {string[]}
62
+ */
63
+ export function collectFleetGitRepos(projectsRoot) {
64
+ if (!fs.existsSync(projectsRoot)) {
65
+ return []
66
+ }
67
+
68
+ const repos = []
69
+ for (const item of fs.readdirSync(projectsRoot, { withFileTypes: true })) {
70
+ if (!item.isDirectory()) continue
71
+ if (item.name === 'node_modules' || item.name.startsWith('.')) continue
72
+
73
+ const repoDir = path.join(projectsRoot, item.name)
74
+ if (isFgDevkitRepoDir(repoDir)) continue
75
+ if (fs.existsSync(path.join(repoDir, '.git'))) {
76
+ repos.push(repoDir)
77
+ }
78
+ }
79
+
80
+ return repos.sort((a, b) => a.localeCompare(b))
81
+ }
82
+
83
+ /** Repos that intentionally maintain multiple Node versions or non-pnpm tooling. */
84
+ export const DEFAULT_NODE_TOOLCHAIN_EXCLUDE = new Set(['jenkins-dockerfile', 'vulnerability-test'])
85
+
86
+ /** Repos excluded from fleet-wide pnpm toolchain bumps (non-pnpm or special tooling). */
87
+ export const DEFAULT_PNPM_TOOLCHAIN_EXCLUDE = new Set(['jenkins-dockerfile', 'vulnerability-test'])
88
+
89
+ /**
90
+ * @param {string} dir
91
+ * @param {Record<string, unknown>} pkg
92
+ * @returns {boolean}
93
+ */
94
+ export function isPnpmPackageJsonDir(dir, pkg) {
95
+ if (typeof pkg?.packageManager === 'string' && pkg.packageManager.startsWith('pnpm@')) {
96
+ return true
97
+ }
98
+ return fs.existsSync(path.join(dir, 'pnpm-lock.yaml'))
99
+ }
100
+
101
+ /**
102
+ * Discover repositories under projectsRoot that use pnpm (packageManager or pnpm-lock.yaml).
103
+ *
104
+ * @param {string} projectsRoot
105
+ * @param {{ exclude?: Set<string> | string[], skipScaffoldVue?: boolean }} [options]
106
+ * @returns {string[]} Absolute repo directory paths, sorted.
107
+ */
108
+ export function collectFleetPnpmRepos(projectsRoot, options = {}) {
109
+ const exclude = new Set([
110
+ ...DEFAULT_FLEET_REPO_EXCLUDE,
111
+ ...DEFAULT_PNPM_TOOLCHAIN_EXCLUDE,
112
+ ...(options.exclude ?? []),
113
+ ])
114
+ const { skipScaffoldVue = true } = options
115
+
116
+ if (!fs.existsSync(projectsRoot)) {
117
+ return []
118
+ }
119
+
120
+ function shouldSkipPackageJsonDir(relDir) {
121
+ const topLevel = relDir.split(path.sep)[0]
122
+ if (exclude.has(topLevel)) {
123
+ return true
124
+ }
125
+ if (!skipScaffoldVue) {
126
+ return false
127
+ }
128
+ const normalized = relDir.split(path.sep).join('/')
129
+ if (normalized === 'scaffold/vue' || normalized.endsWith('/scaffold/vue')) {
130
+ return true
131
+ }
132
+ return false
133
+ }
134
+
135
+ const SKIP_DIR_NAMES = new Set([
136
+ 'node_modules',
137
+ '.git',
138
+ '.next',
139
+ 'dist',
140
+ 'build',
141
+ 'coverage',
142
+ 'storybook-static',
143
+ ])
144
+
145
+ function walk(dir, acc = []) {
146
+ for (const item of fs.readdirSync(dir, { withFileTypes: true })) {
147
+ if (SKIP_DIR_NAMES.has(item.name) || item.name.startsWith('.')) continue
148
+ const p = path.join(dir, item.name)
149
+ if (item.isDirectory()) walk(p, acc)
150
+ else if (item.name === 'package.json') acc.push(p)
151
+ }
152
+ return acc
153
+ }
154
+
155
+ const repos = []
156
+ for (const packageJsonPath of walk(projectsRoot)) {
157
+ try {
158
+ const repoDir = path.dirname(packageJsonPath)
159
+ const relDir = path.relative(projectsRoot, repoDir)
160
+ if (shouldSkipPackageJsonDir(relDir)) continue
161
+ if (isFgDevkitRepoDir(repoDir)) continue
162
+
163
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
164
+ if (!isPnpmPackageJsonDir(repoDir, pkg)) continue
165
+
166
+ repos.push(repoDir)
167
+ } catch {
168
+ // Ignore invalid package.json files.
169
+ }
170
+ }
171
+
172
+ return [...new Set(repos)].sort((a, b) => a.localeCompare(b))
173
+ }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Interactive and CLI-based fleet repository selection for toolchain scripts.
3
+ */
4
+
5
+ import path from 'node:path'
6
+
7
+ /**
8
+ * @param {string} repoDir
9
+ * @param {string} projectsRoot
10
+ * @returns {string}
11
+ */
12
+ export function formatRepoLabel(repoDir, projectsRoot) {
13
+ const rel = path.relative(projectsRoot, repoDir).split(path.sep).join('/')
14
+ return rel === '' || rel === '.' ? path.basename(repoDir) : rel
15
+ }
16
+
17
+ /**
18
+ * @param {string[]} cliArgs
19
+ * @returns {string[] | null}
20
+ */
21
+ export function parseFleetOnlyArg(cliArgs) {
22
+ const onlyArg = cliArgs.find((a) => a.startsWith('--only='))
23
+ if (!onlyArg) {
24
+ return null
25
+ }
26
+ return onlyArg
27
+ .slice('--only='.length)
28
+ .split(',')
29
+ .map((part) => part.trim())
30
+ .filter(Boolean)
31
+ }
32
+
33
+ /**
34
+ * @param {string[]} repos
35
+ * @param {string} projectsRoot
36
+ * @param {string[]} selectors Basename or path relative to projectsRoot.
37
+ * @returns {string[]}
38
+ */
39
+ export function filterReposByOnly(repos, projectsRoot, selectors) {
40
+ const normalizedSelectors = selectors.map((sel) => sel.split(path.sep).join('/'))
41
+ return repos.filter((repoDir) => {
42
+ const label = formatRepoLabel(repoDir, projectsRoot)
43
+ const base = path.basename(repoDir)
44
+ return normalizedSelectors.some(
45
+ (sel) => sel === label || sel === base || label.endsWith(`/${sel}`),
46
+ )
47
+ })
48
+ }
49
+
50
+ /**
51
+ * @param {string} message
52
+ * @param {string[]} repos
53
+ * @param {string} projectsRoot
54
+ * @returns {Promise<string[]>}
55
+ */
56
+ async function promptFleetRepoCheckbox(message, repos, projectsRoot) {
57
+ let checkboxPrompt
58
+ try {
59
+ const inquirerCheckbox = await import('@inquirer/checkbox')
60
+ checkboxPrompt = inquirerCheckbox.default
61
+ } catch {
62
+ return promptFleetRepoInput(message, repos, projectsRoot)
63
+ }
64
+
65
+ const choices = repos.map((repoDir) => ({
66
+ name: formatRepoLabel(repoDir, projectsRoot),
67
+ value: repoDir,
68
+ checked: true,
69
+ }))
70
+
71
+ const selected = await checkboxPrompt({
72
+ message,
73
+ choices,
74
+ pageSize: Math.min(20, choices.length),
75
+ loop: false,
76
+ })
77
+
78
+ return selected
79
+ }
80
+
81
+ /**
82
+ * @param {string} message
83
+ * @param {string[]} repos
84
+ * @param {string} projectsRoot
85
+ * @returns {Promise<string[]>}
86
+ */
87
+ async function promptFleetRepoInput(message, repos, projectsRoot) {
88
+ const inquirerInput = await import('@inquirer/input')
89
+ const labels = repos.map((repoDir) => formatRepoLabel(repoDir, projectsRoot))
90
+ console.log('\n[fleet-picker] Available repositories:')
91
+ for (const label of labels) {
92
+ console.log(` - ${label}`)
93
+ }
94
+
95
+ const answer = await inquirerInput.default({
96
+ message: `${message} (comma-separated; empty = all)`,
97
+ default: '',
98
+ })
99
+
100
+ const trimmed = answer.trim()
101
+ if (!trimmed) {
102
+ return repos
103
+ }
104
+
105
+ const selected = filterReposByOnly(
106
+ repos,
107
+ projectsRoot,
108
+ trimmed.split(',').map((part) => part.trim()),
109
+ )
110
+ if (selected.length === 0) {
111
+ console.error('[fleet-picker] No repositories matched your input.')
112
+ process.exit(1)
113
+ }
114
+ return selected
115
+ }
116
+
117
+ /**
118
+ * @param {string[]} repos
119
+ * @param {string} projectsRoot
120
+ * @param {{ pick?: boolean, only?: string[] | null, all?: boolean, message?: string }} options
121
+ * @returns {Promise<string[]>}
122
+ */
123
+ export async function resolveFleetRepos(repos, projectsRoot, options = {}) {
124
+ const {
125
+ pick = false,
126
+ only = null,
127
+ all = false,
128
+ message = 'Select repositories to update',
129
+ } = options
130
+
131
+ if (only?.length) {
132
+ const filtered = filterReposByOnly(repos, projectsRoot, only)
133
+ if (filtered.length === 0) {
134
+ console.error(`[fleet-picker] No repositories matched --only=${only.join(',')}`)
135
+ process.exit(1)
136
+ }
137
+ return filtered
138
+ }
139
+
140
+ const shouldPrompt = pick || (process.stdin.isTTY && !all)
141
+ if (!shouldPrompt || repos.length === 0) {
142
+ return repos
143
+ }
144
+
145
+ const selected = await promptFleetRepoCheckbox(message, repos, projectsRoot)
146
+ if (selected.length === 0) {
147
+ console.log('[fleet-picker] No repositories selected.')
148
+ process.exit(0)
149
+ }
150
+ return selected
151
+ }
152
+
153
+ /** CLI flags consumed by bump-*-toolchain scripts (not positional arguments). */
154
+ const TOOLCHAIN_CLI_FLAGS = new Set([
155
+ '--dry-run',
156
+ '--skip-fleet',
157
+ '--skip-install',
158
+ '--pick',
159
+ '--all',
160
+ '--no-engines',
161
+ '--fg-devkit-only',
162
+ ])
163
+
164
+ /**
165
+ * @param {string} arg
166
+ * @returns {boolean}
167
+ */
168
+ function isToolchainCliFlag(arg) {
169
+ return TOOLCHAIN_CLI_FLAGS.has(arg) || arg.startsWith('--only=') || arg.startsWith('--engines=')
170
+ }
171
+
172
+ /**
173
+ * @param {string[]} cliArgs
174
+ * @returns {string[]}
175
+ */
176
+ export function filterToolchainPositionalArgs(cliArgs) {
177
+ return cliArgs.filter((arg) => !isToolchainCliFlag(arg))
178
+ }
179
+
180
+ /**
181
+ * @param {string[]} cliArgs
182
+ * @returns {{ pick: boolean, all: boolean, only: string[] | null, filteredArgs: string[] }}
183
+ */
184
+ export function parseFleetSelectionFlags(cliArgs) {
185
+ const pick = cliArgs.includes('--pick')
186
+ const all = cliArgs.includes('--all')
187
+ const only = parseFleetOnlyArg(cliArgs)
188
+ const filteredArgs = filterToolchainPositionalArgs(cliArgs)
189
+ return { pick, all, only, filteredArgs }
190
+ }
@@ -4,6 +4,7 @@
4
4
 
5
5
  import fs from 'node:fs'
6
6
  import path from 'node:path'
7
+ import { isFgDevkitRepoDir } from './fleet-project-repos.mjs'
7
8
 
8
9
  /**
9
10
  * @param {string} projectsRoot
@@ -40,8 +41,9 @@ export function collectFleetVueRepos(projectsRoot, options = {}) {
40
41
  try {
41
42
  const relDir = path.relative(projectsRoot, path.dirname(packageJsonPath))
42
43
  if (shouldSkipPackageJsonDir(relDir)) continue
44
+ const repoDir = path.dirname(packageJsonPath)
45
+ if (isFgDevkitRepoDir(repoDir)) continue
43
46
  const pkg = readJson(packageJsonPath)
44
- if (pkg?.name === 'fg-devkit') continue
45
47
  if (!pkg.dependencies?.vue && !pkg.devDependencies?.vue) continue
46
48
  if (!pkg.dependencies?.['fg-devkit'] && !pkg.devDependencies?.['fg-devkit']) continue
47
49
  repos.push(path.dirname(packageJsonPath))