@ryanatkn/gro 0.129.4 → 0.129.6

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 (124) hide show
  1. package/dist/package.js +3 -3
  2. package/package.json +3 -2
  3. package/src/lib/args.test.ts +59 -0
  4. package/src/lib/args.ts +169 -0
  5. package/src/lib/build.task.ts +37 -0
  6. package/src/lib/changelog.test.ts +138 -0
  7. package/src/lib/changelog.ts +69 -0
  8. package/src/lib/changeset.task.ts +206 -0
  9. package/src/lib/changeset_helpers.ts +13 -0
  10. package/src/lib/check.task.ts +90 -0
  11. package/src/lib/clean.task.ts +46 -0
  12. package/src/lib/clean_fs.ts +54 -0
  13. package/src/lib/cli.ts +97 -0
  14. package/src/lib/commit.task.ts +33 -0
  15. package/src/lib/config.test.ts +71 -0
  16. package/src/lib/config.ts +161 -0
  17. package/src/lib/deploy.task.ts +243 -0
  18. package/src/lib/dev.task.ts +43 -0
  19. package/src/lib/docs/README.gen.md.ts +63 -0
  20. package/src/lib/docs/README.md +20 -0
  21. package/src/lib/docs/build.md +41 -0
  22. package/src/lib/docs/config.md +213 -0
  23. package/src/lib/docs/deploy.md +32 -0
  24. package/src/lib/docs/dev.md +40 -0
  25. package/src/lib/docs/gen.md +269 -0
  26. package/src/lib/docs/gro_plugin_sveltekit_app.md +113 -0
  27. package/src/lib/docs/package_json.md +33 -0
  28. package/src/lib/docs/plugin.md +50 -0
  29. package/src/lib/docs/publish.md +137 -0
  30. package/src/lib/docs/task.md +391 -0
  31. package/src/lib/docs/tasks.gen.md.ts +90 -0
  32. package/src/lib/docs/tasks.md +37 -0
  33. package/src/lib/docs/test.md +52 -0
  34. package/src/lib/env.ts +75 -0
  35. package/src/lib/esbuild_helpers.ts +50 -0
  36. package/src/lib/esbuild_plugin_external_worker.ts +92 -0
  37. package/src/lib/esbuild_plugin_svelte.test.ts +88 -0
  38. package/src/lib/esbuild_plugin_svelte.ts +108 -0
  39. package/src/lib/esbuild_plugin_sveltekit_local_imports.ts +31 -0
  40. package/src/lib/esbuild_plugin_sveltekit_shim_alias.ts +25 -0
  41. package/src/lib/esbuild_plugin_sveltekit_shim_app.ts +41 -0
  42. package/src/lib/esbuild_plugin_sveltekit_shim_env.ts +46 -0
  43. package/src/lib/format.task.ts +30 -0
  44. package/src/lib/format_directory.ts +55 -0
  45. package/src/lib/format_file.test.ts +20 -0
  46. package/src/lib/format_file.ts +49 -0
  47. package/src/lib/fs.ts +18 -0
  48. package/src/lib/gen.task.ts +134 -0
  49. package/src/lib/gen.test.ts +306 -0
  50. package/src/lib/gen.ts +360 -0
  51. package/src/lib/git.test.ts +34 -0
  52. package/src/lib/git.ts +297 -0
  53. package/src/lib/github.ts +46 -0
  54. package/src/lib/gro.config.default.ts +34 -0
  55. package/src/lib/gro.ts +25 -0
  56. package/src/lib/gro_helpers.ts +101 -0
  57. package/src/lib/gro_plugin_gen.ts +95 -0
  58. package/src/lib/gro_plugin_server.ts +288 -0
  59. package/src/lib/gro_plugin_sveltekit_app.ts +257 -0
  60. package/src/lib/gro_plugin_sveltekit_library.ts +74 -0
  61. package/src/lib/hash.test.ts +33 -0
  62. package/src/lib/hash.ts +19 -0
  63. package/src/lib/index.ts +4 -0
  64. package/src/lib/input_path.test.ts +230 -0
  65. package/src/lib/input_path.ts +255 -0
  66. package/src/lib/invoke.ts +27 -0
  67. package/src/lib/invoke_task.ts +116 -0
  68. package/src/lib/lint.task.ts +38 -0
  69. package/src/lib/loader.test.ts +49 -0
  70. package/src/lib/loader.ts +226 -0
  71. package/src/lib/module.test.ts +46 -0
  72. package/src/lib/module.ts +13 -0
  73. package/src/lib/modules.test.ts +63 -0
  74. package/src/lib/modules.ts +112 -0
  75. package/src/lib/package.gen.ts +33 -0
  76. package/src/lib/package.ts +998 -0
  77. package/src/lib/package_json.test.ts +101 -0
  78. package/src/lib/package_json.ts +330 -0
  79. package/src/lib/package_meta.ts +86 -0
  80. package/src/lib/path.ts +23 -0
  81. package/src/lib/path_constants.ts +30 -0
  82. package/src/lib/paths.test.ts +77 -0
  83. package/src/lib/paths.ts +101 -0
  84. package/src/lib/plugin.test.ts +57 -0
  85. package/src/lib/plugin.ts +113 -0
  86. package/src/lib/publish.task.ts +194 -0
  87. package/src/lib/register.ts +3 -0
  88. package/src/lib/reinstall.task.ts +42 -0
  89. package/src/lib/release.task.ts +21 -0
  90. package/src/lib/resolve.task.ts +43 -0
  91. package/src/lib/resolve_node_specifier.test.ts +31 -0
  92. package/src/lib/resolve_node_specifier.ts +55 -0
  93. package/src/lib/resolve_specifier.test.ts +76 -0
  94. package/src/lib/resolve_specifier.ts +61 -0
  95. package/src/lib/run.task.ts +41 -0
  96. package/src/lib/run_gen.test.ts +196 -0
  97. package/src/lib/run_gen.ts +95 -0
  98. package/src/lib/run_task.test.ts +86 -0
  99. package/src/lib/run_task.ts +75 -0
  100. package/src/lib/search_fs.test.ts +56 -0
  101. package/src/lib/search_fs.ts +93 -0
  102. package/src/lib/src_json.test.ts +49 -0
  103. package/src/lib/src_json.ts +153 -0
  104. package/src/lib/svelte_helpers.ts +2 -0
  105. package/src/lib/sveltekit_config.ts +101 -0
  106. package/src/lib/sveltekit_config_global.ts +6 -0
  107. package/src/lib/sveltekit_helpers.ts +132 -0
  108. package/src/lib/sveltekit_shim_app.ts +42 -0
  109. package/src/lib/sveltekit_shim_app_environment.ts +14 -0
  110. package/src/lib/sveltekit_shim_app_forms.ts +20 -0
  111. package/src/lib/sveltekit_shim_app_navigation.ts +23 -0
  112. package/src/lib/sveltekit_shim_app_paths.ts +16 -0
  113. package/src/lib/sveltekit_shim_app_stores.ts +25 -0
  114. package/src/lib/sveltekit_shim_env.ts +45 -0
  115. package/src/lib/sync.task.ts +47 -0
  116. package/src/lib/task.test.ts +84 -0
  117. package/src/lib/task.ts +235 -0
  118. package/src/lib/task_logging.ts +180 -0
  119. package/src/lib/test.task.ts +50 -0
  120. package/src/lib/throttle.test.ts +52 -0
  121. package/src/lib/throttle.ts +63 -0
  122. package/src/lib/typecheck.task.ts +57 -0
  123. package/src/lib/upgrade.task.ts +108 -0
  124. package/src/lib/watch_dir.ts +88 -0
@@ -0,0 +1,76 @@
1
+ import {test} from 'uvu';
2
+ import * as assert from 'uvu/assert';
3
+ import {join} from 'node:path';
4
+
5
+ import {resolve_specifier} from './resolve_specifier.js';
6
+ import {paths} from './paths.js';
7
+
8
+ const dir = paths.source + 'fixtures/';
9
+
10
+ test('resolves a specifier to a file that exists with an unknown file extension', () => {
11
+ assert.equal(resolve_specifier(join(dir, 'test_file.other.ext'), dir), {
12
+ specifier: './test_file.other.ext',
13
+ path_id: join(dir, 'test_file.other.ext'),
14
+ namespace: undefined,
15
+ });
16
+ });
17
+
18
+ test('resolves a ts specifier', () => {
19
+ assert.equal(resolve_specifier(join(dir, 'test_ts.ts'), dir), {
20
+ specifier: './test_ts.js',
21
+ path_id: join(dir, 'test_ts.ts'),
22
+ namespace: 'sveltekit_local_imports_ts',
23
+ });
24
+ });
25
+
26
+ test('resolves relative ts specifiers', () => {
27
+ assert.equal(resolve_specifier('./test_ts.ts', dir), {
28
+ specifier: './test_ts.js',
29
+ path_id: join(dir, 'test_ts.ts'),
30
+ namespace: 'sveltekit_local_imports_ts',
31
+ });
32
+ assert.equal(resolve_specifier('./a/b/test_ts.ts', dir), {
33
+ specifier: './a/b/test_ts.js',
34
+ path_id: join(dir, 'a/b/test_ts.ts'),
35
+ namespace: 'sveltekit_local_imports_ts',
36
+ });
37
+ assert.equal(resolve_specifier('../../test_ts.ts', dir), {
38
+ specifier: '../../test_ts.js',
39
+ path_id: join(dir, '../../test_ts.ts'),
40
+ namespace: 'sveltekit_local_imports_ts',
41
+ });
42
+ });
43
+
44
+ test('resolves an extensionless specifier', () => {
45
+ assert.equal(resolve_specifier(join(dir, 'test_ts'), dir), {
46
+ specifier: './test_ts.js',
47
+ path_id: join(dir, 'test_ts.ts'),
48
+ namespace: 'sveltekit_local_imports_ts',
49
+ });
50
+ });
51
+
52
+ test('resolves a js specifier', () => {
53
+ assert.equal(resolve_specifier(join(dir, 'test_js.js'), dir), {
54
+ specifier: './test_js.js',
55
+ path_id: join(dir, 'test_js.js'),
56
+ namespace: 'sveltekit_local_imports_js',
57
+ });
58
+ });
59
+
60
+ test('resolves a js specifier as ts for a file that does not exist', () => {
61
+ assert.equal(resolve_specifier(join(dir, 'test_missing.js'), dir), {
62
+ specifier: './test_missing.js',
63
+ path_id: join(dir, 'test_missing.ts'),
64
+ namespace: 'sveltekit_local_imports_ts',
65
+ });
66
+ });
67
+
68
+ test('resolves an extensionless specifier for a file that does not exist', () => {
69
+ assert.equal(resolve_specifier(join(dir, 'test_missing'), dir), {
70
+ specifier: './test_missing.js',
71
+ path_id: join(dir, 'test_missing.ts'),
72
+ namespace: 'sveltekit_local_imports_ts',
73
+ });
74
+ });
75
+
76
+ test.run();
@@ -0,0 +1,61 @@
1
+ import {extname, isAbsolute, join, relative} from 'node:path';
2
+ import {existsSync} from 'node:fs';
3
+
4
+ import {replace_extension} from './paths.js';
5
+ import type {Path_Id} from './path.js';
6
+
7
+ export interface Resolved_Specifier {
8
+ specifier: string;
9
+ path_id: Path_Id;
10
+ namespace: undefined | 'sveltekit_local_imports_ts' | 'sveltekit_local_imports_js';
11
+ }
12
+
13
+ /**
14
+ * Maps a `path` import specifier relative to the `importer`,
15
+ * and infer the correct extension following Vite conventions.
16
+ * If no `.js` file is found for the `path` on the filesystem, it assumes `.ts`.
17
+ * @param path
18
+ * @param dir - if defined, enables relative importers like from esbuild plugins
19
+ * @param passthrough_extensions - used to support specifiers that have no file extention, which Vite supports, so we do our best effort
20
+ * @returns
21
+ */
22
+ export const resolve_specifier = (path: string, dir: string): Resolved_Specifier => {
23
+ const absolute_path = isAbsolute(path) ? path : join(dir, path);
24
+
25
+ let mapped_path;
26
+ let path_id;
27
+ let namespace: Resolved_Specifier['namespace'];
28
+
29
+ const ext = extname(absolute_path);
30
+ const is_js = ext === '.js';
31
+ const is_ts = ext === '.ts';
32
+
33
+ if (!is_js && !is_ts && existsSync(absolute_path)) {
34
+ // unrecognized extension and the file exists
35
+ mapped_path = absolute_path;
36
+ path_id = absolute_path;
37
+ } else if (is_ts) {
38
+ // explicitly ts
39
+ mapped_path = replace_extension(absolute_path, '.js');
40
+ path_id = absolute_path;
41
+ namespace = 'sveltekit_local_imports_ts';
42
+ } else {
43
+ // extensionless, or js that points to ts, or just js
44
+ const js_id = is_js ? absolute_path : absolute_path + '.js';
45
+ const ts_id = is_js ? replace_extension(absolute_path, '.ts') : absolute_path + '.ts';
46
+ if (!existsSync(ts_id) && existsSync(js_id)) {
47
+ mapped_path = js_id;
48
+ path_id = js_id;
49
+ namespace = 'sveltekit_local_imports_js';
50
+ } else {
51
+ mapped_path = js_id;
52
+ path_id = ts_id;
53
+ namespace = 'sveltekit_local_imports_ts';
54
+ }
55
+ }
56
+
57
+ let specifier = relative(dir, mapped_path);
58
+ if (specifier[0] !== '.') specifier = './' + specifier;
59
+
60
+ return {specifier, path_id, namespace};
61
+ };
@@ -0,0 +1,41 @@
1
+ import {z} from 'zod';
2
+ import {green, cyan} from '@ryanatkn/belt/styletext.js';
3
+ import {existsSync} from 'node:fs';
4
+
5
+ import {Task_Error, type Task} from './task.js';
6
+ import {resolve_gro_module_path, spawn_with_loader} from './gro_helpers.js';
7
+
8
+ export const Args = z
9
+ .object({
10
+ _: z
11
+ .array(z.string(), {description: 'the file path to run and other node CLI args'})
12
+ .default([]),
13
+ })
14
+ .strict();
15
+ export type Args = z.infer<typeof Args>;
16
+
17
+ export const task: Task<Args> = {
18
+ summary: 'execute a file with the loader, like `node` but works for TypeScript',
19
+ Args,
20
+ run: async ({args, log}) => {
21
+ const {
22
+ _: [path, ...argv],
23
+ } = args;
24
+
25
+ if (!path) {
26
+ log.info(green('\n\nUsage: ') + cyan('gro run path/to/file.ts [...node_args]\n'));
27
+ return;
28
+ }
29
+
30
+ if (!existsSync(path)) {
31
+ throw new Task_Error('Cannot find file to run at path: ' + path);
32
+ }
33
+
34
+ const loader_path = resolve_gro_module_path('loader.js');
35
+
36
+ const spawned = await spawn_with_loader(loader_path, path, argv);
37
+ if (!spawned.ok) {
38
+ throw new Task_Error(`\`gro run ${path}\` failed with exit code ${spawned.code}`);
39
+ }
40
+ },
41
+ };
@@ -0,0 +1,196 @@
1
+ import {suite} from 'uvu';
2
+ import * as assert from 'uvu/assert';
3
+ import {resolve, join} from 'node:path';
4
+ import {Logger} from '@ryanatkn/belt/log.js';
5
+ import {Timings} from '@ryanatkn/belt/timings.js';
6
+
7
+ import type {Genfile_Module_Meta} from './gen.js';
8
+ import {run_gen} from './run_gen.js';
9
+ import {load_config} from './config.js';
10
+
11
+ const log = new Logger('test__gen'); // TODO test logger?
12
+
13
+ /* test__gen */
14
+ const test__gen = suite('gen');
15
+
16
+ test__gen('basic behavior', async () => {
17
+ const path_id_a = resolve('src/foo.gen.ts');
18
+ const path_id_bc = resolve('src/bar/bc');
19
+ let file_a: undefined | {filename: string; content: string};
20
+ let file_b: undefined | {filename: string; content: string};
21
+ let file_c1: undefined | {filename: string; content: string};
22
+ let file_c2: undefined | {filename: string; content: string};
23
+ const mod_a: Genfile_Module_Meta = {
24
+ id: path_id_a,
25
+ mod: {
26
+ gen: (ctx) => {
27
+ assert.is(ctx.origin_id, path_id_a);
28
+ if (file_a) throw Error('Already generated file_a');
29
+ file_a = {
30
+ filename: 'foo.ts',
31
+ content: 'file_a',
32
+ };
33
+ return file_a.content; // here we return the shorthand version
34
+ },
35
+ },
36
+ };
37
+ const mod_b: Genfile_Module_Meta = {
38
+ id: join(path_id_bc, 'mod_b.gen.ts'),
39
+ mod: {
40
+ gen: (ctx) => {
41
+ assert.is(ctx.origin_id, mod_b.id);
42
+ if (file_b) throw Error('Already generated file_b');
43
+ file_b = {
44
+ filename: 'output_b.ts',
45
+ content: 'file_b',
46
+ };
47
+ return file_b;
48
+ },
49
+ },
50
+ };
51
+ const mod_c: Genfile_Module_Meta = {
52
+ id: join(path_id_bc, 'mod_c.gen.ts'),
53
+ mod: {
54
+ gen: (ctx) => {
55
+ assert.is(ctx.origin_id, mod_c.id);
56
+ if (file_c1) throw Error('Already generated file_c1');
57
+ if (file_c2) throw Error('Already generated file_c2');
58
+ file_c1 = {
59
+ filename: 'output_c1.ts',
60
+ content: 'file_c1',
61
+ };
62
+ file_c2 = {
63
+ filename: 'output_c2.ts',
64
+ content: 'file_c2',
65
+ };
66
+ return [file_c1, file_c2];
67
+ },
68
+ },
69
+ };
70
+ const gen_modules_by_input_path = [mod_a, mod_b, mod_c];
71
+ const gen_results = await run_gen(
72
+ gen_modules_by_input_path,
73
+ await load_config(),
74
+ log,
75
+ new Timings(),
76
+ (content, opts) =>
77
+ Promise.resolve(opts.filepath!.endsWith('output_b.ts') ? `${content}/*FORMATTED*/` : content),
78
+ );
79
+ assert.is(gen_results.input_count, 3);
80
+ assert.is(gen_results.output_count, 4);
81
+ assert.is(gen_results.successes.length, 3);
82
+ assert.is(gen_results.failures.length, 0);
83
+ assert.is(gen_results.results.length, 3);
84
+ assert.is(gen_results.results[0], gen_results.successes[0]);
85
+ assert.is(gen_results.results[1], gen_results.successes[1]);
86
+ assert.is(gen_results.results[2], gen_results.successes[2]);
87
+
88
+ const result_a = gen_results.results[0];
89
+ assert.ok(result_a.ok);
90
+ assert.ok(file_a);
91
+ assert.equal(result_a.files, [
92
+ {
93
+ content: file_a.content,
94
+ id: join(mod_a.id, '../', file_a.filename),
95
+ origin_id: mod_a.id,
96
+ format: true,
97
+ },
98
+ ]);
99
+
100
+ const result_b = gen_results.results[1];
101
+ assert.ok(result_b.ok);
102
+ assert.ok(file_b);
103
+ assert.equal(result_b.files, [
104
+ {
105
+ content: `${file_b.content}/*FORMATTED*/`,
106
+ id: join(mod_b.id, '../', file_b.filename),
107
+ origin_id: mod_b.id,
108
+ format: true,
109
+ },
110
+ ]);
111
+ const result_c = gen_results.results[2];
112
+ assert.ok(result_c.ok);
113
+ assert.ok(file_c1);
114
+ assert.ok(file_c2);
115
+ assert.equal(result_c.files, [
116
+ {
117
+ content: file_c1.content,
118
+ id: join(mod_c.id, '../', file_c1.filename),
119
+ origin_id: mod_c.id,
120
+ format: true,
121
+ },
122
+ {
123
+ content: file_c2.content,
124
+ id: join(mod_c.id, '../', file_c2.filename),
125
+ origin_id: mod_c.id,
126
+ format: true,
127
+ },
128
+ ]);
129
+ });
130
+
131
+ test__gen('failing gen function', async () => {
132
+ const path_id_a = resolve('src/foo.gen.ts');
133
+ const path_idB = resolve('src/bar/baz');
134
+ let file_b: undefined | {filename: string; content: string}; // no file_a because it's never generated
135
+ let genError; // this error should be passed through to the result
136
+ // This is the failing gen module.
137
+ // It's ordered first to test that its failure doesn't cascade.
138
+ const mod_a: Genfile_Module_Meta = {
139
+ id: path_id_a,
140
+ mod: {
141
+ gen: () => {
142
+ genError = Error('This fails for testing');
143
+ throw genError;
144
+ },
145
+ },
146
+ };
147
+ const mod_b: Genfile_Module_Meta = {
148
+ id: join(path_idB, 'mod_b.gen.ts'),
149
+ mod: {
150
+ gen: (ctx) => {
151
+ assert.is(ctx.origin_id, mod_b.id);
152
+ if (file_b) throw Error('Already generated file_b');
153
+ file_b = {
154
+ filename: 'output_b.ts',
155
+ content: 'file_b',
156
+ };
157
+ return file_b;
158
+ },
159
+ },
160
+ };
161
+ const gen_modules_by_input_path: Genfile_Module_Meta[] = [mod_a, mod_b];
162
+ const gen_results = await run_gen(
163
+ gen_modules_by_input_path,
164
+ await load_config(),
165
+ log,
166
+ new Timings(),
167
+ );
168
+ assert.is(gen_results.input_count, 2);
169
+ assert.is(gen_results.output_count, 1);
170
+ assert.is(gen_results.successes.length, 1);
171
+ assert.is(gen_results.failures.length, 1);
172
+ assert.is(gen_results.results.length, 2);
173
+ assert.is(gen_results.results[0], gen_results.failures[0]);
174
+ assert.is(gen_results.results[1], gen_results.successes[0]);
175
+
176
+ const result_a = gen_results.results[0];
177
+ assert.ok(result_a);
178
+ assert.ok(!result_a.ok);
179
+ assert.ok(result_a.reason);
180
+ assert.ok(result_a.error);
181
+
182
+ const result_b = gen_results.results[1];
183
+ assert.ok(result_b.ok);
184
+ assert.ok(file_b);
185
+ assert.equal(result_b.files, [
186
+ {
187
+ content: file_b.content,
188
+ id: join(mod_b.id, '../', file_b.filename),
189
+ origin_id: mod_b.id,
190
+ format: true,
191
+ },
192
+ ]);
193
+ });
194
+
195
+ test__gen.run();
196
+ /* test__gen */
@@ -0,0 +1,95 @@
1
+ import {red} from '@ryanatkn/belt/styletext.js';
2
+ import {print_error} from '@ryanatkn/belt/print.js';
3
+ import type {Timings} from '@ryanatkn/belt/timings.js';
4
+ import type {Logger} from '@ryanatkn/belt/log.js';
5
+
6
+ import {
7
+ type Gen_Results,
8
+ type Genfile_Module_Result,
9
+ type Gen_Context,
10
+ type Genfile_Module_Meta,
11
+ to_gen_result,
12
+ type Raw_Gen_Result,
13
+ } from './gen.js';
14
+ import {print_path} from './paths.js';
15
+ import type {format_file as base_format_file} from './format_file.js';
16
+ import type {Gro_Config} from './config.js';
17
+ import {sveltekit_config_global} from './sveltekit_config_global.js';
18
+
19
+ export const GEN_NO_PROD_MESSAGE = 'gen runs only during development';
20
+
21
+ export const run_gen = async (
22
+ gen_modules: Genfile_Module_Meta[],
23
+ config: Gro_Config,
24
+ log: Logger,
25
+ timings: Timings,
26
+ format_file?: typeof base_format_file,
27
+ ): Promise<Gen_Results> => {
28
+ let input_count = 0;
29
+ let output_count = 0;
30
+ const timing_for_run_gen = timings.start('run_gen');
31
+ const results = await Promise.all(
32
+ gen_modules.map(async (module_meta): Promise<Genfile_Module_Result> => {
33
+ input_count++;
34
+ const {id} = module_meta;
35
+ const timing_for_module = timings.start(id);
36
+
37
+ // Perform code generation by calling `gen` on the module.
38
+ const gen_ctx: Gen_Context = {
39
+ config,
40
+ sveltekit_config: sveltekit_config_global,
41
+ origin_id: id,
42
+ log,
43
+ };
44
+ let raw_gen_result: Raw_Gen_Result;
45
+ try {
46
+ raw_gen_result = await module_meta.mod.gen(gen_ctx);
47
+ } catch (err) {
48
+ return {
49
+ ok: false,
50
+ id,
51
+ error: err,
52
+ reason: red(`Error generating ${print_path(id)}`),
53
+ elapsed: timing_for_module(),
54
+ };
55
+ }
56
+
57
+ // Convert the module's return value to a normalized form.
58
+ const gen_result = to_gen_result(id, raw_gen_result);
59
+
60
+ // Format the files if needed.
61
+ const files = format_file
62
+ ? await Promise.all(
63
+ gen_result.files.map(async (file) => {
64
+ if (!file.format) return file;
65
+ try {
66
+ return {...file, content: await format_file(file.content, {filepath: file.id})};
67
+ } catch (err) {
68
+ log.error(
69
+ red(`Error formatting ${print_path(file.id)} via ${print_path(id)}`),
70
+ print_error(err),
71
+ );
72
+ return file;
73
+ }
74
+ }),
75
+ )
76
+ : gen_result.files;
77
+
78
+ output_count += files.length;
79
+ return {
80
+ ok: true,
81
+ id,
82
+ files,
83
+ elapsed: timing_for_module(),
84
+ };
85
+ }),
86
+ );
87
+ return {
88
+ results,
89
+ successes: results.filter((r) => r.ok),
90
+ failures: results.filter((r) => !r.ok),
91
+ input_count,
92
+ output_count,
93
+ elapsed: timing_for_run_gen(),
94
+ };
95
+ };
@@ -0,0 +1,86 @@
1
+ import {test} from 'uvu';
2
+ import * as assert from 'uvu/assert';
3
+ import {Timings} from '@ryanatkn/belt/timings.js';
4
+
5
+ import {run_task} from './run_task.js';
6
+ import {load_config} from './config.js';
7
+
8
+ test('passes args and returns output', async () => {
9
+ const args = {a: 1, _: []};
10
+ const result = await run_task(
11
+ {
12
+ name: 'testTask',
13
+ id: 'foo/testTask',
14
+ mod: {
15
+ task: {
16
+ run: ({args}) => Promise.resolve(args),
17
+ },
18
+ },
19
+ },
20
+ args,
21
+ () => Promise.resolve(),
22
+ await load_config(),
23
+ new Timings(),
24
+ );
25
+ assert.ok(result.ok);
26
+ assert.is(result.output, args);
27
+ });
28
+
29
+ test('invokes a sub task', async () => {
30
+ const args = {a: 1, _: []};
31
+ let invoked_task_name;
32
+ let invoked_args;
33
+ const result = await run_task(
34
+ {
35
+ name: 'testTask',
36
+ id: 'foo/testTask',
37
+ mod: {
38
+ task: {
39
+ run: async ({args, invoke_task}) => {
40
+ await invoke_task('bar/testTask', args);
41
+ return args;
42
+ },
43
+ },
44
+ },
45
+ },
46
+ args,
47
+ (invoking_task_name, invoking_args) => {
48
+ invoked_task_name = invoking_task_name;
49
+ invoked_args = invoking_args;
50
+ return Promise.resolve();
51
+ },
52
+ await load_config(),
53
+ new Timings(),
54
+ );
55
+ assert.ok(result.ok);
56
+ assert.is(invoked_task_name, 'bar/testTask');
57
+ assert.is(invoked_args, args);
58
+ assert.is(result.output, args);
59
+ });
60
+
61
+ test('failing task', async () => {
62
+ let err;
63
+ const result = await run_task(
64
+ {
65
+ name: 'testTask',
66
+ id: 'foo/testTask',
67
+ mod: {
68
+ task: {
69
+ run: () => {
70
+ err = Error();
71
+ throw err;
72
+ },
73
+ },
74
+ },
75
+ },
76
+ {_: []},
77
+ async () => {}, // eslint-disable-line @typescript-eslint/no-empty-function
78
+ await load_config(),
79
+ new Timings(),
80
+ );
81
+ assert.ok(!result.ok);
82
+ assert.ok(result.reason);
83
+ assert.is(result.error, err);
84
+ });
85
+
86
+ test.run();
@@ -0,0 +1,75 @@
1
+ import {cyan, red} from '@ryanatkn/belt/styletext.js';
2
+ import {print_log_label, System_Logger} from '@ryanatkn/belt/log.js';
3
+ import type {Timings} from '@ryanatkn/belt/timings.js';
4
+
5
+ import {parse_args, type Args} from './args.js';
6
+ import type {invoke_task as base_invoke_task} from './invoke_task.js';
7
+ import {log_task_help} from './task_logging.js';
8
+ import type {Gro_Config} from './config.js';
9
+ import {Task_Error, type Task_Module_Meta} from './task.js';
10
+ import {sveltekit_config_global} from './sveltekit_config_global.js';
11
+
12
+ export type Run_Task_Result =
13
+ | {
14
+ ok: true;
15
+ output: unknown;
16
+ }
17
+ | {
18
+ ok: false;
19
+ reason: string;
20
+ error: Error;
21
+ };
22
+
23
+ export const run_task = async (
24
+ task_meta: Task_Module_Meta,
25
+ unparsed_args: Args,
26
+ invoke_task: typeof base_invoke_task,
27
+ config: Gro_Config,
28
+ timings: Timings,
29
+ ): Promise<Run_Task_Result> => {
30
+ const {task} = task_meta.mod;
31
+ const log = new System_Logger(print_log_label(task_meta.name));
32
+
33
+ if (unparsed_args.help) {
34
+ log_task_help(log, task_meta);
35
+ return {ok: true, output: null};
36
+ }
37
+
38
+ // Parse and validate args.
39
+ let args = unparsed_args;
40
+ if (task.Args) {
41
+ const parsed = parse_args(unparsed_args, task.Args);
42
+ if (!parsed.success) {
43
+ log.error(red(`Args validation failed:`), '\n', parsed.error.format());
44
+ throw new Task_Error(`Task args failed validation`);
45
+ }
46
+ args = parsed.data;
47
+ }
48
+
49
+ // Run the task.
50
+ let output: unknown;
51
+ try {
52
+ output = await task.run({
53
+ args,
54
+ config,
55
+ sveltekit_config: sveltekit_config_global,
56
+ log,
57
+ timings,
58
+ invoke_task: (invoked_task_name, invoked_args, invoked_config) =>
59
+ invoke_task(invoked_task_name, invoked_args, invoked_config ?? config, timings),
60
+ });
61
+ } catch (err) {
62
+ return {
63
+ ok: false,
64
+ reason: red(
65
+ err?.constructor?.name === 'Task_Error'
66
+ ? (err.message as string)
67
+ : `Unexpected error running task ${cyan(
68
+ task_meta.name,
69
+ )}. If this is unexpected try running \`npm i\` and \`gro clean\`.`,
70
+ ),
71
+ error: err,
72
+ };
73
+ }
74
+ return {ok: true, output};
75
+ };
@@ -0,0 +1,56 @@
1
+ import {test} from 'uvu';
2
+ import * as assert from 'uvu/assert';
3
+ import {resolve} from 'node:path';
4
+
5
+ import {search_fs} from './search_fs.js';
6
+
7
+ test('search_fs basic behavior', () => {
8
+ const ignored_path = 'test1.foo.ts';
9
+ let has_ignored_path = false;
10
+ const result = search_fs('./src/fixtures', {
11
+ filter: (path) => {
12
+ if (!has_ignored_path) has_ignored_path = path.endsWith(ignored_path);
13
+ return !path.endsWith(ignored_path);
14
+ },
15
+ sort: (a, b) => a.path.localeCompare(b.path) * -1,
16
+ });
17
+ assert.ok(has_ignored_path); // makes sure the test isn't wrong
18
+ const expected_files = [
19
+ 'test2.foo.ts',
20
+ 'test_ts.ts',
21
+ 'test_task_module.task_fixture.ts',
22
+ 'test_js.js',
23
+ 'test_invalid_task_module.ts',
24
+ 'test_file.other.ext',
25
+ 'test_failing_task_module.ts',
26
+ 'some_test_side_effect.ts',
27
+ 'some_test_json.json',
28
+ 'some_test_exports3.ts',
29
+ 'some_test_exports2.ts',
30
+ 'some_test_exports.ts',
31
+ 'modules/some_test_ts.ts',
32
+ 'modules/Some_Test_Svelte.svelte',
33
+ 'modules/some_test_svelte_ts.svelte.ts',
34
+ 'modules/some_test_svelte_js.svelte.js',
35
+ 'modules/some_test_server.ts',
36
+ 'modules/some_test_json.json',
37
+ 'modules/some_test_js.js',
38
+ 'modules/some_test_css.css',
39
+ 'changelog_example.md',
40
+ 'changelog_cache.json',
41
+ 'baz2/test2.baz.ts',
42
+ 'baz1/test1.baz.ts',
43
+ 'bar2/test2.bar.ts',
44
+ 'bar1/test1.bar.ts',
45
+ ];
46
+ assert.equal(
47
+ result.map((f) => f.path),
48
+ expected_files,
49
+ );
50
+ assert.equal(
51
+ result.map((f) => f.id),
52
+ expected_files.map((f) => resolve(`src/fixtures/${f}`)),
53
+ );
54
+ });
55
+
56
+ test.run();