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 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.14.0",
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.8.0",
21
- "@tanstack/vue-query": "5.104.5",
22
- "@types/leaflet": "1.9.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/components": "12.7.0",
28
- "@vueuse/core": "14.4.0",
29
- "chart.js": "4.5.1",
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
- "fds-vue-core": "8.11.2",
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
- "playwright": "1.64.1",
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.5",
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.16.5",
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/eslint-config-standard": "8.0.1",
73
- "@vue/server-renderer": "3.5.44",
67
+ "@vue/language-core": "3.3.13",
74
68
  "@vue/test-utils": "2.5.1",
75
- "arg": "5.0.2",
76
- "babel-eslint": "10.1.0",
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
- "@vitejs/plugin-vue": "6.0.10",
94
- "axios": "1.20.0",
95
- "dotenv": "18.0.8",
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
- "vite": "8.3.5",
99
- "vue": "3.5.44"
100
- }
89
+ "vue": "3.5.44",
90
+ "vue-i18n": "11.4.16",
91
+ "vue-router": "5.4.0"
92
+ },
93
+ "overrides": {}
101
94
  }
@@ -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.26.0",
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.27",
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.34.1",
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.104.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.8",
24
- "fds-vue-core": "8.11.2",
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.5",
34
- "@types/node": "22.16.5",
35
- "fg-devkit": "1.26.0",
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.14.0",
42
+ "packageManager": "pnpm@12.15.0",
43
43
  "engines": {
44
44
  "node": ">=22.13.0"
45
45
  }
@@ -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>')