fg-devkit 1.12.5 → 1.12.7
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/configs/dependency-policy.vue-workspace.json +40 -34
- package/package.json +25 -9
- package/scaffold/vue/package.json +15 -12
- package/scripts/bump-node-toolchain.mjs +260 -0
- package/scripts/bump-pnpm-toolchain.mjs +65 -28
- package/scripts/check-dependency-policy.mjs +8 -0
- package/scripts/dependency-policy-toolchain.mjs +187 -7
- package/scripts/fg-devkit.mjs +214 -3
- package/scripts/fleet-git-commit-push-scoped.mjs +97 -0
- package/scripts/fleet-git-commit-push.mjs +75 -0
- package/scripts/fleet-git-discard-scoped.mjs +93 -0
- package/scripts/fleet-git-discard.mjs +66 -0
- package/scripts/fleet-git-pull-scoped.mjs +92 -0
- package/scripts/fleet-git-pull.mjs +65 -0
- package/scripts/fleet-project-repos.mjs +173 -0
- package/scripts/fleet-repo-picker.mjs +190 -0
- package/scripts/fleet-vue-repos.mjs +3 -1
- package/scripts/fleet-vulns.mjs +2 -1
- package/scripts/git-commit-push-fleet-common.mjs +147 -0
- package/scripts/git-discard-fleet-common.mjs +100 -0
- package/scripts/git-fleet-common.mjs +120 -0
- package/scripts/git-pull-fleet-common.mjs +90 -0
- package/scripts/node-toolchain-sync.mjs +347 -0
- package/scripts/pnpm-toolchain-sync.mjs +231 -0
- package/scripts/scaffold-vue.mjs +2 -2
- package/scripts/sync-dependency-policy.mjs +47 -15
- package/scripts/sync-node-toolchain.mjs +64 -0
- package/scripts/sync-pnpm-toolchain.mjs +59 -0
- package/scripts/sync-scaffold-vue-deps.mjs +12 -5
- package/scaffold/vue/Dockerfile.local +0 -16
|
@@ -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))
|