fg-devkit 1.24.2 → 1.24.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.
package/README.md
CHANGED
|
@@ -95,8 +95,10 @@ To use the shared AI rules from [feelgood/fg-ai-rules](https://github.com/feelgo
|
|
|
95
95
|
pnpm exec fgd setup-ai-rules
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
-
It
|
|
99
|
-
|
|
98
|
+
It adds the `fg` marketplace to Claude Code if it is missing and asks which of its plugins the repo
|
|
99
|
+
should use (the repo's current choice, or `fg-core` and `fg-frontend`, is preselected; pass
|
|
100
|
+
`--plugins=fg-core,fg-backend` to skip the question). It enables those in `.claude/settings.json` and
|
|
101
|
+
installs them once for the user, disabled at user scope. The
|
|
100
102
|
repo's `.claude/settings.json` then switches them on, so every repo that commits that file gets the rules
|
|
101
103
|
and other repos don't. When `.claude/settings.json` changed it commits that file on its own
|
|
102
104
|
(`chore(repo): enable fg-ai-rules Claude Code plugins`). Each developer runs it once on their machine. Needs Claude Code and SSH access to `feelgood/fg-ai-rules`.
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"@lexical/rich-text": "0.52.0",
|
|
18
18
|
"@lexical/link": "0.52.0",
|
|
19
19
|
"@piwikpro/vue-piwik-pro": "2.0.0",
|
|
20
|
-
"@sentry/vue": "11.
|
|
20
|
+
"@sentry/vue": "11.8.0",
|
|
21
21
|
"@tanstack/vue-query": "5.104.3",
|
|
22
22
|
"@types/leaflet": "1.9.22",
|
|
23
23
|
"@unhead/vue": "2.0.5",
|
|
@@ -30,13 +30,13 @@
|
|
|
30
30
|
"content-disposition-header": "0.6.0",
|
|
31
31
|
"date-fns": "4.4.0",
|
|
32
32
|
"element-plus": "2.14.7",
|
|
33
|
-
"fds-vue-core": "8.11.
|
|
33
|
+
"fds-vue-core": "8.11.2",
|
|
34
34
|
"imask": "7.6.1",
|
|
35
35
|
"leaflet": "1.9.4",
|
|
36
36
|
"lexical": "0.52.0",
|
|
37
37
|
"lodash": "4.18.1",
|
|
38
38
|
"lodash-es": "4.18.1",
|
|
39
|
-
"markdown-it": "15.0.
|
|
39
|
+
"markdown-it": "15.0.3",
|
|
40
40
|
"pinia": "4.1.0",
|
|
41
41
|
"playwright": "1.64.1",
|
|
42
42
|
"quill": "2.0.3",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"@tsconfig/node22": "22.0.6",
|
|
68
68
|
"@types/lodash-es": "4.17.12",
|
|
69
69
|
"@types/node": "22.16.5",
|
|
70
|
-
"@vitest/browser": "5.0.
|
|
70
|
+
"@vitest/browser": "5.0.5",
|
|
71
71
|
"@vue/compiler-dom": "3.5.44",
|
|
72
72
|
"@vue/eslint-config-standard": "8.0.1",
|
|
73
73
|
"@vue/server-renderer": "3.5.44",
|
|
@@ -76,15 +76,15 @@
|
|
|
76
76
|
"babel-eslint": "10.1.0",
|
|
77
77
|
"eslint-plugin-storybook": "10.6.2",
|
|
78
78
|
"globals": "15.11.0",
|
|
79
|
-
"jsdom": "30.1.
|
|
80
|
-
"sass": "1.105.
|
|
79
|
+
"jsdom": "30.1.3",
|
|
80
|
+
"sass": "1.105.2",
|
|
81
81
|
"storybook": "10.6.2",
|
|
82
82
|
"tsx": "4.23.15",
|
|
83
83
|
"vega": "6.4.0",
|
|
84
84
|
"vite-plugin-dts": "5.1.2",
|
|
85
85
|
"vite-plugin-vue-devtools": "8.2.1",
|
|
86
86
|
"vite-plugin-vue-inspector": "7.0.0",
|
|
87
|
-
"vitest": "5.0.
|
|
87
|
+
"vitest": "5.0.5",
|
|
88
88
|
"yaml": "2.9.1"
|
|
89
89
|
}
|
|
90
90
|
},
|
package/package.json
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"content-disposition-header": "0.6.0",
|
|
22
22
|
"date-fns": "4.4.0",
|
|
23
23
|
"dotenv": "18.0.8",
|
|
24
|
-
"fds-vue-core": "8.11.
|
|
24
|
+
"fds-vue-core": "8.11.2",
|
|
25
25
|
"pinia": "4.1.0",
|
|
26
26
|
"qrcode.vue": "3.11.0",
|
|
27
27
|
"vue": "3.5.44",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"@intlify/unplugin-vue-i18n": "11.2.6",
|
|
33
33
|
"@tanstack/vue-query-devtools": "6.3.3",
|
|
34
34
|
"@types/node": "22.16.5",
|
|
35
|
-
"fg-devkit": "1.24.
|
|
35
|
+
"fg-devkit": "1.24.4",
|
|
36
36
|
"@vitejs/plugin-vue": "6.0.10",
|
|
37
37
|
"tailwindcss": "4.3.3",
|
|
38
38
|
"tsx": "4.23.15",
|
|
@@ -195,8 +195,15 @@ function assertCleanGitState() {
|
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
let gitPrefix
|
|
199
|
+
|
|
200
|
+
function repoPath(file) {
|
|
201
|
+
gitPrefix ??= runOrFail('git', ['rev-parse', '--show-prefix'], { capture: true }).stdout.trim()
|
|
202
|
+
return `${gitPrefix}${file}`
|
|
203
|
+
}
|
|
204
|
+
|
|
198
205
|
function headContent(file) {
|
|
199
|
-
const result = run('git', ['show', `HEAD:${file}`], { capture: true })
|
|
206
|
+
const result = run('git', ['show', `HEAD:${repoPath(file)}`], { capture: true })
|
|
200
207
|
return result.status === 0 ? result.stdout : undefined
|
|
201
208
|
}
|
|
202
209
|
|
|
@@ -205,11 +212,11 @@ function stageContents(contents) {
|
|
|
205
212
|
let staged = 0
|
|
206
213
|
for (const [file, content] of Object.entries(contents)) {
|
|
207
214
|
if (content === undefined || content === headContent(file)) continue
|
|
208
|
-
const sha = runOrFail('git', ['hash-object', '-w', '--stdin', `--path=${file}`], {
|
|
215
|
+
const sha = runOrFail('git', ['hash-object', '-w', '--stdin', `--path=${repoPath(file)}`], {
|
|
209
216
|
capture: true,
|
|
210
217
|
input: content,
|
|
211
218
|
}).stdout.trim()
|
|
212
|
-
runOrFail('git', ['update-index', '--add', '--cacheinfo', `100644,${sha},${file}`])
|
|
219
|
+
runOrFail('git', ['update-index', '--add', '--cacheinfo', `100644,${sha},${repoPath(file)}`])
|
|
213
220
|
staged += 1
|
|
214
221
|
}
|
|
215
222
|
return staged
|
package/scripts/fg-devkit.mjs
CHANGED
|
@@ -577,7 +577,7 @@ Commands:
|
|
|
577
577
|
sync-vite-plugin-vue-inspector Add/pin vite-plugin-vue-inspector, wire vite.config, install, commit
|
|
578
578
|
sync-consumer-scripts Enforce standard fgd scripts in consumer package.json
|
|
579
579
|
setup-lefthook Write lefthook.yml and install the git hooks
|
|
580
|
-
setup-ai-rules
|
|
580
|
+
setup-ai-rules [--plugins=a,b] Pick the fg-ai-rules Claude Code plugins for this repo, install them once per machine, commit
|
|
581
581
|
full-consumer-setup Run: scripts -> lefthook -> step 3 -> step 2 -> pnpm install -> AI rules
|
|
582
582
|
create-release [release|candidate] [patch|minor|major] [--skip-release-notes]
|
|
583
583
|
Stable release or candidate (X.Y.Z-rc.N); omit = interactive menu
|
|
@@ -1091,9 +1091,9 @@ async function runInteractiveMenu() {
|
|
|
1091
1091
|
description: 'Write lefthook.yml and install the git hooks',
|
|
1092
1092
|
},
|
|
1093
1093
|
{
|
|
1094
|
-
name:
|
|
1094
|
+
name: tagged('AI rules', { interactive: true, autoCommit: true }),
|
|
1095
1095
|
value: 'ai-rules',
|
|
1096
|
-
description: '
|
|
1096
|
+
description: 'Pick the fg-ai-rules plugins for this repo, install them for Claude Code and commit .claude/settings.json',
|
|
1097
1097
|
},
|
|
1098
1098
|
{
|
|
1099
1099
|
name: 'Full setup',
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Set up the shared AI rules from feelgood/fg-ai-rules for Claude Code in the current repo:
|
|
4
4
|
*
|
|
5
|
-
* 1. Add the `fg` marketplace
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* 1. Add the `fg` marketplace to Claude Code on this machine if it is missing, or refresh it.
|
|
6
|
+
* 2. Ask which of its plugins the repo should use (a checklist; the repo's current choice, or
|
|
7
|
+
* fg-core and fg-frontend, is preselected) and write them to .claude/settings.json, keeping
|
|
8
|
+
* every other setting. --plugins=fg-core,fg-backend picks them without asking.
|
|
8
9
|
* 3. Install the plugins once for the user (`--scope user`) and disable them at user scope.
|
|
9
10
|
* Claude Code doesn't install plugins from a repo's settings file on its own, and a
|
|
10
11
|
* project-scope install only covers one repo. A user-scope install covers every repo, and
|
|
@@ -16,19 +17,20 @@
|
|
|
16
17
|
* every other repo that commits the same .claude/settings.json works without running it again.
|
|
17
18
|
*
|
|
18
19
|
* Usage:
|
|
19
|
-
* pnpm exec fg-devkit setup-ai-rules
|
|
20
|
+
* pnpm exec fg-devkit setup-ai-rules [--plugins=fg-core,fg-frontend]
|
|
20
21
|
*/
|
|
21
22
|
|
|
22
23
|
import { spawnSync } from 'node:child_process'
|
|
23
24
|
import fs from 'node:fs'
|
|
24
25
|
import path from 'node:path'
|
|
25
26
|
|
|
26
|
-
import { fileDiffersFromHead, runGit } from './lib/common.mjs'
|
|
27
|
+
import { fileDiffersFromHead, readJson, runGit } from './lib/common.mjs'
|
|
27
28
|
|
|
28
29
|
const TAG = '[setup-ai-rules]'
|
|
29
30
|
const MARKETPLACE = 'fg'
|
|
30
31
|
const MARKETPLACE_REPO = 'feelgood/fg-ai-rules'
|
|
31
|
-
const
|
|
32
|
+
const DEFAULT_PLUGINS = ['fg-core', 'fg-frontend']
|
|
33
|
+
const pluginsArg = process.argv.slice(2).find((arg) => arg.startsWith('--plugins='))
|
|
32
34
|
|
|
33
35
|
const projectRoot = fs.realpathSync(process.cwd())
|
|
34
36
|
const SETTINGS_FILE = '.claude/settings.json'
|
|
@@ -66,26 +68,71 @@ function claudeJson(args) {
|
|
|
66
68
|
}
|
|
67
69
|
}
|
|
68
70
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
function readSettings() {
|
|
72
|
+
if (!fs.existsSync(settingsPath)) return {}
|
|
73
|
+
try {
|
|
74
|
+
return JSON.parse(fs.readFileSync(settingsPath, 'utf8'))
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.error(`${TAG} Could not parse ${settingsPath}: ${err.message}`)
|
|
77
|
+
process.exit(1)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Plugins this repo enables from the marketplace. */
|
|
82
|
+
function enabledInRepo(settings) {
|
|
83
|
+
return Object.entries(settings.enabledPlugins ?? {})
|
|
84
|
+
.filter(([id, enabled]) => enabled && id.endsWith(`@${MARKETPLACE}`))
|
|
85
|
+
.map(([id]) => id.slice(0, -`@${MARKETPLACE}`.length))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Plugins listed in the marketplace on this machine: `[{ name, description }]`. */
|
|
89
|
+
function marketplacePlugins(installLocation) {
|
|
90
|
+
try {
|
|
91
|
+
return readJson(path.join(installLocation, '.claude-plugin', 'marketplace.json')).plugins ?? []
|
|
92
|
+
} catch {
|
|
93
|
+
return []
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function choosePlugins(available, settings) {
|
|
98
|
+
const names = available.map((plugin) => plugin.name)
|
|
99
|
+
if (pluginsArg) {
|
|
100
|
+
const chosen = pluginsArg.slice('--plugins='.length).split(',').filter(Boolean)
|
|
101
|
+
const unknown = chosen.filter((name) => !names.includes(name))
|
|
102
|
+
if (unknown.length > 0) {
|
|
103
|
+
console.error(`${TAG} Unknown plugin(s): ${unknown.join(', ')}. Available: ${names.join(', ')}`)
|
|
104
|
+
return null
|
|
78
105
|
}
|
|
106
|
+
return chosen
|
|
79
107
|
}
|
|
108
|
+
const current = enabledInRepo(settings).filter((name) => names.includes(name))
|
|
109
|
+
const preselected = current.length > 0 ? current : DEFAULT_PLUGINS.filter((name) => names.includes(name))
|
|
110
|
+
if (!process.stdin.isTTY) return preselected
|
|
111
|
+
const checkbox = (await import('@inquirer/checkbox')).default
|
|
112
|
+
return checkbox({
|
|
113
|
+
message: 'Which fg-ai-rules plugins should this repo use?',
|
|
114
|
+
choices: available.map((plugin) => ({
|
|
115
|
+
name: plugin.name,
|
|
116
|
+
value: plugin.name,
|
|
117
|
+
description: plugin.description,
|
|
118
|
+
checked: preselected.includes(plugin.name),
|
|
119
|
+
})),
|
|
120
|
+
required: true,
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Enable the chosen plugins in .claude/settings.json and drop the other fg ones. Returns true when it changed. */
|
|
125
|
+
function updateSettings(settings, plugins) {
|
|
80
126
|
const before = JSON.stringify(settings)
|
|
81
127
|
settings.extraKnownMarketplaces = {
|
|
82
128
|
...settings.extraKnownMarketplaces,
|
|
83
129
|
[MARKETPLACE]: { source: { source: 'github', repo: MARKETPLACE_REPO }, autoUpdate: true },
|
|
84
130
|
}
|
|
85
|
-
settings.enabledPlugins
|
|
86
|
-
|
|
87
|
-
...
|
|
88
|
-
|
|
131
|
+
const others = Object.entries(settings.enabledPlugins ?? {}).filter(([id]) => !id.endsWith(`@${MARKETPLACE}`))
|
|
132
|
+
settings.enabledPlugins = Object.fromEntries([
|
|
133
|
+
...others,
|
|
134
|
+
...plugins.map((plugin) => [`${plugin}@${MARKETPLACE}`, true]),
|
|
135
|
+
])
|
|
89
136
|
if (JSON.stringify(settings) === before) return false
|
|
90
137
|
fs.mkdirSync(path.dirname(settingsPath), { recursive: true })
|
|
91
138
|
fs.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`)
|
|
@@ -106,43 +153,62 @@ function commitSettings() {
|
|
|
106
153
|
return status
|
|
107
154
|
}
|
|
108
155
|
|
|
109
|
-
function printManualSteps() {
|
|
110
|
-
console.log(`${TAG} Run these
|
|
156
|
+
function printManualSteps(plugins) {
|
|
157
|
+
console.log(`${TAG} Run these, then restart Claude Code:`)
|
|
111
158
|
console.log(` claude plugin marketplace add ${MARKETPLACE_REPO}`)
|
|
112
|
-
for (const plugin of
|
|
159
|
+
for (const plugin of plugins) {
|
|
113
160
|
console.log(` claude plugin install ${plugin}@${MARKETPLACE} --scope user`)
|
|
114
161
|
console.log(` claude plugin disable ${plugin}@${MARKETPLACE} --scope user`)
|
|
115
162
|
}
|
|
116
163
|
}
|
|
117
164
|
|
|
118
|
-
function main() {
|
|
165
|
+
async function main() {
|
|
119
166
|
if (!fs.existsSync(path.join(projectRoot, 'package.json'))) {
|
|
120
167
|
console.error(`${TAG} Run from the repo root (no package.json in ${projectRoot}).`)
|
|
121
168
|
return 1
|
|
122
169
|
}
|
|
123
170
|
|
|
124
|
-
const settingsChanged = updateSettings()
|
|
125
|
-
console.log(`${TAG} ${settingsChanged ? 'Updated' : 'Already up to date:'} .claude/settings.json`)
|
|
126
|
-
|
|
127
171
|
const marketplaces = claudeJson(['plugin', 'marketplace', 'list'])
|
|
128
172
|
if (marketplaces === null) {
|
|
129
173
|
const missing = claude(['--version'], { capture: true }).missing
|
|
130
174
|
console.error(`${TAG} ${missing ? 'Claude Code (claude) is not installed.' : 'Could not read Claude Code plugins.'}`)
|
|
131
|
-
printManualSteps()
|
|
175
|
+
printManualSteps(DEFAULT_PLUGINS)
|
|
132
176
|
return 1
|
|
133
177
|
}
|
|
134
178
|
|
|
135
|
-
|
|
179
|
+
let marketplace = marketplaces.find((entry) => entry.name === MARKETPLACE)
|
|
180
|
+
if (marketplace) {
|
|
181
|
+
// Refresh so the checklist shows plugins added to fg-ai-rules since the last update.
|
|
182
|
+
claude(['plugin', 'marketplace', 'update', MARKETPLACE], { capture: true })
|
|
183
|
+
} else {
|
|
136
184
|
console.log(`${TAG} Adding the ${MARKETPLACE} marketplace (${MARKETPLACE_REPO})…`)
|
|
137
185
|
if (claude(['plugin', 'marketplace', 'add', MARKETPLACE_REPO]).status !== 0) {
|
|
138
186
|
console.error(`${TAG} Could not add the marketplace. Check that you can clone ${MARKETPLACE_REPO} over SSH.`)
|
|
139
187
|
return 1
|
|
140
188
|
}
|
|
189
|
+
marketplace = claudeJson(['plugin', 'marketplace', 'list'])?.find((entry) => entry.name === MARKETPLACE)
|
|
141
190
|
}
|
|
142
191
|
|
|
192
|
+
const available = marketplacePlugins(marketplace?.installLocation ?? '')
|
|
193
|
+
if (available.length === 0) {
|
|
194
|
+
console.error(`${TAG} Could not read the plugins in the ${MARKETPLACE} marketplace.`)
|
|
195
|
+
return 1
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const settings = readSettings()
|
|
199
|
+
const plugins = await choosePlugins(available, settings)
|
|
200
|
+
if (!plugins) return 1
|
|
201
|
+
if (plugins.length === 0) {
|
|
202
|
+
console.error(`${TAG} No plugins chosen.`)
|
|
203
|
+
return 1
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const settingsChanged = updateSettings(settings, plugins)
|
|
207
|
+
console.log(`${TAG} ${settingsChanged ? 'Updated' : 'Already up to date:'} ${SETTINGS_FILE} (${plugins.join(', ')})`)
|
|
208
|
+
|
|
143
209
|
const installed = claudeJson(['plugin', 'list']) ?? []
|
|
144
210
|
let failed = false
|
|
145
|
-
for (const plugin of
|
|
211
|
+
for (const plugin of plugins) {
|
|
146
212
|
const id = `${plugin}@${MARKETPLACE}`
|
|
147
213
|
if (installed.some((entry) => entry.id === id && entry.scope === 'user')) {
|
|
148
214
|
console.log(`${TAG} ${plugin} is already installed.`)
|
|
@@ -161,7 +227,7 @@ function main() {
|
|
|
161
227
|
}
|
|
162
228
|
}
|
|
163
229
|
if (failed) {
|
|
164
|
-
printManualSteps()
|
|
230
|
+
printManualSteps(plugins)
|
|
165
231
|
return 1
|
|
166
232
|
}
|
|
167
233
|
|
|
@@ -170,4 +236,4 @@ function main() {
|
|
|
170
236
|
return 0
|
|
171
237
|
}
|
|
172
238
|
|
|
173
|
-
process.exit(main())
|
|
239
|
+
process.exit(await main())
|