-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathgro_plugin_gen.ts
131 lines (120 loc) · 3.85 KB
/
gro_plugin_gen.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import {EMPTY_OBJECT} from '@ryanatkn/belt/object.js';
import {throttle} from '@ryanatkn/belt/throttle.js';
import {Unreachable_Error} from '@ryanatkn/belt/error.js';
import type {Plugin} from './plugin.js';
import type {Args} from './args.js';
import {paths} from './paths.js';
import {find_genfiles, is_gen_path} from './gen.js';
import {spawn_cli} from './cli.js';
import type {File_Filter, Path_Id} from './path.js';
import type {Cleanup_Watch, Source_File} from './filer.js';
const FLUSH_DEBOUNCE_DELAY = 500;
export interface Task_Args extends Args {
watch?: boolean;
}
export interface Gro_Plugin_Gen_Options {
input_paths?: Array<string>;
root_dirs?: Array<string>;
flush_debounce_delay?: number;
}
export const gro_plugin_gen = ({
input_paths = [paths.source],
root_dirs = [paths.source],
flush_debounce_delay = FLUSH_DEBOUNCE_DELAY,
}: Gro_Plugin_Gen_Options = EMPTY_OBJECT): Plugin => {
let flushing_timeout: NodeJS.Timeout | undefined;
const queued_files: Set<string> = new Set();
const queue_gen = (gen_file_id: string) => {
queued_files.add(gen_file_id);
if (flushing_timeout === undefined) {
flushing_timeout = setTimeout(() => {
flushing_timeout = undefined;
void flush_gen_queue();
}); // the timeout batches synchronously
}
};
const flush_gen_queue = throttle(
async () => {
const files = Array.from(queued_files);
queued_files.clear();
await gen(files);
},
{delay: flush_debounce_delay},
);
const gen = (files: Array<string> = []) => spawn_cli('gro', ['gen', ...files]);
let cleanup: Cleanup_Watch | undefined;
return {
name: 'gro_plugin_gen',
setup: async ({watch, dev, log, config, filer}) => {
// For production builds, we assume `gen` is already fresh,
// which should be checked by CI via `gro check` which calls `gro gen --check`.
if (!dev) return;
// Do we need to just generate everything once and exit?
if (!watch) {
log.info('generating and exiting early');
// Run `gen`, first checking if there are any modules to avoid a console error.
// Some parts of the build may have already happened,
// making us miss `build` events for gen dependencies,
// so we run `gen` here even if it's usually wasteful.
const found = find_genfiles(input_paths, root_dirs, config);
if (found.ok && found.value.resolved_input_files.length > 0) {
await gen();
}
return;
}
// When a file builds, check it and its tree of dependents
// for any `.gen.` files that need to run.
cleanup = await filer.watch((change, source_file) => {
if (source_file.external) return;
switch (change.type) {
case 'add':
case 'update': {
if (is_gen_path(source_file.id)) {
queue_gen(source_file.id);
}
const dependent_gen_file_ids = filter_dependents(
source_file,
filer.get_by_id,
is_gen_path,
);
for (const dependent_gen_file_id of dependent_gen_file_ids) {
queue_gen(dependent_gen_file_id);
}
break;
}
case 'delete': {
// TODO delete the generated file(s)? option?
break;
}
default:
throw new Unreachable_Error(change.type);
}
});
},
teardown: async () => {
if (cleanup !== undefined) {
await cleanup();
cleanup = undefined;
}
},
};
};
export const filter_dependents = (
source_file: Source_File,
get_by_id: (id: Path_Id) => Source_File | undefined,
filter?: File_Filter,
results: Set<string> = new Set(),
searched: Set<string> = new Set(),
): Set<string> => {
const {dependents} = source_file;
for (const dependent_id of dependents.keys()) {
if (searched.has(dependent_id)) continue;
searched.add(dependent_id);
if (!filter || filter(dependent_id)) {
results.add(dependent_id);
}
const dependent_source_File = get_by_id(dependent_id)!;
filter_dependents(dependent_source_File, get_by_id, filter, results, searched);
}
return results;
};