@gtkx/cli 2.0.0-beta.2 → 2.0.0-beta.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.
Files changed (144) hide show
  1. package/dist/cli.d.ts +1 -1
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +12 -2
  4. package/dist/cli.js.map +1 -1
  5. package/dist/codegen/config-dependencies.d.ts +3 -0
  6. package/dist/codegen/config-dependencies.d.ts.map +1 -0
  7. package/dist/codegen/config-dependencies.js +355 -0
  8. package/dist/codegen/config-dependencies.js.map +1 -0
  9. package/dist/codegen/reference.d.ts.map +1 -1
  10. package/dist/codegen/reference.js +1 -0
  11. package/dist/codegen/reference.js.map +1 -1
  12. package/dist/codegen/run-codegen.d.ts +3 -2
  13. package/dist/codegen/run-codegen.d.ts.map +1 -1
  14. package/dist/codegen/run-codegen.js +33 -14
  15. package/dist/codegen/run-codegen.js.map +1 -1
  16. package/dist/codegen/store-resolver.d.ts +2 -0
  17. package/dist/codegen/store-resolver.d.ts.map +1 -1
  18. package/dist/codegen/store-resolver.js +6 -3
  19. package/dist/codegen/store-resolver.js.map +1 -1
  20. package/dist/commands/cleanup.d.ts +9 -0
  21. package/dist/commands/cleanup.d.ts.map +1 -0
  22. package/dist/commands/cleanup.js +31 -0
  23. package/dist/commands/cleanup.js.map +1 -0
  24. package/dist/commands/dev.d.ts +1 -0
  25. package/dist/commands/dev.d.ts.map +1 -1
  26. package/dist/commands/dev.js +13 -4
  27. package/dist/commands/dev.js.map +1 -1
  28. package/dist/commands/docs.d.ts.map +1 -1
  29. package/dist/commands/docs.js +1 -0
  30. package/dist/commands/docs.js.map +1 -1
  31. package/dist/deploy/freedesktop/validate.d.ts.map +1 -1
  32. package/dist/deploy/freedesktop/validate.js +11 -4
  33. package/dist/deploy/freedesktop/validate.js.map +1 -1
  34. package/dist/deploy/nfpm/config.d.ts +3 -2
  35. package/dist/deploy/nfpm/config.d.ts.map +1 -1
  36. package/dist/deploy/nfpm/config.js +3 -3
  37. package/dist/deploy/nfpm/config.js.map +1 -1
  38. package/dist/deploy/node-runtime/index.d.ts.map +1 -1
  39. package/dist/deploy/node-runtime/index.js +2 -1
  40. package/dist/deploy/node-runtime/index.js.map +1 -1
  41. package/dist/deploy/payload/stage.d.ts.map +1 -1
  42. package/dist/deploy/payload/stage.js +24 -3
  43. package/dist/deploy/payload/stage.js.map +1 -1
  44. package/dist/deploy/settings/extra-files.d.ts.map +1 -1
  45. package/dist/deploy/settings/extra-files.js +4 -3
  46. package/dist/deploy/settings/extra-files.js.map +1 -1
  47. package/dist/deploy/targets/nfpm-target.d.ts.map +1 -1
  48. package/dist/deploy/targets/nfpm-target.js +12 -7
  49. package/dist/deploy/targets/nfpm-target.js.map +1 -1
  50. package/dist/dev/entry-env.d.ts +2 -1
  51. package/dist/dev/entry-env.d.ts.map +1 -1
  52. package/dist/dev/entry-env.js +2 -1
  53. package/dist/dev/entry-env.js.map +1 -1
  54. package/dist/dev/runner-deps.d.ts +1 -1
  55. package/dist/dev/runner-deps.d.ts.map +1 -1
  56. package/dist/dev/runner-deps.js +3 -3
  57. package/dist/dev/runner-deps.js.map +1 -1
  58. package/dist/dev/runner-main.d.ts.map +1 -1
  59. package/dist/dev/runner-main.js +8 -3
  60. package/dist/dev/runner-main.js.map +1 -1
  61. package/dist/dev/supervisor.d.ts +3 -1
  62. package/dist/dev/supervisor.d.ts.map +1 -1
  63. package/dist/dev/supervisor.js +124 -27
  64. package/dist/dev/supervisor.js.map +1 -1
  65. package/dist/i18n/catalogs.d.ts.map +1 -1
  66. package/dist/i18n/catalogs.js +1 -0
  67. package/dist/i18n/catalogs.js.map +1 -1
  68. package/dist/i18n/source-messages.d.ts +3 -1
  69. package/dist/i18n/source-messages.d.ts.map +1 -1
  70. package/dist/i18n/source-messages.js +167 -37
  71. package/dist/i18n/source-messages.js.map +1 -1
  72. package/dist/internal/build-output.d.ts.map +1 -1
  73. package/dist/internal/build-output.js +53 -6
  74. package/dist/internal/build-output.js.map +1 -1
  75. package/dist/internal/docs-elements.d.ts +1 -0
  76. package/dist/internal/docs-elements.d.ts.map +1 -1
  77. package/dist/internal/docs-elements.js +5 -4
  78. package/dist/internal/docs-elements.js.map +1 -1
  79. package/dist/internal/errors.d.ts +2 -1
  80. package/dist/internal/errors.d.ts.map +1 -1
  81. package/dist/internal/errors.js +1 -1
  82. package/dist/internal/errors.js.map +1 -1
  83. package/dist/internal/parent-process.d.ts +12 -0
  84. package/dist/internal/parent-process.d.ts.map +1 -0
  85. package/dist/internal/parent-process.js +74 -0
  86. package/dist/internal/parent-process.js.map +1 -0
  87. package/dist/internal/prepare-project.d.ts +1 -0
  88. package/dist/internal/prepare-project.d.ts.map +1 -1
  89. package/dist/internal/prepare-project.js +8 -2
  90. package/dist/internal/prepare-project.js.map +1 -1
  91. package/dist/vite-plugins/ensure-store.d.ts +1 -1
  92. package/dist/vite-plugins/ensure-store.d.ts.map +1 -1
  93. package/dist/vite-plugins/ensure-store.js +2 -2
  94. package/dist/vite-plugins/ensure-store.js.map +1 -1
  95. package/dist/vite-plugins/i18n.d.ts +1 -1
  96. package/dist/vite-plugins/i18n.d.ts.map +1 -1
  97. package/dist/vite-plugins/i18n.js +24 -11
  98. package/dist/vite-plugins/i18n.js.map +1 -1
  99. package/dist/vite-plugins/index.d.ts.map +1 -1
  100. package/dist/vite-plugins/index.js +3 -1
  101. package/dist/vite-plugins/index.js.map +1 -1
  102. package/dist/vite-plugins/native.d.ts.map +1 -1
  103. package/dist/vite-plugins/native.js +0 -4
  104. package/dist/vite-plugins/native.js.map +1 -1
  105. package/dist/vite-plugins/settings-worker-env.d.ts +1 -1
  106. package/dist/vite-plugins/settings-worker-env.d.ts.map +1 -1
  107. package/dist/vite-plugins/settings-worker-env.js +2 -2
  108. package/dist/vite-plugins/settings-worker-env.js.map +1 -1
  109. package/dist/vitest-plugin.d.ts +2 -1
  110. package/dist/vitest-plugin.d.ts.map +1 -1
  111. package/dist/vitest-plugin.js +6 -6
  112. package/dist/vitest-plugin.js.map +1 -1
  113. package/package.json +12 -9
  114. package/src/cli.ts +12 -2
  115. package/src/codegen/config-dependencies.ts +470 -0
  116. package/src/codegen/reference.ts +1 -0
  117. package/src/codegen/run-codegen.ts +39 -17
  118. package/src/codegen/store-resolver.ts +8 -3
  119. package/src/commands/cleanup.ts +36 -0
  120. package/src/commands/dev.ts +26 -4
  121. package/src/commands/docs.ts +1 -0
  122. package/src/deploy/freedesktop/validate.ts +13 -4
  123. package/src/deploy/nfpm/config.ts +9 -3
  124. package/src/deploy/node-runtime/index.ts +2 -1
  125. package/src/deploy/payload/stage.ts +42 -3
  126. package/src/deploy/settings/extra-files.ts +5 -3
  127. package/src/deploy/targets/nfpm-target.ts +13 -6
  128. package/src/dev/entry-env.ts +2 -1
  129. package/src/dev/runner-deps.ts +3 -3
  130. package/src/dev/runner-main.ts +10 -3
  131. package/src/dev/supervisor.ts +164 -30
  132. package/src/i18n/catalogs.ts +1 -0
  133. package/src/i18n/source-messages.ts +240 -41
  134. package/src/internal/build-output.ts +79 -7
  135. package/src/internal/docs-elements.ts +10 -4
  136. package/src/internal/errors.ts +1 -1
  137. package/src/internal/parent-process.ts +110 -0
  138. package/src/internal/prepare-project.ts +9 -3
  139. package/src/vite-plugins/ensure-store.ts +2 -2
  140. package/src/vite-plugins/i18n.ts +30 -14
  141. package/src/vite-plugins/index.ts +8 -1
  142. package/src/vite-plugins/native.ts +0 -5
  143. package/src/vite-plugins/settings-worker-env.ts +2 -2
  144. package/src/vitest-plugin.ts +6 -6
@@ -1,9 +1,9 @@
1
1
  import { error, exitCodeForSignal, info, installGracefulShutdown } from "@gtkx/utils";
2
2
  import { fork as nodeFork } from "node:child_process";
3
- import { type FSWatcher, watch as watchFs } from "node:fs";
4
- import { basename, dirname } from "node:path";
3
+ import { type FSWatcher, statSync, watch as watchFs } from "node:fs";
4
+ import { basename, dirname, join } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
- import { DEV_ENTRY_ENV } from "./entry-env.js";
6
+ import { DEV_CONFIG_ENV, DEV_ENTRY_ENV } from "./entry-env.js";
7
7
 
8
8
  type SupervisedChild = {
9
9
  killed: boolean;
@@ -18,25 +18,30 @@ type ForkRunner = (modulePath: string, args: string[], env: NodeJS.ProcessEnv, c
18
18
 
19
19
  type DevWatch = {
20
20
  paths: string[];
21
- regenerate: () => Promise<void>;
21
+ resolvePaths: () => string[];
22
+ regenerate: () => Promise<string[]>;
22
23
  };
23
24
 
24
25
  type SupervisorState = {
25
26
  runnerPath: string;
26
27
  entryPath: string;
28
+ configFile: string;
27
29
  cwd: string;
28
30
  args: string[];
29
31
  watch: DevWatch | undefined;
30
32
  watchers: FSWatcher[];
33
+ restartTimer: DebounceTimer;
31
34
  fork: ForkRunner;
32
35
  child: SupervisedChild | null;
33
36
  isShuttingDown: boolean;
34
37
  isRestarting: boolean;
38
+ isRestartPending: boolean;
35
39
  capturedChildExit: number | undefined;
36
40
  };
37
41
 
38
42
  type DevSupervisorOptions = {
39
43
  entryPath: string;
44
+ configFile: string;
40
45
  cwd: string;
41
46
  args?: string[] | undefined;
42
47
  watch?: DevWatch | undefined;
@@ -128,7 +133,7 @@ const launch = (state: SupervisorState): void => {
128
133
  const child = state.fork(
129
134
  state.runnerPath,
130
135
  state.args,
131
- { ...process.env, [DEV_ENTRY_ENV]: state.entryPath },
136
+ { ...process.env, [DEV_CONFIG_ENV]: state.configFile, [DEV_ENTRY_ENV]: state.entryPath },
132
137
  state.cwd,
133
138
  );
134
139
 
@@ -139,39 +144,87 @@ const launch = (state: SupervisorState): void => {
139
144
  });
140
145
  };
141
146
 
142
- const isRestartBlocked = (state: SupervisorState): boolean => state.isRestarting || state.isShuttingDown;
143
-
144
- const relaunchAfterExit = (state: SupervisorState): void => {
147
+ const runPendingRestart = (state: SupervisorState): void => {
145
148
  state.isRestarting = false;
146
149
 
150
+ if (!state.isRestartPending || state.isShuttingDown) {
151
+ return;
152
+ }
153
+
154
+ state.isRestartPending = false;
155
+ void restart(state);
156
+ };
157
+
158
+ const relaunchAfterExit = (state: SupervisorState): void => {
147
159
  if (state.isShuttingDown) {
160
+ state.isRestarting = false;
161
+
148
162
  return;
149
163
  }
150
164
 
151
165
  info("Restarting dev runner...");
152
166
  launch(state);
167
+ runPendingRestart(state);
168
+ };
169
+
170
+ const isSupervisorShuttingDown = (state: SupervisorState): boolean => state.isShuttingDown;
171
+
172
+ const reconcileConfigWatchers = (
173
+ state: SupervisorState,
174
+ paths: string[],
175
+ previous: ReadonlyMap<string, string | null>,
176
+ ): void => {
177
+ const didChange = didWatchPathsChange(previous, paths);
178
+
179
+ closeWatchers(state);
180
+
181
+ if (state.watch !== undefined) {
182
+ state.watch.paths = paths;
183
+ }
184
+
185
+ if (state.isShuttingDown) {
186
+ return;
187
+ }
188
+
189
+ installConfigWatchers(state);
190
+
191
+ if (didChange && !state.isRestartPending) {
192
+ scheduleRestart(state);
193
+ }
153
194
  };
154
195
 
155
196
  const restart = async (state: SupervisorState): Promise<void> => {
156
197
  const watch = state.watch;
157
198
 
158
- if (watch === undefined || isRestartBlocked(state)) {
199
+ if (watch === undefined || state.isShuttingDown) {
200
+ return;
201
+ }
202
+
203
+ if (state.isRestarting) {
204
+ state.isRestartPending = true;
205
+
159
206
  return;
160
207
  }
161
208
 
162
209
  state.isRestarting = true;
163
- info("gtkx.config.ts changed; regenerating bindings...");
210
+ info(`${basename(state.configFile)} changed; regenerating bindings...`);
211
+ closeWatchers(state);
212
+ watch.paths = watch.resolvePaths();
213
+ const previous = snapshotWatchPaths(watch.paths);
214
+ installConfigWatchers(state);
164
215
 
165
216
  try {
166
- await watch.regenerate();
217
+ const paths = await watch.regenerate();
218
+ reconcileConfigWatchers(state, paths, previous);
167
219
  } catch (error_) {
220
+ reconcileConfigWatchers(state, watch.resolvePaths(), previous);
168
221
  error("Codegen failed; keeping the current dev runner. Fix the error and save again.", error_);
169
- state.isRestarting = false;
222
+ runPendingRestart(state);
170
223
 
171
224
  return;
172
225
  }
173
226
 
174
- if (state.isShuttingDown) {
227
+ if (isSupervisorShuttingDown(state)) {
175
228
  state.isRestarting = false;
176
229
 
177
230
  return;
@@ -180,8 +233,8 @@ const restart = async (state: SupervisorState): Promise<void> => {
180
233
  const current = state.child;
181
234
 
182
235
  if (current === null) {
183
- state.isRestarting = false;
184
236
  launch(state);
237
+ runPendingRestart(state);
185
238
 
186
239
  return;
187
240
  }
@@ -193,12 +246,17 @@ const restart = async (state: SupervisorState): Promise<void> => {
193
246
  forwardSignal(current, "SIGTERM");
194
247
  };
195
248
 
196
- const scheduleRestart = (state: SupervisorState, timer: DebounceTimer): void => {
249
+ const scheduleRestart = (state: SupervisorState): void => {
250
+ const timer = state.restartTimer;
251
+
197
252
  if (timer.handle !== null) {
198
253
  clearTimeout(timer.handle);
199
254
  }
200
255
 
201
- timer.handle = setTimeout(() => void restart(state), CONFIG_DEBOUNCE_MS);
256
+ timer.handle = setTimeout(() => {
257
+ timer.handle = null;
258
+ void restart(state);
259
+ }, CONFIG_DEBOUNCE_MS);
202
260
  };
203
261
 
204
262
  const isWatchedChange = (state: SupervisorState, names: Set<string>, filename: string | Buffer | null): boolean => {
@@ -209,13 +267,76 @@ const isWatchedChange = (state: SupervisorState, names: Set<string>, filename: s
209
267
  return names.has(basename(filename.toString()));
210
268
  };
211
269
 
270
+ const isDirectory = (path: string): boolean => {
271
+ try {
272
+ return statSync(path, { throwIfNoEntry: false })?.isDirectory() === true;
273
+ } catch {
274
+ return false;
275
+ }
276
+ };
277
+
278
+ const nearestWatchTarget = (path: string): { directory: string; name: string } => {
279
+ let directory = dirname(path);
280
+ let name = basename(path);
281
+
282
+ while (!isDirectory(directory)) {
283
+ const parent = dirname(directory);
284
+
285
+ if (parent === directory) {
286
+ break;
287
+ }
288
+
289
+ name = basename(directory);
290
+ directory = parent;
291
+ }
292
+
293
+ return { directory, name };
294
+ };
295
+
296
+ const watchTargets = (paths: string[]): Set<string> =>
297
+ new Set(paths.flatMap((path) => {
298
+ const { directory, name } = nearestWatchTarget(path);
299
+
300
+ return [path, join(directory, name)];
301
+ }));
302
+
303
+ const watchPathState = (path: string): string | null => {
304
+ try {
305
+ const stat = statSync(path, { bigint: true, throwIfNoEntry: false });
306
+
307
+ return stat === undefined
308
+ ? null
309
+ : [stat.dev, stat.ino, stat.mode, stat.size, stat.mtimeNs, stat.ctimeNs].join(":");
310
+ } catch {
311
+ return null;
312
+ }
313
+ };
314
+
315
+ const snapshotWatchPaths = (paths: string[]): Map<string, string | null> =>
316
+ new Map([...watchTargets(paths)].map((path) => [path, watchPathState(path)]));
317
+
318
+ const didWatchPathsChange = (
319
+ previous: ReadonlyMap<string, string | null>,
320
+ paths: string[],
321
+ ): boolean => {
322
+ for (const path of watchTargets(paths)) {
323
+ const current = watchPathState(path);
324
+
325
+ if (previous.has(path) ? previous.get(path) !== current : current !== null) {
326
+ return true;
327
+ }
328
+ }
329
+
330
+ return false;
331
+ };
332
+
212
333
  const groupWatchNamesByDirectory = (paths: string[]): Map<string, Set<string>> => {
213
334
  const namesByDirectory: Map<string, Set<string>> = new Map();
214
335
 
215
336
  for (const path of paths) {
216
- const directory = dirname(path);
337
+ const { directory, name } = nearestWatchTarget(path);
217
338
  const names = namesByDirectory.get(directory) ?? new Set<string>();
218
- names.add(basename(path));
339
+ names.add(name);
219
340
  namesByDirectory.set(directory, names);
220
341
  }
221
342
 
@@ -226,16 +347,21 @@ const watchConfigDirectory = (
226
347
  state: SupervisorState,
227
348
  directory: string,
228
349
  names: Set<string>,
229
- timer: DebounceTimer,
230
350
  ): void => {
231
- const watcher = watchFs(directory, (_event, filename) => {
232
- if (isWatchedChange(state, names, filename)) {
233
- scheduleRestart(state, timer);
234
- }
235
- });
351
+ try {
352
+ const watcher = watchFs(directory, (_event, filename) => {
353
+ if (isWatchedChange(state, names, filename)) {
354
+ scheduleRestart(state);
355
+ }
356
+ });
236
357
 
237
- watcher.on("error", (): void => undefined);
238
- state.watchers.push(watcher);
358
+ watcher.on("error", (cause) => {
359
+ error(`Configuration watch failed for ${directory}`, cause);
360
+ });
361
+ state.watchers.push(watcher);
362
+ } catch (error_) {
363
+ error(`Configuration watch failed for ${directory}`, error_);
364
+ }
239
365
  };
240
366
 
241
367
  const installConfigWatchers = (state: SupervisorState): void => {
@@ -245,10 +371,8 @@ const installConfigWatchers = (state: SupervisorState): void => {
245
371
  return;
246
372
  }
247
373
 
248
- const timer: DebounceTimer = { handle: null };
249
-
250
374
  for (const [directory, names] of groupWatchNamesByDirectory(watch.paths)) {
251
- watchConfigDirectory(state, directory, names, timer);
375
+ watchConfigDirectory(state, directory, names);
252
376
  }
253
377
  };
254
378
 
@@ -256,6 +380,8 @@ const closeWatchers = (state: SupervisorState): void => {
256
380
  for (const watcher of state.watchers) {
257
381
  watcher.close();
258
382
  }
383
+
384
+ state.watchers = [];
259
385
  };
260
386
 
261
387
  const shutdownOnSignal = (state: SupervisorState, signal: NodeJS.Signals): Promise<void> =>
@@ -263,6 +389,11 @@ const shutdownOnSignal = (state: SupervisorState, signal: NodeJS.Signals): Promi
263
389
  state.isShuttingDown = true;
264
390
  closeWatchers(state);
265
391
 
392
+ if (state.restartTimer.handle !== null) {
393
+ clearTimeout(state.restartTimer.handle);
394
+ state.restartTimer.handle = null;
395
+ }
396
+
266
397
  if (!state.child) {
267
398
  resolve();
268
399
 
@@ -286,19 +417,22 @@ const installShutdown = (state: SupervisorState): void => {
286
417
  };
287
418
 
288
419
  const runDevSupervisor = async (options: DevSupervisorOptions): Promise<never> => {
289
- const { entryPath, cwd, args = [], watch, fork = defaultForkRunner } = options;
420
+ const { entryPath, configFile, cwd, args = [], watch, fork = defaultForkRunner } = options;
290
421
 
291
422
  const state: SupervisorState = {
292
423
  runnerPath: fileURLToPath(DEV_RUNNER_URL),
293
424
  entryPath,
425
+ configFile,
294
426
  cwd,
295
427
  args,
296
428
  watch,
297
429
  watchers: [],
430
+ restartTimer: { handle: null },
298
431
  fork,
299
432
  child: null,
300
433
  isShuttingDown: false,
301
434
  isRestarting: false,
435
+ isRestartPending: false,
302
436
  capturedChildExit: undefined,
303
437
  };
304
438
 
@@ -133,6 +133,7 @@ const initializeCatalog = (catalog: Catalog, template: string, output: string):
133
133
  `--input=${template}`,
134
134
  `--output-file=${output}`,
135
135
  ],
136
+ options: { stdio: "pipe" },
136
137
  target: catalog.path,
137
138
  });
138
139
  };