@ryanatkn/gro 0.112.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 (222) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +257 -0
  3. package/dist/args.d.ts +59 -0
  4. package/dist/args.js +132 -0
  5. package/dist/args.test.d.ts +1 -0
  6. package/dist/args.test.js +43 -0
  7. package/dist/build.task.d.ts +11 -0
  8. package/dist/build.task.js +24 -0
  9. package/dist/changelog.d.ts +8 -0
  10. package/dist/changelog.js +47 -0
  11. package/dist/changelog.test.d.ts +1 -0
  12. package/dist/changelog.test.js +118 -0
  13. package/dist/changeset.task.d.ts +49 -0
  14. package/dist/changeset.task.js +141 -0
  15. package/dist/check.task.d.ts +47 -0
  16. package/dist/check.task.js +77 -0
  17. package/dist/clean.task.d.ts +26 -0
  18. package/dist/clean.task.js +41 -0
  19. package/dist/clean_fs.d.ts +9 -0
  20. package/dist/clean_fs.js +27 -0
  21. package/dist/cli.d.ts +11 -0
  22. package/dist/cli.js +25 -0
  23. package/dist/commit.task.d.ts +11 -0
  24. package/dist/commit.task.js +22 -0
  25. package/dist/config.d.ts +21 -0
  26. package/dist/config.js +42 -0
  27. package/dist/config.test.d.ts +1 -0
  28. package/dist/config.test.js +8 -0
  29. package/dist/deploy.task.d.ts +47 -0
  30. package/dist/deploy.task.js +198 -0
  31. package/dist/dev.task.d.ts +22 -0
  32. package/dist/dev.task.js +32 -0
  33. package/dist/docs/README.gen.md.d.ts +5 -0
  34. package/dist/docs/README.gen.md.js +53 -0
  35. package/dist/docs/README.md +20 -0
  36. package/dist/docs/build.md +41 -0
  37. package/dist/docs/config.md +162 -0
  38. package/dist/docs/deploy.md +32 -0
  39. package/dist/docs/dev.md +40 -0
  40. package/dist/docs/gen.md +241 -0
  41. package/dist/docs/gro_plugin_sveltekit_frontend.md +97 -0
  42. package/dist/docs/package_json.md +29 -0
  43. package/dist/docs/plugin.md +50 -0
  44. package/dist/docs/publish.md +144 -0
  45. package/dist/docs/task.md +377 -0
  46. package/dist/docs/tasks.gen.md.d.ts +2 -0
  47. package/dist/docs/tasks.gen.md.js +60 -0
  48. package/dist/docs/tasks.md +35 -0
  49. package/dist/docs/test.md +52 -0
  50. package/dist/env.d.ts +10 -0
  51. package/dist/env.js +47 -0
  52. package/dist/esbuild_helpers.d.ts +14 -0
  53. package/dist/esbuild_helpers.js +36 -0
  54. package/dist/esbuild_plugin_external_worker.d.ts +22 -0
  55. package/dist/esbuild_plugin_external_worker.js +49 -0
  56. package/dist/esbuild_plugin_svelte.d.ts +9 -0
  57. package/dist/esbuild_plugin_svelte.js +49 -0
  58. package/dist/esbuild_plugin_sveltekit_local_imports.d.ts +7 -0
  59. package/dist/esbuild_plugin_sveltekit_local_imports.js +30 -0
  60. package/dist/esbuild_plugin_sveltekit_shim_alias.d.ts +6 -0
  61. package/dist/esbuild_plugin_sveltekit_shim_alias.js +16 -0
  62. package/dist/esbuild_plugin_sveltekit_shim_app.d.ts +8 -0
  63. package/dist/esbuild_plugin_sveltekit_shim_app.js +23 -0
  64. package/dist/esbuild_plugin_sveltekit_shim_env.d.ts +10 -0
  65. package/dist/esbuild_plugin_sveltekit_shim_env.js +18 -0
  66. package/dist/format.task.d.ts +11 -0
  67. package/dist/format.task.js +24 -0
  68. package/dist/format_directory.d.ts +2 -0
  69. package/dist/format_directory.js +27 -0
  70. package/dist/format_file.d.ts +8 -0
  71. package/dist/format_file.js +42 -0
  72. package/dist/format_file.test.d.ts +1 -0
  73. package/dist/format_file.test.js +16 -0
  74. package/dist/fs.d.ts +7 -0
  75. package/dist/fs.js +19 -0
  76. package/dist/fs.test.d.ts +1 -0
  77. package/dist/fs.test.js +16 -0
  78. package/dist/gen.d.ts +57 -0
  79. package/dist/gen.js +81 -0
  80. package/dist/gen.task.d.ts +14 -0
  81. package/dist/gen.task.js +103 -0
  82. package/dist/gen.test.d.ts +1 -0
  83. package/dist/gen.test.js +239 -0
  84. package/dist/gen_module.d.ts +46 -0
  85. package/dist/gen_module.js +54 -0
  86. package/dist/gen_module.test.d.ts +1 -0
  87. package/dist/gen_module.test.js +30 -0
  88. package/dist/git.d.ts +76 -0
  89. package/dist/git.js +200 -0
  90. package/dist/git.test.d.ts +1 -0
  91. package/dist/git.test.js +18 -0
  92. package/dist/github.d.ts +35 -0
  93. package/dist/github.js +32 -0
  94. package/dist/gro.config.default.d.ts +12 -0
  95. package/dist/gro.config.default.js +31 -0
  96. package/dist/gro.d.ts +2 -0
  97. package/dist/gro.js +19 -0
  98. package/dist/gro_helpers.d.ts +43 -0
  99. package/dist/gro_helpers.js +79 -0
  100. package/dist/gro_plugin_gen.d.ts +6 -0
  101. package/dist/gro_plugin_gen.js +80 -0
  102. package/dist/gro_plugin_server.d.ts +77 -0
  103. package/dist/gro_plugin_server.js +152 -0
  104. package/dist/gro_plugin_sveltekit_app.d.ts +27 -0
  105. package/dist/gro_plugin_sveltekit_app.js +180 -0
  106. package/dist/gro_plugin_sveltekit_library.d.ts +4 -0
  107. package/dist/gro_plugin_sveltekit_library.js +42 -0
  108. package/dist/hash.d.ts +5 -0
  109. package/dist/hash.js +14 -0
  110. package/dist/hash.test.d.ts +1 -0
  111. package/dist/hash.test.js +25 -0
  112. package/dist/index.d.ts +4 -0
  113. package/dist/index.js +3 -0
  114. package/dist/input_path.d.ts +48 -0
  115. package/dist/input_path.js +161 -0
  116. package/dist/input_path.test.d.ts +1 -0
  117. package/dist/input_path.test.js +106 -0
  118. package/dist/invoke.d.ts +1 -0
  119. package/dist/invoke.js +18 -0
  120. package/dist/invoke_task.d.ts +20 -0
  121. package/dist/invoke_task.js +140 -0
  122. package/dist/lint.task.d.ts +11 -0
  123. package/dist/lint.task.js +29 -0
  124. package/dist/loader.d.ts +4 -0
  125. package/dist/loader.js +153 -0
  126. package/dist/module.d.ts +3 -0
  127. package/dist/module.js +6 -0
  128. package/dist/module.test.d.ts +1 -0
  129. package/dist/module.test.js +41 -0
  130. package/dist/modules.d.ts +60 -0
  131. package/dist/modules.js +103 -0
  132. package/dist/modules.test.d.ts +1 -0
  133. package/dist/modules.test.js +182 -0
  134. package/dist/package.d.ts +939 -0
  135. package/dist/package.gen.d.ts +7 -0
  136. package/dist/package.gen.js +26 -0
  137. package/dist/package.js +887 -0
  138. package/dist/package_json.d.ts +342 -0
  139. package/dist/package_json.js +212 -0
  140. package/dist/package_json.test.d.ts +1 -0
  141. package/dist/package_json.test.js +77 -0
  142. package/dist/path.d.ts +12 -0
  143. package/dist/path.js +8 -0
  144. package/dist/paths.d.ts +60 -0
  145. package/dist/paths.js +128 -0
  146. package/dist/paths.test.d.ts +1 -0
  147. package/dist/paths.test.js +49 -0
  148. package/dist/plugin.d.ts +36 -0
  149. package/dist/plugin.js +80 -0
  150. package/dist/plugin.test.d.ts +1 -0
  151. package/dist/plugin.test.js +54 -0
  152. package/dist/print_task.d.ts +4 -0
  153. package/dist/print_task.js +124 -0
  154. package/dist/publish.task.d.ts +32 -0
  155. package/dist/publish.task.js +125 -0
  156. package/dist/release.task.d.ts +5 -0
  157. package/dist/release.task.js +18 -0
  158. package/dist/resolve_node_specifier.d.ts +8 -0
  159. package/dist/resolve_node_specifier.js +39 -0
  160. package/dist/resolve_node_specifier.test.d.ts +1 -0
  161. package/dist/resolve_node_specifier.test.js +21 -0
  162. package/dist/resolve_specifier.d.ts +15 -0
  163. package/dist/resolve_specifier.js +51 -0
  164. package/dist/resolve_specifier.test.d.ts +1 -0
  165. package/dist/resolve_specifier.test.js +66 -0
  166. package/dist/run.task.d.ts +11 -0
  167. package/dist/run.task.js +31 -0
  168. package/dist/run_gen.d.ts +6 -0
  169. package/dist/run_gen.js +74 -0
  170. package/dist/run_gen.test.d.ts +1 -0
  171. package/dist/run_gen.test.js +182 -0
  172. package/dist/run_task.d.ts +13 -0
  173. package/dist/run_task.js +44 -0
  174. package/dist/run_task.test.d.ts +1 -0
  175. package/dist/run_task.test.js +63 -0
  176. package/dist/search_fs.d.ts +11 -0
  177. package/dist/search_fs.js +22 -0
  178. package/dist/search_fs.test.d.ts +1 -0
  179. package/dist/search_fs.test.js +46 -0
  180. package/dist/src_json.d.ts +256 -0
  181. package/dist/src_json.js +110 -0
  182. package/dist/src_json.test.d.ts +1 -0
  183. package/dist/src_json.test.js +52 -0
  184. package/dist/sveltekit_config.d.ts +36 -0
  185. package/dist/sveltekit_config.js +51 -0
  186. package/dist/sveltekit_shim_app.d.ts +10 -0
  187. package/dist/sveltekit_shim_app.js +31 -0
  188. package/dist/sveltekit_shim_app_environment.d.ts +10 -0
  189. package/dist/sveltekit_shim_app_environment.js +12 -0
  190. package/dist/sveltekit_shim_app_forms.d.ts +5 -0
  191. package/dist/sveltekit_shim_app_forms.js +13 -0
  192. package/dist/sveltekit_shim_app_navigation.d.ts +10 -0
  193. package/dist/sveltekit_shim_app_navigation.js +11 -0
  194. package/dist/sveltekit_shim_app_paths.d.ts +11 -0
  195. package/dist/sveltekit_shim_app_paths.js +6 -0
  196. package/dist/sveltekit_shim_app_stores.d.ts +6 -0
  197. package/dist/sveltekit_shim_app_stores.js +17 -0
  198. package/dist/sveltekit_shim_env.d.ts +4 -0
  199. package/dist/sveltekit_shim_env.js +23 -0
  200. package/dist/sync.task.d.ts +30 -0
  201. package/dist/sync.task.js +45 -0
  202. package/dist/task.d.ts +29 -0
  203. package/dist/task.js +17 -0
  204. package/dist/task.test.d.ts +1 -0
  205. package/dist/task.test.js +22 -0
  206. package/dist/task_module.d.ts +14 -0
  207. package/dist/task_module.js +19 -0
  208. package/dist/task_module.test.d.ts +1 -0
  209. package/dist/task_module.test.js +70 -0
  210. package/dist/test.task.d.ts +20 -0
  211. package/dist/test.task.js +43 -0
  212. package/dist/throttle.d.ts +16 -0
  213. package/dist/throttle.js +59 -0
  214. package/dist/throttle.test.d.ts +1 -0
  215. package/dist/throttle.test.js +49 -0
  216. package/dist/typecheck.task.d.ts +5 -0
  217. package/dist/typecheck.task.js +38 -0
  218. package/dist/upgrade.task.d.ts +14 -0
  219. package/dist/upgrade.task.js +37 -0
  220. package/dist/watch_dir.d.ts +30 -0
  221. package/dist/watch_dir.js +59 -0
  222. package/package.json +422 -0
@@ -0,0 +1,161 @@
1
+ import { join, isAbsolute, basename } from 'node:path';
2
+ import { strip_end, strip_start } from '@ryanatkn/belt/string.js';
3
+ import { stat } from 'node:fs/promises';
4
+ import { lib_path_to_import_id, replace_root_dir, gro_dir_basename, gro_paths, LIB_DIR, LIB_PATH, is_this_project_gro, gro_sveltekit_dist_dir, paths, } from './paths.js';
5
+ import { to_path_data } from './path.js';
6
+ import { exists } from './fs.js';
7
+ import { search_fs } from './search_fs.js';
8
+ /**
9
+ * Raw input paths are paths that users provide to Gro to reference files
10
+ * enhanced with Gro's conventions like `.test.`, `.task.`, and `.gen.`.
11
+ *
12
+ * A raw input path can be:
13
+ *
14
+ * - a relative path to a file, e.g. `src/foo/bar.test.ts`
15
+ * - a file without an extension, e.g. `src/foo/bar` if `extensions` is `.test.ts`
16
+ * - a directory containing any number of files, e.g. `src/foo`
17
+ * - any of the above without the leading `src/` or with a leading `./`
18
+ * - any of the above but leading with `gro/` to ignore the local directory
19
+ * - an absolute path to a file or directory in the current directory or Gro's
20
+ *
21
+ * The input path API lets the caller customize the allowable extensions.
22
+ * That means that the caller can look for `.test.` files but not `.gen.`,
23
+ * or both, or neither, depending on its needs.
24
+ *
25
+ * In the future we may want to support globbing or regexps.
26
+ */
27
+ export const resolve_input_path = (raw_input_path) => {
28
+ if (isAbsolute(raw_input_path))
29
+ return strip_end(raw_input_path, '/');
30
+ // Allow prefix `./` and just remove it if it's there.
31
+ let base_path = strip_end(strip_start(raw_input_path, './'), '/');
32
+ let paths;
33
+ // If it's prefixed with `gro/` or exactly `gro`, use the Gro paths.
34
+ if (is_this_project_gro || (base_path + '/').startsWith(gro_dir_basename)) {
35
+ paths = gro_paths;
36
+ base_path = strip_end(strip_start(base_path + '/', gro_dir_basename), '/');
37
+ }
38
+ // Handle `src/lib` by itself without conflicting with `src/libFoo` names.
39
+ if (base_path === LIB_PATH)
40
+ base_path = ''; // TODO @multiple get from the sveltekit config
41
+ // Allow prefix `src/lib/` and just remove it if it's there.
42
+ base_path = strip_start(base_path, LIB_DIR);
43
+ return lib_path_to_import_id(base_path, paths);
44
+ };
45
+ export const resolve_input_paths = (raw_input_paths) => raw_input_paths?.length ? raw_input_paths.map((p) => resolve_input_path(p)) : [paths.source];
46
+ /**
47
+ * Gets a list of possible source ids for each input path with `extensions`,
48
+ * duplicating each under `root_dirs`.
49
+ * This is first used to fall back to the Gro dir to search for tasks.
50
+ * It's the helper used in implementations of `get_possible_source_ids_for_input_path` below.
51
+ */
52
+ export const get_possible_source_ids = (input_path, extensions, root_dirs) => {
53
+ const possible_source_ids = [input_path];
54
+ if (!input_path.endsWith('/')) {
55
+ for (const extension of extensions) {
56
+ if (!input_path.endsWith(extension)) {
57
+ possible_source_ids.push(input_path + extension);
58
+ // Support task directories, so `src/lib/a/a.task.ts` works like `src/a.task.ts`.
59
+ possible_source_ids.push(join(input_path, basename(input_path) + extension));
60
+ }
61
+ }
62
+ }
63
+ if (root_dirs?.length) {
64
+ const ids = possible_source_ids.slice(); // make a copy or infinitely loop!
65
+ for (const root_dir of root_dirs) {
66
+ if (input_path.startsWith(root_dir))
67
+ continue; // avoid duplicates
68
+ const is_gro_dist = root_dir === gro_sveltekit_dist_dir; // TODO hacky to handle Gro importing its JS tasks from dist/
69
+ for (const possible_source_id of ids) {
70
+ if (is_gro_dist && !possible_source_id.endsWith('.js'))
71
+ continue;
72
+ // TODO hacky to handle Gro importing its JS tasks from dist/
73
+ possible_source_ids.push(is_gro_dist
74
+ ? gro_sveltekit_dist_dir + strip_start(possible_source_id, paths.lib)
75
+ : replace_root_dir(possible_source_id, root_dir, paths));
76
+ }
77
+ }
78
+ }
79
+ return possible_source_ids;
80
+ };
81
+ /**
82
+ * Gets the path data for each input path,
83
+ * searching for the possibilities based on `extensions`
84
+ * and stopping at the first match.
85
+ * Parameterized by `exists` and `stat` so it's fs-agnostic.
86
+ */
87
+ export const load_source_path_data_by_input_path = async (input_paths, get_possible_source_ids_for_input_path) => {
88
+ const source_id_path_data_by_input_path = new Map();
89
+ const unmapped_input_paths = [];
90
+ for (const input_path of input_paths) {
91
+ let file_path_data = null;
92
+ let dir_path_data = null;
93
+ const possible_source_ids = get_possible_source_ids_for_input_path
94
+ ? get_possible_source_ids_for_input_path(input_path)
95
+ : [input_path];
96
+ for (const possible_source_id of possible_source_ids) {
97
+ if (!(await exists(possible_source_id)))
98
+ continue; // eslint-disable-line no-await-in-loop
99
+ const stats = await stat(possible_source_id); // eslint-disable-line no-await-in-loop
100
+ if (stats.isDirectory()) {
101
+ if (!dir_path_data) {
102
+ dir_path_data = to_path_data(possible_source_id, stats);
103
+ }
104
+ }
105
+ else {
106
+ file_path_data = to_path_data(possible_source_id, stats);
107
+ break;
108
+ }
109
+ }
110
+ if (file_path_data || dir_path_data) {
111
+ source_id_path_data_by_input_path.set(input_path, file_path_data || dir_path_data); // the ! is needed because TypeScript inference fails
112
+ }
113
+ else {
114
+ unmapped_input_paths.push(input_path);
115
+ }
116
+ }
117
+ return { source_id_path_data_by_input_path, unmapped_input_paths };
118
+ };
119
+ /**
120
+ * Finds all of the matching files for the given input paths.
121
+ * Parameterized by `find_files` so it's fs-agnostic.
122
+ * De-dupes source ids.
123
+ */
124
+ export const load_source_ids_by_input_path = async (source_id_path_data_by_input_path, custom_search_fs = search_fs) => {
125
+ const source_ids_by_input_path = new Map();
126
+ const input_directories_with_no_files = [];
127
+ const existing_source_ids = new Set();
128
+ for (const [input_path, path_data] of source_id_path_data_by_input_path) {
129
+ const { id } = path_data;
130
+ if (path_data.isDirectory) {
131
+ const files = await custom_search_fs(id, { files_only: false }); // eslint-disable-line no-await-in-loop
132
+ if (files.size) {
133
+ const source_ids = [];
134
+ let has_files = false;
135
+ for (const path of files.keys()) {
136
+ has_files = true;
137
+ const source_id = join(id, path);
138
+ if (!existing_source_ids.has(source_id)) {
139
+ existing_source_ids.add(source_id);
140
+ source_ids.push(source_id);
141
+ }
142
+ }
143
+ if (source_ids.length) {
144
+ source_ids_by_input_path.set(input_path, source_ids);
145
+ }
146
+ if (!has_files) {
147
+ input_directories_with_no_files.push(input_path);
148
+ }
149
+ // do callers ever need `inputDirectoriesWithDuplicateFiles`?
150
+ }
151
+ else {
152
+ input_directories_with_no_files.push(input_path);
153
+ }
154
+ }
155
+ else if (!existing_source_ids.has(id)) {
156
+ existing_source_ids.add(id);
157
+ source_ids_by_input_path.set(input_path, [id]);
158
+ }
159
+ }
160
+ return { source_ids_by_input_path, input_directories_with_no_files };
161
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,106 @@
1
+ import { suite } from 'uvu';
2
+ import * as assert from 'uvu/assert';
3
+ import { resolve } from 'node:path';
4
+ import { resolve_input_path, resolve_input_paths, load_source_ids_by_input_path, get_possible_source_ids, } from './input_path.js';
5
+ import { paths } from './paths.js';
6
+ /* test__resolve_input_path */
7
+ const test__resolve_input_path = suite('resolve_input_path');
8
+ test__resolve_input_path('basic behavior', () => {
9
+ const target = resolve('dist/foo/bar.ts');
10
+ assert.is(resolve_input_path('foo/bar.ts'), target);
11
+ assert.is(resolve_input_path('src/lib/foo/bar.ts'), target);
12
+ assert.is(resolve_input_path('./src/lib/foo/bar.ts'), target);
13
+ assert.is(resolve_input_path('./foo/bar.ts'), target); // questionable
14
+ assert.is(resolve_input_path(target), target);
15
+ assert.is.not(resolve_input_path('bar.ts'), target);
16
+ });
17
+ test__resolve_input_path('directories', () => {
18
+ const target_dir = resolve('dist/foo/bar');
19
+ assert.is(resolve_input_path('foo/bar'), target_dir);
20
+ assert.is(resolve_input_path('foo/bar/'), target_dir);
21
+ assert.is(resolve_input_path('./foo/bar'), target_dir);
22
+ assert.is(resolve_input_path('./foo/bar/'), target_dir);
23
+ assert.is.not(resolve_input_path('bar'), target_dir);
24
+ });
25
+ test__resolve_input_path.run();
26
+ /* test__resolve_input_path */
27
+ /* test__resolve_input_paths */
28
+ const test__resolve_input_paths = suite('resolve_input_paths');
29
+ test__resolve_input_paths('resolves multiple input path forms', () => {
30
+ assert.equal(resolve_input_paths(['foo/bar.ts', 'baz', './']), [
31
+ resolve('dist/foo/bar.ts'),
32
+ resolve('dist/baz'),
33
+ resolve('dist') + '/',
34
+ ]);
35
+ });
36
+ test__resolve_input_paths.run();
37
+ /* test__resolve_input_paths */
38
+ /* test__get_possible_source_ids */
39
+ const test__get_possible_source_ids = suite('get_possible_source_ids');
40
+ test__get_possible_source_ids('in the gro directory', () => {
41
+ const input_path = resolve('src/foo/bar');
42
+ assert.equal(get_possible_source_ids(input_path, ['.baz.ts']), [
43
+ input_path,
44
+ input_path + '.baz.ts',
45
+ input_path + '/bar.baz.ts',
46
+ ]);
47
+ });
48
+ test__get_possible_source_ids('does not repeat the extension', () => {
49
+ const input_path = resolve('src/foo/bar.baz.ts');
50
+ assert.equal(get_possible_source_ids(input_path, ['.baz.ts']), [input_path]);
51
+ });
52
+ test__get_possible_source_ids('does not repeat with the same root directory', () => {
53
+ const input_path = resolve('src/foo/bar.baz.ts');
54
+ assert.equal(get_possible_source_ids(input_path, ['.baz.ts'], [paths.root, paths.root]), [
55
+ input_path,
56
+ ]);
57
+ });
58
+ test__get_possible_source_ids('implied to be a directory by trailing slash', () => {
59
+ const input_path = resolve('src/foo/bar') + '/';
60
+ assert.equal(get_possible_source_ids(input_path, ['.baz.ts']), [input_path]);
61
+ });
62
+ test__get_possible_source_ids.run();
63
+ /* test__get_possible_source_ids */
64
+ /* test__load_source_ids_by_input_path */
65
+ const test__load_source_ids_by_input_path = suite('load_source_ids_by_input_path', async () => {
66
+ const test_files = {
67
+ 'fake/test1.bar.ts': new Map([['fake/test1.bar.ts', { isDirectory: () => false }]]),
68
+ 'fake/test2.bar.ts': new Map([['fake/test2.bar.ts', { isDirectory: () => false }]]),
69
+ 'fake/test3': new Map([
70
+ ['fake/test3', { isDirectory: () => true }],
71
+ ['a.ts', { isDirectory: () => false }],
72
+ ['b.ts', { isDirectory: () => false }],
73
+ ]),
74
+ // duplicate
75
+ 'fake/': new Map([
76
+ ['fake/test3', { isDirectory: () => true }],
77
+ ['test3/a.ts', { isDirectory: () => false }],
78
+ ]),
79
+ // duplicate and not
80
+ fake: new Map([
81
+ ['fake/test3', { isDirectory: () => true }],
82
+ ['test3/a.ts', { isDirectory: () => false }],
83
+ ['test3/c.ts', { isDirectory: () => false }],
84
+ ]),
85
+ 'fake/nomatches': new Map([['fake/nomatches', { isDirectory: () => true }]]),
86
+ };
87
+ const result = await load_source_ids_by_input_path(new Map([
88
+ ['fake/test1.bar.ts', { id: 'fake/test1.bar.ts', isDirectory: false }],
89
+ ['fake/test2', { id: 'fake/test2.bar.ts', isDirectory: false }],
90
+ ['fake/test3', { id: 'fake/test3', isDirectory: true }],
91
+ ['fake/', { id: 'fake/', isDirectory: true }],
92
+ ['fake', { id: 'fake', isDirectory: true }],
93
+ ['fake/nomatches', { id: 'fake/nomatches', isDirectory: true }],
94
+ ]), async (id) => test_files[id]);
95
+ assert.equal(result, {
96
+ source_ids_by_input_path: new Map([
97
+ ['fake/test1.bar.ts', ['fake/test1.bar.ts']],
98
+ ['fake/test2', ['fake/test2.bar.ts']],
99
+ ['fake/test3', ['fake/test3/a.ts', 'fake/test3/b.ts']],
100
+ ['fake', ['fake/test3/c.ts']],
101
+ ]),
102
+ input_directories_with_no_files: ['fake/nomatches'],
103
+ });
104
+ });
105
+ test__load_source_ids_by_input_path.run();
106
+ /* test__load_source_ids_by_input_path */
@@ -0,0 +1 @@
1
+ export {};
package/dist/invoke.js ADDED
@@ -0,0 +1,18 @@
1
+ import { attach_process_error_handlers } from '@ryanatkn/belt/process.js';
2
+ import { invoke_task } from './invoke_task.js';
3
+ import { to_task_args } from './args.js';
4
+ import { load_config } from './config.js';
5
+ /*
6
+
7
+ This module invokes the Gro CLI which in turn invokes tasks.
8
+ Tasks are the CLI's primary concept.
9
+ To learn more about them, see `src/lib/docs/task.md`.
10
+
11
+ When the CLI is invoked it passes the first CLI arg as `task_name` to `invoke_task`,
12
+ and the rest of the args are forwarded to the task's `run` function.
13
+
14
+ */
15
+ // handle uncaught errors
16
+ attach_process_error_handlers((err) => err?.constructor?.name === 'Task_Error' ? 'Task_Error' : null);
17
+ const { task_name, args } = to_task_args();
18
+ await invoke_task(task_name, args, await load_config());
@@ -0,0 +1,20 @@
1
+ import { type Args } from './args.js';
2
+ import type { Gro_Config } from './config.js';
3
+ /**
4
+ * Invokes Gro tasks by name using the filesystem as the source.
5
+ *
6
+ * When a task is invoked,
7
+ * Gro first searches for tasks in the current working directory.
8
+ * and falls back to searching Gro's directory, if the two are different.
9
+ * See `src/lib/input_path.ts` for info about what "task_name" can refer to.
10
+ * If it matches a directory, all of the tasks within it are logged,
11
+ * both in the current working directory and Gro.
12
+ *
13
+ * This code is particularly hairy because
14
+ * we're accepting a wide range of user input
15
+ * and trying to do the right thing.
16
+ * Precise error messages are especially difficult and
17
+ * there are some subtle differences in the complex logical branches.
18
+ * The comments describe each condition.
19
+ */
20
+ export declare const invoke_task: (task_name: string, args: Args, config: Gro_Config, timings?: any) => Promise<void>;
@@ -0,0 +1,140 @@
1
+ import { cyan, red, gray } from 'kleur/colors';
2
+ import { Logger, System_Logger, print_log_label } from '@ryanatkn/belt/log.js';
3
+ import { create_stopwatch, Timings } from '@ryanatkn/belt/timings.js';
4
+ import { print_ms, print_timings } from '@ryanatkn/belt/print.js';
5
+ import { to_forwarded_args } from './args.js';
6
+ import { run_task } from './run_task.js';
7
+ import { resolve_input_path } from './input_path.js';
8
+ import { is_task_path } from './task.js';
9
+ import { is_gro_id, is_this_project_gro, print_path, print_path_or_gro_path, gro_sveltekit_dist_dir, to_gro_input_path, } from './paths.js';
10
+ import { find_modules, load_modules } from './modules.js';
11
+ import { find_task_modules, load_task_module } from './task_module.js';
12
+ import { load_gro_package_json } from './package_json.js';
13
+ import { log_available_tasks, log_error_reasons } from './print_task.js';
14
+ import { search_fs } from './search_fs.js';
15
+ /**
16
+ * Invokes Gro tasks by name using the filesystem as the source.
17
+ *
18
+ * When a task is invoked,
19
+ * Gro first searches for tasks in the current working directory.
20
+ * and falls back to searching Gro's directory, if the two are different.
21
+ * See `src/lib/input_path.ts` for info about what "task_name" can refer to.
22
+ * If it matches a directory, all of the tasks within it are logged,
23
+ * both in the current working directory and Gro.
24
+ *
25
+ * This code is particularly hairy because
26
+ * we're accepting a wide range of user input
27
+ * and trying to do the right thing.
28
+ * Precise error messages are especially difficult and
29
+ * there are some subtle differences in the complex logical branches.
30
+ * The comments describe each condition.
31
+ */
32
+ export const invoke_task = async (task_name, args, config, timings = new Timings()) => {
33
+ const log = new System_Logger(print_log_label(task_name || 'gro'));
34
+ log.info('invoking', task_name ? cyan(task_name) : 'gro');
35
+ const total_timing = create_stopwatch();
36
+ // Check if the caller just wants to see the version.
37
+ if (!task_name && (args.version || args.v)) {
38
+ const gro_package_json = await load_gro_package_json();
39
+ log.info(`${gray('v')}${cyan(gro_package_json.version)}`);
40
+ log.info(`🕒 ${print_ms(total_timing())}`);
41
+ return;
42
+ }
43
+ // Resolve the input path for the provided task name.
44
+ const input_path = resolve_input_path(task_name);
45
+ // Find the task or directory specified by the `input_path`.
46
+ // Fall back to searching the Gro directory as well.
47
+ const find_modules_result = await find_task_modules([input_path], undefined, [
48
+ gro_sveltekit_dist_dir,
49
+ ]);
50
+ if (find_modules_result.ok) {
51
+ // Found a match either in the current working directory or Gro's directory.
52
+ const path_data = find_modules_result.source_id_path_data_by_input_path.get(input_path); // this is null safe because result is ok
53
+ if (!path_data.isDirectory) {
54
+ // The input path matches a file, so load and run it.
55
+ // Try to load the task module.
56
+ const load_modules_result = await load_modules(find_modules_result.source_ids_by_input_path, load_task_module);
57
+ if (load_modules_result.ok) {
58
+ // We found a task module. Run it!
59
+ // `path_data` is not a directory, so there's a single task module here.
60
+ const task = load_modules_result.modules[0];
61
+ log.info(`→ ${cyan(task.name)} ${(task.mod.task.summary && gray(task.mod.task.summary)) || ''}`);
62
+ const timing_to_run_task = timings.start('run task ' + task_name);
63
+ const result = await run_task(task, { ...args, ...to_forwarded_args(`gro ${task.name}`) }, invoke_task, config, timings);
64
+ timing_to_run_task();
65
+ if (result.ok) {
66
+ log.info(`✓ ${cyan(task.name)}`);
67
+ }
68
+ else {
69
+ log.info(`${red('🞩')} ${cyan(task.name)}`);
70
+ log_error_reasons(log, [result.reason]);
71
+ throw result.error;
72
+ }
73
+ }
74
+ else {
75
+ log_error_reasons(log, load_modules_result.reasons);
76
+ process.exit(1);
77
+ }
78
+ }
79
+ else {
80
+ // The input path matches a directory. Log the tasks but don't run them.
81
+ if (is_this_project_gro) {
82
+ // Is the Gro directory the same as the cwd? Log the matching files.
83
+ await log_available_tasks(log, print_path(path_data.id), find_modules_result.source_ids_by_input_path);
84
+ }
85
+ else if (is_gro_id(path_data.id)) {
86
+ // TODO delete this?
87
+ // Does the Gro directory contain the matching files? Log them.
88
+ await log_available_tasks(log, print_path_or_gro_path(path_data.id), find_modules_result.source_ids_by_input_path);
89
+ }
90
+ else {
91
+ // The Gro directory is not the same as the cwd
92
+ // and it doesn't contain the matching files.
93
+ // Find all of the possible matches in the Gro directory as well,
94
+ // and log everything out.
95
+ // Ignore any errors - the directory may not exist or have any files!
96
+ const gro_dir_find_modules_result = await to_gro_dir_find_modules_result(input_path, log);
97
+ // Then log the current working directory matches.
98
+ await log_available_tasks(log, print_path(path_data.id), find_modules_result.source_ids_by_input_path, !gro_dir_find_modules_result.ok);
99
+ }
100
+ }
101
+ }
102
+ else if (find_modules_result.type === 'input_directories_with_no_files') {
103
+ // The input path matched a directory, but it contains no matching files.
104
+ if (is_this_project_gro ||
105
+ // this is null safe because of the failure type
106
+ is_gro_id(find_modules_result.source_id_path_data_by_input_path.get(input_path).id)) {
107
+ // If the directory is inside Gro, just log the errors.
108
+ log_error_reasons(log, find_modules_result.reasons);
109
+ process.exit(1);
110
+ }
111
+ else {
112
+ // If there's a matching directory in the current working directory,
113
+ // but it has no matching files, we still want to search Gro's directory.
114
+ const gro_dir_find_modules_result = await to_gro_dir_find_modules_result(input_path, log);
115
+ if (!gro_dir_find_modules_result.ok) {
116
+ // Log the original errors, not the Gro-specific ones.
117
+ log_error_reasons(log, find_modules_result.reasons);
118
+ process.exit(1);
119
+ }
120
+ }
121
+ }
122
+ else {
123
+ // Some other find modules result failure happened, so log it out.
124
+ // (currently, just "unmapped_input_paths")
125
+ log_error_reasons(log, find_modules_result.reasons);
126
+ process.exit(1);
127
+ }
128
+ print_timings(timings, log);
129
+ log.info(`🕒 ${print_ms(total_timing())}`);
130
+ };
131
+ const to_gro_dir_find_modules_result = async (input_path, log) => {
132
+ const gro_dir_input_path = to_gro_input_path(input_path);
133
+ const gro_dir_find_modules_result = await find_modules([gro_dir_input_path], (id) => search_fs(id, { filter: (path) => is_task_path(path) }));
134
+ if (gro_dir_find_modules_result.ok) {
135
+ const gro_path_data = gro_dir_find_modules_result.source_id_path_data_by_input_path.get(gro_dir_input_path);
136
+ // Log the Gro matches.
137
+ await log_available_tasks(log, print_path_or_gro_path(gro_path_data.id), gro_dir_find_modules_result.source_ids_by_input_path);
138
+ }
139
+ return gro_dir_find_modules_result;
140
+ };
@@ -0,0 +1,11 @@
1
+ import { z } from 'zod';
2
+ import { type Task } from './task.js';
3
+ export declare const Args: z.ZodObject<{
4
+ _: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
5
+ }, "strict", z.ZodTypeAny, {
6
+ _: string[];
7
+ }, {
8
+ _?: string[] | undefined;
9
+ }>;
10
+ export type Args = z.infer<typeof Args>;
11
+ export declare const task: Task<Args>;
@@ -0,0 +1,29 @@
1
+ import { print_spawn_result } from '@ryanatkn/belt/process.js';
2
+ import { z } from 'zod';
3
+ import { Task_Error } from './task.js';
4
+ import { print_command_args, serialize_args, to_forwarded_args } from './args.js';
5
+ import { SOURCE_DIRNAME } from './paths.js';
6
+ import { find_cli, spawn_cli } from './cli.js';
7
+ export const Args = z
8
+ .object({
9
+ _: z.array(z.string(), { description: 'paths to serve' }).default([SOURCE_DIRNAME]),
10
+ })
11
+ .strict();
12
+ export const task = {
13
+ summary: 'run eslint',
14
+ Args,
15
+ run: async ({ log, args }) => {
16
+ if (!(await find_cli('eslint'))) {
17
+ log.info('ESLint is not installed; skipping linting');
18
+ return;
19
+ }
20
+ const { _ } = args;
21
+ const forwarded_args = { _, 'max-warnings': 0, ...to_forwarded_args('eslint') };
22
+ const serialized_args = serialize_args(forwarded_args);
23
+ log.info(print_command_args(['eslint'].concat(serialized_args)));
24
+ const eslintResult = await spawn_cli('eslint', serialized_args);
25
+ if (!eslintResult?.ok) {
26
+ throw new Task_Error(`ESLint found some problems. ${print_spawn_result(eslintResult)}`);
27
+ }
28
+ },
29
+ };
@@ -0,0 +1,4 @@
1
+ /// <reference types="node" />
2
+ import type { LoadHook, ResolveHook } from 'node:module';
3
+ export declare const load: LoadHook;
4
+ export declare const resolve: ResolveHook;
package/dist/loader.js ADDED
@@ -0,0 +1,153 @@
1
+ import * as esbuild from 'esbuild';
2
+ import { compile, preprocess } from 'svelte/compiler';
3
+ import { fileURLToPath, pathToFileURL } from 'node:url';
4
+ import { dirname, join, relative } from 'node:path';
5
+ import { escape_regexp } from '@ryanatkn/belt/regexp.js';
6
+ import { render_env_shim_module } from './sveltekit_shim_env.js';
7
+ import { render_sveltekit_shim_app_environment, render_sveltekit_shim_app_paths, sveltekit_shim_app_environment_matcher, sveltekit_shim_app_paths_matcher, sveltekit_shim_app_specifiers, } from './sveltekit_shim_app.js';
8
+ import { init_sveltekit_config } from './sveltekit_config.js';
9
+ import { paths, NODE_MODULES_DIRNAME } from './paths.js';
10
+ import { to_define_import_meta_env, ts_transform_options } from './esbuild_helpers.js';
11
+ import { resolve_specifier } from './resolve_specifier.js';
12
+ import { resolve_node_specifier } from './resolve_node_specifier.js';
13
+ /*
14
+
15
+ Usage via `$lib/run.task.ts`:
16
+
17
+ ```bash
18
+ gro run foo.ts
19
+ ```
20
+
21
+ Direct usage (see also `$lib/gro.ts`):
22
+
23
+ ```bash
24
+ 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
25
+ ```
26
+
27
+ TODO how to improve that gnarly import line? was originally designed for the now-deprecated `--loader`
28
+
29
+ */
30
+ // TODO sourcemaps for svelte and the svelte preprocessors
31
+ // TODO `import.meta.resolve` wasn't available in loaders when this was first implemented, but might be now
32
+ // dev is always true in the loader
33
+ const dev = true;
34
+ const dir = paths.root;
35
+ const { alias, base_url, assets_url, env_dir, private_prefix, public_prefix, svelte_compile_options, svelte_preprocessors, } = await init_sveltekit_config(dir); // always load it to keep things simple ahead
36
+ const final_ts_transform_options = {
37
+ ...ts_transform_options,
38
+ define: to_define_import_meta_env(dev, base_url),
39
+ sourcemap: 'inline',
40
+ };
41
+ const aliases = Object.entries({ $lib: 'src/lib', ...alias });
42
+ const ts_matcher = /\.(ts|tsx|mts|cts)$/u;
43
+ const svelte_matcher = /\.(svelte)$/u;
44
+ const json_matcher = /\.(json)$/u;
45
+ const env_matcher = /src\/lib\/\$env\/(static|dynamic)\/(public|private)$/u;
46
+ const node_modules_matcher = new RegExp(escape_regexp('/' + NODE_MODULES_DIRNAME + '/'), 'u');
47
+ const package_json_cache = {};
48
+ export const load = async (url, context, nextLoad) => {
49
+ if (sveltekit_shim_app_paths_matcher.test(url)) {
50
+ // $app/paths shim
51
+ return {
52
+ format: 'module',
53
+ shortCircuit: true,
54
+ source: render_sveltekit_shim_app_paths(base_url, assets_url),
55
+ };
56
+ }
57
+ else if (sveltekit_shim_app_environment_matcher.test(url)) {
58
+ // $app/environment shim
59
+ return {
60
+ format: 'module',
61
+ shortCircuit: true,
62
+ source: render_sveltekit_shim_app_environment(dev),
63
+ };
64
+ }
65
+ else if (ts_matcher.test(url)) {
66
+ // ts
67
+ const loaded = await nextLoad(url, context.format === 'module' ? context : { ...context, format: 'module' });
68
+ const transformed = await esbuild.transform(loaded.source.toString(), // eslint-disable-line @typescript-eslint/no-base-to-string
69
+ { ...final_ts_transform_options, sourcefile: url });
70
+ return { format: 'module', shortCircuit: true, source: transformed.code };
71
+ }
72
+ else if (svelte_matcher.test(url)) {
73
+ // svelte
74
+ // TODO support sourcemaps
75
+ const loaded = await nextLoad(url, context.format === 'module' ? context : { ...context, format: 'module' });
76
+ const raw_source = loaded.source.toString(); // eslint-disable-line @typescript-eslint/no-base-to-string
77
+ const preprocessed = svelte_preprocessors
78
+ ? await preprocess(raw_source, svelte_preprocessors, {
79
+ filename: relative(dir, fileURLToPath(url)),
80
+ })
81
+ : null;
82
+ const source = preprocessed?.code ?? raw_source;
83
+ const transformed = compile(source, svelte_compile_options);
84
+ return { format: 'module', shortCircuit: true, source: transformed.js.code };
85
+ }
86
+ else if (json_matcher.test(url)) {
87
+ // json
88
+ // 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?)
89
+ const loaded = await nextLoad(url);
90
+ const raw_source = loaded.source.toString(); // eslint-disable-line @typescript-eslint/no-base-to-string
91
+ const source = `export default ` + raw_source;
92
+ return { format: 'module', shortCircuit: true, source };
93
+ }
94
+ else {
95
+ // neither ts nor svelte
96
+ const matched_env = env_matcher.exec(url);
97
+ if (matched_env) {
98
+ const mode = matched_env[1];
99
+ const visibility = matched_env[2];
100
+ return {
101
+ format: 'module',
102
+ shortCircuit: true,
103
+ source: await render_env_shim_module(dev, mode, visibility, public_prefix, private_prefix, env_dir),
104
+ };
105
+ }
106
+ }
107
+ return nextLoad(url, context);
108
+ };
109
+ export const resolve = async (specifier, context, nextResolve) => {
110
+ if (specifier === '$env/static/public' ||
111
+ specifier === '$env/static/private' ||
112
+ specifier === '$env/dynamic/public' ||
113
+ specifier === '$env/dynamic/private') {
114
+ // The returned `url` is validated before `load` is called,
115
+ // so we need a slightly roundabout strategy to pass through the specifier for virtual files.
116
+ return {
117
+ url: pathToFileURL(join(dir, 'src/lib', specifier)).href,
118
+ format: 'module',
119
+ shortCircuit: true,
120
+ };
121
+ }
122
+ const parent_url = context.parentURL;
123
+ if (!parent_url || node_modules_matcher.test(parent_url)) {
124
+ return nextResolve(specifier, context);
125
+ }
126
+ const shimmed = sveltekit_shim_app_specifiers.get(specifier);
127
+ if (shimmed !== undefined) {
128
+ return nextResolve(shimmed, context);
129
+ }
130
+ let path = specifier;
131
+ // Map the path with the SvelteKit aliases.
132
+ for (const [from, to] of aliases) {
133
+ if (path.startsWith(from)) {
134
+ path = join(dir, to, path.substring(from.length));
135
+ break;
136
+ }
137
+ }
138
+ // The specifier `path` has now been mapped to its final form, so we can inspect it.
139
+ if (path[0] !== '.' && path[0] !== '/') {
140
+ // Resolve to `node_modules`.
141
+ if (svelte_matcher.test(path) || json_matcher.test(path)) {
142
+ // Match the behavior of Vite and esbuild for Svelte and JSON imports.
143
+ // TODO maybe `.ts` too
144
+ const source_id = await resolve_node_specifier(path, dir, parent_url, package_json_cache);
145
+ return { url: pathToFileURL(source_id).href, format: 'module', shortCircuit: true };
146
+ }
147
+ else {
148
+ return nextResolve(path, context);
149
+ }
150
+ }
151
+ const { source_id } = await resolve_specifier(path, dirname(fileURLToPath(parent_url)));
152
+ return { url: pathToFileURL(source_id).href, format: 'module', shortCircuit: true };
153
+ };
@@ -0,0 +1,3 @@
1
+ export declare const MODULE_PATH_SRC_PREFIX: string;
2
+ export declare const MODULE_PATH_LIB_PREFIX = "$lib/";
3
+ export declare const is_external_module: (module_name: string) => boolean;