@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,49 @@
1
+ import {test} from 'uvu';
2
+ import * as assert from 'uvu/assert';
3
+ import {resolve} from 'node:path';
4
+
5
+ test('import js', async () => {
6
+ const imported = await import(resolve('src/fixtures/modules/some_test_ts.js'));
7
+ assert.ok(imported);
8
+ assert.is(imported.a, 'ok');
9
+ });
10
+
11
+ test('import ts', async () => {
12
+ const imported = await import(resolve('src/fixtures/modules/some_test_ts.ts'));
13
+ assert.ok(imported);
14
+ assert.is(imported.a, 'ok');
15
+ });
16
+
17
+ test('import json', async () => {
18
+ const imported = await import(resolve('src/fixtures/modules/some_test_json.json'));
19
+ assert.ok(imported);
20
+ assert.is(imported.default.a, 'ok');
21
+ });
22
+
23
+ test('import css as a no-op', async () => {
24
+ const imported = await import(resolve('src/fixtures/modules/some_test_css.css'));
25
+ assert.is(typeof imported.default, 'string');
26
+ assert.ok(imported);
27
+ });
28
+
29
+ test('import svelte', async () => {
30
+ const imported = await import(resolve('src/fixtures/modules/Some_Test_Svelte.svelte'));
31
+ assert.ok(imported);
32
+ assert.is(imported.a, 'ok');
33
+ });
34
+
35
+ test('import svelte.js', async () => {
36
+ const imported = await import(resolve('src/fixtures/modules/some_test_svelte_js.svelte.js'));
37
+ assert.ok(imported.Some_Test_Svelte_Js);
38
+ const instance = new imported.Some_Test_Svelte_Js();
39
+ assert.is(instance.a, 'ok');
40
+ });
41
+
42
+ test('import svelte.ts', async () => {
43
+ const imported = await import(resolve('src/fixtures/modules/some_test_svelte_ts.svelte.ts'));
44
+ assert.ok(imported.Some_Test_Svelte_Ts);
45
+ const instance = new imported.Some_Test_Svelte_Ts();
46
+ assert.is(instance.a, 'ok');
47
+ });
48
+
49
+ test.run();
@@ -0,0 +1,226 @@
1
+ import * as esbuild from 'esbuild';
2
+ import {compile, compileModule, preprocess} from 'svelte/compiler';
3
+ import {fileURLToPath, pathToFileURL} from 'node:url';
4
+ import {dirname, join} from 'node:path';
5
+ import type {LoadHook, ResolveHook} from 'node:module';
6
+ import {escape_regexp} from '@ryanatkn/belt/regexp.js';
7
+
8
+ import {render_env_shim_module} from './sveltekit_shim_env.js';
9
+ import {
10
+ render_sveltekit_shim_app_environment,
11
+ render_sveltekit_shim_app_paths,
12
+ SVELTEKIT_SHIM_APP_ENVIRONMENT_MATCHER,
13
+ SVELTEKIT_SHIM_APP_PATHS_MATCHER,
14
+ sveltekit_shim_app_specifiers,
15
+ } from './sveltekit_shim_app.js';
16
+ import {sveltekit_config_global} from './sveltekit_config_global.js';
17
+ import {SVELTE_MATCHER, SVELTE_RUNES_MATCHER} from './svelte_helpers.js';
18
+ import {paths} from './paths.js';
19
+ import {NODE_MODULES_DIRNAME} from './path_constants.js';
20
+ import {to_define_import_meta_env, ts_transform_options} from './esbuild_helpers.js';
21
+ import {resolve_specifier} from './resolve_specifier.js';
22
+ import {resolve_node_specifier} from './resolve_node_specifier.js';
23
+ import type {Package_Json} from './package_json.js';
24
+
25
+ /*
26
+
27
+ Usage via `$lib/register.ts`:
28
+
29
+ ```bash
30
+ node --import @ryanatkn/gro/register.js foo.ts
31
+ ```
32
+
33
+ Usage via `$lib/run.task.ts`:
34
+
35
+ ```bash
36
+ gro run foo.ts
37
+ ```
38
+
39
+ Direct usage without register (see also `$lib/gro.ts`):
40
+
41
+ ```bash
42
+ node --import 'data:text/javascript,import {register} from "node:module"; import {pathToFileURL} from "node:url"; register("@ryanatkn/gro/loader.js", pathToFileURL("./"));' --enable-source-maps' foo.ts
43
+ ```
44
+
45
+ TODO how to improve that gnarly import line? was originally designed for the now-deprecated `--loader`
46
+
47
+ */
48
+
49
+ // TODO support `?raw` import variants
50
+ // TODO sourcemaps for svelte and the svelte preprocessors
51
+ // TODO `import.meta.resolve` wasn't available in loaders when this was first implemented, but might be now
52
+
53
+ // dev is always true in the loader
54
+ const dev = true;
55
+
56
+ const dir = paths.root;
57
+
58
+ const {
59
+ alias,
60
+ base_url,
61
+ assets_url,
62
+ env_dir,
63
+ private_prefix,
64
+ public_prefix,
65
+ svelte_compile_options,
66
+ svelte_compile_module_options,
67
+ svelte_preprocessors,
68
+ } = sveltekit_config_global;
69
+
70
+ const final_ts_transform_options: esbuild.TransformOptions = {
71
+ ...ts_transform_options,
72
+ define: to_define_import_meta_env(dev, base_url),
73
+ sourcemap: 'inline',
74
+ };
75
+
76
+ const aliases = Object.entries({$lib: 'src/lib', ...alias});
77
+
78
+ const TS_MATCHER = /\.(ts|tsx|mts|cts)$/u;
79
+ const JSON_MATCHER = /\.(json)$/u;
80
+ const NOOP_MATCHER = /\.(css|svg)$/u; // TODO others? configurable?
81
+ const ENV_MATCHER = /src\/lib\/\$env\/(static|dynamic)\/(public|private)$/u;
82
+ const NODE_MODULES_MATCHER = new RegExp(escape_regexp('/' + NODE_MODULES_DIRNAME + '/'), 'u');
83
+
84
+ const package_json_cache: Record<string, Package_Json> = {};
85
+
86
+ export const load: LoadHook = async (url, context, nextLoad) => {
87
+ if (SVELTEKIT_SHIM_APP_PATHS_MATCHER.test(url)) {
88
+ // SvelteKit `$app/paths` shim
89
+ return {
90
+ format: 'module',
91
+ shortCircuit: true,
92
+ source: render_sveltekit_shim_app_paths(base_url, assets_url),
93
+ };
94
+ } else if (SVELTEKIT_SHIM_APP_ENVIRONMENT_MATCHER.test(url)) {
95
+ // SvelteKit `$app/environment` shim
96
+ return {
97
+ format: 'module',
98
+ shortCircuit: true,
99
+ source: render_sveltekit_shim_app_environment(dev),
100
+ };
101
+ } else if (SVELTE_RUNES_MATCHER.test(url)) {
102
+ // Svelte runes in js/ts
103
+ // TODO support sourcemaps
104
+ const loaded = await nextLoad(
105
+ url,
106
+ context.format === 'module' ? context : {...context, format: 'module'}, // TODO dunno why this is needed, specifically with tests
107
+ );
108
+ const filename = fileURLToPath(url);
109
+ const source = loaded.source!.toString(); // eslint-disable-line @typescript-eslint/no-base-to-string
110
+ const transformed = compileModule(source, {...svelte_compile_module_options, filename});
111
+ return {format: 'module', shortCircuit: true, source: transformed.js.code};
112
+ } else if (TS_MATCHER.test(url)) {
113
+ // ts
114
+ const loaded = await nextLoad(
115
+ url,
116
+ context.format === 'module' ? context : {...context, format: 'module'}, // TODO dunno why this is needed, specifically with tests
117
+ );
118
+ const transformed = await esbuild.transform(
119
+ loaded.source!.toString(), // eslint-disable-line @typescript-eslint/no-base-to-string
120
+ {...final_ts_transform_options, sourcefile: url},
121
+ );
122
+ return {format: 'module', shortCircuit: true, source: transformed.code};
123
+ } else if (SVELTE_MATCHER.test(url)) {
124
+ // Svelte
125
+ // TODO support sourcemaps
126
+ const loaded = await nextLoad(
127
+ url,
128
+ context.format === 'module' ? context : {...context, format: 'module'}, // TODO dunno why this is needed, specifically with tests
129
+ );
130
+ const filename = fileURLToPath(url);
131
+ const raw_source = loaded.source!.toString(); // eslint-disable-line @typescript-eslint/no-base-to-string
132
+ const preprocessed = svelte_preprocessors
133
+ ? await preprocess(raw_source, svelte_preprocessors, {filename})
134
+ : null;
135
+ const source = preprocessed?.code ?? raw_source;
136
+ const transformed = compile(source, {...svelte_compile_options, filename});
137
+ return {format: 'module', shortCircuit: true, source: transformed.js.code};
138
+ } else if (JSON_MATCHER.test(url)) {
139
+ // json
140
+ // TODO probably follow esbuild and also export every top-level property for objects from the module - https://esbuild.github.io/content-types/#json (type generation?)
141
+ const loaded = await nextLoad(url);
142
+ const raw_source = loaded.source!.toString(); // eslint-disable-line @typescript-eslint/no-base-to-string
143
+ const source = `export default ` + raw_source;
144
+ return {format: 'module', shortCircuit: true, source};
145
+ } else if (NOOP_MATCHER.test(url)) {
146
+ // no-ops like `.css` and `.svg`
147
+ const source = `export default 'no-op import from ${url}'`;
148
+ return {format: 'module', shortCircuit: true, source};
149
+ } else {
150
+ const matched_env = ENV_MATCHER.exec(url);
151
+ if (matched_env) {
152
+ // SvelteKit `$env`
153
+ const mode: 'static' | 'dynamic' = matched_env[1] as any;
154
+ const visibility: 'public' | 'private' = matched_env[2] as any;
155
+ return {
156
+ format: 'module',
157
+ shortCircuit: true,
158
+ source: render_env_shim_module(
159
+ dev,
160
+ mode,
161
+ visibility,
162
+ public_prefix,
163
+ private_prefix,
164
+ env_dir,
165
+ ),
166
+ };
167
+ }
168
+ }
169
+
170
+ // fallback to default behavior
171
+ return nextLoad(url, context);
172
+ };
173
+
174
+ export const resolve: ResolveHook = async (specifier, context, nextResolve) => {
175
+ if (
176
+ specifier === '$env/static/public' ||
177
+ specifier === '$env/static/private' ||
178
+ specifier === '$env/dynamic/public' ||
179
+ specifier === '$env/dynamic/private'
180
+ ) {
181
+ // The returned `url` is validated before `load` is called,
182
+ // so we need a slightly roundabout strategy to pass through the specifier for virtual files.
183
+ return {
184
+ url: pathToFileURL(join(dir, 'src/lib', specifier)).href,
185
+ format: 'module',
186
+ shortCircuit: true,
187
+ };
188
+ }
189
+
190
+ const parent_url = context.parentURL;
191
+ if (!parent_url || NODE_MODULES_MATCHER.test(parent_url)) {
192
+ return nextResolve(specifier, context);
193
+ }
194
+
195
+ const shimmed = sveltekit_shim_app_specifiers.get(specifier);
196
+ if (shimmed !== undefined) {
197
+ return nextResolve(shimmed, context);
198
+ }
199
+
200
+ let path = specifier;
201
+
202
+ // Map the path with the SvelteKit aliases.
203
+ for (const [from, to] of aliases) {
204
+ if (path.startsWith(from)) {
205
+ path = join(dir, to, path.substring(from.length));
206
+ break;
207
+ }
208
+ }
209
+
210
+ // The specifier `path` has now been mapped to its final form, so we can inspect it.
211
+ if (path[0] !== '.' && path[0] !== '/') {
212
+ // Resolve to `node_modules`.
213
+ if (SVELTE_MATCHER.test(path) || JSON_MATCHER.test(path)) {
214
+ // Match the behavior of Vite and esbuild for Svelte and JSON imports.
215
+ // TODO maybe `.ts` too
216
+ const path_id = resolve_node_specifier(path, dir, parent_url, package_json_cache);
217
+ return {url: pathToFileURL(path_id).href, format: 'module', shortCircuit: true};
218
+ } else {
219
+ return nextResolve(path, context);
220
+ }
221
+ }
222
+
223
+ const {path_id} = resolve_specifier(path, dirname(fileURLToPath(parent_url)));
224
+
225
+ return {url: pathToFileURL(path_id).href, format: 'module', shortCircuit: true};
226
+ };
@@ -0,0 +1,46 @@
1
+ import {suite} from 'uvu';
2
+ import * as assert from 'uvu/assert';
3
+
4
+ import {is_external_module} from './module.js';
5
+
6
+ /* test__is_external_module */
7
+ const test__is_external_module = suite('is_external_module');
8
+
9
+ test__is_external_module('internal browser module patterns', () => {
10
+ assert.is(is_external_module('./foo'), false);
11
+ assert.is(is_external_module('./foo.js'), false);
12
+ assert.is(is_external_module('../foo'), false);
13
+ assert.is(is_external_module('../foo.js'), false);
14
+ assert.is(is_external_module('../../../foo'), false);
15
+ assert.is(is_external_module('../../../foo.js'), false);
16
+ assert.is(is_external_module('/foo'), false);
17
+ assert.is(is_external_module('/foo.js'), false);
18
+ assert.is(is_external_module('src/foo'), false);
19
+ assert.is(is_external_module('src/foo.js'), false);
20
+ assert.is(is_external_module('$lib/foo'), false);
21
+ assert.is(is_external_module('$lib/foo.js'), false);
22
+ assert.is(is_external_module('./foo/bar/baz'), false);
23
+ assert.is(is_external_module('./foo/bar/baz.js'), false);
24
+ assert.is(is_external_module('../foo/bar/baz'), false);
25
+ assert.is(is_external_module('../foo/bar/baz.js'), false);
26
+ assert.is(is_external_module('../../../foo/bar/baz'), false);
27
+ assert.is(is_external_module('../../../foo/bar/baz.js'), false);
28
+ assert.is(is_external_module('/foo/bar/baz'), false);
29
+ assert.is(is_external_module('/foo/bar/baz.js'), false);
30
+ assert.is(is_external_module('src/foo/bar/baz'), false);
31
+ assert.is(is_external_module('src/foo/bar/baz.js'), false);
32
+ assert.is(is_external_module('$lib/foo/bar/baz'), false);
33
+ assert.is(is_external_module('$lib/foo/bar/baz.js'), false);
34
+ });
35
+
36
+ test__is_external_module('external browser module patterns', () => {
37
+ assert.is(is_external_module('foo'), true);
38
+ assert.is(is_external_module('foo.js'), true);
39
+ assert.is(is_external_module('foo/bar/baz'), true);
40
+ assert.is(is_external_module('foo/bar/baz.js'), true);
41
+ assert.is(is_external_module('@foo/bar/baz'), true);
42
+ assert.is(is_external_module('@foo/bar/baz.js'), true);
43
+ });
44
+
45
+ test__is_external_module.run();
46
+ /* test__is_external_module */
@@ -0,0 +1,13 @@
1
+ import {LIB_DIRNAME} from './paths.js';
2
+ import {SOURCE_DIR, SOURCE_DIRNAME} from './path_constants.js';
3
+
4
+ export const MODULE_PATH_SRC_PREFIX = SOURCE_DIR;
5
+ export const MODULE_PATH_LIB_PREFIX = `$${LIB_DIRNAME}/`;
6
+
7
+ const INTERNAL_MODULE_MATCHER = new RegExp(
8
+ `^(\\.?\\.?|${SOURCE_DIRNAME}|\\$${LIB_DIRNAME})\\/`,
9
+ 'u',
10
+ );
11
+
12
+ export const is_external_module = (module_name: string): boolean =>
13
+ !INTERNAL_MODULE_MATCHER.test(module_name);
@@ -0,0 +1,63 @@
1
+ import {test} from 'uvu';
2
+ import * as assert from 'uvu/assert';
3
+ import {resolve} from 'node:path';
4
+
5
+ import {load_module} from './modules.js';
6
+
7
+ // TODO if we import directly, svelte-package generates types in `src/fixtures`
8
+ /* eslint-disable no-useless-concat */
9
+ const mod_test1 = await import('../fixtures/' + 'test1.foo.js');
10
+
11
+ test('load_module basic behavior', async () => {
12
+ const id = resolve('src/fixtures/test1.foo.js');
13
+ let validated_mod;
14
+ const result = await load_module(id, (mod): mod is any => {
15
+ validated_mod = mod;
16
+ return true;
17
+ });
18
+ assert.ok(result.ok);
19
+ assert.is(result.id, id);
20
+ assert.is(result.mod, validated_mod);
21
+ assert.is(result.mod, mod_test1);
22
+ });
23
+
24
+ test('load_module without validation', async () => {
25
+ const id = resolve('src/fixtures/test1.foo.js');
26
+ const result = await load_module(id);
27
+ assert.ok(result.ok);
28
+ assert.is(result.id, id);
29
+ assert.is(result.mod, mod_test1);
30
+ });
31
+
32
+ test('load_module fails validation', async () => {
33
+ const id = resolve('src/fixtures/test1.foo.js');
34
+ let validated_mod;
35
+ const test_validation = (mod: Record<string, any>) => {
36
+ validated_mod = mod;
37
+ return false;
38
+ };
39
+ const result = await load_module(id, test_validation as any);
40
+ assert.ok(!result.ok);
41
+ if (result.type === 'failed_validation') {
42
+ assert.is(result.validation, test_validation.name);
43
+ assert.is(result.id, id);
44
+ assert.is(result.mod, validated_mod);
45
+ assert.is(result.mod, mod_test1);
46
+ } else {
47
+ throw Error('Should be invalid');
48
+ }
49
+ });
50
+
51
+ test('load_module fails to import', async () => {
52
+ const id = resolve('foo/test/failure');
53
+ const result = await load_module(id);
54
+ assert.ok(!result.ok);
55
+ if (result.type === 'failed_import') {
56
+ assert.is(result.id, id);
57
+ assert.ok(result.error instanceof Error);
58
+ } else {
59
+ throw Error('Should fail to import');
60
+ }
61
+ });
62
+
63
+ test.run();
@@ -0,0 +1,112 @@
1
+ import type {Timings} from '@ryanatkn/belt/timings.js';
2
+ import {Unreachable_Error} from '@ryanatkn/belt/error.js';
3
+ import type {Result} from '@ryanatkn/belt/result.js';
4
+ import {print_error} from '@ryanatkn/belt/print.js';
5
+
6
+ import type {Resolved_Input_File} from './input_path.js';
7
+ import {print_path} from './paths.js';
8
+ import type {Path_Id} from './path.js';
9
+
10
+ export interface Module_Meta<T_Module extends Record<string, any> = Record<string, any>> {
11
+ id: Path_Id;
12
+ mod: T_Module;
13
+ }
14
+
15
+ export type Load_Module_Result<T_Module> = Result<
16
+ {id: Path_Id; mod: T_Module},
17
+ Load_Module_Failure
18
+ >;
19
+ export type Load_Module_Failure =
20
+ | {ok: false; type: 'failed_import'; id: Path_Id; error: Error}
21
+ | {
22
+ ok: false;
23
+ type: 'failed_validation';
24
+ id: Path_Id;
25
+ mod: Record<string, any>;
26
+ validation: string;
27
+ };
28
+
29
+ export const load_module = async <T_Module extends Record<string, any>>(
30
+ id: Path_Id,
31
+ validate?: (mod: Record<string, any>) => mod is T_Module,
32
+ ): Promise<Load_Module_Result<T_Module>> => {
33
+ let mod;
34
+ try {
35
+ mod = await import(id);
36
+ } catch (err) {
37
+ return {ok: false, type: 'failed_import', id, error: err};
38
+ }
39
+ if (validate && !validate(mod)) {
40
+ return {ok: false, type: 'failed_validation', id, mod, validation: validate.name};
41
+ }
42
+ return {ok: true, id, mod};
43
+ };
44
+
45
+ export interface Load_Modules_Failure<T_Module_Meta extends Module_Meta> {
46
+ type: 'load_module_failures';
47
+ load_module_failures: Load_Module_Failure[];
48
+ reasons: string[];
49
+ // still return the modules and timings, deferring to the caller
50
+ modules: T_Module_Meta[];
51
+ }
52
+
53
+ export type Load_Modules_Result<T_Module_Meta extends Module_Meta> = Result<
54
+ {
55
+ modules: T_Module_Meta[];
56
+ },
57
+ Load_Modules_Failure<T_Module_Meta>
58
+ >;
59
+
60
+ // TODO parallelize and sort afterwards
61
+ export const load_modules = async <
62
+ T_Module extends Record<string, any>,
63
+ T_Module_Meta extends Module_Meta<T_Module>,
64
+ >(
65
+ resolved_input_files: Resolved_Input_File[],
66
+ validate: (mod: any) => mod is T_Module,
67
+ map_module_meta: (resolved_input_file: Resolved_Input_File, mod: T_Module) => T_Module_Meta,
68
+ timings?: Timings,
69
+ ): Promise<Load_Modules_Result<T_Module_Meta>> => {
70
+ const timing_to_load_modules = timings?.start('load modules');
71
+ const modules: T_Module_Meta[] = [];
72
+ const load_module_failures: Load_Module_Failure[] = [];
73
+ const reasons: string[] = [];
74
+ for (const resolved_input_file of resolved_input_files.values()) {
75
+ const {id, input_path} = resolved_input_file;
76
+ const result = await load_module(id, validate); // eslint-disable-line no-await-in-loop
77
+ if (result.ok) {
78
+ modules.push(map_module_meta(resolved_input_file, result.mod));
79
+ } else {
80
+ load_module_failures.push(result);
81
+ switch (result.type) {
82
+ case 'failed_import': {
83
+ reasons.push(
84
+ `Module import ${print_path(id)} failed from input ${print_path(
85
+ input_path,
86
+ )}: ${print_error(result.error)}`,
87
+ );
88
+ break;
89
+ }
90
+ case 'failed_validation': {
91
+ reasons.push(`Module ${print_path(id)} failed validation '${result.validation}'.`);
92
+ break;
93
+ }
94
+ default:
95
+ throw new Unreachable_Error(result);
96
+ }
97
+ }
98
+ }
99
+ timing_to_load_modules?.();
100
+
101
+ if (load_module_failures.length) {
102
+ return {
103
+ ok: false,
104
+ type: 'load_module_failures',
105
+ load_module_failures,
106
+ reasons,
107
+ modules,
108
+ };
109
+ }
110
+
111
+ return {ok: true, modules};
112
+ };
@@ -0,0 +1,33 @@
1
+ import type {Gen} from './gen.js';
2
+ import {load_package_json} from './package_json.js';
3
+ import {IS_THIS_GRO, to_root_path} from './paths.js';
4
+ import {create_src_json} from './src_json.js';
5
+
6
+ // TODO rename? `Package_Json + Src_Json = package.ts` currently, idk
7
+
8
+ // TODO consider an api that uses magic imports like SvelteKit's `$app`, like `$repo/package.json`
9
+
10
+ /**
11
+ * A convenience `gen` file that outputs `$lib/package.ts`,
12
+ * which mirrors `package.json` but in TypeScript,
13
+ * allowing apps to import typesafe data from their own `package.json`.
14
+ */
15
+ export const gen: Gen = ({origin_id}) => {
16
+ const package_json = load_package_json();
17
+ const src_json = create_src_json(package_json);
18
+
19
+ return `
20
+ // generated by ${to_root_path(origin_id)}
21
+
22
+ import type {Package_Json} from '${
23
+ IS_THIS_GRO ? './package_json.js' : '@ryanatkn/gro/package_json.js'
24
+ }';
25
+ import type {Src_Json} from '${IS_THIS_GRO ? './src_json.js' : '@ryanatkn/gro/src_json.js'}';
26
+
27
+ export const package_json = ${JSON.stringify(package_json)} satisfies Package_Json;
28
+
29
+ export const src_json = ${JSON.stringify(src_json)} satisfies Src_Json;
30
+
31
+ // generated by ${to_root_path(origin_id)}
32
+ `;
33
+ };