@ryanatkn/gro 0.163.0 → 0.164.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/dist/filer.d.ts +10 -4
- package/dist/filer.d.ts.map +1 -1
- package/dist/filer.js +149 -61
- package/dist/gen.d.ts +6 -1
- package/dist/gen.d.ts.map +1 -1
- package/dist/gen.task.d.ts.map +1 -1
- package/dist/gen.task.js +10 -4
- package/dist/gro.config.default.d.ts.map +1 -1
- package/dist/gro.config.default.js +3 -25
- package/dist/gro_plugin_gen.js +2 -2
- package/dist/invoke_task.d.ts.map +1 -1
- package/dist/invoke_task.js +13 -6
- package/dist/package.js +5 -5
- package/dist/package_json.d.ts.map +1 -1
- package/dist/package_json.js +2 -0
- package/dist/run_gen.d.ts +3 -1
- package/dist/run_gen.d.ts.map +1 -1
- package/dist/run_gen.js +5 -2
- package/dist/task.d.ts +2 -1
- package/dist/task.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/lib/filer.ts +161 -65
- package/src/lib/gen.task.ts +24 -4
- package/src/lib/gen.ts +7 -1
- package/src/lib/gro.config.default.ts +3 -19
- package/src/lib/gro_plugin_gen.ts +4 -4
- package/src/lib/invoke_task.ts +13 -5
- package/src/lib/package.ts +5 -5
- package/src/lib/package_json.ts +3 -0
- package/src/lib/run_gen.ts +8 -1
- package/src/lib/task.ts +3 -1
package/dist/run_gen.js
CHANGED
|
@@ -4,7 +4,7 @@ import { to_gen_result, } 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';
|
|
7
|
-
export const run_gen = async (gen_modules, config, log, timings, format_file) => {
|
|
7
|
+
export const run_gen = async (gen_modules, config, filer, log, timings, invoke_task, format_file) => {
|
|
8
8
|
let input_count = 0;
|
|
9
9
|
let output_count = 0;
|
|
10
10
|
const timing_for_run_gen = timings.start('run_gen');
|
|
@@ -16,9 +16,12 @@ export const run_gen = async (gen_modules, config, log, timings, format_file) =>
|
|
|
16
16
|
const gen_ctx = {
|
|
17
17
|
config,
|
|
18
18
|
svelte_config: default_svelte_config,
|
|
19
|
+
filer,
|
|
20
|
+
log,
|
|
21
|
+
timings,
|
|
22
|
+
invoke_task,
|
|
19
23
|
origin_id: id,
|
|
20
24
|
origin_path: to_root_path(id),
|
|
21
|
-
log,
|
|
22
25
|
};
|
|
23
26
|
let raw_gen_result;
|
|
24
27
|
try {
|
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:
|
|
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[];
|
package/dist/task.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/task.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,uBAAuB,CAAC;AAElD,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAC3B,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,2BAA2B,CAAC;AAEvD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,0BAA0B,CAAC;AAGrD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,iBAAiB,CAAC;AAChD,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAGN,KAAK,UAAU,EACf,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAe,KAAK,oBAAoB,EAAE,KAAK,WAAW,EAAC,MAAM,cAAc,CAAC;AACvF,OAAO,KAAK,EAAC,KAAK,EAAC,MAAM,YAAY,CAAC;AAEtC,MAAM,WAAW,IAAI,CACpB,MAAM,GAAG,IAAI,EACb,aAAa,SAAS,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,4CAA4C;AACjH,QAAQ,GAAG,OAAO;IAElB,GAAG,EAAE,CAAC,GAAG,EAAE,YAAY,CAAC,MAAM,CAAC,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,aAAa,CAAC;CACrB;AAED,MAAM,WAAW,YAAY,CAAC,MAAM,GAAG,MAAM;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,UAAU,CAAC;IACnB,aAAa,EAAE,oBAAoB,CAAC;IACpC,KAAK,EAAE,KAAK,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;
|
|
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.
|
|
3
|
+
"version": "0.164.1",
|
|
4
4
|
"description": "task runner and toolkit extending SvelteKit",
|
|
5
5
|
"motto": "generate, run, optimize",
|
|
6
6
|
"glyph": "🌰",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
}
|
|
78
78
|
},
|
|
79
79
|
"optionalDependencies": {
|
|
80
|
-
"@ryanatkn/moss": ">=0.
|
|
80
|
+
"@ryanatkn/moss": ">=0.33.0",
|
|
81
81
|
"vitest": "^3"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"@changesets/types": "^6.1.0",
|
|
86
86
|
"@ryanatkn/eslint-config": "^0.8.0",
|
|
87
87
|
"@ryanatkn/fuz": "^0.145.0",
|
|
88
|
-
"@ryanatkn/moss": "^0.
|
|
88
|
+
"@ryanatkn/moss": "^0.33.0",
|
|
89
89
|
"@sveltejs/adapter-static": "^3.0.9",
|
|
90
90
|
"@sveltejs/kit": "^2.37.1",
|
|
91
91
|
"@sveltejs/package": "^2.5.0",
|
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
|
|
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
|
-
|
|
63
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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,
|
|
176
|
-
if (!this.#ready) return;
|
|
292
|
+
#notify_change(change: Watcher_Change, disknode: Disknode): void {
|
|
177
293
|
for (const listener of this.#listeners) {
|
|
178
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
312
|
+
#remove_listener(listener: On_Filer_Change): void {
|
|
202
313
|
this.#listeners.delete(listener);
|
|
203
|
-
|
|
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
|
|
320
|
+
let disknode: Disknode | null;
|
|
211
321
|
switch (change.type) {
|
|
212
322
|
case 'add':
|
|
213
323
|
case 'update': {
|
|
214
|
-
|
|
324
|
+
disknode = this.#update(change.path);
|
|
215
325
|
break;
|
|
216
326
|
}
|
|
217
327
|
case 'delete': {
|
|
218
|
-
|
|
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 (
|
|
225
|
-
this.#notify_change(change,
|
|
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
|
-
|
|
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} =
|
|
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
|
|
266
|
-
if (!
|
|
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 ${
|
|
364
|
+
`[filer.filter_dependents] dependent source file ${dependent_id} not found for ${disknode.id}`,
|
|
269
365
|
);
|
|
270
366
|
continue;
|
|
271
367
|
}
|
|
272
|
-
filter_dependents(
|
|
368
|
+
filter_dependents(dependent_disknode, get_by_id, filter, results, searched);
|
|
273
369
|
}
|
|
274
370
|
return results;
|
|
275
371
|
};
|
package/src/lib/gen.task.ts
CHANGED
|
@@ -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(
|
|
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 {
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import {resolve} from 'node:path';
|
|
2
|
-
|
|
3
1
|
import type {Create_Gro_Config} from './gro_config.ts';
|
|
4
2
|
import {gro_plugin_sveltekit_library} from './gro_plugin_sveltekit_library.ts';
|
|
5
3
|
import {has_server, gro_plugin_server} from './gro_plugin_server.ts';
|
|
6
4
|
import {gro_plugin_sveltekit_app} from './gro_plugin_sveltekit_app.ts';
|
|
7
5
|
import {has_sveltekit_app, has_sveltekit_library} from './sveltekit_helpers.ts';
|
|
8
6
|
import {gro_plugin_gen} from './gro_plugin_gen.ts';
|
|
9
|
-
import {
|
|
10
|
-
import {find_first_existing_file} from './search_fs.ts';
|
|
7
|
+
import {load_package_json} from './package_json.ts';
|
|
11
8
|
|
|
12
9
|
// TODO hacky, maybe extract utils?
|
|
13
10
|
|
|
@@ -23,29 +20,16 @@ import {find_first_existing_file} from './search_fs.ts';
|
|
|
23
20
|
const config: Create_Gro_Config = async (cfg, svelte_config) => {
|
|
24
21
|
const package_json = load_package_json(); // TODO gets wastefully loaded by some plugins, maybe put in plugin/task context? how does that interact with `map_package_json`?
|
|
25
22
|
|
|
26
|
-
const [
|
|
23
|
+
const [has_server_result, has_sveltekit_library_result, has_sveltekit_app_result] =
|
|
27
24
|
await Promise.all([
|
|
28
|
-
has_dep('@ryanatkn/moss', package_json),
|
|
29
25
|
has_server(),
|
|
30
26
|
has_sveltekit_library(package_json, svelte_config),
|
|
31
27
|
has_sveltekit_app(),
|
|
32
28
|
]);
|
|
33
29
|
|
|
34
|
-
const local_moss_plugin_path = find_first_existing_file([
|
|
35
|
-
'./src/lib/gro_plugin_moss.ts',
|
|
36
|
-
'./src/gro_plugin_moss.ts',
|
|
37
|
-
'./src/routes/gro_plugin_moss.ts', // TODO probably remove this
|
|
38
|
-
]);
|
|
39
|
-
|
|
40
30
|
// put things that generate files before SvelteKit so it can see them
|
|
41
|
-
cfg.plugins =
|
|
31
|
+
cfg.plugins = () =>
|
|
42
32
|
[
|
|
43
|
-
// TODO probably belongs in the gen system
|
|
44
|
-
local_moss_plugin_path
|
|
45
|
-
? (await import(resolve(local_moss_plugin_path))).gro_plugin_moss()
|
|
46
|
-
: has_moss_dep
|
|
47
|
-
? (await import('@ryanatkn/moss/gro_plugin_moss.js')).gro_plugin_moss()
|
|
48
|
-
: null, // lazy load to avoid errors if it's not installed
|
|
49
33
|
gro_plugin_gen(),
|
|
50
34
|
has_server_result.ok ? gro_plugin_server() : null,
|
|
51
35
|
has_sveltekit_library_result.ok ? gro_plugin_sveltekit_library() : null,
|
|
@@ -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
|
|
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:
|
|
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:
|
|
108
|
+
teardown: () => {
|
|
109
109
|
if (cleanup_watch) {
|
|
110
|
-
|
|
110
|
+
cleanup_watch();
|
|
111
111
|
cleanup_watch = undefined;
|
|
112
112
|
}
|
|
113
113
|
},
|
package/src/lib/invoke_task.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
};
|
package/src/lib/package.ts
CHANGED
|
@@ -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.
|
|
8
|
+
version: '0.164.1',
|
|
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.
|
|
67
|
+
optionalDependencies: {'@ryanatkn/moss': '>=0.33.0', 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.145.0',
|
|
73
|
-
'@ryanatkn/moss': '^0.
|
|
73
|
+
'@ryanatkn/moss': '^0.33.0',
|
|
74
74
|
'@sveltejs/adapter-static': '^3.0.9',
|
|
75
75
|
'@sveltejs/kit': '^2.37.1',
|
|
76
76
|
'@sveltejs/package': '^2.5.0',
|
|
@@ -272,7 +272,7 @@ export const package_json: Package_Json = {
|
|
|
272
272
|
|
|
273
273
|
export const src_json: Src_Json = {
|
|
274
274
|
name: '@ryanatkn/gro',
|
|
275
|
-
version: '0.
|
|
275
|
+
version: '0.164.1',
|
|
276
276
|
modules: {
|
|
277
277
|
'.': {
|
|
278
278
|
path: 'index.ts',
|
|
@@ -495,7 +495,6 @@ export const src_json: Src_Json = {
|
|
|
495
495
|
'./filer.js': {
|
|
496
496
|
path: 'filer.ts',
|
|
497
497
|
declarations: [
|
|
498
|
-
{name: 'Cleanup_Watch', kind: 'type'},
|
|
499
498
|
{name: 'On_Filer_Change', kind: 'type'},
|
|
500
499
|
{name: 'Filer_Options', kind: 'type'},
|
|
501
500
|
{name: 'Filer', kind: 'class'},
|
|
@@ -981,6 +980,7 @@ export const src_json: Src_Json = {
|
|
|
981
980
|
declarations: [
|
|
982
981
|
{name: 'Task', kind: 'type'},
|
|
983
982
|
{name: 'Task_Context', kind: 'type'},
|
|
983
|
+
{name: 'Invoke_Task', kind: 'type'},
|
|
984
984
|
{name: 'TASK_FILE_SUFFIX_TS', kind: 'variable'},
|
|
985
985
|
{name: 'TASK_FILE_SUFFIX_JS', kind: 'variable'},
|
|
986
986
|
{name: 'TASK_FILE_SUFFIXES', kind: 'variable'},
|
package/src/lib/package_json.ts
CHANGED
|
@@ -115,6 +115,9 @@ export const update_package_json = async (
|
|
|
115
115
|
|
|
116
116
|
const is_index = (path: string): boolean => path === 'index.ts' || path === 'index.js';
|
|
117
117
|
|
|
118
|
+
// TODO support subpath patterns as the main concise way to do things
|
|
119
|
+
// https://nodejs.org/api/packages.html#subpath-patterns
|
|
120
|
+
|
|
118
121
|
export const to_package_exports = (paths: Array<string>): Package_Json_Exports => {
|
|
119
122
|
const sorted = paths
|
|
120
123
|
.slice()
|