@ryanatkn/gro 0.162.2 → 0.164.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.
@@ -4,11 +4,12 @@
4
4
  * There may be a cleaner workaround but I couldn't find it.
5
5
  * @see https://github.com/nodejs/loaders for details about the forthcoming virtual file support
6
6
  */
7
- import type { resolve as base_resolve, resolveRoute as base_resolveRoute } from '$app/paths';
7
+ import type { resolve as base_resolve, asset as base_asset, resolveRoute as base_resolveRoute } from '$app/paths';
8
8
  export declare const assets = "";
9
9
  /** @deprecated */
10
10
  export declare const base = "";
11
11
  export declare const resolve: typeof base_resolve;
12
12
  /** @deprecated */
13
13
  export declare const resolveRoute: typeof base_resolveRoute;
14
+ export declare const asset: typeof base_asset;
14
15
  //# sourceMappingURL=sveltekit_shim_app_paths.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sveltekit_shim_app_paths.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/sveltekit_shim_app_paths.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AAEH,OAAO,KAAK,EAAC,OAAO,IAAI,YAAY,EAAE,YAAY,IAAI,iBAAiB,EAAC,MAAM,YAAY,CAAC;AAG3F,eAAO,MAAM,MAAM,KAAK,CAAC;AACzB,kBAAkB;AAClB,eAAO,MAAM,IAAI,KAAK,CAAC;AACvB,eAAO,MAAM,OAAO,EAAE,OAAO,YAAyD,CAAC;AACvF,kBAAkB;AAClB,eAAO,MAAM,YAAY,EAAE,OAAO,iBAAwB,CAAC"}
1
+ {"version":3,"file":"sveltekit_shim_app_paths.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/sveltekit_shim_app_paths.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AAEH,OAAO,KAAK,EACX,OAAO,IAAI,YAAY,EACvB,KAAK,IAAI,UAAU,EACnB,YAAY,IAAI,iBAAiB,EACjC,MAAM,YAAY,CAAC;AAGpB,eAAO,MAAM,MAAM,KAAK,CAAC;AACzB,kBAAkB;AAClB,eAAO,MAAM,IAAI,KAAK,CAAC;AACvB,eAAO,MAAM,OAAO,EAAE,OAAO,YAAyD,CAAC;AACvF,kBAAkB;AAClB,eAAO,MAAM,YAAY,EAAE,OAAO,iBAAwB,CAAC;AAC3D,eAAO,MAAM,KAAK,EAAE,OAAO,UAAuD,CAAC"}
@@ -4,6 +4,7 @@ import { noop } from '@ryanatkn/belt/function.js';
4
4
  export const assets = '';
5
5
  /** @deprecated */
6
6
  export const base = '';
7
- export const resolve = (v) => ('/' + v.replace(/^\//, '')); // TODO needs to use SvelteKit config base, should we just import it?
7
+ export const resolve = (v) => ('/' + v.replace(/^\//, '')); // TODO needs to use SvelteKit config base
8
8
  /** @deprecated */
9
9
  export const resolveRoute = noop; // eslint-disable-line @typescript-eslint/no-deprecated
10
+ export const asset = (v) => ('/' + v.replace(/^\//, '')); // TODO needs to use SvelteKit config base
package/dist/task.d.ts CHANGED
@@ -22,8 +22,9 @@ export interface Task_Context<T_Args = object> {
22
22
  filer: Filer;
23
23
  log: Logger;
24
24
  timings: Timings;
25
- invoke_task: (task_name: string, args?: Args, config?: Gro_Config) => Promise<void>;
25
+ invoke_task: Invoke_Task;
26
26
  }
27
+ export type Invoke_Task = (task_name: string, args?: Args, config?: Gro_Config) => Promise<void>;
27
28
  export declare const TASK_FILE_SUFFIX_TS = ".task.ts";
28
29
  export declare const TASK_FILE_SUFFIX_JS = ".task.js";
29
30
  export declare const TASK_FILE_SUFFIXES: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/task.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/task.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryanatkn/gro",
3
- "version": "0.162.2",
3
+ "version": "0.164.0",
4
4
  "description": "task runner and toolkit extending SvelteKit",
5
5
  "motto": "generate, run, optimize",
6
6
  "glyph": "🌰",
@@ -51,15 +51,15 @@
51
51
  "dependencies": {
52
52
  "@ryanatkn/belt": "^0.34.1",
53
53
  "chokidar": "^4.0.3",
54
- "dotenv": "^17.2.1",
54
+ "dotenv": "^17.2.2",
55
55
  "esm-env": "^1.2.2",
56
56
  "mri": "^1.2.0",
57
- "oxc-parser": "^0.82.3",
57
+ "oxc-parser": "^0.87.0",
58
58
  "prettier": "^3.6.2",
59
59
  "prettier-plugin-svelte": "^3.4.0",
60
60
  "ts-blank-space": "^0.6.2",
61
61
  "tslib": "^2.8.1",
62
- "zod": "^4.1.4"
62
+ "zod": "^4.1.5"
63
63
  },
64
64
  "peerDependencies": {
65
65
  "@sveltejs/kit": "^2",
@@ -87,17 +87,17 @@
87
87
  "@ryanatkn/fuz": "^0.145.0",
88
88
  "@ryanatkn/moss": "^0.32.0",
89
89
  "@sveltejs/adapter-static": "^3.0.9",
90
- "@sveltejs/kit": "^2.36.3",
90
+ "@sveltejs/kit": "^2.37.1",
91
91
  "@sveltejs/package": "^2.5.0",
92
- "@sveltejs/vite-plugin-svelte": "^6.1.3",
93
- "@types/node": "^24.3.0",
92
+ "@sveltejs/vite-plugin-svelte": "^6.1.4",
93
+ "@types/node": "^24.3.1",
94
94
  "esbuild": "^0.25.9",
95
- "eslint": "^9.34.0",
96
- "eslint-plugin-svelte": "^3.11.0",
97
- "svelte": "^5.38.6",
95
+ "eslint": "^9.35.0",
96
+ "eslint-plugin-svelte": "^3.12.2",
97
+ "svelte": "^5.38.7",
98
98
  "svelte-check": "^4.3.1",
99
99
  "typescript": "^5.9.2",
100
- "typescript-eslint": "^8.41.0",
100
+ "typescript-eslint": "^8.42.0",
101
101
  "vitest": "^3.2.4"
102
102
  },
103
103
  "prettier": {
package/src/lib/filer.ts CHANGED
@@ -2,7 +2,6 @@ import {EMPTY_OBJECT} from '@ryanatkn/belt/object.js';
2
2
  import {existsSync, readFileSync, statSync} from 'node:fs';
3
3
  import {dirname, resolve} from 'node:path';
4
4
  import type {Omit_Strict} from '@ryanatkn/belt/types.js';
5
- import {wait} from '@ryanatkn/belt/async.js';
6
5
  import {isBuiltin} from 'node:module';
7
6
  import {fileURLToPath, pathToFileURL} from 'node:url';
8
7
  import {Unreachable_Error} from '@ryanatkn/belt/error.js';
@@ -27,9 +26,7 @@ import type {Disknode} from './disknode.ts';
27
26
 
28
27
  const aliases = Object.entries(default_svelte_config.alias);
29
28
 
30
- export type Cleanup_Watch = () => Promise<void>;
31
-
32
- export type On_Filer_Change = (change: Watcher_Change, source_file: Disknode) => void;
29
+ export type On_Filer_Change = (change: Watcher_Change, disknode: Disknode) => void;
33
30
 
34
31
  export interface Filer_Options {
35
32
  watch_dir?: typeof watch_dir;
@@ -41,6 +38,7 @@ export interface Filer_Options {
41
38
  export class Filer {
42
39
  readonly root_dir: Path_Id;
43
40
 
41
+ // TODO rename everything to `disknode`
44
42
  readonly files: Map<Path_Id, Disknode> = new Map();
45
43
 
46
44
  #watch_dir: typeof watch_dir;
@@ -48,6 +46,11 @@ export class Filer {
48
46
 
49
47
  #log?: Logger;
50
48
 
49
+ #listeners: Set<On_Filer_Change> = new Set();
50
+ #watching: Watch_Node_Fs | undefined;
51
+ #initing: Promise<void> | undefined;
52
+ #closing: Promise<void> | undefined;
53
+
51
54
  constructor(options: Filer_Options = EMPTY_OBJECT) {
52
55
  this.#watch_dir = options.watch_dir ?? watch_dir;
53
56
  this.#watch_dir_options = options.watch_dir_options ?? EMPTY_OBJECT;
@@ -58,11 +61,9 @@ export class Filer {
58
61
  // package.json/gro.config.ts/tsconfig.json/svelte.config.js/vite.config.ts to invalidate everything
59
62
  this.#log = options.log;
60
63
  }
61
-
62
- #watching: Watch_Node_Fs | undefined;
63
- #listeners: Set<On_Filer_Change> = new Set();
64
-
65
- #ready = false;
64
+ get inited(): boolean {
65
+ return this.#watching !== undefined;
66
+ }
66
67
 
67
68
  get_by_id = (id: Path_Id): Disknode | undefined => {
68
69
  return this.files.get(id);
@@ -88,6 +89,121 @@ export class Filer {
88
89
  return file;
89
90
  };
90
91
 
92
+ /**
93
+ * Initialize the filer to populate files without watching.
94
+ * Safe to call multiple times - subsequent calls are no-ops.
95
+ * Used by gen files to access the file graph.
96
+ */
97
+ async init(): Promise<void> {
98
+ // if already initing, return the existing promise
99
+ if (this.#initing) return this.#initing;
100
+
101
+ // if already initialized, just ensure ready
102
+ if (this.#watching) {
103
+ return this.#watching.init();
104
+ }
105
+
106
+ // start new initialization
107
+ this.#initing = this.#init();
108
+ try {
109
+ await this.#initing;
110
+ } catch (err) {
111
+ // use shared cleanup logic
112
+ this.#cleanup();
113
+ throw err;
114
+ } finally {
115
+ this.#initing = undefined;
116
+ }
117
+ }
118
+
119
+ async #init(): Promise<void> {
120
+ const watcher = this.#watch_dir({
121
+ ...this.#watch_dir_options,
122
+ dir: this.root_dir,
123
+ on_change: this.#on_change,
124
+ });
125
+
126
+ try {
127
+ await watcher.init();
128
+
129
+ // check if close() was called during init
130
+ if (this.#closing) {
131
+ await watcher.close();
132
+ return;
133
+ }
134
+
135
+ // only set after successful init and not closing
136
+ this.#watching = watcher;
137
+ } catch (err) {
138
+ // clean up watcher on error, but don't let close error mask init error
139
+ try {
140
+ await watcher.close();
141
+ } catch {
142
+ // ignore close errors - init error is more important
143
+ }
144
+ throw err;
145
+ }
146
+ }
147
+
148
+ async watch(listener: On_Filer_Change): Promise<() => void> {
149
+ await this.#add_listener(listener);
150
+ return () => {
151
+ this.#remove_listener(listener);
152
+ };
153
+ }
154
+
155
+ /**
156
+ * Internal cleanup of all state - can be called safely from anywhere
157
+ */
158
+ #cleanup(): void {
159
+ this.#listeners.clear();
160
+ this.files.clear();
161
+ this.#watching = undefined;
162
+ // #initing is handled in finally block of init()
163
+ }
164
+
165
+ close(): Promise<void> {
166
+ // if already closing, return existing promise
167
+ if (this.#closing) return this.#closing;
168
+
169
+ // if already closed and not initing, nothing to do
170
+ if (!this.#watching && !this.#initing) return Promise.resolve();
171
+
172
+ // start new close operation
173
+ const closing = this.#close();
174
+ this.#closing = closing;
175
+ // Clean up after completion, but don't change the returned promise
176
+ // Use void to ensure we don't accidentally return the .then() promise
177
+ void closing.then(
178
+ () => {
179
+ this.#closing = undefined;
180
+ },
181
+ () => {
182
+ this.#closing = undefined;
183
+ },
184
+ );
185
+ return this.#closing;
186
+ }
187
+
188
+ async #close(): Promise<void> {
189
+ // wait for any pending initialization to complete
190
+ if (this.#initing) {
191
+ try {
192
+ await this.#initing;
193
+ } catch {
194
+ // ignore errors during close
195
+ }
196
+ }
197
+
198
+ // close watcher if it exists
199
+ if (this.#watching) {
200
+ await this.#watching.close();
201
+ }
202
+
203
+ // clean up all state
204
+ this.#cleanup();
205
+ }
206
+
91
207
  #update(id: Path_Id): Disknode | null {
92
208
  const file = this.get_or_create(id);
93
209
 
@@ -153,93 +269,73 @@ export class Filer {
153
269
 
154
270
  file.contents = null; // clear contents in case it gets re-added later, we want the change to be detected
155
271
 
156
- let found = false;
157
- for (const d of this.files.values()) {
158
- if (d.dependencies.has(file.id)) {
159
- found = true;
160
- break;
161
- }
272
+ file.dependencies.clear();
273
+
274
+ // keep the file in memory if other files still depend on it
275
+ if (file.dependents.size === 0) {
276
+ this.files.delete(id);
162
277
  }
163
- if (!found) this.files.delete(id);
164
278
 
165
279
  return file;
166
280
  }
167
281
 
168
282
  #sync_listener_with_files(listener: On_Filer_Change): void {
169
- if (!this.#ready) return;
170
- for (const source_file of this.files.values()) {
171
- listener({type: 'add', path: source_file.id, is_directory: false}, source_file);
283
+ for (const disknode of this.files.values()) {
284
+ try {
285
+ listener({type: 'add', path: disknode.id, is_directory: false}, disknode);
286
+ } catch (err) {
287
+ this.#log?.error('[filer] Listener error during sync:', err);
288
+ }
172
289
  }
173
290
  }
174
291
 
175
- #notify_change(change: Watcher_Change, source_file: Disknode): void {
176
- if (!this.#ready) return;
292
+ #notify_change(change: Watcher_Change, disknode: Disknode): void {
177
293
  for (const listener of this.#listeners) {
178
- listener(change, source_file);
294
+ try {
295
+ listener(change, disknode);
296
+ } catch (err) {
297
+ this.#log?.error('[filer] Listener error during change notification:', err);
298
+ }
179
299
  }
180
300
  }
181
301
 
182
302
  async #add_listener(listener: On_Filer_Change): Promise<void> {
183
303
  this.#listeners.add(listener);
184
- if (this.#watching) {
185
- // if already watching, call the listener for all existing files after init
186
- await this.#watching.init();
187
- await wait(); // wait a tick to ensure the `this.#ready` value is updated below first
188
- this.#sync_listener_with_files(listener);
189
- return;
190
- }
191
- this.#watching = this.#watch_dir({
192
- ...this.#watch_dir_options,
193
- dir: this.root_dir,
194
- on_change: this.#on_change,
195
- });
196
- await this.#watching.init();
197
- this.#ready = true;
304
+
305
+ // ensure initialized
306
+ await this.init();
307
+
308
+ // notify of existing files
198
309
  this.#sync_listener_with_files(listener);
199
310
  }
200
311
 
201
- async #remove_listener(listener: On_Filer_Change): Promise<void> {
312
+ #remove_listener(listener: On_Filer_Change): void {
202
313
  this.#listeners.delete(listener);
203
- if (this.#listeners.size === 0) {
204
- await this.close(); // TODO is this right? should `watch` be async?
205
- }
314
+ // keep watching active even with no listeners, only close() tears down
206
315
  }
207
316
 
208
317
  #on_change: Watcher_Change_Callback = (change) => {
318
+ if (this.#closing) return; // ignore changes during close
209
319
  if (change.is_directory) return; // TODO manage directories?
210
- let source_file: Disknode | null;
320
+ let disknode: Disknode | null;
211
321
  switch (change.type) {
212
322
  case 'add':
213
323
  case 'update': {
214
- source_file = this.#update(change.path);
324
+ disknode = this.#update(change.path);
215
325
  break;
216
326
  }
217
327
  case 'delete': {
218
- source_file = this.#remove(change.path);
328
+ disknode = this.#remove(change.path);
219
329
  break;
220
330
  }
221
331
  default:
222
332
  throw new Unreachable_Error(change.type);
223
333
  }
224
- if (source_file) {
225
- this.#notify_change(change, source_file);
334
+ if (disknode && this.#listeners.size > 0) {
335
+ this.#notify_change(change, disknode);
226
336
  }
227
337
  };
228
338
 
229
- async watch(listener: On_Filer_Change): Promise<Cleanup_Watch> {
230
- await this.#add_listener(listener);
231
- return () => this.#remove_listener(listener);
232
- }
233
-
234
- async close(): Promise<void> {
235
- this.#ready = false;
236
- this.#listeners.clear();
237
- if (this.#watching) {
238
- await this.#watching.close();
239
- this.#watching = undefined;
240
- }
241
- }
242
-
243
339
  #is_external(id: string): boolean {
244
340
  const {filter} = this.#watch_dir_options;
245
341
  return !id.startsWith(this.root_dir + '/') || (!!filter && !filter(id, false));
@@ -248,28 +344,28 @@ export class Filer {
248
344
 
249
345
  // TODO maybe `Disknode` class?
250
346
  export const filter_dependents = (
251
- source_file: Disknode,
347
+ disknode: Disknode,
252
348
  get_by_id: (id: Path_Id) => Disknode | undefined,
253
349
  filter?: File_Filter,
254
350
  results: Set<string> = new Set(),
255
351
  searched: Set<string> = new Set(),
256
352
  log?: Logger,
257
353
  ): Set<string> => {
258
- const {dependents} = source_file;
354
+ const {dependents} = disknode;
259
355
  for (const dependent_id of dependents.keys()) {
260
356
  if (searched.has(dependent_id)) continue;
261
357
  searched.add(dependent_id);
262
358
  if (!filter || filter(dependent_id)) {
263
359
  results.add(dependent_id);
264
360
  }
265
- const dependent_source_file = get_by_id(dependent_id);
266
- if (!dependent_source_file) {
361
+ const dependent_disknode = get_by_id(dependent_id);
362
+ if (!dependent_disknode) {
267
363
  log?.warn(
268
- `[filer.filter_dependents] dependent source file ${dependent_id} not found for ${source_file.id}`,
364
+ `[filer.filter_dependents] dependent source file ${dependent_id} not found for ${disknode.id}`,
269
365
  );
270
366
  continue;
271
367
  }
272
- filter_dependents(dependent_source_file, get_by_id, filter, results, searched);
368
+ filter_dependents(dependent_disknode, get_by_id, filter, results, searched);
273
369
  }
274
370
  return results;
275
371
  };
@@ -35,12 +35,10 @@ export const Args = z.strictObject({
35
35
  });
36
36
  export type Args = z.infer<typeof Args>;
37
37
 
38
- // TODO test - especially making sure nothing gets genned
39
- // if there's any validation or import errors
40
38
  export const task: Task<Args> = {
41
39
  summary: 'run code generation scripts',
42
40
  Args,
43
- run: async ({args, log, timings, config}): Promise<void> => {
41
+ run: async ({args, filer, log, timings, config, invoke_task}): Promise<void> => {
44
42
  const {_: raw_input_paths, root_dirs, check} = args;
45
43
 
46
44
  const input_paths = to_input_paths(raw_input_paths);
@@ -71,7 +69,15 @@ export const task: Task<Args> = {
71
69
 
72
70
  // run `gen` on each of the modules
73
71
  const timing_to_generate_code = timings.start('generate code'); // TODO this ignores `gen_results.elapsed` - should it return `Timings` instead?
74
- const gen_results = await run_gen(loaded_genfiles.modules, config, log, timings, format_file);
72
+ const gen_results = await run_gen(
73
+ loaded_genfiles.modules,
74
+ config,
75
+ filer,
76
+ log,
77
+ timings,
78
+ invoke_task,
79
+ format_file,
80
+ );
75
81
  timing_to_generate_code();
76
82
 
77
83
  const fail_count = gen_results.failures.length;
@@ -96,6 +102,20 @@ export const task: Task<Args> = {
96
102
  )} ${analyzed.is_new ? 'is new' : 'has changed'}.`,
97
103
  ),
98
104
  );
105
+
106
+ // DEBUG HACK: Show current vs changed content
107
+ if (!analyzed.is_new) {
108
+ log.info(
109
+ `\n=== CURRENT CONTENT (${print_path(analyzed.file.id)}) ===\n${analyzed.existing_content}\n=== END CURRENT ===`,
110
+ );
111
+ log.info(
112
+ `\n=== CHANGED CONTENT (${print_path(analyzed.file.id)}) ===\n${analyzed.file.content}\n=== END CHANGED ===`,
113
+ );
114
+ } else {
115
+ log.info(
116
+ `\n=== NEW FILE CONTENT (${print_path(analyzed.file.id)}) ===\n${analyzed.file.content}\n=== END NEW ===`,
117
+ );
118
+ }
99
119
  }
100
120
  if (has_unexpected_changes) {
101
121
  throw new Task_Error(
package/src/lib/gen.ts CHANGED
@@ -19,6 +19,8 @@ import {
19
19
  type Resolved_Input_Path,
20
20
  } from './input_path.ts';
21
21
  import {search_fs} from './search_fs.ts';
22
+ import type {Filer} from './filer.ts';
23
+ import type {Invoke_Task} from './task.ts';
22
24
 
23
25
  export const GEN_FILE_PATTERN_TEXT = 'gen';
24
26
  export const GEN_FILE_PATTERN = '.' + GEN_FILE_PATTERN_TEXT + '.';
@@ -40,6 +42,10 @@ export type Gen = (ctx: Gen_Context) => Raw_Gen_Result | Promise<Raw_Gen_Result>
40
42
  export interface Gen_Context {
41
43
  config: Gro_Config;
42
44
  svelte_config: Parsed_Svelte_Config;
45
+ filer: Filer;
46
+ log: Logger;
47
+ timings: Timings;
48
+ invoke_task: Invoke_Task;
43
49
  /**
44
50
  * Same as `import.meta.url` but in path form.
45
51
  */
@@ -48,8 +54,8 @@ export interface Gen_Context {
48
54
  * The `origin_id` relative to the root dir.
49
55
  */
50
56
  origin_path: string;
51
- log: Logger;
52
57
  }
58
+
53
59
  // TODO consider other return data - metadata? effects? non-file build artifacts?
54
60
  export type Raw_Gen_Result = string | Raw_Gen_File | null | Array<Raw_Gen_Result>;
55
61
  export interface Raw_Gen_File {
@@ -7,7 +7,7 @@ import type {Args} from './args.ts';
7
7
  import {paths} from './paths.ts';
8
8
  import {find_genfiles, is_gen_path} from './gen.ts';
9
9
  import {spawn_cli} from './cli.ts';
10
- import {filter_dependents, type Cleanup_Watch} from './filer.ts';
10
+ import {filter_dependents} from './filer.ts';
11
11
 
12
12
  const FLUSH_DEBOUNCE_DELAY = 500;
13
13
 
@@ -49,7 +49,7 @@ export const gro_plugin_gen = ({
49
49
  // TODO do this in-process - will it cause caching issues with the current impl?
50
50
  const gen = (files: Array<string> = []) => spawn_cli('gro', ['gen', ...files]);
51
51
 
52
- let cleanup_watch: Cleanup_Watch | undefined;
52
+ let cleanup_watch: (() => void) | undefined;
53
53
 
54
54
  return {
55
55
  name: 'gro_plugin_gen',
@@ -105,9 +105,9 @@ export const gro_plugin_gen = ({
105
105
  }
106
106
  });
107
107
  },
108
- teardown: async () => {
108
+ teardown: () => {
109
109
  if (cleanup_watch) {
110
- await cleanup_watch();
110
+ cleanup_watch();
111
111
  cleanup_watch = undefined;
112
112
  }
113
113
  },
@@ -44,13 +44,21 @@ export const invoke_task = async (
44
44
  const log = new System_Logger(print_log_label(task_name || 'gro'));
45
45
  log.info('invoking', task_name ? st('cyan', task_name) : 'gro');
46
46
 
47
+ // track if we created the filer
48
+ const owns_filer = !initial_filer;
47
49
  const filer = initial_filer ?? new Filer({log});
48
50
 
51
+ const owns_timings = !initial_timings;
49
52
  const timings = initial_timings ?? new Timings();
50
53
 
51
54
  const total_timing = create_stopwatch();
52
- const finish = () => {
53
- if (!initial_timings) return; // print timings only for the top-level task
55
+ const finish = async () => {
56
+ // cleanup filer only if we created it and it was initialized
57
+ if (owns_filer && filer.inited) {
58
+ await filer.close();
59
+ }
60
+
61
+ if (owns_timings) return; // kinda weird, print timings only for the top-level task
54
62
  print_timings(timings, log);
55
63
  log.info(`🕒 ${print_ms(total_timing())}`);
56
64
  };
@@ -59,7 +67,7 @@ export const invoke_task = async (
59
67
  if (!task_name && (args?.version || args?.v)) {
60
68
  const gro_package_json = load_gro_package_json();
61
69
  log.info(`${st('gray', 'v')}${st('cyan', gro_package_json.version)}`);
62
- finish();
70
+ await finish();
63
71
  return;
64
72
  }
65
73
 
@@ -91,7 +99,7 @@ export const invoke_task = async (
91
99
  if (resolved_input_files.length > 1 || resolved_input_files[0].resolved_input_path.is_directory) {
92
100
  // The input path matches a directory. Log the tasks but don't run them.
93
101
  log_tasks(log, loaded_tasks);
94
- finish();
102
+ await finish();
95
103
  return;
96
104
  }
97
105
 
@@ -119,5 +127,5 @@ export const invoke_task = async (
119
127
  }
120
128
  log.info(`✓ ${st('cyan', task.name)}`);
121
129
 
122
- finish();
130
+ await finish();
123
131
  };