fg-devkit 1.26.0 → 1.27.1
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 +22 -0
- package/configs/dependency-policy.vue-workspace.json +33 -40
- package/configs/lefthook.yml +2 -0
- package/package.json +3 -3
- package/scaffold/vue/package.json +7 -7
- package/scripts/fg-devkit.mjs +39 -0
- package/scripts/lib/vite-build-watch.mjs +13 -0
- package/scripts/link-fds-vue-core.mjs +285 -0
package/README.md
CHANGED
|
@@ -282,6 +282,28 @@ Set `PIWIK_CLIENT_ID` and `PIWIK_CLIENT_SECRET` in the environment, `.env.local`
|
|
|
282
282
|
|
|
283
283
|
Session and event slots are assigned by Piwik as the next free number in that scope. The command creates one dimension at a time and stops if the returned slot is not the local slot. Deactivating a dimension does not free the slot. Product dimensions send the local slot (1–20).
|
|
284
284
|
|
|
285
|
+
## Local fds-vue-core in a consuming repo
|
|
286
|
+
|
|
287
|
+
Replaces the yalc flow. Clone `fds-vue-core` next to the consuming repo, then in the consuming repo:
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
pnpm exec fgd link-fds-vue-core # or: link-fds-vue-core ../path/to/fds-vue-core
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
It builds `fds-vue-core`, sets the dependency to `file:../fds-vue-core` and runs `pnpm install`. pnpm copies the build into `node_modules` and resolves `vue` from the consuming repo, so there is one `vue` at runtime and in the types. The command keeps watching `fds-vue-core/src`: each change rebuilds, reinstalls and restarts a running Vite dev server. Ctrl+C stops watching; `--no-watch` links without watching.
|
|
294
|
+
|
|
295
|
+
When `fds-vue-core` has a `build:assets` script, the watch uses `vite build --watch` plus `build:assets` instead of the full `pnpm build` (about 11 s instead of about 21 s per change). Types still come from `vite-plugin-dts`; only the extra `vue-tsc` type check is skipped, so a type error in `fds-vue-core` does not stop the update.
|
|
296
|
+
|
|
297
|
+
To link several repos at once, run `link-fds-vue-core` in each. The first one watches; the others only join its list of linked repos (kept in `fds-vue-core/node_modules/.fg-devkit-linked-consumers.json`). One build then updates every linked repo.
|
|
298
|
+
|
|
299
|
+
Before committing:
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
pnpm exec fgd unlink-fds-vue-core
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
This restores the version from the last commit. The shared Lefthook pre-commit hook runs `fgd check-local-links` and blocks commits where the staged `package.json` or `pnpm-lock.yaml` has a `file:` or `link:` dependency.
|
|
306
|
+
|
|
285
307
|
## Local development with pnpm link
|
|
286
308
|
|
|
287
309
|
Use this flow to test local `fg-devkit` changes in a consuming repo.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"toolchain": {
|
|
3
|
-
"packageManager": "pnpm@12.
|
|
3
|
+
"packageManager": "pnpm@12.15.0",
|
|
4
4
|
"node": {
|
|
5
5
|
"nvmrc": "22",
|
|
6
6
|
"engines": ">=22.13.0",
|
|
@@ -13,49 +13,43 @@
|
|
|
13
13
|
"@e965/xlsx": "0.20.3",
|
|
14
14
|
"@element-plus/icons-vue": "2.3.2",
|
|
15
15
|
"@lexical/html": "0.52.0",
|
|
16
|
+
"@lexical/link": "0.52.0",
|
|
16
17
|
"@lexical/list": "0.52.0",
|
|
17
18
|
"@lexical/rich-text": "0.52.0",
|
|
18
|
-
"@lexical/link": "0.52.0",
|
|
19
19
|
"@piwikpro/vue-piwik-pro": "2.0.0",
|
|
20
|
-
"@sentry/vue": "11.
|
|
21
|
-
"@tanstack/vue-query": "5.
|
|
22
|
-
"@
|
|
23
|
-
"@unhead/vue": "2.0.5",
|
|
24
|
-
"@vue-leaflet/vue-leaflet": "0.10.1",
|
|
20
|
+
"@sentry/vue": "11.9.0",
|
|
21
|
+
"@tanstack/vue-query": "5.105.0",
|
|
22
|
+
"@unhead/vue": "3.4.3",
|
|
25
23
|
"@vuelidate/core": "2.0.3",
|
|
26
24
|
"@vuelidate/validators": "2.0.4",
|
|
27
|
-
"@vueuse/
|
|
28
|
-
"
|
|
29
|
-
"chart.js": "4.
|
|
25
|
+
"@vueuse/core": "15.0.0",
|
|
26
|
+
"axios": "1.20.0",
|
|
27
|
+
"chart.js": "4.6.1",
|
|
30
28
|
"content-disposition-header": "0.6.0",
|
|
31
29
|
"date-fns": "4.4.0",
|
|
32
30
|
"element-plus": "2.14.7",
|
|
33
|
-
"
|
|
31
|
+
"exceljs": "4.4.0",
|
|
32
|
+
"fds-vue-core": "8.11.5",
|
|
34
33
|
"imask": "7.6.1",
|
|
35
|
-
"leaflet": "1.9.4",
|
|
36
34
|
"lexical": "0.52.0",
|
|
35
|
+
"libphonenumber-js": "1.13.15",
|
|
37
36
|
"lodash": "4.18.1",
|
|
38
37
|
"lodash-es": "4.18.1",
|
|
38
|
+
"maplibre-gl": "6.16.0",
|
|
39
39
|
"markdown-it": "15.0.3",
|
|
40
|
+
"personnummer": "3.3.1",
|
|
40
41
|
"pinia": "4.1.0",
|
|
41
|
-
"
|
|
42
|
+
"qrcode.vue": "3.11.0",
|
|
42
43
|
"quill": "2.0.3",
|
|
43
|
-
"randexp": "0.6.1",
|
|
44
|
-
"register-service-worker": "1.7.2",
|
|
45
44
|
"vega-embed": "7.3.0",
|
|
46
45
|
"vega-lite": "6.5.0",
|
|
47
46
|
"vue-chartjs": "5.3.4",
|
|
48
|
-
"vue-i18n": "11.4.16",
|
|
49
47
|
"vue-quilly": "1.1.6",
|
|
50
|
-
"vue-router": "5.4.0",
|
|
51
48
|
"vue3-touch-keyboard": "1.3.9",
|
|
52
|
-
"vuex": "4.1.0"
|
|
53
|
-
"vuex-persist": "3.1.3",
|
|
54
|
-
"yup": "1.7.1"
|
|
49
|
+
"vuex": "4.1.0"
|
|
55
50
|
},
|
|
56
51
|
"devDependencies": {
|
|
57
52
|
"@chromatic-com/storybook": "5.6.0",
|
|
58
|
-
"@eslint/js": "9.39.1",
|
|
59
53
|
"@intlify/unplugin-vue-i18n": "11.2.7",
|
|
60
54
|
"@playwright/test": "1.64.1",
|
|
61
55
|
"@storybook/addon-a11y": "10.6.3",
|
|
@@ -63,39 +57,38 @@
|
|
|
63
57
|
"@storybook/addon-vitest": "10.6.3",
|
|
64
58
|
"@storybook/vue3": "10.6.3",
|
|
65
59
|
"@storybook/vue3-vite": "10.6.3",
|
|
66
|
-
"@tanstack/vue-query-devtools": "6.3.
|
|
67
|
-
"@tsconfig/node22": "22.0.6",
|
|
60
|
+
"@tanstack/vue-query-devtools": "6.3.6",
|
|
68
61
|
"@types/lodash-es": "4.17.12",
|
|
69
|
-
"@types/node": "22.
|
|
62
|
+
"@types/node": "22.20.4",
|
|
63
|
+
"@vitejs/plugin-vue": "6.0.10",
|
|
70
64
|
"@vitest/browser": "5.0.5",
|
|
65
|
+
"@vitest/browser-playwright": "5.0.5",
|
|
71
66
|
"@vue/compiler-dom": "3.5.44",
|
|
72
|
-
"@vue/
|
|
73
|
-
"@vue/server-renderer": "3.5.44",
|
|
67
|
+
"@vue/language-core": "3.3.13",
|
|
74
68
|
"@vue/test-utils": "2.5.1",
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"eslint-plugin-storybook": "10.6.3",
|
|
78
|
-
"globals": "15.11.0",
|
|
69
|
+
"fg-devkit": "1.27.0",
|
|
70
|
+
"happy-dom": "21.0.4",
|
|
79
71
|
"jsdom": "30.1.3",
|
|
72
|
+
"randexp": "0.6.1",
|
|
80
73
|
"sass": "1.106.1",
|
|
81
74
|
"storybook": "10.6.3",
|
|
82
|
-
"tsx": "4.23.15",
|
|
83
75
|
"vega": "6.4.0",
|
|
76
|
+
"vite": "8.3.5",
|
|
84
77
|
"vite-plugin-dts": "5.1.2",
|
|
85
78
|
"vite-plugin-vue-devtools": "8.2.1",
|
|
86
79
|
"vite-plugin-vue-inspector": "7.0.0",
|
|
87
|
-
"vitest": "5.0.5"
|
|
88
|
-
"yaml": "2.9.1"
|
|
80
|
+
"vitest": "5.0.5"
|
|
89
81
|
}
|
|
90
82
|
},
|
|
91
83
|
"managedAnywhere": {
|
|
92
84
|
"@tailwindcss/vite": "4.3.3",
|
|
93
|
-
"@
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
"qrcode.vue": "3.11.0",
|
|
85
|
+
"@vue/devtools-api": "8.2.1",
|
|
86
|
+
"dotenv": "18.0.9",
|
|
87
|
+
"playwright": "1.64.1",
|
|
97
88
|
"tailwindcss": "4.3.3",
|
|
98
|
-
"
|
|
99
|
-
"vue": "
|
|
100
|
-
|
|
89
|
+
"vue": "3.5.44",
|
|
90
|
+
"vue-i18n": "11.4.16",
|
|
91
|
+
"vue-router": "5.4.0"
|
|
92
|
+
},
|
|
93
|
+
"overrides": {}
|
|
101
94
|
}
|
package/configs/lefthook.yml
CHANGED
|
@@ -4,3 +4,5 @@ pre-commit:
|
|
|
4
4
|
run: zsh -lc "node ./node_modules/fg-devkit/scripts/fg-devkit.mjs lint ."
|
|
5
5
|
typecheck:
|
|
6
6
|
run: zsh -lc "node ./node_modules/fg-devkit/scripts/fg-devkit.mjs typecheck"
|
|
7
|
+
local-links:
|
|
8
|
+
run: zsh -lc "node ./node_modules/fg-devkit/scripts/fg-devkit.mjs check-local-links"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fg-devkit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.27.1",
|
|
4
4
|
"description": "Shared configs, scripts, and DX tooling for Vue / TypeScript frontend repos",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -54,13 +54,13 @@
|
|
|
54
54
|
"@intlify/eslint-plugin-vue-i18n": "4.5.1",
|
|
55
55
|
"@typescript-eslint/eslint-plugin": "8.72.0",
|
|
56
56
|
"@typescript-eslint/parser": "8.72.0",
|
|
57
|
-
"@vitest/eslint-plugin": "1.6.
|
|
57
|
+
"@vitest/eslint-plugin": "1.6.28",
|
|
58
58
|
"@vue/eslint-config-prettier": "10.2.0",
|
|
59
59
|
"eslint": "10.13.0",
|
|
60
60
|
"eslint-plugin-prettier": "5.5.7",
|
|
61
61
|
"eslint-plugin-vue": "10.11.1",
|
|
62
62
|
"eslint-plugin-vuejs-accessibility": "2.6.3",
|
|
63
|
-
"fallow": "3.
|
|
63
|
+
"fallow": "3.36.0",
|
|
64
64
|
"git-cliff": "2.14.2",
|
|
65
65
|
"lefthook": "2.2.2",
|
|
66
66
|
"npm-check-updates": "23.1.1",
|
|
@@ -16,12 +16,12 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@tailwindcss/vite": "4.3.3",
|
|
19
|
-
"@tanstack/vue-query": "5.
|
|
19
|
+
"@tanstack/vue-query": "5.105.0",
|
|
20
20
|
"axios": "1.20.0",
|
|
21
21
|
"content-disposition-header": "0.6.0",
|
|
22
22
|
"date-fns": "4.4.0",
|
|
23
|
-
"dotenv": "18.0.
|
|
24
|
-
"fds-vue-core": "8.11.
|
|
23
|
+
"dotenv": "18.0.9",
|
|
24
|
+
"fds-vue-core": "8.11.5",
|
|
25
25
|
"pinia": "4.1.0",
|
|
26
26
|
"qrcode.vue": "3.11.0",
|
|
27
27
|
"vue": "3.5.44",
|
|
@@ -30,16 +30,16 @@
|
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@intlify/unplugin-vue-i18n": "11.2.7",
|
|
33
|
-
"@tanstack/vue-query-devtools": "6.3.
|
|
34
|
-
"@types/node": "22.
|
|
35
|
-
"fg-devkit": "1.
|
|
33
|
+
"@tanstack/vue-query-devtools": "6.3.6",
|
|
34
|
+
"@types/node": "22.20.4",
|
|
35
|
+
"fg-devkit": "1.27.1",
|
|
36
36
|
"@vitejs/plugin-vue": "6.0.10",
|
|
37
37
|
"tailwindcss": "4.3.3",
|
|
38
38
|
"tsx": "4.23.15",
|
|
39
39
|
"vite": "8.3.5",
|
|
40
40
|
"vite-plugin-vue-inspector": "7.0.0"
|
|
41
41
|
},
|
|
42
|
-
"packageManager": "pnpm@12.
|
|
42
|
+
"packageManager": "pnpm@12.15.0",
|
|
43
43
|
"engines": {
|
|
44
44
|
"node": ">=22.13.0"
|
|
45
45
|
}
|
package/scripts/fg-devkit.mjs
CHANGED
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
* pnpm exec fg-devkit sync-vscode-settings
|
|
26
26
|
* pnpm exec fg-devkit sync-consumer-scripts
|
|
27
27
|
* pnpm exec fg-devkit setup-lefthook
|
|
28
|
+
* pnpm exec fg-devkit link-fds-vue-core [path] [--no-watch]
|
|
29
|
+
* pnpm exec fg-devkit unlink-fds-vue-core
|
|
30
|
+
* pnpm exec fg-devkit check-local-links
|
|
28
31
|
* pnpm exec fg-devkit full-consumer-setup
|
|
29
32
|
* pnpm exec fg-devkit create-release [release|candidate] [patch|minor|major] [--skip-release-notes]
|
|
30
33
|
* pnpm exec fg-devkit generate-release-notes [--tag=vX.Y.Z]
|
|
@@ -614,6 +617,10 @@ Commands:
|
|
|
614
617
|
sync-vite-plugin-vue-inspector Add/pin vite-plugin-vue-inspector, wire vite.config, install, commit
|
|
615
618
|
sync-consumer-scripts Enforce standard fgd scripts in consumer package.json
|
|
616
619
|
setup-lefthook Write lefthook.yml and install the git hooks
|
|
620
|
+
link-fds-vue-core [path] [--no-watch]
|
|
621
|
+
Use a local fds-vue-core checkout (default ../fds-vue-core) and rebuild it on changes
|
|
622
|
+
unlink-fds-vue-core Go back to the fds-vue-core version from the last commit
|
|
623
|
+
check-local-links Fail when staged package.json or pnpm-lock.yaml has a file: or link: dependency
|
|
617
624
|
setup-ai-rules [--plugins=a,b] Pick the fg-ai-rules Claude Code plugins for this repo, install them once per machine, commit
|
|
618
625
|
full-consumer-setup Run: scripts -> lefthook -> step 3 -> step 2 -> pnpm install -> AI rules
|
|
619
626
|
create-release [release|candidate] [patch|minor|major] [--skip-release-notes]
|
|
@@ -717,6 +724,7 @@ async function runInteractiveMenu() {
|
|
|
717
724
|
description: 'Overwrite shared config files with the versions in fg-devkit',
|
|
718
725
|
},
|
|
719
726
|
{ name: 'Setup', value: 'setup', description: 'Git hooks and first-time setup of a repo' },
|
|
727
|
+
{ name: 'Local dev fds-vue-core', value: 'local-fds-vue-core', description: 'Develop against a local fds-vue-core checkout' },
|
|
720
728
|
{
|
|
721
729
|
name: 'Dead code (Fallow)',
|
|
722
730
|
value: 'fallow-dead-code',
|
|
@@ -1150,6 +1158,25 @@ async function runInteractiveMenu() {
|
|
|
1150
1158
|
return runSelectedAction(() => (choice === 'lefthook' ? runSetupLefthook() : runFullConsumerSetup()))
|
|
1151
1159
|
}
|
|
1152
1160
|
|
|
1161
|
+
if (group === 'local-fds-vue-core') {
|
|
1162
|
+
const choice = await selectPrompt({
|
|
1163
|
+
message: 'Local dev fds-vue-core',
|
|
1164
|
+
choices: [
|
|
1165
|
+
{
|
|
1166
|
+
name: 'Link and watch',
|
|
1167
|
+
value: 'link',
|
|
1168
|
+
description: 'Use ../fds-vue-core and rebuild it when its src changes',
|
|
1169
|
+
},
|
|
1170
|
+
{ name: 'Unlink', value: 'unlink', description: 'Go back to the fds-vue-core version from the last commit' },
|
|
1171
|
+
{ name: 'Cancel', value: 'cancel' },
|
|
1172
|
+
],
|
|
1173
|
+
default: 'link',
|
|
1174
|
+
loop: false,
|
|
1175
|
+
})
|
|
1176
|
+
if (choice === 'cancel') continue
|
|
1177
|
+
return runSelectedAction(() => runNode('link-fds-vue-core.mjs', [choice]))
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1153
1180
|
if (group === 'fallow-dead-code') {
|
|
1154
1181
|
return runSelectedAction(() => runBundledFallow(['dead-code']))
|
|
1155
1182
|
}
|
|
@@ -1305,6 +1332,18 @@ async function main() {
|
|
|
1305
1332
|
process.exitCode = await runCommit(rest)
|
|
1306
1333
|
return
|
|
1307
1334
|
}
|
|
1335
|
+
if (sub === 'link-fds-vue-core') {
|
|
1336
|
+
process.exitCode = runNode('link-fds-vue-core.mjs', ['link', ...rest])
|
|
1337
|
+
return
|
|
1338
|
+
}
|
|
1339
|
+
if (sub === 'unlink-fds-vue-core') {
|
|
1340
|
+
process.exitCode = runNode('link-fds-vue-core.mjs', ['unlink'])
|
|
1341
|
+
return
|
|
1342
|
+
}
|
|
1343
|
+
if (sub === 'check-local-links') {
|
|
1344
|
+
process.exitCode = runNode('link-fds-vue-core.mjs', ['check'])
|
|
1345
|
+
return
|
|
1346
|
+
}
|
|
1308
1347
|
if (sub === 'scaffold-vue') {
|
|
1309
1348
|
process.exitCode = runNode('scaffold-vue.mjs', rest)
|
|
1310
1349
|
return
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { pathToFileURL } from 'node:url'
|
|
4
|
+
|
|
5
|
+
const require = createRequire(path.join(process.cwd(), 'package.json'))
|
|
6
|
+
const { build } = await import(pathToFileURL(require.resolve('vite')).href)
|
|
7
|
+
|
|
8
|
+
const watcher = await build({ build: { watch: {} } })
|
|
9
|
+
watcher.on('event', (event) => {
|
|
10
|
+
if (event.code === 'END') process.send?.({ type: 'built' })
|
|
11
|
+
if (event.code === 'ERROR') process.send?.({ type: 'failed' })
|
|
12
|
+
event.result?.close?.()
|
|
13
|
+
})
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Develops fds-vue-core locally inside one or more consumer repos, without yalc.
|
|
4
|
+
*
|
|
5
|
+
* link [path] [--no-watch]
|
|
6
|
+
* Points the consumer's fds-vue-core dependency at a local checkout (default ../fds-vue-core) with
|
|
7
|
+
* file: and runs pnpm install. pnpm copies the package into node_modules and resolves vue from the
|
|
8
|
+
* consumer, so runtime and types use one vue. The consumer is added to the checkout's list of
|
|
9
|
+
* linked repos (node_modules/.fg-devkit-linked-consumers.json in fds-vue-core).
|
|
10
|
+
* Then watches fds-vue-core: on each change it rebuilds once, reinstalls in every linked repo and
|
|
11
|
+
* restarts their running Vite dev servers (clears node_modules/.vite and touches vite.config.*).
|
|
12
|
+
* When fds-vue-core has a build:assets script, the watch runs vite build --watch (types come from
|
|
13
|
+
* vite-plugin-dts) plus build:assets instead of the full pnpm build, which skips the vue-tsc type check.
|
|
14
|
+
* When another link is already watching the same checkout, this repo is only added to the list and
|
|
15
|
+
* that watcher updates it too. Ctrl+C stops watching; the links stay until unlink.
|
|
16
|
+
* unlink
|
|
17
|
+
* Restores the fds-vue-core version from the last commit (HEAD:package.json), runs pnpm install and
|
|
18
|
+
* removes the repo from the checkout's list of linked repos.
|
|
19
|
+
* check
|
|
20
|
+
* Fails when the staged package.json or pnpm-lock.yaml contains a file: or link: dependency.
|
|
21
|
+
* Used by the shared lefthook pre-commit hook.
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* pnpm exec fg-devkit link-fds-vue-core [path] [--no-watch]
|
|
25
|
+
* pnpm exec fg-devkit unlink-fds-vue-core
|
|
26
|
+
* pnpm exec fg-devkit check-local-links
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import fs from 'node:fs'
|
|
30
|
+
import path from 'node:path'
|
|
31
|
+
import { spawnSync, spawn, fork } from 'node:child_process'
|
|
32
|
+
import { fileURLToPath } from 'node:url'
|
|
33
|
+
|
|
34
|
+
const PACKAGE_NAME = 'fds-vue-core'
|
|
35
|
+
const DEPENDENCY_FIELDS = ['dependencies', 'devDependencies']
|
|
36
|
+
const VITE_CONFIG_CANDIDATES = ['vite.config.ts', 'vite.config.mts', 'vite.config.js', 'vite.config.mjs']
|
|
37
|
+
const LOCAL_SPEC_PATTERN = /^(file|link):/
|
|
38
|
+
const LINKED_CONSUMERS_FILE = '.fg-devkit-linked-consumers.json'
|
|
39
|
+
const WATCHER_PID_FILE = '.fg-devkit-link-watcher.pid'
|
|
40
|
+
|
|
41
|
+
const cwd = process.cwd()
|
|
42
|
+
const scriptDirectory = path.dirname(fileURLToPath(import.meta.url))
|
|
43
|
+
|
|
44
|
+
function fail(message) {
|
|
45
|
+
console.error(`[link-fds-vue-core] ${message}`)
|
|
46
|
+
process.exit(1)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function log(message) {
|
|
50
|
+
console.log(`[link-fds-vue-core] ${message}`)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function runSync(command, args, runCwd) {
|
|
54
|
+
const result = spawnSync(command, args, { stdio: 'inherit', cwd: runCwd })
|
|
55
|
+
return result.status === 0
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function readPackageJson(consumerPath) {
|
|
59
|
+
const packageJsonPath = path.join(consumerPath, 'package.json')
|
|
60
|
+
if (!fs.existsSync(packageJsonPath)) return null
|
|
61
|
+
return JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function findDependencyField(packageJson) {
|
|
65
|
+
return DEPENDENCY_FIELDS.find((name) => packageJson?.[name]?.[PACKAGE_NAME])
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function requireDependencyField(consumerPath) {
|
|
69
|
+
const field = findDependencyField(readPackageJson(consumerPath))
|
|
70
|
+
if (!field) fail(`${PACKAGE_NAME} is not a dependency in ${path.join(consumerPath, 'package.json')}`)
|
|
71
|
+
return field
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function setDependencySpec(consumerPath, spec) {
|
|
75
|
+
const packageJson = readPackageJson(consumerPath)
|
|
76
|
+
packageJson[findDependencyField(packageJson)][PACKAGE_NAME] = spec
|
|
77
|
+
fs.writeFileSync(path.join(consumerPath, 'package.json'), `${JSON.stringify(packageJson, null, 2)}\n`, 'utf8')
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function linkedLibraryPath(consumerPath) {
|
|
81
|
+
const packageJson = readPackageJson(consumerPath)
|
|
82
|
+
const spec = packageJson?.[findDependencyField(packageJson)]?.[PACKAGE_NAME]
|
|
83
|
+
if (!spec?.startsWith('file:')) return null
|
|
84
|
+
return path.resolve(consumerPath, spec.slice('file:'.length))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function linkedConsumersPath(libraryPath) {
|
|
88
|
+
return path.join(libraryPath, 'node_modules', LINKED_CONSUMERS_FILE)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function readLinkedConsumers(libraryPath) {
|
|
92
|
+
try {
|
|
93
|
+
return JSON.parse(fs.readFileSync(linkedConsumersPath(libraryPath), 'utf8'))
|
|
94
|
+
} catch {
|
|
95
|
+
return []
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function writeLinkedConsumers(libraryPath, consumers) {
|
|
100
|
+
fs.mkdirSync(path.join(libraryPath, 'node_modules'), { recursive: true })
|
|
101
|
+
fs.writeFileSync(linkedConsumersPath(libraryPath), `${JSON.stringify(consumers, null, 2)}\n`, 'utf8')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function currentLinkedConsumers(libraryPath) {
|
|
105
|
+
const consumers = readLinkedConsumers(libraryPath).filter(
|
|
106
|
+
(consumerPath) => linkedLibraryPath(consumerPath) === libraryPath,
|
|
107
|
+
)
|
|
108
|
+
writeLinkedConsumers(libraryPath, consumers)
|
|
109
|
+
return consumers
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function watcherPidPath(libraryPath) {
|
|
113
|
+
return path.join(libraryPath, 'node_modules', WATCHER_PID_FILE)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function isWatcherRunning(libraryPath) {
|
|
117
|
+
try {
|
|
118
|
+
process.kill(Number(fs.readFileSync(watcherPidPath(libraryPath), 'utf8')), 0)
|
|
119
|
+
return true
|
|
120
|
+
} catch {
|
|
121
|
+
return false
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function restartViteDevServer(consumerPath) {
|
|
126
|
+
fs.rmSync(path.join(consumerPath, 'node_modules', '.vite'), { recursive: true, force: true })
|
|
127
|
+
const viteConfig = VITE_CONFIG_CANDIDATES.map((name) => path.join(consumerPath, name)).find((file) =>
|
|
128
|
+
fs.existsSync(file),
|
|
129
|
+
)
|
|
130
|
+
if (!viteConfig) return
|
|
131
|
+
const now = new Date()
|
|
132
|
+
fs.utimesSync(viteConfig, now, now)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function installInConsumer(consumerPath) {
|
|
136
|
+
if (!runSync('pnpm', ['install'], consumerPath)) {
|
|
137
|
+
console.error(`[link-fds-vue-core] pnpm install failed in ${consumerPath}.`)
|
|
138
|
+
return false
|
|
139
|
+
}
|
|
140
|
+
restartViteDevServer(consumerPath)
|
|
141
|
+
return true
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function updateLinkedConsumers(libraryPath) {
|
|
145
|
+
const consumers = currentLinkedConsumers(libraryPath)
|
|
146
|
+
const updated = consumers.filter((consumerPath) => installInConsumer(consumerPath))
|
|
147
|
+
log(`Updated ${updated.length} of ${consumers.length} linked repos: ${updated.map((consumerPath) => path.basename(consumerPath)).join(', ') || 'none'}.`)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function hasBuildAssetsScript(libraryPath) {
|
|
151
|
+
return Boolean(readPackageJson(libraryPath).scripts?.['build:assets'])
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function watchWithViteBuildWatch(libraryPath) {
|
|
155
|
+
const child = fork(path.join(scriptDirectory, 'lib', 'vite-build-watch.mjs'), [], { cwd: libraryPath })
|
|
156
|
+
child.on('message', (message) => {
|
|
157
|
+
if (message.type !== 'built') {
|
|
158
|
+
console.error('[link-fds-vue-core] Build failed; waiting for the next change.')
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
if (!runSync('pnpm', ['run', 'build:assets'], libraryPath)) {
|
|
162
|
+
console.error('[link-fds-vue-core] build:assets failed; waiting for the next change.')
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
updateLinkedConsumers(libraryPath)
|
|
166
|
+
})
|
|
167
|
+
child.on('exit', (code) => process.exit(code ?? 1))
|
|
168
|
+
log(`Watching ${libraryPath} with vite build --watch for all linked repos. Press Ctrl+C to stop (the links stay until unlink-fds-vue-core).`)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function watchWithFullBuild(libraryPath) {
|
|
172
|
+
const sourcePath = path.join(libraryPath, 'src')
|
|
173
|
+
let timer = null
|
|
174
|
+
let building = false
|
|
175
|
+
let changedWhileBuilding = false
|
|
176
|
+
|
|
177
|
+
const rebuild = () => {
|
|
178
|
+
if (building) {
|
|
179
|
+
changedWhileBuilding = true
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
building = true
|
|
183
|
+
const child = spawn('pnpm', ['build'], { stdio: 'inherit', cwd: libraryPath })
|
|
184
|
+
child.on('close', (code) => {
|
|
185
|
+
if (code === 0) {
|
|
186
|
+
updateLinkedConsumers(libraryPath)
|
|
187
|
+
} else {
|
|
188
|
+
console.error('[link-fds-vue-core] Build failed; waiting for the next change.')
|
|
189
|
+
}
|
|
190
|
+
building = false
|
|
191
|
+
if (changedWhileBuilding) {
|
|
192
|
+
changedWhileBuilding = false
|
|
193
|
+
rebuild()
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
fs.watch(sourcePath, { recursive: true }, () => {
|
|
199
|
+
clearTimeout(timer)
|
|
200
|
+
timer = setTimeout(rebuild, 300)
|
|
201
|
+
})
|
|
202
|
+
log(`Watching ${sourcePath} for all linked repos. Press Ctrl+C to stop (the links stay until unlink-fds-vue-core).`)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function startWatching(libraryPath, useViteBuildWatch) {
|
|
206
|
+
fs.writeFileSync(watcherPidPath(libraryPath), String(process.pid), 'utf8')
|
|
207
|
+
process.on('exit', () => fs.rmSync(watcherPidPath(libraryPath), { force: true }))
|
|
208
|
+
process.on('SIGINT', () => process.exit(0))
|
|
209
|
+
process.on('SIGTERM', () => process.exit(0))
|
|
210
|
+
if (useViteBuildWatch) watchWithViteBuildWatch(libraryPath)
|
|
211
|
+
else watchWithFullBuild(libraryPath)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function link(args) {
|
|
215
|
+
const pathArgument = args.find((argument) => !argument.startsWith('--'))
|
|
216
|
+
const libraryPath = path.resolve(cwd, pathArgument ?? path.join('..', PACKAGE_NAME))
|
|
217
|
+
const libraryPackageJson = readPackageJson(libraryPath)
|
|
218
|
+
if (!libraryPackageJson) fail(`No package.json at ${libraryPath}`)
|
|
219
|
+
if (libraryPackageJson.name !== PACKAGE_NAME) fail(`${libraryPath} is not ${PACKAGE_NAME}`)
|
|
220
|
+
if (libraryPath === cwd) fail(`Run this in a repo that uses ${PACKAGE_NAME}, not in ${PACKAGE_NAME} itself.`)
|
|
221
|
+
|
|
222
|
+
requireDependencyField(cwd)
|
|
223
|
+
const watcherRunning = isWatcherRunning(libraryPath)
|
|
224
|
+
const watch = !watcherRunning && !args.includes('--no-watch')
|
|
225
|
+
const useViteBuildWatch = watch && hasBuildAssetsScript(libraryPath)
|
|
226
|
+
if (!watcherRunning && !useViteBuildWatch) {
|
|
227
|
+
log(`Building ${libraryPath}...`)
|
|
228
|
+
if (!runSync('pnpm', ['build'], libraryPath)) fail('Build failed.')
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
setDependencySpec(cwd, `file:${path.relative(cwd, libraryPath)}`)
|
|
232
|
+
writeLinkedConsumers(libraryPath, [...new Set([...currentLinkedConsumers(libraryPath), cwd])])
|
|
233
|
+
if (!useViteBuildWatch && !installInConsumer(cwd)) process.exit(1)
|
|
234
|
+
log(`${PACKAGE_NAME} is linked to ${libraryPath}. Run unlink-fds-vue-core before you commit.`)
|
|
235
|
+
|
|
236
|
+
if (watcherRunning) {
|
|
237
|
+
log('Another link-fds-vue-core is already watching this checkout; it updates this repo too.')
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
if (watch) startWatching(libraryPath, useViteBuildWatch)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function unlink() {
|
|
244
|
+
const field = requireDependencyField(cwd)
|
|
245
|
+
const libraryPath = linkedLibraryPath(cwd)
|
|
246
|
+
const head = spawnSync('git', ['show', 'HEAD:package.json'], { cwd, encoding: 'utf8' })
|
|
247
|
+
if (head.status !== 0) fail('Could not read package.json from the last commit.')
|
|
248
|
+
const committedSpec = JSON.parse(head.stdout)[field]?.[PACKAGE_NAME]
|
|
249
|
+
if (!committedSpec || LOCAL_SPEC_PATTERN.test(committedSpec)) {
|
|
250
|
+
fail(`The last commit has no published ${PACKAGE_NAME} version in ${field}; set it in package.json by hand.`)
|
|
251
|
+
}
|
|
252
|
+
setDependencySpec(cwd, committedSpec)
|
|
253
|
+
if (libraryPath) currentLinkedConsumers(libraryPath)
|
|
254
|
+
if (!installInConsumer(cwd)) process.exit(1)
|
|
255
|
+
log(`${PACKAGE_NAME} is back on ${committedSpec}.`)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function readStagedFile(file) {
|
|
259
|
+
const result = spawnSync('git', ['show', `:${file}`], { cwd, encoding: 'utf8' })
|
|
260
|
+
return result.status === 0 ? result.stdout : ''
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function check() {
|
|
264
|
+
const stagedPackageJson = readStagedFile('package.json')
|
|
265
|
+
const packageJson = stagedPackageJson ? JSON.parse(stagedPackageJson) : {}
|
|
266
|
+
const localDependencies = [...DEPENDENCY_FIELDS, 'optionalDependencies'].flatMap((field) =>
|
|
267
|
+
Object.entries(packageJson[field] ?? {})
|
|
268
|
+
.filter(([, spec]) => LOCAL_SPEC_PATTERN.test(spec))
|
|
269
|
+
.map(([name, spec]) => `${name}: ${spec}`),
|
|
270
|
+
)
|
|
271
|
+
const lockfileHasLocalDependency = /specifier: (file|link):/.test(readStagedFile('pnpm-lock.yaml'))
|
|
272
|
+
if (localDependencies.length === 0 && !lockfileHasLocalDependency) return
|
|
273
|
+
|
|
274
|
+
console.error('[check-local-links] Local dependency links are staged for commit:')
|
|
275
|
+
for (const dependency of localDependencies) console.error(` ${dependency}`)
|
|
276
|
+
if (lockfileHasLocalDependency) console.error(' pnpm-lock.yaml has a file: or link: specifier')
|
|
277
|
+
console.error('[check-local-links] Run: pnpm exec fgd unlink-fds-vue-core (or git commit --no-verify for WIP).')
|
|
278
|
+
process.exit(1)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const [command, ...args] = process.argv.slice(2)
|
|
282
|
+
if (command === 'link') link(args)
|
|
283
|
+
else if (command === 'unlink') unlink()
|
|
284
|
+
else if (command === 'check') check()
|
|
285
|
+
else fail('Usage: link-fds-vue-core.mjs <link [path] [--no-watch] | unlink | check>')
|