fg-devkit 1.8.1 → 1.8.4

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.
@@ -1,7 +1,5 @@
1
1
  pre-commit:
2
2
  commands:
3
- audit:
4
- run: zsh -lc "node ./node_modules/fg-devkit/scripts/lefthook-audit-if-enabled.mjs"
5
3
  lint-check:
6
4
  run: zsh -lc "node ./node_modules/fg-devkit/scripts/fg-devkit.mjs lint ."
7
5
  typecheck:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fg-devkit",
3
- "version": "1.8.1",
3
+ "version": "1.8.4",
4
4
  "description": "Shared configs, scripts, and DX tooling for Vue / TypeScript frontend repos",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -45,7 +45,7 @@
45
45
  "./scripts/poeditor-upload.mjs": "./scripts/poeditor-upload.mjs",
46
46
  "./scripts/scaffold-vue.mjs": "./scripts/scaffold-vue.mjs",
47
47
  "./scripts/fg-devkit.mjs": "./scripts/fg-devkit.mjs",
48
- "./scripts/lefthook-audit-if-enabled.mjs": "./scripts/lefthook-audit-if-enabled.mjs",
48
+ "./scripts/resolve-candidate-version.mjs": "./scripts/resolve-candidate-version.mjs",
49
49
  "./scripts/release.sh": "./scripts/release.sh",
50
50
  "./scripts/cz/.cz-config.cjs": "./scripts/cz/.cz-config.cjs",
51
51
  "./cursor-rules/core-coding-standards.mdc": "./.cursor/rules/core-coding-standards.mdc"
@@ -34,7 +34,7 @@
34
34
  "@tanstack/vue-query-devtools": "6.1.28",
35
35
  "@types/node": "22.16.5",
36
36
  "@vitejs/plugin-vue": "6.0.6",
37
- "fg-devkit": "1.8.1",
37
+ "fg-devkit": "1.8.3",
38
38
  "start-server-and-test": "2.1.3",
39
39
  "tailwindcss": "4.3.0",
40
40
  "tsx": "4.21.0",
@@ -1,9 +1,6 @@
1
1
  /**
2
2
  * cz-git config used by fg-devkit commit flows.
3
3
  * Keep this in czrc-compatible shape for `czg --config`.
4
- *
5
- * Note: interactive `fg-devkit commit` asks each dep/vuln commit whether Lefthook should run
6
- * `pnpm audit` for that commit only (via env, not persisted).
7
4
  */
8
5
  module.exports = {
9
6
  types: [
@@ -20,7 +20,7 @@
20
20
  * pnpm exec fg-devkit setup-lefthook
21
21
  * pnpm exec fg-devkit prune-consumer-deps [--dry-run]
22
22
  * pnpm exec fg-devkit full-consumer-setup
23
- * pnpm exec fg-devkit create-release [patch|minor|major]
23
+ * pnpm exec fg-devkit create-release [release|candidate] [patch|minor|major]
24
24
  * pnpm exec fg-devkit commit [cz-args...] (czg/cz-git with fg-devkit bundled config)
25
25
  * pnpm exec fg-devkit commit-vuln (staged package.json version bumps → vuln() commit)
26
26
  * pnpm exec fg-devkit lint [eslint-args...] (run ESLint with fg-devkit bundled packages)
@@ -72,7 +72,7 @@ function runNode(scriptRelative, args = [], options = {}) {
72
72
 
73
73
  /**
74
74
  * Run release.sh (pnpm version bump, git push, gh release). Uses consumer cwd.
75
- * Optional first arg: patch | minor | major (otherwise the script prompts interactively).
75
+ * Args: [release|candidate] [patch|minor|major] or [patch|minor|major] [release|candidate].
76
76
  */
77
77
  function runReleaseScript(args = []) {
78
78
  const shPath = path.join(scriptDir, 'release.sh')
@@ -273,18 +273,6 @@ async function runCzg(args = [], extraEnv = {}) {
273
273
  })
274
274
  }
275
275
 
276
- let runLefthookAuditThisCommit = true
277
- if (isDepOrVuln) {
278
- runLefthookAuditThisCommit = await selectPrompt({
279
- message: 'Run pnpm audit on commit (Lefthook pre-commit)?',
280
- choices: [
281
- { value: true, name: 'Yes' },
282
- { value: false, name: 'No' },
283
- ],
284
- default: true,
285
- })
286
- }
287
-
288
276
  if (!isDepOrVuln) {
289
277
  shortcutId = await ask('Shortcut ID digits only (optional, e.g. 1234):')
290
278
  subject = await ask('Commit subject (imperative, no trailing period):', {
@@ -305,16 +293,10 @@ async function runCzg(args = [], extraEnv = {}) {
305
293
  const finalSubject = selectedType === 'dep' ? depSubject : selectedType === 'vuln' ? vulnSubject : subject
306
294
  const commitMessage = `${selectedType}${scopePart}: ${finalSubject}${shortcutPart}${bodyPart}`
307
295
 
308
- const gitEnv = { ...process.env, ...extraEnv }
309
- delete gitEnv.FG_DEVKIT_LEFTHOOK_AUDIT
310
- if (isDepOrVuln) {
311
- gitEnv.FG_DEVKIT_LEFTHOOK_AUDIT = runLefthookAuditThisCommit ? '1' : '0'
312
- }
313
-
314
296
  const r = spawnSync('git', ['commit', ...args, '-m', commitMessage], {
315
297
  stdio: 'inherit',
316
298
  cwd: process.cwd(),
317
- env: gitEnv,
299
+ env: { ...process.env, ...extraEnv },
318
300
  })
319
301
  if (r.error) {
320
302
  console.error('[fg-devkit]', r.error.message)
@@ -511,7 +493,8 @@ Commands:
511
493
  setup-lefthook Setup lefthook and migrate from husky/lint-staged
512
494
  prune-consumer-deps [--dry-run] Remove consumer tooling deps bundled by fg-devkit
513
495
  full-consumer-setup Run: step 7 -> scripts -> lefthook -> step 3 -> step 2 -> pnpm install
514
- create-release [bump] Version bump, git push, GitHub release (patch|minor|major; omit = prompt)
496
+ create-release [release|candidate] [patch|minor|major]
497
+ Stable release or candidate (X.Y.Z-rc.N); omit = interactive menu
515
498
  (alias: createRelease)
516
499
  commit [args...] czg/cz-git with fg-devkit bundled config
517
500
  commit-vuln Commit staged dependency bumps as vuln() (alias: vuln-commit)
@@ -653,8 +636,20 @@ async function runInteractiveMenu() {
653
636
  }
654
637
 
655
638
  if (group === 'release') {
656
- const releaseChoice = await selectPrompt({
639
+ const releaseKind = await selectPrompt({
657
640
  message: 'Create release',
641
+ choices: [
642
+ { name: 'Release', value: 'release' },
643
+ { name: 'Candidate', value: 'candidate' },
644
+ { name: 'Cancel', value: 'cancel' },
645
+ ],
646
+ default: 'release',
647
+ loop: false,
648
+ })
649
+ if (releaseKind === 'cancel') continue
650
+
651
+ const bumpChoice = await selectPrompt({
652
+ message: releaseKind === 'candidate' ? 'Candidate' : 'Release',
658
653
  choices: [
659
654
  { name: 'Patch', value: 'patch' },
660
655
  { name: 'Minor', value: 'minor' },
@@ -664,8 +659,11 @@ async function runInteractiveMenu() {
664
659
  default: 'patch',
665
660
  loop: false,
666
661
  })
667
- if (releaseChoice === 'cancel') continue
668
- return runSelectedAction(() => runReleaseScript([releaseChoice]))
662
+ if (bumpChoice === 'cancel') continue
663
+
664
+ const releaseArgs =
665
+ releaseKind === 'candidate' ? [bumpChoice, 'candidate'] : [bumpChoice]
666
+ return runSelectedAction(() => runReleaseScript(releaseArgs))
669
667
  }
670
668
 
671
669
  if (group === 'lint-typecheck') {
@@ -3,12 +3,29 @@
3
3
  # Interactive or scripted release for this package: bumps version with pnpm, pushes commits/tags,
4
4
  # and creates a GitHub Release with generated notes (requires gh CLI, authenticated).
5
5
  #
6
- # Usage: ./scripts/release.sh [major|minor|patch]
7
- # With no argument, prompts for patch/minor/major.
6
+ # Usage:
7
+ # ./scripts/release.sh [patch|minor|major] # stable release (default mode)
8
+ # ./scripts/release.sh [patch|minor|major] candidate # prerelease tag X.Y.Z-rc.N
9
+ # ./scripts/release.sh candidate [patch|minor|major]
10
+ #
11
+ # Candidate tags follow SemVer prerelease format: <version>-rc.<number> (e.g. 2.8.0-rc.1).
8
12
 
9
13
  set -euo pipefail
10
14
 
11
- bump_type="${1:-}"
15
+ bump_type=""
16
+ release_mode="release"
17
+
18
+ for arg in "$@"; do
19
+ case "$arg" in
20
+ patch | minor | major) bump_type="$arg" ;;
21
+ release | candidate) release_mode="$arg" ;;
22
+ *)
23
+ echo "Unknown argument: $arg"
24
+ echo "Usage: ./scripts/release.sh [patch|minor|major] [release|candidate]"
25
+ exit 1
26
+ ;;
27
+ esac
28
+ done
12
29
 
13
30
  if [[ -z "$bump_type" ]]; then
14
31
  echo "Select release type:"
@@ -29,7 +46,7 @@ if [[ -z "$bump_type" ]]; then
29
46
  fi
30
47
 
31
48
  if [[ "$bump_type" != "major" && "$bump_type" != "minor" && "$bump_type" != "patch" ]]; then
32
- echo "Usage: ./scripts/release.sh [major|minor|patch]"
49
+ echo "Usage: ./scripts/release.sh [patch|minor|major] [release|candidate]"
33
50
  exit 1
34
51
  fi
35
52
 
@@ -50,22 +67,61 @@ fi
50
67
 
51
68
  previous_tag="$(git describe --tags --abbrev=0 2>/dev/null || true)"
52
69
 
53
- pnpm version "$bump_type" --no-git-tag-version
54
-
55
- new_version="$(node -p "require('./package.json').version")"
56
- new_tag="v${new_version}"
57
-
58
70
  is_fg_devkit_repo="$(
59
71
  node -e "const fs=require('fs'); try { const p=JSON.parse(fs.readFileSync('./package.json','utf8')); process.stdout.write(p.name==='fg-devkit' ? 'yes' : 'no'); } catch { process.stdout.write('no'); }"
60
72
  )"
61
73
 
62
- if [[ "$is_fg_devkit_repo" == "yes" && -f "./scripts/sync-scaffold-vue-deps.mjs" && -f "./scaffold/vue/package.json" ]]; then
63
- node ./scripts/sync-scaffold-vue-deps.mjs
64
- git add package.json scaffold/vue/package.json
65
- else
66
- git add package.json
74
+ sync_fg_devkit_package_files() {
75
+ if [[ "$is_fg_devkit_repo" == "yes" && -f "./scripts/sync-scaffold-vue-deps.mjs" && -f "./scaffold/vue/package.json" ]]; then
76
+ node ./scripts/sync-scaffold-vue-deps.mjs
77
+ git add package.json scaffold/vue/package.json
78
+ else
79
+ git add package.json
80
+ fi
81
+ }
82
+
83
+ if [[ "$release_mode" == "candidate" ]]; then
84
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
85
+ new_version="$(node "${script_dir}/resolve-candidate-version.mjs" "$bump_type")"
86
+
87
+ node -e "
88
+ const fs = require('fs');
89
+ const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
90
+ pkg.version = process.argv[1];
91
+ fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
92
+ " "$new_version"
93
+
94
+ sync_fg_devkit_package_files
95
+
96
+ new_tag="v${new_version}"
97
+ git commit -m "${new_tag}"
98
+ git tag "${new_tag}"
99
+
100
+ git push
101
+ git push --tags
102
+
103
+ if [[ -n "$previous_tag" ]]; then
104
+ gh release create "$new_tag" \
105
+ --prerelease \
106
+ --generate-notes \
107
+ --notes-start-tag "$previous_tag"
108
+ else
109
+ gh release create "$new_tag" \
110
+ --prerelease \
111
+ --generate-notes
112
+ fi
113
+
114
+ echo "Candidate release created: ${new_tag}"
115
+ exit 0
67
116
  fi
68
117
 
118
+ pnpm version "$bump_type" --no-git-tag-version
119
+
120
+ new_version="$(node -p "require('./package.json').version")"
121
+ new_tag="v${new_version}"
122
+
123
+ sync_fg_devkit_package_files
124
+
69
125
  git commit -m "${new_tag}"
70
126
  git tag "${new_tag}"
71
127
 
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Resolves next candidate version: <semver>-rc.<n> (e.g. 2.8.0-rc.2).
4
+ * Usage: node resolve-candidate-version.mjs <patch|minor|major>
5
+ */
6
+ import fs from 'node:fs'
7
+ import { execSync } from 'node:child_process'
8
+
9
+ const bump = process.argv[2]
10
+ if (bump !== 'patch' && bump !== 'minor' && bump !== 'major') {
11
+ console.error('[resolve-candidate-version] Usage: node resolve-candidate-version.mjs <patch|minor|major>')
12
+ process.exit(1)
13
+ }
14
+
15
+ const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'))
16
+ const v = pkg.version
17
+
18
+ /** @param {string} ver @param {'patch'|'minor'|'major'} type */
19
+ function bumpSemver(ver, type) {
20
+ const m = ver.match(/^(\d+)\.(\d+)\.(\d+)$/)
21
+ if (!m) throw new Error(`Invalid semver base: ${ver}`)
22
+ let maj = Number(m[1])
23
+ let min = Number(m[2])
24
+ let pat = Number(m[3])
25
+ if (type === 'major') {
26
+ maj += 1
27
+ min = 0
28
+ pat = 0
29
+ } else if (type === 'minor') {
30
+ min += 1
31
+ pat = 0
32
+ } else if (type === 'patch') {
33
+ pat += 1
34
+ }
35
+ return `${maj}.${min}.${pat}`
36
+ }
37
+
38
+ const plainMatch = v.match(/^(\d+\.\d+\.\d+)/)
39
+ if (!plainMatch) {
40
+ console.error(`[resolve-candidate-version] Could not parse version: ${v}`)
41
+ process.exit(1)
42
+ }
43
+ const plain = plainMatch[1]
44
+
45
+ /** @type {string} */
46
+ let targetBase
47
+ if (/^\d+\.\d+\.\d+-rc\.\d+$/.test(v)) {
48
+ targetBase = plain
49
+ } else {
50
+ targetBase = bumpSemver(plain, bump)
51
+ }
52
+
53
+ /** @param {string} base @param {string[]} tags */
54
+ function maxRcForBase(base, tags) {
55
+ const prefixes = [`${base}-rc.`, `v${base}-rc.`]
56
+ let max = 0
57
+ for (const tag of tags) {
58
+ for (const prefix of prefixes) {
59
+ if (tag.startsWith(prefix)) {
60
+ const n = Number(tag.slice(prefix.length))
61
+ if (!Number.isNaN(n)) max = Math.max(max, n)
62
+ }
63
+ }
64
+ }
65
+ return max
66
+ }
67
+
68
+ /** @type {string[]} */
69
+ let tags = []
70
+ try {
71
+ tags = execSync('git tag -l', { encoding: 'utf8' })
72
+ .split('\n')
73
+ .map((t) => t.trim())
74
+ .filter(Boolean)
75
+ } catch {
76
+ tags = []
77
+ }
78
+
79
+ const nextRc = maxRcForBase(targetBase, tags) + 1
80
+ process.stdout.write(`${targetBase}-rc.${nextRc}`)
@@ -6,8 +6,6 @@
6
6
  * - removes legacy lint-staged config block from package.json
7
7
  * - removes legacy prepare hook scripts for husky/lefthook bootstrap
8
8
  *
9
- * Lefthook `audit` skips only when `FG_DEVKIT_LEFTHOOK_AUDIT=0` for that commit (dep/vuln flow).
10
- *
11
9
  * Usage:
12
10
  * node node_modules/fg-devkit/scripts/sync-consumer-lefthook.mjs
13
11
  * pnpm exec fg-devkit setup-lefthook
@@ -1,23 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Lefthook pre-commit helper: runs `pnpm audit` unless this commit was started with
4
- * `FG_DEVKIT_LEFTHOOK_AUDIT=0` (set only for the current `git commit` by `fg-devkit commit`
5
- * when the user chooses "No" on dep/vuln). Unset or `1` runs audit.
6
- */
7
- import { spawnSync } from 'node:child_process'
8
-
9
- function shouldRunAudit() {
10
- const v = process.env.FG_DEVKIT_LEFTHOOK_AUDIT
11
- return v !== '0' && v !== 'false'
12
- }
13
-
14
- if (!shouldRunAudit()) {
15
- console.log('[fg-devkit] Skipping pnpm audit (FG_DEVKIT_LEFTHOOK_AUDIT=0 for this commit).')
16
- process.exit(0)
17
- }
18
-
19
- const r = spawnSync('pnpm', ['audit'], {
20
- stdio: 'inherit',
21
- cwd: process.cwd(),
22
- })
23
- process.exit(r.status ?? 1)