@ryanatkn/gro 0.161.1 → 0.162.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.
Files changed (106) hide show
  1. package/README.md +8 -7
  2. package/dist/build.task.d.ts.map +1 -1
  3. package/dist/build.task.js +4 -2
  4. package/dist/check.task.d.ts +0 -1
  5. package/dist/check.task.d.ts.map +1 -1
  6. package/dist/check.task.js +8 -8
  7. package/dist/child_process_logging.js +1 -1
  8. package/dist/constants.d.ts +10 -1
  9. package/dist/constants.d.ts.map +1 -1
  10. package/dist/constants.js +12 -3
  11. package/dist/dev.task.d.ts +0 -1
  12. package/dist/dev.task.d.ts.map +1 -1
  13. package/dist/dev.task.js +5 -7
  14. package/dist/disknode.d.ts +18 -0
  15. package/dist/disknode.d.ts.map +1 -0
  16. package/dist/disknode.js +1 -0
  17. package/dist/esbuild_plugin_sveltekit_shim_env.d.ts.map +1 -1
  18. package/dist/esbuild_plugin_sveltekit_shim_env.js +1 -2
  19. package/dist/filer.d.ts +6 -21
  20. package/dist/filer.d.ts.map +1 -1
  21. package/dist/filer.js +11 -6
  22. package/dist/format_directory.js +2 -2
  23. package/dist/gen.task.d.ts.map +1 -1
  24. package/dist/gen.task.js +56 -12
  25. package/dist/git.js +3 -3
  26. package/dist/gro.config.default.d.ts.map +1 -1
  27. package/dist/gro.config.default.js +2 -1
  28. package/dist/gro_config.d.ts +1 -1
  29. package/dist/gro_config.d.ts.map +1 -1
  30. package/dist/gro_config.js +5 -5
  31. package/dist/gro_plugin_gen.d.ts.map +1 -1
  32. package/dist/gro_plugin_gen.js +7 -6
  33. package/dist/gro_plugin_server.d.ts.map +1 -1
  34. package/dist/gro_plugin_server.js +16 -25
  35. package/dist/gro_plugin_sveltekit_app.d.ts.map +1 -1
  36. package/dist/gro_plugin_sveltekit_app.js +1 -2
  37. package/dist/gro_plugin_sveltekit_library.d.ts.map +1 -1
  38. package/dist/gro_plugin_sveltekit_library.js +2 -1
  39. package/dist/invoke.js +6 -4
  40. package/dist/package.d.ts.map +1 -1
  41. package/dist/package.gen.d.ts.map +1 -1
  42. package/dist/package.gen.js +5 -1
  43. package/dist/package.js +41 -30
  44. package/dist/package_json.d.ts +4 -3
  45. package/dist/package_json.d.ts.map +1 -1
  46. package/dist/package_json.js +12 -10
  47. package/dist/parse_imports.js +2 -2
  48. package/dist/paths.js +2 -2
  49. package/dist/reinstall.task.js +1 -1
  50. package/dist/resolve_specifier.d.ts.map +1 -1
  51. package/dist/src_json.d.ts.map +1 -1
  52. package/dist/src_json.js +3 -1
  53. package/dist/svelte_config.d.ts.map +1 -1
  54. package/dist/svelte_config.js +2 -0
  55. package/dist/sveltekit_helpers.d.ts +0 -7
  56. package/dist/sveltekit_helpers.d.ts.map +1 -1
  57. package/dist/sveltekit_helpers.js +1 -8
  58. package/dist/sync.task.d.ts +0 -1
  59. package/dist/sync.task.d.ts.map +1 -1
  60. package/dist/sync.task.js +1 -2
  61. package/dist/task.d.ts.map +1 -1
  62. package/dist/test.task.d.ts +2 -3
  63. package/dist/test.task.d.ts.map +1 -1
  64. package/dist/test.task.js +31 -30
  65. package/dist/test_helpers.d.ts +1 -0
  66. package/dist/test_helpers.d.ts.map +1 -1
  67. package/dist/test_helpers.js +1 -0
  68. package/dist/typecheck.task.d.ts.map +1 -1
  69. package/dist/typecheck.task.js +2 -1
  70. package/dist/upgrade.task.d.ts.map +1 -1
  71. package/dist/upgrade.task.js +2 -1
  72. package/package.json +34 -19
  73. package/src/lib/build.task.ts +3 -2
  74. package/src/lib/check.task.ts +7 -8
  75. package/src/lib/child_process_logging.ts +1 -1
  76. package/src/lib/constants.ts +12 -3
  77. package/src/lib/dev.task.ts +4 -7
  78. package/src/lib/disknode.ts +21 -0
  79. package/src/lib/esbuild_plugin_sveltekit_shim_env.ts +1 -2
  80. package/src/lib/filer.ts +23 -35
  81. package/src/lib/format_directory.ts +2 -2
  82. package/src/lib/gen.task.ts +83 -23
  83. package/src/lib/git.ts +3 -3
  84. package/src/lib/gro.config.default.ts +2 -1
  85. package/src/lib/gro_config.ts +11 -7
  86. package/src/lib/gro_plugin_gen.ts +8 -6
  87. package/src/lib/gro_plugin_server.ts +18 -24
  88. package/src/lib/gro_plugin_sveltekit_app.ts +1 -2
  89. package/src/lib/gro_plugin_sveltekit_library.ts +2 -5
  90. package/src/lib/invoke.ts +6 -4
  91. package/src/lib/package.gen.ts +5 -1
  92. package/src/lib/package.ts +41 -30
  93. package/src/lib/package_json.ts +11 -9
  94. package/src/lib/parse_imports.ts +2 -2
  95. package/src/lib/paths.ts +2 -2
  96. package/src/lib/reinstall.task.ts +1 -1
  97. package/src/lib/resolve_specifier.ts +2 -0
  98. package/src/lib/src_json.ts +3 -1
  99. package/src/lib/svelte_config.ts +3 -0
  100. package/src/lib/sveltekit_helpers.ts +7 -13
  101. package/src/lib/sync.task.ts +1 -2
  102. package/src/lib/task.ts +0 -2
  103. package/src/lib/test.task.ts +31 -32
  104. package/src/lib/test_helpers.ts +2 -0
  105. package/src/lib/typecheck.task.ts +2 -1
  106. package/src/lib/upgrade.task.ts +2 -1
package/src/lib/filer.ts CHANGED
@@ -21,31 +21,15 @@ import {paths} from './paths.ts';
21
21
  import {parse_imports} from './parse_imports.ts';
22
22
  import {resolve_specifier} from './resolve_specifier.ts';
23
23
  import {default_svelte_config} from './svelte_config.ts';
24
- import {map_sveltekit_aliases, SVELTEKIT_GLOBAL_SPECIFIER} from './sveltekit_helpers.ts';
24
+ import {map_sveltekit_aliases} from './sveltekit_helpers.ts';
25
+ import {SVELTEKIT_GLOBAL_SPECIFIER} from './constants.ts';
26
+ import type {Disknode} from './disknode.ts';
25
27
 
26
28
  const aliases = Object.entries(default_svelte_config.alias);
27
29
 
28
- export interface Source_File {
29
- id: Path_Id;
30
- // TODO figure out the best API that makes this lazy
31
- /**
32
- * `null` contents means it doesn't exist.
33
- * We create the file in memory to track its dependents regardless of its existence on disk.
34
- */
35
- contents: string | null;
36
- /**
37
- * Is the source file outside of the `root_dir` or excluded by `watch_dir_options.filter`?
38
- */
39
- external: boolean;
40
- ctime: number | null;
41
- mtime: number | null;
42
- dependents: Map<Path_Id, Source_File>;
43
- dependencies: Map<Path_Id, Source_File>;
44
- }
45
-
46
30
  export type Cleanup_Watch = () => Promise<void>;
47
31
 
48
- export type On_Filer_Change = (change: Watcher_Change, source_file: Source_File) => void;
32
+ export type On_Filer_Change = (change: Watcher_Change, source_file: Disknode) => void;
49
33
 
50
34
  export interface Filer_Options {
51
35
  watch_dir?: typeof watch_dir;
@@ -57,7 +41,7 @@ export interface Filer_Options {
57
41
  export class Filer {
58
42
  readonly root_dir: Path_Id;
59
43
 
60
- readonly files: Map<Path_Id, Source_File> = new Map();
44
+ readonly files: Map<Path_Id, Disknode> = new Map();
61
45
 
62
46
  #watch_dir: typeof watch_dir;
63
47
  #watch_dir_options: Partial<Watch_Dir_Options>;
@@ -68,6 +52,10 @@ export class Filer {
68
52
  this.#watch_dir = options.watch_dir ?? watch_dir;
69
53
  this.#watch_dir_options = options.watch_dir_options ?? EMPTY_OBJECT;
70
54
  this.root_dir = resolve(options.watch_dir_options?.dir ?? paths.source);
55
+ // TODO for package.json maybe another array of files/dirs to watch to invalidate everything?
56
+ // or instead of that, think of taking an array of config objects that can specify invalidation rules,
57
+ // so package.json would be configured differently than ./src, and we could add a default with
58
+ // package.json/gro.config.ts/tsconfig.json/svelte.config.js/vite.config.ts to invalidate everything
71
59
  this.#log = options.log;
72
60
  }
73
61
 
@@ -76,14 +64,14 @@ export class Filer {
76
64
 
77
65
  #ready = false;
78
66
 
79
- get_by_id = (id: Path_Id): Source_File | undefined => {
67
+ get_by_id = (id: Path_Id): Disknode | undefined => {
80
68
  return this.files.get(id);
81
69
  };
82
70
 
83
- get_or_create = (id: Path_Id): Source_File => {
71
+ get_or_create = (id: Path_Id): Disknode => {
84
72
  const existing = this.get_by_id(id);
85
73
  if (existing) return existing;
86
- const file: Source_File = {
74
+ const file: Disknode = {
87
75
  id,
88
76
  contents: null,
89
77
  external: this.#is_external(id), // TODO maybe filter externals by default? the user needs to configure the filer then
@@ -100,14 +88,14 @@ export class Filer {
100
88
  return file;
101
89
  };
102
90
 
103
- #update(id: Path_Id): Source_File | null {
91
+ #update(id: Path_Id): Disknode | null {
104
92
  const file = this.get_or_create(id);
105
93
 
106
94
  const stats = existsSync(id) ? statSync(id) : null;
107
95
  file.ctime = stats?.ctimeMs ?? null;
108
96
  file.mtime = stats?.mtimeMs ?? null;
109
97
 
110
- const new_contents = stats ? readFileSync(id, 'utf8') : null; // TODO need to lazily load contents, probably turn `Source_File` into a class
98
+ const new_contents = stats ? readFileSync(id, 'utf8') : null; // TODO need to lazily load contents, probably turn `Disknode` into a class
111
99
 
112
100
  if (file.contents === new_contents) {
113
101
  return null;
@@ -159,7 +147,7 @@ export class Filer {
159
147
  return file;
160
148
  }
161
149
 
162
- #remove(id: Path_Id): Source_File | null {
150
+ #remove(id: Path_Id): Disknode | null {
163
151
  const file = this.get_by_id(id);
164
152
  if (!file) return null; // this is safe because the object would exist if any other file referenced it as a dependency or dependent
165
153
 
@@ -177,14 +165,14 @@ export class Filer {
177
165
  return file;
178
166
  }
179
167
 
180
- #notify_listener(listener: On_Filer_Change): void {
168
+ #sync_listener_with_files(listener: On_Filer_Change): void {
181
169
  if (!this.#ready) return;
182
170
  for (const source_file of this.files.values()) {
183
171
  listener({type: 'add', path: source_file.id, is_directory: false}, source_file);
184
172
  }
185
173
  }
186
174
 
187
- #notify_change(change: Watcher_Change, source_file: Source_File): void {
175
+ #notify_change(change: Watcher_Change, source_file: Disknode): void {
188
176
  if (!this.#ready) return;
189
177
  for (const listener of this.#listeners) {
190
178
  listener(change, source_file);
@@ -197,7 +185,7 @@ export class Filer {
197
185
  // if already watching, call the listener for all existing files after init
198
186
  await this.#watching.init();
199
187
  await wait(); // wait a tick to ensure the `this.#ready` value is updated below first
200
- this.#notify_listener(listener);
188
+ this.#sync_listener_with_files(listener);
201
189
  return;
202
190
  }
203
191
  this.#watching = this.#watch_dir({
@@ -207,7 +195,7 @@ export class Filer {
207
195
  });
208
196
  await this.#watching.init();
209
197
  this.#ready = true;
210
- this.#notify_listener(listener);
198
+ this.#sync_listener_with_files(listener);
211
199
  }
212
200
 
213
201
  async #remove_listener(listener: On_Filer_Change): Promise<void> {
@@ -219,7 +207,7 @@ export class Filer {
219
207
 
220
208
  #on_change: Watcher_Change_Callback = (change) => {
221
209
  if (change.is_directory) return; // TODO manage directories?
222
- let source_file: Source_File | null;
210
+ let source_file: Disknode | null;
223
211
  switch (change.type) {
224
212
  case 'add':
225
213
  case 'update': {
@@ -258,10 +246,10 @@ export class Filer {
258
246
  }
259
247
  }
260
248
 
261
- // TODO maybe `Source_File` class?
249
+ // TODO maybe `Disknode` class?
262
250
  export const filter_dependents = (
263
- source_file: Source_File,
264
- get_by_id: (id: Path_Id) => Source_File | undefined,
251
+ source_file: Disknode,
252
+ get_by_id: (id: Path_Id) => Disknode | undefined,
265
253
  filter?: File_Filter,
266
254
  results: Set<string> = new Set(),
267
255
  searched: Set<string> = new Set(),
@@ -8,7 +8,7 @@ import {
8
8
  SVELTE_CONFIG_FILENAME,
9
9
  VITE_CONFIG_FILENAME,
10
10
  TSCONFIG_FILENAME,
11
- GRO_CONFIG_PATH,
11
+ GRO_CONFIG_FILENAME,
12
12
  PM_CLI_DEFAULT,
13
13
  PRETTIER_CLI_DEFAULT,
14
14
  } from './constants.ts';
@@ -18,7 +18,7 @@ import {spawn_cli, to_cli_name, type Cli} from './cli.ts';
18
18
  const EXTENSIONS_DEFAULT = 'ts,js,json,svelte,html,css,md,yml';
19
19
  const ROOT_PATHS_DEFAULT = `${[
20
20
  README_FILENAME,
21
- GRO_CONFIG_PATH,
21
+ GRO_CONFIG_FILENAME,
22
22
  SVELTE_CONFIG_FILENAME,
23
23
  VITE_CONFIG_FILENAME,
24
24
  TSCONFIG_FILENAME,
@@ -9,7 +9,14 @@ import {Raw_Input_Path, to_input_paths} from './input_path.ts';
9
9
  import {format_file} from './format_file.ts';
10
10
  import {print_path} from './paths.ts';
11
11
  import {log_error_reasons} from './task_logging.ts';
12
- import {write_gen_results, analyze_gen_results, find_genfiles, load_genfiles} from './gen.ts';
12
+ import {
13
+ write_gen_results,
14
+ analyze_gen_results,
15
+ find_genfiles,
16
+ load_genfiles,
17
+ type Analyzed_Gen_Result,
18
+ type Gen_Results,
19
+ } from './gen.ts';
13
20
  import {SOURCE_DIRNAME} from './constants.ts';
14
21
 
15
22
  export const Args = z.strictObject({
@@ -106,31 +113,16 @@ export const task: Task<Args> = {
106
113
  timing_to_output_results();
107
114
  }
108
115
 
109
- // TODO these final printed results could be improved showing a breakdown per file id
116
+ // collect and format output with summary
117
+ const output_lines = collect_output_lines(gen_results, analyzed_gen_results);
110
118
  const new_count = analyzed_gen_results.filter((r) => r.is_new).length;
111
- const changed_count = analyzed_gen_results.filter((r) => r.has_changed).length;
119
+ const changed_count = analyzed_gen_results.filter((r) => r.has_changed && !r.is_new).length;
112
120
  const unchanged_count = analyzed_gen_results.filter((r) => !r.is_new && !r.has_changed).length;
113
- let log_result = st('green', 'gen results:');
114
- log_result += `\n\t${new_count} ` + st('gray', 'new');
115
- log_result += `\n\t${changed_count} ` + st('gray', 'changed');
116
- log_result += `\n\t${unchanged_count} ` + st('gray', 'unchanged');
117
- for (const result of gen_results.results) {
118
- log_result += `\n\t${result.ok ? st('green', '✓') : st('red', '🞩')} ${
119
- result.ok ? result.files.length : 0
120
- } ${st('gray', 'in')} ${print_ms(result.elapsed)} ${st('gray', '←')} ${print_path(
121
- result.id,
122
- )} ${st('gray', '→')} ${
123
- result.ok ? result.files.map((f) => print_path(f.id)).join(', ') : print_error(result.error)
124
- }`;
125
- }
126
- log.info(log_result);
121
+ const error_count = gen_results.failures.length;
122
+
127
123
  log.info(
128
- st(
129
- 'green',
130
- `generated ${gen_results.output_count} file${plural(gen_results.output_count)} from ${
131
- gen_results.successes.length
132
- } input file${plural(gen_results.successes.length)}`,
133
- ),
124
+ format_gen_output(output_lines) +
125
+ `\n\n\t${new_count} ${st(new_count > 0 ? 'green' : 'gray', 'new')}, ${changed_count} ${st(changed_count > 0 ? 'cyan' : 'gray', 'changed')}, ${unchanged_count} ${st('gray', 'unchanged')}${error_count ? `, ${error_count} ${st('red', 'error' + plural(error_count))}` : ''} from ${gen_results.input_count} input file${plural(gen_results.input_count)}`,
134
126
  );
135
127
 
136
128
  if (fail_count) {
@@ -141,3 +133,71 @@ export const task: Task<Args> = {
141
133
  }
142
134
  },
143
135
  };
136
+
137
+ interface Gen_Status {
138
+ symbol: string;
139
+ color: Parameters<typeof st>[0];
140
+ text: string;
141
+ }
142
+
143
+ const format_gen_status = (analyzed: Analyzed_Gen_Result | undefined): Gen_Status => {
144
+ if (!analyzed) return {symbol: '?', color: 'gray', text: 'unknown'};
145
+ if (analyzed.is_new) return {symbol: '●', color: 'green', text: 'new'};
146
+ if (analyzed.has_changed) return {symbol: '◐', color: 'cyan', text: 'changed'};
147
+ return {symbol: '○', color: 'gray', text: 'unchanged'};
148
+ };
149
+
150
+ interface Output_Line {
151
+ status: Gen_Status;
152
+ elapsed: string;
153
+ source: string;
154
+ target: string;
155
+ is_error: boolean;
156
+ }
157
+
158
+ const collect_output_lines = (
159
+ gen_results: Gen_Results,
160
+ analyzed_gen_results: Array<Analyzed_Gen_Result>,
161
+ ): Array<Output_Line> => {
162
+ const output_lines: Array<Output_Line> = [];
163
+
164
+ for (const result of gen_results.results) {
165
+ if (result.ok) {
166
+ for (const file of result.files) {
167
+ const analyzed = analyzed_gen_results.find((a) => a.file.id === file.id);
168
+ output_lines.push({
169
+ status: format_gen_status(analyzed),
170
+ elapsed: print_ms(result.elapsed),
171
+ source: print_path(result.id),
172
+ target: print_path(file.id),
173
+ is_error: false,
174
+ });
175
+ }
176
+ } else {
177
+ output_lines.push({
178
+ status: {symbol: '🞩', color: 'red', text: 'error'},
179
+ elapsed: print_ms(result.elapsed),
180
+ source: print_path(result.id),
181
+ target: st('red', result.error.stack || result.error.message || 'error'),
182
+ is_error: true,
183
+ });
184
+ }
185
+ }
186
+
187
+ return output_lines;
188
+ };
189
+
190
+ const format_gen_output = (output_lines: Array<Output_Line>): string => {
191
+ // calculate column widths for alignment
192
+ const max_elapsed_length = Math.max(...output_lines.map((l) => l.elapsed.length));
193
+ const max_source_length = Math.max(...output_lines.map((l) => l.source.length));
194
+
195
+ // format the output lines
196
+ let log_result = 'gen results:';
197
+ for (const line of output_lines) {
198
+ const elapsed_text = line.elapsed.padStart(max_elapsed_length);
199
+ const source_text = line.source.padEnd(max_source_length);
200
+ log_result += `\n\t${st(line.status.color, line.status.symbol)} ${elapsed_text} ${source_text} → ${line.target}`;
201
+ }
202
+ return log_result;
203
+ };
package/src/lib/git.ts CHANGED
@@ -20,7 +20,7 @@ export type Git_Branch = Flavored<string, 'Git_Branch'>;
20
20
  export const git_current_branch_name = async (options?: SpawnOptions): Promise<Git_Branch> => {
21
21
  const {stdout} = await spawn_out('git', ['rev-parse', '--abbrev-ref', 'HEAD'], options);
22
22
  if (!stdout) throw Error('git_current_branch_name failed');
23
- const branch_name = stdout.toString().trim() as Git_Branch;
23
+ const branch_name = stdout.trim() as Git_Branch;
24
24
  return branch_name;
25
25
  };
26
26
 
@@ -251,7 +251,7 @@ export const git_current_commit_hash = async (
251
251
  const final_branch = branch ?? (await git_current_branch_name(options));
252
252
  const {stdout} = await spawn_out('git', ['show-ref', '-s', final_branch], options);
253
253
  if (!stdout) return null; // TODO hack for CI
254
- return stdout.toString().split('\n')[0].trim();
254
+ return stdout.split('\n')[0].trim();
255
255
  };
256
256
 
257
257
  /**
@@ -266,7 +266,7 @@ export const git_current_branch_first_commit_hash = async (
266
266
  options,
267
267
  );
268
268
  if (!stdout) throw Error('git_current_branch_first_commit_hash failed');
269
- return stdout.toString().trim();
269
+ return stdout.trim();
270
270
  };
271
271
 
272
272
  /**
@@ -33,7 +33,8 @@ const config: Create_Gro_Config = async (cfg, svelte_config) => {
33
33
 
34
34
  const local_moss_plugin_path = find_first_existing_file([
35
35
  './src/lib/gro_plugin_moss.ts',
36
- './src/routes/gro_plugin_moss.ts',
36
+ './src/gro_plugin_moss.ts',
37
+ './src/routes/gro_plugin_moss.ts', // TODO probably remove this
37
38
  ]);
38
39
 
39
40
  // put things that generate files before SvelteKit so it can see them
@@ -3,7 +3,7 @@ import {existsSync} from 'node:fs';
3
3
 
4
4
  import {GRO_DIST_DIR, IS_THIS_GRO, paths} from './paths.ts';
5
5
  import {
6
- GRO_CONFIG_PATH,
6
+ GRO_CONFIG_FILENAME,
7
7
  JS_CLI_DEFAULT,
8
8
  NODE_MODULES_DIRNAME,
9
9
  PM_CLI_DEFAULT,
@@ -121,8 +121,9 @@ export const EXPORTS_EXCLUDER_DEFAULT = /(\.md|\.(test|ignore)\.|\/(test|fixture
121
121
  * Transforms a `Raw_Gro_Config` to the more strict `Gro_Config`.
122
122
  * This allows users to provide a more relaxed config.
123
123
  */
124
- export const normalize_gro_config = (raw_config: Raw_Gro_Config): Gro_Config => {
124
+ export const cook_gro_config = (raw_config: Raw_Gro_Config): Gro_Config => {
125
125
  const empty_config = create_empty_gro_config();
126
+
126
127
  // All of the raw config properties are optional,
127
128
  // so fall back to the empty values when `undefined`.
128
129
  const {
@@ -133,6 +134,7 @@ export const normalize_gro_config = (raw_config: Raw_Gro_Config): Gro_Config =>
133
134
  js_cli = empty_config.js_cli,
134
135
  pm_cli = empty_config.pm_cli,
135
136
  } = raw_config;
137
+
136
138
  return {
137
139
  plugins,
138
140
  map_package_json,
@@ -152,18 +154,20 @@ export interface Gro_Config_Module {
152
154
  }
153
155
 
154
156
  export const load_gro_config = async (dir = paths.root): Promise<Gro_Config> => {
155
- const default_config = normalize_gro_config(
156
- await create_default_config(create_empty_gro_config()),
157
- );
158
- const config_path = join(dir, GRO_CONFIG_PATH);
157
+ const default_config = cook_gro_config(await create_default_config(create_empty_gro_config()));
158
+
159
+ const config_path = join(dir, GRO_CONFIG_FILENAME);
159
160
  if (!existsSync(config_path)) {
160
161
  // No user config file found, so return the default.
161
162
  return default_config;
162
163
  }
164
+
163
165
  // Import the user's `gro.config.ts`.
164
166
  const config_module = await import(config_path);
167
+
165
168
  validate_gro_config_module(config_module, config_path);
166
- return normalize_gro_config(
169
+
170
+ return cook_gro_config(
167
171
  typeof config_module.default === 'function'
168
172
  ? await config_module.default(default_config)
169
173
  : config_module.default,
@@ -45,9 +45,11 @@ export const gro_plugin_gen = ({
45
45
  },
46
46
  {delay: flush_debounce_delay},
47
47
  );
48
+
49
+ // TODO do this in-process - will it cause caching issues with the current impl?
48
50
  const gen = (files: Array<string> = []) => spawn_cli('gro', ['gen', ...files]);
49
51
 
50
- let cleanup: Cleanup_Watch | undefined;
52
+ let cleanup_watch: Cleanup_Watch | undefined;
51
53
 
52
54
  return {
53
55
  name: 'gro_plugin_gen',
@@ -73,7 +75,7 @@ export const gro_plugin_gen = ({
73
75
 
74
76
  // When a file builds, check it and its tree of dependents
75
77
  // for any `.gen.` files that need to run.
76
- cleanup = await filer.watch((change, source_file) => {
78
+ cleanup_watch = await filer.watch((change, source_file) => {
77
79
  if (source_file.external) return;
78
80
  switch (change.type) {
79
81
  case 'add':
@@ -95,7 +97,7 @@ export const gro_plugin_gen = ({
95
97
  break;
96
98
  }
97
99
  case 'delete': {
98
- // TODO delete the generated file(s)? option?
100
+ // TODO delete the generated file(s)? option? because it may be surprising
99
101
  break;
100
102
  }
101
103
  default:
@@ -104,9 +106,9 @@ export const gro_plugin_gen = ({
104
106
  });
105
107
  },
106
108
  teardown: async () => {
107
- if (cleanup !== undefined) {
108
- await cleanup();
109
- cleanup = undefined;
109
+ if (cleanup_watch) {
110
+ await cleanup_watch();
111
+ cleanup_watch = undefined;
110
112
  }
111
113
  },
112
114
  };
@@ -12,7 +12,6 @@ import type {Plugin} from './plugin.ts';
12
12
  import {base_path_to_path_id, LIB_DIRNAME, paths} from './paths.ts';
13
13
  import type {Path_Id} from './path.ts';
14
14
  import {GRO_DEV_DIRNAME, SERVER_DIST_PATH} from './constants.ts';
15
- import {watch_dir, type Watch_Node_Fs} from './watch_dir.ts';
16
15
  import {parse_svelte_config, default_svelte_config} from './svelte_config.ts';
17
16
  import {esbuild_plugin_sveltekit_shim_app} from './esbuild_plugin_sveltekit_shim_app.ts';
18
17
  import {esbuild_plugin_sveltekit_shim_env} from './esbuild_plugin_sveltekit_shim_env.ts';
@@ -122,14 +121,14 @@ export const gro_plugin_server = ({
122
121
  cli_command,
123
122
  run, // `dev` default is not available in this scope
124
123
  }: Gro_Plugin_Server_Options = {}): Plugin => {
125
- let build_ctx: esbuild.BuildContext | null = null;
126
- let watcher: Watch_Node_Fs | null = null;
127
- let server_process: Restartable_Process | null = null;
128
- let deps: Set<Path_Id> | null = null;
124
+ let build_ctx: esbuild.BuildContext | undefined;
125
+ let cleanup_watch: (() => void) | undefined;
126
+ let server_process: Restartable_Process | undefined;
127
+ let deps: Set<Path_Id> | undefined;
129
128
 
130
129
  return {
131
130
  name: 'gro_plugin_server',
132
- setup: async ({dev, watch, timings, log, config}) => {
131
+ setup: async ({dev, watch, timings, log, config, filer}) => {
133
132
  const parsed_svelte_config =
134
133
  !svelte_config && strip_end(dir, '/') === process.cwd()
135
134
  ? default_svelte_config
@@ -231,22 +230,13 @@ export const gro_plugin_server = ({
231
230
 
232
231
  await rebuild();
233
232
 
234
- // uses chokidar instead of esbuild's watcher for efficiency
235
233
  if (watch) {
236
- let watcher_ready = false;
237
- // TODO maybe reuse this watcher globally via an option,
238
- // because it watches all of `$lib`, and that means it excludes `$routes`
239
- // while also including a lot of client files we don't care about,
240
- // but we can't discern which of `$lib` to watch ahead of time
241
- watcher = watch_dir({
242
- dir: paths.lib,
243
- on_change: (change) => {
244
- if (!watcher_ready || !deps?.has(change.path)) return;
245
- void rebuild();
246
- },
234
+ cleanup_watch = await filer.watch((change) => {
235
+ if (!deps?.has(change.path)) {
236
+ return;
237
+ }
238
+ void rebuild();
247
239
  });
248
- await watcher.init();
249
- watcher_ready = true;
250
240
  }
251
241
 
252
242
  if (!existsSync(server_outpath)) {
@@ -263,16 +253,20 @@ export const gro_plugin_server = ({
263
253
  }
264
254
  },
265
255
  teardown: async () => {
256
+ if (cleanup_watch) {
257
+ cleanup_watch();
258
+ cleanup_watch = undefined;
259
+ }
260
+
266
261
  if (server_process) {
267
262
  const s = server_process; // avoid possible issue where a build is in progress, don't want to issue a restart, could be fixed upstream in `spawn_restartable_process`
268
- server_process = null;
263
+ server_process = undefined;
269
264
  await s.kill();
270
265
  }
271
- if (watcher) {
272
- await watcher.close();
273
- }
266
+
274
267
  if (build_ctx) {
275
268
  await build_ctx.dispose();
269
+ build_ctx = undefined;
276
270
  }
277
271
  },
278
272
  };
@@ -10,8 +10,7 @@ import {find_cli, spawn_cli, spawn_cli_process} from './cli.ts';
10
10
  import {type Map_Src_Json, serialize_src_json, create_src_json} from './src_json.ts';
11
11
  import {EXPORTS_EXCLUDER_DEFAULT} from './gro_config.ts';
12
12
  import {default_svelte_config} from './svelte_config.ts';
13
- import {SOURCE_DIRNAME} from './constants.ts';
14
- import {VITE_CLI} from './sveltekit_helpers.ts';
13
+ import {SOURCE_DIRNAME, VITE_CLI} from './constants.ts';
15
14
 
16
15
  export interface Gro_Plugin_Sveltekit_App_Options {
17
16
  /**
@@ -3,11 +3,8 @@ import {print_spawn_result, spawn} from '@ryanatkn/belt/process.js';
3
3
  import type {Plugin} from './plugin.ts';
4
4
  import {Task_Error} from './task.ts';
5
5
  import {load_package_json} from './package_json.ts';
6
- import {
7
- SVELTE_PACKAGE_CLI,
8
- run_svelte_package,
9
- type Svelte_Package_Options,
10
- } from './sveltekit_helpers.ts';
6
+ import {run_svelte_package, type Svelte_Package_Options} from './sveltekit_helpers.ts';
7
+ import {SVELTE_PACKAGE_CLI} from './constants.ts';
11
8
 
12
9
  export interface Gro_Plugin_Sveltekit_Library_Options {
13
10
  /**
package/src/lib/invoke.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import {attach_process_error_handlers} from '@ryanatkn/belt/process.js';
2
2
  import {configure_log_colors} from '@ryanatkn/belt/log.js';
3
- import {set_colors} from '@ryanatkn/belt/print.js';
4
- import {styleText} from 'node:util';
3
+ import {configure_print_colors} from '@ryanatkn/belt/print.js';
5
4
 
6
5
  import {invoke_task} from './invoke_task.ts';
7
6
  import {to_task_args} from './args.ts';
@@ -25,8 +24,11 @@ attach_process_error_handlers(
25
24
  (err) => (err.constructor.name === 'Silent_Error' ? '' : null),
26
25
  );
27
26
 
28
- configure_log_colors(styleText);
29
- set_colors(styleText);
27
+ if (!process.env.NO_COLOR) {
28
+ const {styleText} = await import('node:util');
29
+ configure_log_colors(styleText);
30
+ configure_print_colors(styleText);
31
+ }
30
32
 
31
33
  await sveltekit_sync_if_obviously_needed();
32
34
 
@@ -13,6 +13,10 @@ import type {Gen} from './gen.ts';
13
13
  */
14
14
  export const gen: Gen = ({origin_path}) => {
15
15
  const package_json = load_package_json();
16
+ // TODO this can error with bad json data, do we need to update the package.json here first?
17
+ // similarly do this in other places too?
18
+ // part of the problem is that the package_json actually has a dependency on each of its exports and their deps,
19
+ // but only when they're added/removed right? see gro_plugin_gen for details
16
20
  const src_json = create_src_json(package_json, undefined);
17
21
 
18
22
  const is_this_belt = package_json.name === '@ryanatkn/belt'; // TODO more robust?
@@ -23,7 +27,7 @@ export const gen: Gen = ({origin_path}) => {
23
27
  import type {Package_Json} from '${
24
28
  is_this_belt ? '$lib/package_json.js' : '@ryanatkn/belt/package_json.js'
25
29
  }';
26
- import type {Src_Json} from '${is_this_belt ? './src_json.js' : '@ryanatkn/belt/src_json.js'}';
30
+ import type {Src_Json} from '${is_this_belt ? '$lib/src_json.js' : '@ryanatkn/belt/src_json.js'}';
27
31
 
28
32
  export const package_json: Package_Json = ${JSON.stringify(package_json)} as any;
29
33