@ryanatkn/gro 0.165.1 → 0.166.0

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 +1 @@
1
- {"version":3,"file":"dev.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/dev.task.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AACpC,OAAO,EAAU,KAAK,cAAc,EAAC,MAAM,aAAa,CAAC;AAGzD,eAAO,MAAM,IAAI;;;;;;kBAWf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC,MAAM,MAAM,eAAe,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;AAEnD,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CAoB3B,CAAC"}
1
+ {"version":3,"file":"dev.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/dev.task.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AACpC,OAAO,EAAU,KAAK,cAAc,EAAC,MAAM,aAAa,CAAC;AAGzD,eAAO,MAAM,IAAI;;;;;;kBAWf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC,MAAM,MAAM,eAAe,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;AAEnD,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CA2B3B,CAAC"}
package/dist/dev.task.js CHANGED
@@ -23,12 +23,20 @@ export const task = {
23
23
  if (sync || install) {
24
24
  if (!sync)
25
25
  log.warn('sync is false but install is true, so ignoring the sync option');
26
- await invoke_task('sync', { install });
26
+ await invoke_task('sync', { install, gen: !watch });
27
27
  }
28
28
  const plugins = await Plugins.create({ ...ctx, dev: true, watch });
29
29
  await plugins.setup();
30
30
  if (!watch) {
31
31
  await plugins.teardown();
32
32
  }
33
+ else {
34
+ // TODO maybe redesign for this API to be explicitly cancelable?
35
+ // Keep the task running indefinitely in watch mode.
36
+ // This prevents invoke_task from calling finish() and closing the filer.
37
+ await new Promise(() => {
38
+ // Never resolves - keeps filer and listeners alive.
39
+ });
40
+ }
33
41
  },
34
42
  };
package/dist/filer.d.ts CHANGED
@@ -19,6 +19,7 @@ export declare class Filer {
19
19
  get inited(): boolean;
20
20
  get_by_id: (id: Path_Id) => Disknode | undefined;
21
21
  get_or_create: (id: Path_Id) => Disknode;
22
+ filter(predicate: (disknode: Disknode) => boolean): Array<Disknode> | null;
22
23
  /**
23
24
  * Initialize the filer to populate files without watching.
24
25
  * Safe to call multiple times - subsequent calls are no-ops.
@@ -28,5 +29,5 @@ export declare class Filer {
28
29
  watch(listener: On_Filer_Change): Promise<() => void>;
29
30
  close(): Promise<void>;
30
31
  }
31
- export declare const filter_dependents: (disknode: Disknode, get_by_id: (id: Path_Id) => Disknode | undefined, filter?: File_Filter, results?: Set<string>, searched?: Set<string>, log?: Logger) => Set<string>;
32
+ export declare const filter_dependents: (disknode: Disknode, get_by_id: (id: Path_Id) => Disknode | undefined, filter?: File_Filter, results?: Set<Path_Id>, searched?: Set<Path_Id>, log?: Logger) => Set<Path_Id>;
32
33
  //# sourceMappingURL=filer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"filer.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/filer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,yBAAyB,CAAC;AAIzD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,uBAAuB,CAAC;AAClD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gCAAgC,CAAC;AAEjE,OAAO,KAAK,EAAC,WAAW,EAAE,OAAO,EAAC,MAAM,WAAW,CAAC;AACpD,OAAO,EACN,SAAS,EAET,KAAK,cAAc,EACnB,KAAK,iBAAiB,EAEtB,MAAM,gBAAgB,CAAC;AAOxB,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,eAAe,CAAC;AAI5C,MAAM,MAAM,eAAe,GAAG,CAAC,MAAM,EAAE,cAAc,EAAE,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;AAEnF,MAAM,WAAW,aAAa;IAC7B,SAAS,CAAC,EAAE,OAAO,SAAS,CAAC;IAC7B,iBAAiB,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,iBAAiB,EAAE,WAAW,CAAC,CAAC,CAAC;IACzE,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAClD,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,qBAAa,KAAK;;IACjB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAG3B,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAa;gBAYvC,OAAO,GAAE,aAA4B;IAUjD,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,SAAS,GAAI,IAAI,OAAO,KAAG,QAAQ,GAAG,SAAS,CAE7C;IAEF,aAAa,GAAI,IAAI,OAAO,KAAG,QAAQ,CAkBrC;IAEF;;;;OAIG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAmDrB,KAAK,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC;IAiB3D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAkLtB;AAGD,eAAO,MAAM,iBAAiB,GAC7B,UAAU,QAAQ,EAClB,WAAW,CAAC,EAAE,EAAE,OAAO,KAAK,QAAQ,GAAG,SAAS,EAChD,SAAS,WAAW,EACpB,UAAS,GAAG,CAAC,MAAM,CAAa,EAChC,WAAU,GAAG,CAAC,MAAM,CAAa,EACjC,MAAM,MAAM,KACV,GAAG,CAAC,MAAM,CAkBZ,CAAC"}
1
+ {"version":3,"file":"filer.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/filer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,yBAAyB,CAAC;AAIzD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,uBAAuB,CAAC;AAClD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gCAAgC,CAAC;AAEjE,OAAO,KAAK,EAAC,WAAW,EAAE,OAAO,EAAC,MAAM,WAAW,CAAC;AACpD,OAAO,EACN,SAAS,EAET,KAAK,cAAc,EACnB,KAAK,iBAAiB,EAEtB,MAAM,gBAAgB,CAAC;AAOxB,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,eAAe,CAAC;AAI5C,MAAM,MAAM,eAAe,GAAG,CAAC,MAAM,EAAE,cAAc,EAAE,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;AAEnF,MAAM,WAAW,aAAa;IAC7B,SAAS,CAAC,EAAE,OAAO,SAAS,CAAC;IAC7B,iBAAiB,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,iBAAiB,EAAE,WAAW,CAAC,CAAC,CAAC;IACzE,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAClD,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,qBAAa,KAAK;;IACjB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAG3B,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAa;gBAYvC,OAAO,GAAE,aAA4B;IAUjD,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,SAAS,GAAI,IAAI,OAAO,KAAG,QAAQ,GAAG,SAAS,CAE7C;IAEF,aAAa,GAAI,IAAI,OAAO,KAAG,QAAQ,CAkBrC;IAEF,MAAM,CAAC,SAAS,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,IAAI;IAU1E;;;;OAIG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAmDrB,KAAK,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC;IAiB3D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAmLtB;AAGD,eAAO,MAAM,iBAAiB,GAC7B,UAAU,QAAQ,EAClB,WAAW,CAAC,EAAE,EAAE,OAAO,KAAK,QAAQ,GAAG,SAAS,EAChD,SAAS,WAAW,EACpB,UAAS,GAAG,CAAC,OAAO,CAAa,EACjC,WAAU,GAAG,CAAC,OAAO,CAAa,EAClC,MAAM,MAAM,KACV,GAAG,CAAC,OAAO,CAkBb,CAAC"}
package/dist/filer.js CHANGED
@@ -59,6 +59,15 @@ export class Filer {
59
59
  }
60
60
  return file;
61
61
  };
62
+ filter(predicate) {
63
+ let found = null;
64
+ for (const disknode of this.files.values()) {
65
+ if (predicate(disknode)) {
66
+ (found ??= []).push(disknode);
67
+ }
68
+ }
69
+ return found;
70
+ }
62
71
  /**
63
72
  * Initialize the filer to populate files without watching.
64
73
  * Safe to call multiple times - subsequent calls are no-ops.
@@ -167,10 +176,11 @@ export class Filer {
167
176
  #update(id) {
168
177
  const file = this.get_or_create(id);
169
178
  const stats = existsSync(id) ? statSync(id) : null;
179
+ const old_mtime = file.mtime;
170
180
  file.ctime = stats?.ctimeMs ?? null;
171
181
  file.mtime = stats?.mtimeMs ?? null;
172
182
  const new_contents = stats ? readFileSync(id, 'utf8') : null; // TODO need to lazily load contents, probably turn `Disknode` into a class
173
- if (file.contents === new_contents) {
183
+ if (file.mtime === old_mtime && file.contents === new_contents) {
174
184
  return null;
175
185
  }
176
186
  file.contents = new_contents;
package/dist/gen.d.ts CHANGED
@@ -21,7 +21,18 @@ export interface Gen_File {
21
21
  origin_id: Path_Id;
22
22
  format: boolean;
23
23
  }
24
- export type Gen = (ctx: Gen_Context) => Raw_Gen_Result | Promise<Raw_Gen_Result>;
24
+ export type Gen_Dependencies = 'all' | Gen_Dependencies_Config | Gen_Dependencies_Resolver;
25
+ export interface Gen_Dependencies_Config {
26
+ patterns?: Array<RegExp>;
27
+ files?: Array<Path_Id>;
28
+ }
29
+ export type Gen_Dependencies_Resolver = (ctx: Gen_Context) => Gen_Dependencies_Config | 'all' | Promise<Gen_Dependencies_Config | 'all'>;
30
+ export type Gen = Gen_Function | Gen_Config;
31
+ export type Gen_Function = (ctx: Gen_Context) => Raw_Gen_Result | Promise<Raw_Gen_Result>;
32
+ export interface Gen_Config {
33
+ generate: Gen_Function;
34
+ dependencies?: Gen_Dependencies;
35
+ }
25
36
  export interface Gen_Context {
26
37
  config: Gro_Config;
27
38
  svelte_config: Parsed_Svelte_Config;
@@ -121,4 +132,5 @@ export type Load_Genfiles_Result = Result<{
121
132
  export type Load_Genfiles_Failure = Load_Modules_Failure<Genfile_Module_Meta>;
122
133
  export declare const load_genfiles: (found_genfiles: Found_Genfiles, timings?: Timings) => Promise<Load_Genfiles_Result>;
123
134
  export declare const validate_gen_module: (mod: Record<string, any>) => mod is Genfile_Module;
135
+ export declare const normalize_gen_config: (gen: Gen) => Gen_Config;
124
136
  //# sourceMappingURL=gen.d.ts.map
package/dist/gen.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"gen.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gen.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gen.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gen.ts"],"names":[],"mappings":"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"}
package/dist/gen.js CHANGED
@@ -182,4 +182,12 @@ export const load_genfiles = async (found_genfiles, timings) => {
182
182
  value: { modules: loaded_modules.modules, found_genfiles },
183
183
  };
184
184
  };
185
- export const validate_gen_module = (mod) => typeof mod.gen === 'function';
185
+ export const validate_gen_module = (mod) => {
186
+ if (typeof mod.gen === 'function')
187
+ return true;
188
+ if (typeof mod.gen === 'object' && mod.gen !== null && typeof mod.gen.generate === 'function') {
189
+ return true;
190
+ }
191
+ return false;
192
+ };
193
+ export const normalize_gen_config = (gen) => typeof gen === 'function' ? { generate: gen } : gen;
@@ -1 +1 @@
1
- {"version":3,"file":"gen.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gen.task.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,EAAa,KAAK,IAAI,EAAC,MAAM,WAAW,CAAC;AAgBhD,eAAO,MAAM,IAAI;;;;kBAaf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CAuG3B,CAAC"}
1
+ {"version":3,"file":"gen.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gen.task.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,EAAa,KAAK,IAAI,EAAC,MAAM,WAAW,CAAC;AAgBhD,eAAO,MAAM,IAAI;;;;kBAaf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CAqG3B,CAAC"}
package/dist/gen.task.js CHANGED
@@ -79,8 +79,6 @@ export const task = {
79
79
  }
80
80
  }
81
81
  else {
82
- // write generated files to disk
83
- log.info('writing generated files to disk');
84
82
  const timing_to_output_results = timings.start('output results');
85
83
  await write_gen_results(gen_results, analyzed_gen_results, log);
86
84
  timing_to_output_results();
@@ -0,0 +1,12 @@
1
+ import type { Logger } from '@ryanatkn/belt/log.js';
2
+ import type { Timings } from '@ryanatkn/belt/timings.js';
3
+ import type { Gro_Config } from './gro_config.ts';
4
+ import type { Filer } from './filer.ts';
5
+ import type { Invoke_Task } from './task.ts';
6
+ import { type Gen_Dependencies } from './gen.ts';
7
+ import type { Path_Id } from './path.ts';
8
+ /**
9
+ * Check if a file change should trigger a gen file.
10
+ */
11
+ export declare const should_trigger_gen: (gen_file_id: Path_Id, changed_file_id: Path_Id, config: Gro_Config, filer: Filer, log: Logger, timings: Timings, invoke_task: Invoke_Task, dependencies_cache: Map<Path_Id, Gen_Dependencies | null>) => Promise<boolean>;
12
+ //# sourceMappingURL=gen_helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gen_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gen_helpers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,uBAAuB,CAAC;AAClD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,2BAA2B,CAAC;AAEvD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,iBAAiB,CAAC;AAChD,OAAO,KAAK,EAAC,KAAK,EAAC,MAAM,YAAY,CAAC;AACtC,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,WAAW,CAAC;AAC3C,OAAO,EAIN,KAAK,gBAAgB,EACrB,MAAM,UAAU,CAAC;AAGlB,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AAEvC;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAC9B,aAAa,OAAO,EACpB,iBAAiB,OAAO,EACxB,QAAQ,UAAU,EAClB,OAAO,KAAK,EACZ,KAAK,MAAM,EACX,SAAS,OAAO,EAChB,aAAa,WAAW,EACxB,oBAAoB,GAAG,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAC,KACvD,OAAO,CAAC,OAAO,CA0CjB,CAAC"}
@@ -0,0 +1,86 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ import { pathToFileURL } from 'node:url';
10
+ import { normalize_gen_config, validate_gen_module, } from "./gen.js";
11
+ import { default_svelte_config } from "./svelte_config.js";
12
+ import { to_root_path } from "./paths.js";
13
+ /**
14
+ * Check if a file change should trigger a gen file.
15
+ */
16
+ export const should_trigger_gen = async (gen_file_id, changed_file_id, config, filer, log, timings, invoke_task, dependencies_cache) => {
17
+ // Always trigger if the gen file itself changed
18
+ if (gen_file_id === changed_file_id) {
19
+ // Invalidate cache for this gen file
20
+ dependencies_cache.delete(gen_file_id);
21
+ return true;
22
+ }
23
+ let dependencies;
24
+ if (!dependencies_cache.has(gen_file_id)) {
25
+ dependencies = await resolve_gen_dependencies(gen_file_id, config, filer, log, timings, invoke_task);
26
+ dependencies_cache.set(gen_file_id, dependencies);
27
+ }
28
+ else {
29
+ dependencies = dependencies_cache.get(gen_file_id);
30
+ }
31
+ if (!dependencies)
32
+ return false;
33
+ if (dependencies === 'all')
34
+ return true;
35
+ if (typeof dependencies !== 'function') {
36
+ if (dependencies.patterns) {
37
+ if (dependencies.patterns.some((p) => p.test(changed_file_id))) {
38
+ return true;
39
+ }
40
+ }
41
+ if (dependencies.files) {
42
+ if (dependencies.files.includes(changed_file_id)) {
43
+ return true;
44
+ }
45
+ }
46
+ }
47
+ return false;
48
+ };
49
+ /**
50
+ * Resolve dependencies for a gen file.
51
+ * Uses cache-busting to get fresh imports, allowing dependency
52
+ * declarations to update during watch mode without restart.
53
+ */
54
+ const resolve_gen_dependencies = async (gen_file_id, config, filer, log, timings, invoke_task) => {
55
+ try {
56
+ const url = pathToFileURL(gen_file_id);
57
+ url.searchParams.set('t', Date.now().toString());
58
+ const module = await import(__rewriteRelativeImportExtension(url.href, true));
59
+ if (!validate_gen_module(module)) {
60
+ return null;
61
+ }
62
+ const gen_config = normalize_gen_config(module.gen);
63
+ if (!gen_config.dependencies) {
64
+ return null;
65
+ }
66
+ let dependencies = gen_config.dependencies;
67
+ if (typeof dependencies === 'function') {
68
+ const gen_ctx = {
69
+ config,
70
+ svelte_config: default_svelte_config,
71
+ filer,
72
+ log,
73
+ timings,
74
+ invoke_task,
75
+ origin_id: gen_file_id,
76
+ origin_path: to_root_path(gen_file_id),
77
+ };
78
+ dependencies = await dependencies(gen_ctx);
79
+ }
80
+ return dependencies;
81
+ }
82
+ catch (err) {
83
+ log.error(`Failed to resolve dependencies for ${gen_file_id}:`, err);
84
+ return null;
85
+ }
86
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"gro_plugin_gen.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gro_plugin_gen.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AAQpC,MAAM,WAAW,SAAU,SAAQ,IAAI;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,sBAAsB;IACtC,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC5B,SAAS,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1B,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,eAAO,MAAM,cAAc,GAAI,oDAI5B,sBAAqC,KAAG,MAuF1C,CAAC"}
1
+ {"version":3,"file":"gro_plugin_gen.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gro_plugin_gen.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AAcpC,MAAM,WAAW,SAAU,SAAQ,IAAI;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,sBAAsB;IACtC,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC5B,SAAS,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1B,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,eAAO,MAAM,cAAc,GAAI,oDAI5B,sBAAqC,KAAG,MA2H1C,CAAC"}
@@ -3,60 +3,81 @@ import { throttle } from '@ryanatkn/belt/throttle.js';
3
3
  import { Unreachable_Error } from '@ryanatkn/belt/error.js';
4
4
  import { paths } from "./paths.js";
5
5
  import { find_genfiles, is_gen_path } from "./gen.js";
6
- import { spawn_cli } from "./cli.js";
7
6
  import { filter_dependents } from "./filer.js";
7
+ import { should_trigger_gen } from "./gen_helpers.js";
8
+ import { spawn_cli } from "./cli.js";
8
9
  const FLUSH_DEBOUNCE_DELAY = 500;
10
+ // TODO is cache busting a good idea here to speed up and run in-process?
11
+ // await invoke_task('gen', {_: files, bust_cache: true});
12
+ const gen = (files = []) => spawn_cli('gro', ['gen', ...files]);
9
13
  export const gro_plugin_gen = ({ input_paths = [paths.source], root_dirs = [paths.source], flush_debounce_delay = FLUSH_DEBOUNCE_DELAY, } = EMPTY_OBJECT) => {
10
- let flushing_timeout;
11
14
  const queued_files = new Set();
12
- const queue_gen = (gen_file_id) => {
13
- queued_files.add(gen_file_id);
14
- if (flushing_timeout === undefined) {
15
- flushing_timeout = setTimeout(() => {
16
- flushing_timeout = undefined;
17
- void flush_gen_queue();
18
- }); // the timeout batches synchronously
19
- }
20
- };
21
- const flush_gen_queue = throttle(async () => {
22
- const files = Array.from(queued_files);
23
- queued_files.clear();
24
- await gen(files);
25
- }, { delay: flush_debounce_delay });
26
- // TODO do this in-process - will it cause caching issues with the current impl?
27
- const gen = (files = []) => spawn_cli('gro', ['gen', ...files]);
15
+ // Cache for gen file declared dependencies to avoid repeated imports
16
+ const gen_dependencies_cache = new Map();
28
17
  let cleanup_watch;
29
18
  return {
30
19
  name: 'gro_plugin_gen',
31
- setup: async ({ watch, dev, log, config, filer }) => {
20
+ setup: async ({ watch, dev, log, config, filer, invoke_task, timings }) => {
32
21
  // For production builds, we assume `gen` is already fresh,
33
22
  // which should be checked by CI via `gro check` which calls `gro gen --check`.
34
23
  if (!dev)
35
24
  return;
36
25
  // Do we need to just generate everything once and exit?
37
26
  if (!watch) {
38
- log.info('generating and exiting early');
39
27
  // Run `gen`, first checking if there are any modules to avoid a console error.
40
28
  // Some parts of the build may have already happened,
41
29
  // making us miss `build` events for gen dependencies,
42
- // so we run `gen` here even if it's usually wasteful.
30
+ // so we run a full `gen` here even if it's usually wasteful.
43
31
  const found = find_genfiles(input_paths, root_dirs, config);
44
32
  if (found.ok && found.value.resolved_input_files.length > 0) {
45
33
  await gen();
46
34
  }
47
35
  return;
48
36
  }
37
+ const queue_gen = (gen_file_id) => {
38
+ queued_files.add(gen_file_id);
39
+ void flush_gen_queue();
40
+ };
41
+ const flush_gen_queue = throttle(async () => {
42
+ const files = Array.from(queued_files);
43
+ log.info(files.length === 0
44
+ ? '[gen] generating all files'
45
+ : `[gen] generating ${files.length} file${files.length === 1 ? '' : 's'}`);
46
+ queued_files.clear();
47
+ await gen(files);
48
+ // run again?
49
+ if (queued_files.size > 0) {
50
+ log.info(`[gen] re-running for ${queued_files.size} more queued file${queued_files.size === 1 ? '' : 's'}`);
51
+ setTimeout(flush_gen_queue); // setTimeout is needed bc of throttle behavior
52
+ }
53
+ else {
54
+ log.info('[gen] queue empty, done');
55
+ }
56
+ }, { delay: flush_debounce_delay, when: 'trailing' });
49
57
  // When a file builds, check it and its tree of dependents
50
58
  // for any `.gen.` files that need to run.
51
- cleanup_watch = await filer.watch((change, source_file) => {
59
+ cleanup_watch = await filer.watch(async (change, source_file) => {
52
60
  if (source_file.external)
53
61
  return;
54
62
  switch (change.type) {
55
63
  case 'add':
56
64
  case 'update': {
65
+ // Queue the gen file itself if it changed
57
66
  if (is_gen_path(source_file.id)) {
58
67
  queue_gen(source_file.id);
59
68
  }
69
+ // Find all current gen files and check their dependencies
70
+ const gen_files = filer.filter((d) => !d.external && is_gen_path(d.id));
71
+ if (gen_files) {
72
+ for (const gen_file of gen_files) {
73
+ // eslint-disable-next-line no-await-in-loop
74
+ const should_trigger = await should_trigger_gen(gen_file.id, source_file.id, config, filer, log, timings, invoke_task, gen_dependencies_cache);
75
+ if (should_trigger) {
76
+ queue_gen(gen_file.id);
77
+ }
78
+ }
79
+ }
80
+ // Check import-based dependents
60
81
  const dependent_gen_file_ids = filter_dependents(source_file, filer.get_by_id, is_gen_path, undefined, undefined, log);
61
82
  for (const dependent_gen_file_id of dependent_gen_file_ids) {
62
83
  queue_gen(dependent_gen_file_id);
@@ -64,7 +85,10 @@ export const gro_plugin_gen = ({ input_paths = [paths.source], root_dirs = [path
64
85
  break;
65
86
  }
66
87
  case 'delete': {
67
- // TODO delete the generated file(s)? option? because it may be surprising
88
+ if (is_gen_path(source_file.id)) {
89
+ gen_dependencies_cache.delete(source_file.id);
90
+ }
91
+ // I think for the gen plugin this is best as a no-op? avoids broken attempts
68
92
  break;
69
93
  }
70
94
  default:
@@ -1 +1 @@
1
- {"version":3,"file":"package.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/package.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gCAAgC,CAAC;AACjE,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,4BAA4B,CAAC;AAEzD,eAAO,MAAM,YAAY,EAAE,YAiGnB,CAAC;AAET,eAAO,MAAM,QAAQ,EAAE,QAsxBf,CAAC"}
1
+ {"version":3,"file":"package.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/package.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gCAAgC,CAAC;AACjE,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,4BAA4B,CAAC;AAEzD,eAAO,MAAM,YAAY,EAAE,YAiGnB,CAAC;AAET,eAAO,MAAM,QAAQ,EAAE,QAgyBf,CAAC"}
package/dist/package.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // generated by src/lib/package.gen.ts
2
2
  export const package_json = {
3
3
  name: '@ryanatkn/gro',
4
- version: '0.165.1',
4
+ version: '0.166.0',
5
5
  description: 'task runner and toolkit extending SvelteKit',
6
6
  motto: 'generate, run, optimize',
7
7
  glyph: '🌰',
@@ -59,13 +59,13 @@ export const package_json = {
59
59
  vitest: '^3',
60
60
  },
61
61
  peerDependenciesMeta: { '@sveltejs/kit': { optional: true }, vitest: { optional: true } },
62
- optionalDependencies: { '@ryanatkn/moss': '>=0.33.0', vitest: '^3' },
62
+ optionalDependencies: { vitest: '^3' },
63
63
  devDependencies: {
64
64
  '@changesets/changelog-git': '^0.2.1',
65
65
  '@changesets/types': '^6.1.0',
66
66
  '@ryanatkn/eslint-config': '^0.8.0',
67
67
  '@ryanatkn/fuz': '^0.146.0',
68
- '@ryanatkn/moss': '^0.34.0',
68
+ '@ryanatkn/moss': '^0.34.1',
69
69
  '@sveltejs/adapter-static': '^3.0.9',
70
70
  '@sveltejs/kit': '^2.37.1',
71
71
  '@sveltejs/package': '^2.5.0',
@@ -75,7 +75,7 @@ export const package_json = {
75
75
  eslint: '^9.35.0',
76
76
  'eslint-plugin-svelte': '^3.12.2',
77
77
  svelte: '^5.38.7',
78
- 'svelte-check': '^4.3.1',
78
+ 'svelte-check': '^4.3.2',
79
79
  typescript: '^5.9.2',
80
80
  'typescript-eslint': '^8.42.0',
81
81
  vitest: '^3.2.4',
@@ -98,7 +98,7 @@ export const package_json = {
98
98
  };
99
99
  export const src_json = {
100
100
  name: '@ryanatkn/gro',
101
- version: '0.165.1',
101
+ version: '0.166.0',
102
102
  modules: {
103
103
  '.': {
104
104
  path: 'index.ts',
@@ -343,6 +343,10 @@ export const src_json = {
343
343
  ],
344
344
  },
345
345
  './fs.js': { path: 'fs.ts', declarations: [{ name: 'empty_dir', kind: 'function' }] },
346
+ './gen_helpers.js': {
347
+ path: 'gen_helpers.ts',
348
+ declarations: [{ name: 'should_trigger_gen', kind: 'function' }],
349
+ },
346
350
  './gen.task.js': {
347
351
  path: 'gen.task.ts',
348
352
  declarations: [
@@ -358,7 +362,12 @@ export const src_json = {
358
362
  { name: 'is_gen_path', kind: 'function' },
359
363
  { name: 'Gen_Result', kind: 'type' },
360
364
  { name: 'Gen_File', kind: 'type' },
365
+ { name: 'Gen_Dependencies', kind: 'type' },
366
+ { name: 'Gen_Dependencies_Config', kind: 'type' },
367
+ { name: 'Gen_Dependencies_Resolver', kind: 'type' },
361
368
  { name: 'Gen', kind: 'type' },
369
+ { name: 'Gen_Function', kind: 'type' },
370
+ { name: 'Gen_Config', kind: 'type' },
362
371
  { name: 'Gen_Context', kind: 'type' },
363
372
  { name: 'Raw_Gen_Result', kind: 'type' },
364
373
  { name: 'Raw_Gen_File', kind: 'type' },
@@ -383,6 +392,7 @@ export const src_json = {
383
392
  { name: 'Load_Genfiles_Failure', kind: 'type' },
384
393
  { name: 'load_genfiles', kind: 'function' },
385
394
  { name: 'validate_gen_module', kind: 'function' },
395
+ { name: 'normalize_gen_config', kind: 'function' },
386
396
  ],
387
397
  },
388
398
  './git.js': {
@@ -1 +1 @@
1
- {"version":3,"file":"run_gen.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/run_gen.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,2BAA2B,CAAC;AACvD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,uBAAuB,CAAC;AAElD,OAAO,EACN,KAAK,WAAW,EAGhB,KAAK,mBAAmB,EAGxB,MAAM,UAAU,CAAC;AAElB,OAAO,KAAK,EAAC,WAAW,IAAI,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AACtE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,iBAAiB,CAAC;AAEhD,OAAO,KAAK,EAAC,KAAK,EAAC,MAAM,YAAY,CAAC;AACtC,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,WAAW,CAAC;AAE3C,eAAO,MAAM,mBAAmB,qCAAqC,CAAC;AAEtE,eAAO,MAAM,OAAO,GACnB,aAAa,KAAK,CAAC,mBAAmB,CAAC,EACvC,QAAQ,UAAU,EAClB,OAAO,KAAK,EACZ,KAAK,MAAM,EACX,SAAS,OAAO,EAChB,aAAa,WAAW,EACxB,cAAc,OAAO,gBAAgB,KACnC,OAAO,CAAC,WAAW,CAwErB,CAAC"}
1
+ {"version":3,"file":"run_gen.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/run_gen.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,2BAA2B,CAAC;AACvD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,uBAAuB,CAAC;AAElD,OAAO,EACN,KAAK,WAAW,EAGhB,KAAK,mBAAmB,EAIxB,MAAM,UAAU,CAAC;AAElB,OAAO,KAAK,EAAC,WAAW,IAAI,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AACtE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,iBAAiB,CAAC;AAEhD,OAAO,KAAK,EAAC,KAAK,EAAC,MAAM,YAAY,CAAC;AACtC,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,WAAW,CAAC;AAE3C,eAAO,MAAM,mBAAmB,qCAAqC,CAAC;AAEtE,eAAO,MAAM,OAAO,GACnB,aAAa,KAAK,CAAC,mBAAmB,CAAC,EACvC,QAAQ,UAAU,EAClB,OAAO,KAAK,EACZ,KAAK,MAAM,EACX,SAAS,OAAO,EAChB,aAAa,WAAW,EACxB,cAAc,OAAO,gBAAgB,KACnC,OAAO,CAAC,WAAW,CAwErB,CAAC"}
package/dist/run_gen.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { styleText as st } from 'node:util';
2
2
  import { print_error } from '@ryanatkn/belt/print.js';
3
- import { to_gen_result, } from "./gen.js";
3
+ import { to_gen_result, normalize_gen_config, } from "./gen.js";
4
4
  import { print_path, to_root_path } from "./paths.js";
5
5
  import { default_svelte_config } from "./svelte_config.js";
6
6
  export const GEN_NO_PROD_MESSAGE = 'gen runs only during development';
@@ -12,7 +12,7 @@ export const run_gen = async (gen_modules, config, filer, log, timings, invoke_t
12
12
  input_count++;
13
13
  const { id } = module_meta;
14
14
  const timing_for_module = timings.start(id);
15
- // Perform code generation by calling `gen` on the module.
15
+ const gen_config = normalize_gen_config(module_meta.mod.gen);
16
16
  const gen_ctx = {
17
17
  config,
18
18
  svelte_config: default_svelte_config,
@@ -25,7 +25,7 @@ export const run_gen = async (gen_modules, config, filer, log, timings, invoke_t
25
25
  };
26
26
  let raw_gen_result;
27
27
  try {
28
- raw_gen_result = await module_meta.mod.gen(gen_ctx);
28
+ raw_gen_result = await gen_config.generate(gen_ctx);
29
29
  }
30
30
  catch (err) {
31
31
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"watch_dir.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/watch_dir.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,eAAe,EAAiB,MAAM,UAAU,CAAC;AAKrE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,WAAW,CAAC;AAI3C,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,OAAO,CAAC;CACtB;AACD,MAAM,MAAM,mBAAmB,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAC9D,MAAM,MAAM,uBAAuB,GAAG,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;AAEvE,MAAM,WAAW,iBAAiB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,uBAAuB,CAAC;IACnC,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,eAAO,MAAM,SAAS,GAAI,iDAMvB,iBAAiB,KAAG,aA6CtB,CAAC"}
1
+ {"version":3,"file":"watch_dir.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/watch_dir.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,eAAe,EAAiB,MAAM,UAAU,CAAC;AAKrE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,WAAW,CAAC;AAI3C,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,OAAO,CAAC;CACtB;AACD,MAAM,MAAM,mBAAmB,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAC9D,MAAM,MAAM,uBAAuB,GAAG,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;AAEvE,MAAM,WAAW,iBAAiB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,uBAAuB,CAAC;IACnC,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,eAAO,MAAM,SAAS,GAAI,iDAMvB,iBAAiB,KAAG,aA+CtB,CAAC"}
package/dist/watch_dir.js CHANGED
@@ -29,8 +29,9 @@ export const watch_dir = ({ dir, on_change, filter, absolute = true, chokidar, }
29
29
  watcher.on('change', (path, s) => {
30
30
  const stats = s ?? statSync(path);
31
31
  const final_path = absolute ? path : relative(dir, path);
32
- if (filter && !filter(final_path, stats.isDirectory()))
32
+ if (filter && !filter(final_path, stats.isDirectory())) {
33
33
  return;
34
+ }
34
35
  on_change({ type: 'update', path: final_path, is_directory: stats.isDirectory() });
35
36
  });
36
37
  watcher.on('unlink', (path) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryanatkn/gro",
3
- "version": "0.165.1",
3
+ "version": "0.166.0",
4
4
  "description": "task runner and toolkit extending SvelteKit",
5
5
  "motto": "generate, run, optimize",
6
6
  "glyph": "🌰",
@@ -77,7 +77,6 @@
77
77
  }
78
78
  },
79
79
  "optionalDependencies": {
80
- "@ryanatkn/moss": ">=0.33.0",
81
80
  "vitest": "^3"
82
81
  },
83
82
  "devDependencies": {
@@ -85,7 +84,7 @@
85
84
  "@changesets/types": "^6.1.0",
86
85
  "@ryanatkn/eslint-config": "^0.8.0",
87
86
  "@ryanatkn/fuz": "^0.146.0",
88
- "@ryanatkn/moss": "^0.34.0",
87
+ "@ryanatkn/moss": "^0.34.1",
89
88
  "@sveltejs/adapter-static": "^3.0.9",
90
89
  "@sveltejs/kit": "^2.37.1",
91
90
  "@sveltejs/package": "^2.5.0",
@@ -95,7 +94,7 @@
95
94
  "eslint": "^9.35.0",
96
95
  "eslint-plugin-svelte": "^3.12.2",
97
96
  "svelte": "^5.38.7",
98
- "svelte-check": "^4.3.1",
97
+ "svelte-check": "^4.3.2",
99
98
  "typescript": "^5.9.2",
100
99
  "typescript-eslint": "^8.42.0",
101
100
  "vitest": "^3.2.4"
@@ -31,13 +31,20 @@ export const task: Task<Args> = {
31
31
 
32
32
  if (sync || install) {
33
33
  if (!sync) log.warn('sync is false but install is true, so ignoring the sync option');
34
- await invoke_task('sync', {install});
34
+ await invoke_task('sync', {install, gen: !watch});
35
35
  }
36
36
 
37
37
  const plugins = await Plugins.create({...ctx, dev: true, watch});
38
38
  await plugins.setup();
39
39
  if (!watch) {
40
40
  await plugins.teardown();
41
+ } else {
42
+ // TODO maybe redesign for this API to be explicitly cancelable?
43
+ // Keep the task running indefinitely in watch mode.
44
+ // This prevents invoke_task from calling finish() and closing the filer.
45
+ await new Promise(() => {
46
+ // Never resolves - keeps filer and listeners alive.
47
+ });
41
48
  }
42
49
  },
43
50
  };
package/src/lib/filer.ts CHANGED
@@ -89,6 +89,16 @@ export class Filer {
89
89
  return file;
90
90
  };
91
91
 
92
+ filter(predicate: (disknode: Disknode) => boolean): Array<Disknode> | null {
93
+ let found: Array<Disknode> | null = null;
94
+ for (const disknode of this.files.values()) {
95
+ if (predicate(disknode)) {
96
+ (found ??= []).push(disknode);
97
+ }
98
+ }
99
+ return found;
100
+ }
101
+
92
102
  /**
93
103
  * Initialize the filer to populate files without watching.
94
104
  * Safe to call multiple times - subsequent calls are no-ops.
@@ -208,12 +218,13 @@ export class Filer {
208
218
  const file = this.get_or_create(id);
209
219
 
210
220
  const stats = existsSync(id) ? statSync(id) : null;
221
+ const old_mtime = file.mtime;
211
222
  file.ctime = stats?.ctimeMs ?? null;
212
223
  file.mtime = stats?.mtimeMs ?? null;
213
224
 
214
225
  const new_contents = stats ? readFileSync(id, 'utf8') : null; // TODO need to lazily load contents, probably turn `Disknode` into a class
215
226
 
216
- if (file.contents === new_contents) {
227
+ if (file.mtime === old_mtime && file.contents === new_contents) {
217
228
  return null;
218
229
  }
219
230
 
@@ -336,7 +347,7 @@ export class Filer {
336
347
  }
337
348
  };
338
349
 
339
- #is_external(id: string): boolean {
350
+ #is_external(id: Path_Id): boolean {
340
351
  const {filter} = this.#watch_dir_options;
341
352
  return !id.startsWith(this.root_dir + '/') || (!!filter && !filter(id, false));
342
353
  }
@@ -347,10 +358,10 @@ export const filter_dependents = (
347
358
  disknode: Disknode,
348
359
  get_by_id: (id: Path_Id) => Disknode | undefined,
349
360
  filter?: File_Filter,
350
- results: Set<string> = new Set(),
351
- searched: Set<string> = new Set(),
361
+ results: Set<Path_Id> = new Set(),
362
+ searched: Set<Path_Id> = new Set(),
352
363
  log?: Logger,
353
- ): Set<string> => {
364
+ ): Set<Path_Id> => {
354
365
  const {dependents} = disknode;
355
366
  for (const dependent_id of dependents.keys()) {
356
367
  if (searched.has(dependent_id)) continue;
@@ -112,8 +112,6 @@ export const task: Task<Args> = {
112
112
  log.info('check passed, no files have changed');
113
113
  }
114
114
  } else {
115
- // write generated files to disk
116
- log.info('writing generated files to disk');
117
115
  const timing_to_output_results = timings.start('output results');
118
116
  await write_gen_results(gen_results, analyzed_gen_results, log);
119
117
  timing_to_output_results();
package/src/lib/gen.ts CHANGED
@@ -38,7 +38,28 @@ export interface Gen_File {
38
38
  format: boolean;
39
39
  }
40
40
 
41
- export type Gen = (ctx: Gen_Context) => Raw_Gen_Result | Promise<Raw_Gen_Result>;
41
+ export type Gen_Dependencies = 'all' | Gen_Dependencies_Config | Gen_Dependencies_Resolver;
42
+
43
+ export interface Gen_Dependencies_Config {
44
+ patterns?: Array<RegExp>;
45
+ files?: Array<Path_Id>;
46
+ }
47
+
48
+ export type Gen_Dependencies_Resolver = (
49
+ ctx: Gen_Context,
50
+ ) => Gen_Dependencies_Config | 'all' | Promise<Gen_Dependencies_Config | 'all'>;
51
+
52
+ export type Gen = Gen_Function | Gen_Config;
53
+
54
+ export type Gen_Function = (ctx: Gen_Context) => Raw_Gen_Result | Promise<Raw_Gen_Result>;
55
+
56
+ export interface Gen_Config {
57
+ generate: Gen_Function;
58
+ dependencies?: Gen_Dependencies;
59
+ // TODO think about what could be added
60
+ // cache?: boolean;
61
+ }
62
+
42
63
  export interface Gen_Context {
43
64
  config: Gro_Config;
44
65
  svelte_config: Parsed_Svelte_Config;
@@ -362,5 +383,13 @@ export const load_genfiles = async (
362
383
  };
363
384
  };
364
385
 
365
- export const validate_gen_module = (mod: Record<string, any>): mod is Genfile_Module =>
366
- typeof mod.gen === 'function';
386
+ export const validate_gen_module = (mod: Record<string, any>): mod is Genfile_Module => {
387
+ if (typeof mod.gen === 'function') return true;
388
+ if (typeof mod.gen === 'object' && mod.gen !== null && typeof mod.gen.generate === 'function') {
389
+ return true;
390
+ }
391
+ return false;
392
+ };
393
+
394
+ export const normalize_gen_config = (gen: Gen): Gen_Config =>
395
+ typeof gen === 'function' ? {generate: gen} : gen;
@@ -0,0 +1,120 @@
1
+ import {pathToFileURL} from 'node:url';
2
+ import type {Logger} from '@ryanatkn/belt/log.js';
3
+ import type {Timings} from '@ryanatkn/belt/timings.js';
4
+
5
+ import type {Gro_Config} from './gro_config.ts';
6
+ import type {Filer} from './filer.ts';
7
+ import type {Invoke_Task} from './task.ts';
8
+ import {
9
+ normalize_gen_config,
10
+ validate_gen_module,
11
+ type Gen_Context,
12
+ type Gen_Dependencies,
13
+ } from './gen.ts';
14
+ import {default_svelte_config} from './svelte_config.ts';
15
+ import {to_root_path} from './paths.ts';
16
+ import type {Path_Id} from './path.ts';
17
+
18
+ /**
19
+ * Check if a file change should trigger a gen file.
20
+ */
21
+ export const should_trigger_gen = async (
22
+ gen_file_id: Path_Id,
23
+ changed_file_id: Path_Id,
24
+ config: Gro_Config,
25
+ filer: Filer,
26
+ log: Logger,
27
+ timings: Timings,
28
+ invoke_task: Invoke_Task,
29
+ dependencies_cache: Map<Path_Id, Gen_Dependencies | null>,
30
+ ): Promise<boolean> => {
31
+ // Always trigger if the gen file itself changed
32
+ if (gen_file_id === changed_file_id) {
33
+ // Invalidate cache for this gen file
34
+ dependencies_cache.delete(gen_file_id);
35
+ return true;
36
+ }
37
+
38
+ let dependencies: Gen_Dependencies | null | undefined;
39
+ if (!dependencies_cache.has(gen_file_id)) {
40
+ dependencies = await resolve_gen_dependencies(
41
+ gen_file_id,
42
+ config,
43
+ filer,
44
+ log,
45
+ timings,
46
+ invoke_task,
47
+ );
48
+ dependencies_cache.set(gen_file_id, dependencies);
49
+ } else {
50
+ dependencies = dependencies_cache.get(gen_file_id);
51
+ }
52
+
53
+ if (!dependencies) return false;
54
+
55
+ if (dependencies === 'all') return true;
56
+
57
+ if (typeof dependencies !== 'function') {
58
+ if (dependencies.patterns) {
59
+ if (dependencies.patterns.some((p) => p.test(changed_file_id))) {
60
+ return true;
61
+ }
62
+ }
63
+
64
+ if (dependencies.files) {
65
+ if (dependencies.files.includes(changed_file_id)) {
66
+ return true;
67
+ }
68
+ }
69
+ }
70
+
71
+ return false;
72
+ };
73
+
74
+ /**
75
+ * Resolve dependencies for a gen file.
76
+ * Uses cache-busting to get fresh imports, allowing dependency
77
+ * declarations to update during watch mode without restart.
78
+ */
79
+ const resolve_gen_dependencies = async (
80
+ gen_file_id: string,
81
+ config: Gro_Config,
82
+ filer: Filer,
83
+ log: Logger,
84
+ timings: Timings,
85
+ invoke_task: Invoke_Task,
86
+ ): Promise<Gen_Dependencies | null> => {
87
+ try {
88
+ const url = pathToFileURL(gen_file_id);
89
+ url.searchParams.set('t', Date.now().toString());
90
+ const module = await import(url.href);
91
+ if (!validate_gen_module(module)) {
92
+ return null;
93
+ }
94
+
95
+ const gen_config = normalize_gen_config(module.gen);
96
+ if (!gen_config.dependencies) {
97
+ return null;
98
+ }
99
+
100
+ let dependencies = gen_config.dependencies;
101
+ if (typeof dependencies === 'function') {
102
+ const gen_ctx: Gen_Context = {
103
+ config,
104
+ svelte_config: default_svelte_config,
105
+ filer,
106
+ log,
107
+ timings,
108
+ invoke_task,
109
+ origin_id: gen_file_id,
110
+ origin_path: to_root_path(gen_file_id),
111
+ };
112
+ dependencies = await dependencies(gen_ctx);
113
+ }
114
+
115
+ return dependencies;
116
+ } catch (err) {
117
+ log.error(`Failed to resolve dependencies for ${gen_file_id}:`, err);
118
+ return null;
119
+ }
120
+ };
@@ -5,12 +5,18 @@ import {Unreachable_Error} from '@ryanatkn/belt/error.js';
5
5
  import type {Plugin} from './plugin.ts';
6
6
  import type {Args} from './args.ts';
7
7
  import {paths} from './paths.ts';
8
- import {find_genfiles, is_gen_path} from './gen.ts';
9
- import {spawn_cli} from './cli.ts';
8
+ import {find_genfiles, is_gen_path, type Gen_Dependencies} from './gen.ts';
10
9
  import {filter_dependents} from './filer.ts';
10
+ import {should_trigger_gen} from './gen_helpers.ts';
11
+ import {spawn_cli} from './cli.ts';
12
+ import type {Path_Id} from './path.ts';
11
13
 
12
14
  const FLUSH_DEBOUNCE_DELAY = 500;
13
15
 
16
+ // TODO is cache busting a good idea here to speed up and run in-process?
17
+ // await invoke_task('gen', {_: files, bust_cache: true});
18
+ const gen = (files: Array<string> = []) => spawn_cli('gro', ['gen', ...files]);
19
+
14
20
  export interface Task_Args extends Args {
15
21
  watch?: boolean;
16
22
  }
@@ -26,46 +32,26 @@ export const gro_plugin_gen = ({
26
32
  root_dirs = [paths.source],
27
33
  flush_debounce_delay = FLUSH_DEBOUNCE_DELAY,
28
34
  }: Gro_Plugin_Gen_Options = EMPTY_OBJECT): Plugin => {
29
- let flushing_timeout: NodeJS.Timeout | undefined;
30
35
  const queued_files: Set<string> = new Set();
31
- const queue_gen = (gen_file_id: string) => {
32
- queued_files.add(gen_file_id);
33
- if (flushing_timeout === undefined) {
34
- flushing_timeout = setTimeout(() => {
35
- flushing_timeout = undefined;
36
- void flush_gen_queue();
37
- }); // the timeout batches synchronously
38
- }
39
- };
40
- const flush_gen_queue = throttle(
41
- async () => {
42
- const files = Array.from(queued_files);
43
- queued_files.clear();
44
- await gen(files);
45
- },
46
- {delay: flush_debounce_delay},
47
- );
48
36
 
49
- // TODO do this in-process - will it cause caching issues with the current impl?
50
- const gen = (files: Array<string> = []) => spawn_cli('gro', ['gen', ...files]);
37
+ // Cache for gen file declared dependencies to avoid repeated imports
38
+ const gen_dependencies_cache: Map<Path_Id, Gen_Dependencies | null> = new Map();
51
39
 
52
40
  let cleanup_watch: (() => void) | undefined;
53
41
 
54
42
  return {
55
43
  name: 'gro_plugin_gen',
56
- setup: async ({watch, dev, log, config, filer}) => {
44
+ setup: async ({watch, dev, log, config, filer, invoke_task, timings}) => {
57
45
  // For production builds, we assume `gen` is already fresh,
58
46
  // which should be checked by CI via `gro check` which calls `gro gen --check`.
59
47
  if (!dev) return;
60
48
 
61
49
  // Do we need to just generate everything once and exit?
62
50
  if (!watch) {
63
- log.info('generating and exiting early');
64
-
65
51
  // Run `gen`, first checking if there are any modules to avoid a console error.
66
52
  // Some parts of the build may have already happened,
67
53
  // making us miss `build` events for gen dependencies,
68
- // so we run `gen` here even if it's usually wasteful.
54
+ // so we run a full `gen` here even if it's usually wasteful.
69
55
  const found = find_genfiles(input_paths, root_dirs, config);
70
56
  if (found.ok && found.value.resolved_input_files.length > 0) {
71
57
  await gen();
@@ -73,16 +59,69 @@ export const gro_plugin_gen = ({
73
59
  return;
74
60
  }
75
61
 
62
+ const queue_gen = (gen_file_id: string) => {
63
+ queued_files.add(gen_file_id);
64
+ void flush_gen_queue();
65
+ };
66
+
67
+ const flush_gen_queue = throttle(
68
+ async () => {
69
+ const files = Array.from(queued_files);
70
+ log.info(
71
+ files.length === 0
72
+ ? '[gen] generating all files'
73
+ : `[gen] generating ${files.length} file${files.length === 1 ? '' : 's'}`,
74
+ );
75
+ queued_files.clear();
76
+ await gen(files);
77
+
78
+ // run again?
79
+ if (queued_files.size > 0) {
80
+ log.info(
81
+ `[gen] re-running for ${queued_files.size} more queued file${queued_files.size === 1 ? '' : 's'}`,
82
+ );
83
+ setTimeout(flush_gen_queue); // setTimeout is needed bc of throttle behavior
84
+ } else {
85
+ log.info('[gen] queue empty, done');
86
+ }
87
+ },
88
+ {delay: flush_debounce_delay, when: 'trailing'},
89
+ );
90
+
76
91
  // When a file builds, check it and its tree of dependents
77
92
  // for any `.gen.` files that need to run.
78
- cleanup_watch = await filer.watch((change, source_file) => {
93
+ cleanup_watch = await filer.watch(async (change, source_file) => {
79
94
  if (source_file.external) return;
80
95
  switch (change.type) {
81
96
  case 'add':
82
97
  case 'update': {
98
+ // Queue the gen file itself if it changed
83
99
  if (is_gen_path(source_file.id)) {
84
100
  queue_gen(source_file.id);
85
101
  }
102
+
103
+ // Find all current gen files and check their dependencies
104
+ const gen_files = filer.filter((d) => !d.external && is_gen_path(d.id));
105
+ if (gen_files) {
106
+ for (const gen_file of gen_files) {
107
+ // eslint-disable-next-line no-await-in-loop
108
+ const should_trigger = await should_trigger_gen(
109
+ gen_file.id,
110
+ source_file.id,
111
+ config,
112
+ filer,
113
+ log,
114
+ timings,
115
+ invoke_task,
116
+ gen_dependencies_cache,
117
+ );
118
+ if (should_trigger) {
119
+ queue_gen(gen_file.id);
120
+ }
121
+ }
122
+ }
123
+
124
+ // Check import-based dependents
86
125
  const dependent_gen_file_ids = filter_dependents(
87
126
  source_file,
88
127
  filer.get_by_id,
@@ -97,7 +136,10 @@ export const gro_plugin_gen = ({
97
136
  break;
98
137
  }
99
138
  case 'delete': {
100
- // TODO delete the generated file(s)? option? because it may be surprising
139
+ if (is_gen_path(source_file.id)) {
140
+ gen_dependencies_cache.delete(source_file.id);
141
+ }
142
+ // I think for the gen plugin this is best as a no-op? avoids broken attempts
101
143
  break;
102
144
  }
103
145
  default:
@@ -5,7 +5,7 @@ import type {Src_Json} from '@ryanatkn/belt/src_json.js';
5
5
 
6
6
  export const package_json: Package_Json = {
7
7
  name: '@ryanatkn/gro',
8
- version: '0.165.1',
8
+ version: '0.166.0',
9
9
  description: 'task runner and toolkit extending SvelteKit',
10
10
  motto: 'generate, run, optimize',
11
11
  glyph: '🌰',
@@ -64,13 +64,13 @@ export const package_json: Package_Json = {
64
64
  vitest: '^3',
65
65
  },
66
66
  peerDependenciesMeta: {'@sveltejs/kit': {optional: true}, vitest: {optional: true}},
67
- optionalDependencies: {'@ryanatkn/moss': '>=0.33.0', vitest: '^3'},
67
+ optionalDependencies: {vitest: '^3'},
68
68
  devDependencies: {
69
69
  '@changesets/changelog-git': '^0.2.1',
70
70
  '@changesets/types': '^6.1.0',
71
71
  '@ryanatkn/eslint-config': '^0.8.0',
72
72
  '@ryanatkn/fuz': '^0.146.0',
73
- '@ryanatkn/moss': '^0.34.0',
73
+ '@ryanatkn/moss': '^0.34.1',
74
74
  '@sveltejs/adapter-static': '^3.0.9',
75
75
  '@sveltejs/kit': '^2.37.1',
76
76
  '@sveltejs/package': '^2.5.0',
@@ -80,7 +80,7 @@ export const package_json: Package_Json = {
80
80
  eslint: '^9.35.0',
81
81
  'eslint-plugin-svelte': '^3.12.2',
82
82
  svelte: '^5.38.7',
83
- 'svelte-check': '^4.3.1',
83
+ 'svelte-check': '^4.3.2',
84
84
  typescript: '^5.9.2',
85
85
  'typescript-eslint': '^8.42.0',
86
86
  vitest: '^3.2.4',
@@ -104,7 +104,7 @@ export const package_json: Package_Json = {
104
104
 
105
105
  export const src_json: Src_Json = {
106
106
  name: '@ryanatkn/gro',
107
- version: '0.165.1',
107
+ version: '0.166.0',
108
108
  modules: {
109
109
  '.': {
110
110
  path: 'index.ts',
@@ -349,6 +349,10 @@ export const src_json: Src_Json = {
349
349
  ],
350
350
  },
351
351
  './fs.js': {path: 'fs.ts', declarations: [{name: 'empty_dir', kind: 'function'}]},
352
+ './gen_helpers.js': {
353
+ path: 'gen_helpers.ts',
354
+ declarations: [{name: 'should_trigger_gen', kind: 'function'}],
355
+ },
352
356
  './gen.task.js': {
353
357
  path: 'gen.task.ts',
354
358
  declarations: [
@@ -364,7 +368,12 @@ export const src_json: Src_Json = {
364
368
  {name: 'is_gen_path', kind: 'function'},
365
369
  {name: 'Gen_Result', kind: 'type'},
366
370
  {name: 'Gen_File', kind: 'type'},
371
+ {name: 'Gen_Dependencies', kind: 'type'},
372
+ {name: 'Gen_Dependencies_Config', kind: 'type'},
373
+ {name: 'Gen_Dependencies_Resolver', kind: 'type'},
367
374
  {name: 'Gen', kind: 'type'},
375
+ {name: 'Gen_Function', kind: 'type'},
376
+ {name: 'Gen_Config', kind: 'type'},
368
377
  {name: 'Gen_Context', kind: 'type'},
369
378
  {name: 'Raw_Gen_Result', kind: 'type'},
370
379
  {name: 'Raw_Gen_File', kind: 'type'},
@@ -389,6 +398,7 @@ export const src_json: Src_Json = {
389
398
  {name: 'Load_Genfiles_Failure', kind: 'type'},
390
399
  {name: 'load_genfiles', kind: 'function'},
391
400
  {name: 'validate_gen_module', kind: 'function'},
401
+ {name: 'normalize_gen_config', kind: 'function'},
392
402
  ],
393
403
  },
394
404
  './git.js': {
@@ -10,6 +10,7 @@ import {
10
10
  type Genfile_Module_Meta,
11
11
  to_gen_result,
12
12
  type Raw_Gen_Result,
13
+ normalize_gen_config,
13
14
  } from './gen.ts';
14
15
  import {print_path, to_root_path} from './paths.ts';
15
16
  import type {format_file as base_format_file} from './format_file.ts';
@@ -38,7 +39,7 @@ export const run_gen = async (
38
39
  const {id} = module_meta;
39
40
  const timing_for_module = timings.start(id);
40
41
 
41
- // Perform code generation by calling `gen` on the module.
42
+ const gen_config = normalize_gen_config(module_meta.mod.gen);
42
43
  const gen_ctx: Gen_Context = {
43
44
  config,
44
45
  svelte_config: default_svelte_config,
@@ -51,7 +52,7 @@ export const run_gen = async (
51
52
  };
52
53
  let raw_gen_result: Raw_Gen_Result;
53
54
  try {
54
- raw_gen_result = await module_meta.mod.gen(gen_ctx);
55
+ raw_gen_result = await gen_config.generate(gen_ctx);
55
56
  } catch (err) {
56
57
  return {
57
58
  ok: false,
@@ -63,7 +63,9 @@ export const watch_dir = ({
63
63
  watcher.on('change', (path, s) => {
64
64
  const stats = s ?? statSync(path);
65
65
  const final_path = absolute ? path : relative(dir, path);
66
- if (filter && !filter(final_path, stats.isDirectory())) return;
66
+ if (filter && !filter(final_path, stats.isDirectory())) {
67
+ return;
68
+ }
67
69
  on_change({type: 'update', path: final_path, is_directory: stats.isDirectory()});
68
70
  });
69
71
  watcher.on('unlink', (path) => {