@fuzdev/gro 0.192.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 (323) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +283 -0
  3. package/dist/args.d.ts +37 -0
  4. package/dist/args.d.ts.map +1 -0
  5. package/dist/args.js +102 -0
  6. package/dist/build.task.d.ts +20 -0
  7. package/dist/build.task.d.ts.map +1 -0
  8. package/dist/build.task.js +119 -0
  9. package/dist/build_cache.d.ts +100 -0
  10. package/dist/build_cache.d.ts.map +1 -0
  11. package/dist/build_cache.js +299 -0
  12. package/dist/changelog.d.ts +11 -0
  13. package/dist/changelog.d.ts.map +1 -0
  14. package/dist/changelog.js +47 -0
  15. package/dist/changeset.task.d.ts +35 -0
  16. package/dist/changeset.task.d.ts.map +1 -0
  17. package/dist/changeset.task.js +151 -0
  18. package/dist/changeset_helpers.d.ts +17 -0
  19. package/dist/changeset_helpers.d.ts.map +1 -0
  20. package/dist/changeset_helpers.js +7 -0
  21. package/dist/check.task.d.ts +28 -0
  22. package/dist/check.task.d.ts.map +1 -0
  23. package/dist/check.task.js +104 -0
  24. package/dist/child_process_logging.d.ts +10 -0
  25. package/dist/child_process_logging.d.ts.map +1 -0
  26. package/dist/child_process_logging.js +26 -0
  27. package/dist/clean.task.d.ts +15 -0
  28. package/dist/clean.task.d.ts.map +1 -0
  29. package/dist/clean.task.js +40 -0
  30. package/dist/clean_fs.d.ts +9 -0
  31. package/dist/clean_fs.d.ts.map +1 -0
  32. package/dist/clean_fs.js +28 -0
  33. package/dist/cli.d.ts +34 -0
  34. package/dist/cli.d.ts.map +1 -0
  35. package/dist/cli.js +61 -0
  36. package/dist/commit.task.d.ts +11 -0
  37. package/dist/commit.task.d.ts.map +1 -0
  38. package/dist/commit.task.js +24 -0
  39. package/dist/constants.d.ts +46 -0
  40. package/dist/constants.d.ts.map +1 -0
  41. package/dist/constants.js +52 -0
  42. package/dist/deploy.task.d.ts +29 -0
  43. package/dist/deploy.task.d.ts.map +1 -0
  44. package/dist/deploy.task.js +217 -0
  45. package/dist/dev.task.d.ts +16 -0
  46. package/dist/dev.task.d.ts.map +1 -0
  47. package/dist/dev.task.js +44 -0
  48. package/dist/disknode.d.ts +23 -0
  49. package/dist/disknode.d.ts.map +1 -0
  50. package/dist/disknode.js +1 -0
  51. package/dist/env.d.ts +11 -0
  52. package/dist/env.d.ts.map +1 -0
  53. package/dist/env.js +49 -0
  54. package/dist/esbuild_helpers.d.ts +16 -0
  55. package/dist/esbuild_helpers.d.ts.map +1 -0
  56. package/dist/esbuild_helpers.js +36 -0
  57. package/dist/esbuild_plugin_external_worker.d.ts +23 -0
  58. package/dist/esbuild_plugin_external_worker.d.ts.map +1 -0
  59. package/dist/esbuild_plugin_external_worker.js +55 -0
  60. package/dist/esbuild_plugin_svelte.d.ts +15 -0
  61. package/dist/esbuild_plugin_svelte.d.ts.map +1 -0
  62. package/dist/esbuild_plugin_svelte.js +83 -0
  63. package/dist/esbuild_plugin_sveltekit_local_imports.d.ts +8 -0
  64. package/dist/esbuild_plugin_sveltekit_local_imports.d.ts.map +1 -0
  65. package/dist/esbuild_plugin_sveltekit_local_imports.js +30 -0
  66. package/dist/esbuild_plugin_sveltekit_shim_alias.d.ts +7 -0
  67. package/dist/esbuild_plugin_sveltekit_shim_alias.d.ts.map +1 -0
  68. package/dist/esbuild_plugin_sveltekit_shim_alias.js +18 -0
  69. package/dist/esbuild_plugin_sveltekit_shim_app.d.ts +9 -0
  70. package/dist/esbuild_plugin_sveltekit_shim_app.d.ts.map +1 -0
  71. package/dist/esbuild_plugin_sveltekit_shim_app.js +22 -0
  72. package/dist/esbuild_plugin_sveltekit_shim_env.d.ts +11 -0
  73. package/dist/esbuild_plugin_sveltekit_shim_env.d.ts.map +1 -0
  74. package/dist/esbuild_plugin_sveltekit_shim_env.js +18 -0
  75. package/dist/filer.d.ts +33 -0
  76. package/dist/filer.d.ts.map +1 -0
  77. package/dist/filer.js +385 -0
  78. package/dist/format.task.d.ts +11 -0
  79. package/dist/format.task.d.ts.map +1 -0
  80. package/dist/format.task.js +27 -0
  81. package/dist/format_directory.d.ts +13 -0
  82. package/dist/format_directory.d.ts.map +1 -0
  83. package/dist/format_directory.js +40 -0
  84. package/dist/format_file.d.ts +9 -0
  85. package/dist/format_file.d.ts.map +1 -0
  86. package/dist/format_file.js +42 -0
  87. package/dist/gen.d.ts +142 -0
  88. package/dist/gen.d.ts.map +1 -0
  89. package/dist/gen.js +199 -0
  90. package/dist/gen.task.d.ts +12 -0
  91. package/dist/gen.task.d.ts.map +1 -0
  92. package/dist/gen.task.js +149 -0
  93. package/dist/gen_helpers.d.ts +11 -0
  94. package/dist/gen_helpers.d.ts.map +1 -0
  95. package/dist/gen_helpers.js +76 -0
  96. package/dist/github.d.ts +19 -0
  97. package/dist/github.d.ts.map +1 -0
  98. package/dist/github.js +33 -0
  99. package/dist/gro.config.default.d.ts +13 -0
  100. package/dist/gro.config.default.d.ts.map +1 -0
  101. package/dist/gro.config.default.js +33 -0
  102. package/dist/gro.d.ts +3 -0
  103. package/dist/gro.d.ts.map +1 -0
  104. package/dist/gro.js +21 -0
  105. package/dist/gro_config.d.ts +115 -0
  106. package/dist/gro_config.d.ts.map +1 -0
  107. package/dist/gro_config.js +114 -0
  108. package/dist/gro_helpers.d.ts +49 -0
  109. package/dist/gro_helpers.d.ts.map +1 -0
  110. package/dist/gro_helpers.js +97 -0
  111. package/dist/gro_plugin_gen.d.ts +12 -0
  112. package/dist/gro_plugin_gen.d.ts.map +1 -0
  113. package/dist/gro_plugin_gen.js +101 -0
  114. package/dist/gro_plugin_server.d.ts +80 -0
  115. package/dist/gro_plugin_server.d.ts.map +1 -0
  116. package/dist/gro_plugin_server.js +167 -0
  117. package/dist/gro_plugin_sveltekit_app.d.ts +9 -0
  118. package/dist/gro_plugin_sveltekit_app.d.ts.map +1 -0
  119. package/dist/gro_plugin_sveltekit_app.js +42 -0
  120. package/dist/gro_plugin_sveltekit_library.d.ts +16 -0
  121. package/dist/gro_plugin_sveltekit_library.d.ts.map +1 -0
  122. package/dist/gro_plugin_sveltekit_library.js +34 -0
  123. package/dist/index.d.ts +9 -0
  124. package/dist/index.d.ts.map +1 -0
  125. package/dist/index.js +4 -0
  126. package/dist/input_path.d.ts +64 -0
  127. package/dist/input_path.d.ts.map +1 -0
  128. package/dist/input_path.js +199 -0
  129. package/dist/invoke.d.ts +2 -0
  130. package/dist/invoke.d.ts.map +1 -0
  131. package/dist/invoke.js +28 -0
  132. package/dist/invoke_task.d.ts +30 -0
  133. package/dist/invoke_task.d.ts.map +1 -0
  134. package/dist/invoke_task.js +104 -0
  135. package/dist/lint.task.d.ts +11 -0
  136. package/dist/lint.task.d.ts.map +1 -0
  137. package/dist/lint.task.js +32 -0
  138. package/dist/loader.d.ts +6 -0
  139. package/dist/loader.d.ts.map +1 -0
  140. package/dist/loader.js +192 -0
  141. package/dist/module.d.ts +4 -0
  142. package/dist/module.d.ts.map +1 -0
  143. package/dist/module.js +6 -0
  144. package/dist/modules.d.ts +36 -0
  145. package/dist/modules.d.ts.map +1 -0
  146. package/dist/modules.js +71 -0
  147. package/dist/package_json.d.ts +32 -0
  148. package/dist/package_json.d.ts.map +1 -0
  149. package/dist/package_json.js +178 -0
  150. package/dist/parse_exports.d.ts +20 -0
  151. package/dist/parse_exports.d.ts.map +1 -0
  152. package/dist/parse_exports.js +65 -0
  153. package/dist/parse_exports_context.d.ts +21 -0
  154. package/dist/parse_exports_context.d.ts.map +1 -0
  155. package/dist/parse_exports_context.js +332 -0
  156. package/dist/parse_imports.d.ts +5 -0
  157. package/dist/parse_imports.d.ts.map +1 -0
  158. package/dist/parse_imports.js +140 -0
  159. package/dist/paths.d.ts +41 -0
  160. package/dist/paths.d.ts.map +1 -0
  161. package/dist/paths.js +69 -0
  162. package/dist/plugin.d.ts +36 -0
  163. package/dist/plugin.d.ts.map +1 -0
  164. package/dist/plugin.js +78 -0
  165. package/dist/publish.task.d.ts +26 -0
  166. package/dist/publish.task.d.ts.map +1 -0
  167. package/dist/publish.task.js +176 -0
  168. package/dist/register.d.ts +2 -0
  169. package/dist/register.d.ts.map +1 -0
  170. package/dist/register.js +2 -0
  171. package/dist/reinstall.task.d.ts +8 -0
  172. package/dist/reinstall.task.d.ts.map +1 -0
  173. package/dist/reinstall.task.js +35 -0
  174. package/dist/release.task.d.ts +8 -0
  175. package/dist/release.task.d.ts.map +1 -0
  176. package/dist/release.task.js +20 -0
  177. package/dist/resolve.task.d.ts +11 -0
  178. package/dist/resolve.task.d.ts.map +1 -0
  179. package/dist/resolve.task.js +38 -0
  180. package/dist/resolve_specifier.d.ts +22 -0
  181. package/dist/resolve_specifier.d.ts.map +1 -0
  182. package/dist/resolve_specifier.js +57 -0
  183. package/dist/run.task.d.ts +16 -0
  184. package/dist/run.task.d.ts.map +1 -0
  185. package/dist/run.task.js +52 -0
  186. package/dist/run_gen.d.ts +10 -0
  187. package/dist/run_gen.d.ts.map +1 -0
  188. package/dist/run_gen.js +73 -0
  189. package/dist/run_task.d.ts +17 -0
  190. package/dist/run_task.d.ts.map +1 -0
  191. package/dist/run_task.js +45 -0
  192. package/dist/source_json.d.ts +7 -0
  193. package/dist/source_json.d.ts.map +1 -0
  194. package/dist/source_json.js +145 -0
  195. package/dist/svelte_config.d.ts +57 -0
  196. package/dist/svelte_config.d.ts.map +1 -0
  197. package/dist/svelte_config.js +81 -0
  198. package/dist/sveltekit_helpers.d.ts +75 -0
  199. package/dist/sveltekit_helpers.d.ts.map +1 -0
  200. package/dist/sveltekit_helpers.js +94 -0
  201. package/dist/sveltekit_shim_app.d.ts +11 -0
  202. package/dist/sveltekit_shim_app.d.ts.map +1 -0
  203. package/dist/sveltekit_shim_app.js +31 -0
  204. package/dist/sveltekit_shim_app_environment.d.ts +13 -0
  205. package/dist/sveltekit_shim_app_environment.d.ts.map +1 -0
  206. package/dist/sveltekit_shim_app_environment.js +14 -0
  207. package/dist/sveltekit_shim_app_forms.d.ts +5 -0
  208. package/dist/sveltekit_shim_app_forms.d.ts.map +1 -0
  209. package/dist/sveltekit_shim_app_forms.js +6 -0
  210. package/dist/sveltekit_shim_app_navigation.d.ts +10 -0
  211. package/dist/sveltekit_shim_app_navigation.d.ts.map +1 -0
  212. package/dist/sveltekit_shim_app_navigation.js +11 -0
  213. package/dist/sveltekit_shim_app_paths.d.ts +17 -0
  214. package/dist/sveltekit_shim_app_paths.d.ts.map +1 -0
  215. package/dist/sveltekit_shim_app_paths.js +10 -0
  216. package/dist/sveltekit_shim_app_state.d.ts +5 -0
  217. package/dist/sveltekit_shim_app_state.d.ts.map +1 -0
  218. package/dist/sveltekit_shim_app_state.js +26 -0
  219. package/dist/sveltekit_shim_env.d.ts +5 -0
  220. package/dist/sveltekit_shim_env.d.ts.map +1 -0
  221. package/dist/sveltekit_shim_env.js +23 -0
  222. package/dist/sync.task.d.ts +16 -0
  223. package/dist/sync.task.d.ts.map +1 -0
  224. package/dist/sync.task.js +39 -0
  225. package/dist/task.d.ts +98 -0
  226. package/dist/task.d.ts.map +1 -0
  227. package/dist/task.js +109 -0
  228. package/dist/task_logging.d.ts +6 -0
  229. package/dist/task_logging.d.ts.map +1 -0
  230. package/dist/task_logging.js +201 -0
  231. package/dist/test.task.d.ts +13 -0
  232. package/dist/test.task.d.ts.map +1 -0
  233. package/dist/test.task.js +53 -0
  234. package/dist/typecheck.task.d.ts +13 -0
  235. package/dist/typecheck.task.d.ts.map +1 -0
  236. package/dist/typecheck.task.js +68 -0
  237. package/dist/upgrade.task.d.ts +20 -0
  238. package/dist/upgrade.task.d.ts.map +1 -0
  239. package/dist/upgrade.task.js +111 -0
  240. package/dist/watch_dir.d.ts +36 -0
  241. package/dist/watch_dir.d.ts.map +1 -0
  242. package/dist/watch_dir.js +69 -0
  243. package/package.json +149 -0
  244. package/src/lib/args.ts +115 -0
  245. package/src/lib/build.task.ts +151 -0
  246. package/src/lib/build_cache.ts +378 -0
  247. package/src/lib/changelog.ts +69 -0
  248. package/src/lib/changeset.task.ts +228 -0
  249. package/src/lib/changeset_helpers.ts +14 -0
  250. package/src/lib/check.task.ts +132 -0
  251. package/src/lib/child_process_logging.ts +38 -0
  252. package/src/lib/clean.task.ts +48 -0
  253. package/src/lib/clean_fs.ts +54 -0
  254. package/src/lib/cli.ts +98 -0
  255. package/src/lib/commit.task.ts +34 -0
  256. package/src/lib/constants.ts +56 -0
  257. package/src/lib/deploy.task.ts +287 -0
  258. package/src/lib/dev.task.ts +52 -0
  259. package/src/lib/disknode.ts +26 -0
  260. package/src/lib/env.ts +78 -0
  261. package/src/lib/esbuild_helpers.ts +49 -0
  262. package/src/lib/esbuild_plugin_external_worker.ts +94 -0
  263. package/src/lib/esbuild_plugin_svelte.ts +134 -0
  264. package/src/lib/esbuild_plugin_sveltekit_local_imports.ts +38 -0
  265. package/src/lib/esbuild_plugin_sveltekit_shim_alias.ts +27 -0
  266. package/src/lib/esbuild_plugin_sveltekit_shim_app.ts +42 -0
  267. package/src/lib/esbuild_plugin_sveltekit_shim_env.ts +47 -0
  268. package/src/lib/filer.ts +458 -0
  269. package/src/lib/format.task.ts +44 -0
  270. package/src/lib/format_directory.ts +65 -0
  271. package/src/lib/format_file.ts +49 -0
  272. package/src/lib/gen.task.ts +206 -0
  273. package/src/lib/gen.ts +406 -0
  274. package/src/lib/gen_helpers.ts +131 -0
  275. package/src/lib/github.ts +46 -0
  276. package/src/lib/gro.config.default.ts +42 -0
  277. package/src/lib/gro.ts +29 -0
  278. package/src/lib/gro_config.ts +254 -0
  279. package/src/lib/gro_helpers.ts +108 -0
  280. package/src/lib/gro_plugin_gen.ts +149 -0
  281. package/src/lib/gro_plugin_server.ts +288 -0
  282. package/src/lib/gro_plugin_sveltekit_app.ts +58 -0
  283. package/src/lib/gro_plugin_sveltekit_library.ts +63 -0
  284. package/src/lib/index.ts +8 -0
  285. package/src/lib/input_path.ts +254 -0
  286. package/src/lib/invoke.ts +34 -0
  287. package/src/lib/invoke_task.ts +139 -0
  288. package/src/lib/lint.task.ts +39 -0
  289. package/src/lib/loader.ts +229 -0
  290. package/src/lib/module.ts +13 -0
  291. package/src/lib/modules.ts +117 -0
  292. package/src/lib/package_json.ts +255 -0
  293. package/src/lib/parse_exports.ts +100 -0
  294. package/src/lib/parse_exports_context.ts +395 -0
  295. package/src/lib/parse_imports.ts +180 -0
  296. package/src/lib/paths.ts +111 -0
  297. package/src/lib/plugin.ts +106 -0
  298. package/src/lib/publish.task.ts +228 -0
  299. package/src/lib/register.ts +3 -0
  300. package/src/lib/reinstall.task.ts +45 -0
  301. package/src/lib/release.task.ts +26 -0
  302. package/src/lib/resolve.task.ts +43 -0
  303. package/src/lib/resolve_specifier.ts +81 -0
  304. package/src/lib/run.task.ts +65 -0
  305. package/src/lib/run_gen.ts +110 -0
  306. package/src/lib/run_task.ts +82 -0
  307. package/src/lib/source_json.ts +183 -0
  308. package/src/lib/svelte_config.ts +140 -0
  309. package/src/lib/sveltekit_helpers.ts +193 -0
  310. package/src/lib/sveltekit_shim_app.ts +41 -0
  311. package/src/lib/sveltekit_shim_app_environment.ts +16 -0
  312. package/src/lib/sveltekit_shim_app_forms.ts +13 -0
  313. package/src/lib/sveltekit_shim_app_navigation.ts +23 -0
  314. package/src/lib/sveltekit_shim_app_paths.ts +26 -0
  315. package/src/lib/sveltekit_shim_app_state.ts +35 -0
  316. package/src/lib/sveltekit_shim_env.ts +45 -0
  317. package/src/lib/sync.task.ts +47 -0
  318. package/src/lib/task.ts +245 -0
  319. package/src/lib/task_logging.ts +255 -0
  320. package/src/lib/test.task.ts +63 -0
  321. package/src/lib/typecheck.task.ts +81 -0
  322. package/src/lib/upgrade.task.ts +148 -0
  323. package/src/lib/watch_dir.ts +115 -0
@@ -0,0 +1,131 @@
1
+ import {resolve} from 'node:path';
2
+ import type {Logger} from '@fuzdev/fuz_util/log.js';
3
+ import type {Timings} from '@fuzdev/fuz_util/timings.js';
4
+ import type {PathId} from '@fuzdev/fuz_util/path.js';
5
+
6
+ import type {GroConfig} from './gro_config.ts';
7
+ import {filter_dependents, type Filer} from './filer.ts';
8
+ import type {InvokeTask} from './task.ts';
9
+ import {
10
+ normalize_gen_config,
11
+ validate_gen_module,
12
+ type GenContext,
13
+ type GenDependencies,
14
+ type GenDependenciesConfig,
15
+ } from './gen.ts';
16
+ import {default_svelte_config} from './svelte_config.ts';
17
+ import {to_root_path} from './paths.ts';
18
+ import {load_module} from './modules.ts';
19
+
20
+ /**
21
+ * Check if a file change should trigger a gen file.
22
+ */
23
+ export const should_trigger_gen = async (
24
+ gen_file_id: PathId,
25
+ changed_file_id: PathId,
26
+ config: GroConfig,
27
+ filer: Filer,
28
+ log: Logger,
29
+ timings: Timings,
30
+ invoke_task: InvokeTask,
31
+ ): Promise<boolean> => {
32
+ // Always trigger if the gen file itself changed
33
+ const is_self_change = gen_file_id === changed_file_id;
34
+ if (is_self_change) return true;
35
+
36
+ // Check if gen file depends on the changed file (directly or transitively)
37
+ const changed_disknode = filer.get_by_id(changed_file_id);
38
+ let should_bust_cache = false;
39
+ if (changed_disknode) {
40
+ const dependents = filter_dependents(
41
+ changed_disknode,
42
+ filer.get_by_id,
43
+ (id) => id === gen_file_id, // filter for just our gen file
44
+ );
45
+ should_bust_cache = dependents.has(gen_file_id);
46
+ }
47
+
48
+ // Resolve dependencies (with cache busting if the changed file is a dependency of the gen file)
49
+ const dependencies = await resolve_gen_dependencies(
50
+ gen_file_id,
51
+ changed_file_id,
52
+ should_bust_cache,
53
+ config,
54
+ filer,
55
+ log,
56
+ timings,
57
+ invoke_task,
58
+ );
59
+
60
+ if (!dependencies) return false;
61
+
62
+ return (
63
+ dependencies === 'all' ||
64
+ dependencies.patterns?.some((p) => p.test(changed_file_id)) ||
65
+ dependencies.files?.includes(changed_file_id) ||
66
+ false
67
+ );
68
+ };
69
+
70
+ /**
71
+ * Resolve dependencies for a gen file.
72
+ * Uses cache-busting only when the gen file itself changes,
73
+ * otherwise relies on Node's module caching.
74
+ */
75
+ const resolve_gen_dependencies = async (
76
+ gen_file_id: string,
77
+ changed_file_id: PathId | undefined,
78
+ bust_cache: boolean,
79
+ config: GroConfig,
80
+ filer: Filer,
81
+ log: Logger,
82
+ timings: Timings,
83
+ invoke_task: InvokeTask,
84
+ ): Promise<GenDependenciesConfig | 'all' | null> => {
85
+ const result = await load_module(gen_file_id, validate_gen_module, bust_cache);
86
+
87
+ if (!result.ok) {
88
+ if (result.type === 'failed_import') {
89
+ log.error(`Failed to import ${gen_file_id}:`, result.error);
90
+ }
91
+ return null;
92
+ }
93
+
94
+ const gen_config = normalize_gen_config(result.mod.gen);
95
+ if (!gen_config.dependencies) {
96
+ return null;
97
+ }
98
+
99
+ let dependencies: GenDependencies | null = gen_config.dependencies;
100
+ if (typeof dependencies === 'function') {
101
+ const gen_ctx: GenContext = {
102
+ config,
103
+ svelte_config: default_svelte_config,
104
+ filer,
105
+ log,
106
+ timings,
107
+ invoke_task,
108
+ origin_id: gen_file_id,
109
+ origin_path: to_root_path(gen_file_id),
110
+ changed_file_id,
111
+ };
112
+ dependencies = await dependencies(gen_ctx);
113
+ }
114
+
115
+ if (dependencies === null || dependencies === 'all') {
116
+ return dependencies;
117
+ }
118
+
119
+ // At this point, dependencies is either 'all' or GenDependenciesConfig
120
+ // For static dependencies, also normalize empty objects
121
+ if (!dependencies.patterns?.length && !dependencies.files?.length) {
122
+ return null;
123
+ }
124
+
125
+ // Normalize file paths to absolute paths
126
+ if (dependencies.files) {
127
+ dependencies.files = dependencies.files.map((f) => resolve(f));
128
+ }
129
+
130
+ return dependencies;
131
+ };
@@ -0,0 +1,46 @@
1
+ // TODO if this grows at all, use `@octokit/request`,
2
+ // for now it's just calling a single endpoint so we do it manually
3
+ // and we specify just the types we need
4
+
5
+ import {FetchValueCache, fetch_value} from '@fuzdev/fuz_util/fetch.js';
6
+ import type {Logger} from '@fuzdev/fuz_util/log.js';
7
+ import {z} from 'zod';
8
+
9
+ export const GITHUB_REPO_MATCHER = /.+github.com\/(.+)\/(.+)/;
10
+
11
+ export const GithubPullRequest = z.looseObject({
12
+ url: z.string(),
13
+ id: z.number(),
14
+ html_url: z.string(),
15
+ number: z.number(),
16
+ user: z.looseObject({
17
+ login: z.string(),
18
+ }),
19
+ });
20
+ export type GithubPullRequest = z.infer<typeof GithubPullRequest>;
21
+
22
+ /**
23
+ * @see https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#list-pull-requests-associated-with-a-commit
24
+ */
25
+ export const github_fetch_commit_prs = async (
26
+ owner: string,
27
+ repo: string,
28
+ commit_sha: string,
29
+ token?: string,
30
+ log?: Logger,
31
+ cache?: FetchValueCache,
32
+ api_version?: string,
33
+ ): Promise<Array<GithubPullRequest> | null> => {
34
+ const headers = api_version ? new Headers({'x-github-api-version': api_version}) : undefined;
35
+ const url = `https://api.github.com/repos/${owner}/${repo}/commits/${commit_sha}/pulls`;
36
+ const fetched = await fetch_value(url, {
37
+ request: {headers},
38
+ parse: (v: Array<any>) => v.map((p) => GithubPullRequest.parse(p)),
39
+ token,
40
+ cache,
41
+ return_early_from_cache: true,
42
+ log,
43
+ });
44
+ if (!fetched.ok) return null;
45
+ return fetched.value;
46
+ };
@@ -0,0 +1,42 @@
1
+ import type {CreateGroConfig} from './gro_config.ts';
2
+ import {gro_plugin_sveltekit_library} from './gro_plugin_sveltekit_library.ts';
3
+ import {has_server, gro_plugin_server} from './gro_plugin_server.ts';
4
+ import {gro_plugin_sveltekit_app} from './gro_plugin_sveltekit_app.ts';
5
+ import {has_sveltekit_app, has_sveltekit_library} from './sveltekit_helpers.ts';
6
+ import {gro_plugin_gen} from './gro_plugin_gen.ts';
7
+ import {package_json_load} from './package_json.ts';
8
+
9
+ // TODO hacky, maybe extract utils?
10
+
11
+ /**
12
+ * This is the default config that's passed to `gro.config.ts`
13
+ * if it exists in the current project, and if not, this is the final config.
14
+ * It looks at the SvelteKit config and filesystem and tries to do the right thing:
15
+ *
16
+ * - if `src/routes`, assumes a SvelteKit frontend - respects `KitConfig.kit.files.routes`
17
+ * - if `src/lib`, assumes a Node library - respects `KitConfig.kit.files.lib`
18
+ * - if `src/lib/server/server.ts`, assumes a Node server - needs config
19
+ */
20
+ const config: CreateGroConfig = async (cfg, svelte_config) => {
21
+ const package_json = await package_json_load(); // TODO gets wastefully loaded by some plugins, maybe put in plugin/task context? how does that interact with `map_package_json`?
22
+
23
+ const [has_server_result, has_sveltekit_library_result, has_sveltekit_app_result] =
24
+ await Promise.all([
25
+ has_server(),
26
+ has_sveltekit_library(package_json, svelte_config),
27
+ has_sveltekit_app(),
28
+ ]);
29
+
30
+ // put things that generate files before SvelteKit so it can see them
31
+ cfg.plugins = () =>
32
+ [
33
+ gro_plugin_gen(),
34
+ has_server_result.ok ? gro_plugin_server() : null,
35
+ has_sveltekit_library_result.ok ? gro_plugin_sveltekit_library() : null,
36
+ has_sveltekit_app_result.ok ? gro_plugin_sveltekit_app() : null,
37
+ ].filter((v) => v !== null);
38
+
39
+ return cfg;
40
+ };
41
+
42
+ export default config;
package/src/lib/gro.ts ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env -S node --experimental-import-meta-resolve --experimental-strip-types --disable-warning=ExperimentalWarning
2
+
3
+ // @sync Node options to `$lib/gro_helpers.ts`
4
+
5
+ import {join} from 'node:path';
6
+
7
+ import {spawn_result_is_exited} from '@fuzdev/fuz_util/process.js';
8
+
9
+ import {resolve_gro_module_path, spawn_with_loader} from './gro_helpers.ts';
10
+
11
+ /*
12
+
13
+ This file is a loader for the Gro CLI.
14
+ Its only purpose is to import the `invoke.js` script in the correct directory.
15
+ By using `resolve_gro_module_path` it lets the global Gro CLI defer
16
+ to a local installation of Gro if one is available,
17
+ and it also provides special handling for the case
18
+ where we're running Gro inside Gro's own repo for development.
19
+
20
+ */
21
+
22
+ const invoke_path = resolve_gro_module_path('invoke.js');
23
+
24
+ const loader_path = join(invoke_path, '../loader.js');
25
+
26
+ const spawned = await spawn_with_loader(loader_path, invoke_path, process.argv.slice(2));
27
+ if (!spawned.ok) {
28
+ process.exitCode = spawn_result_is_exited(spawned) ? spawned.code : 1;
29
+ }
@@ -0,0 +1,254 @@
1
+ import {join, resolve} from 'node:path';
2
+ import {fs_exists} from '@fuzdev/fuz_util/fs.js';
3
+ import {identity} from '@fuzdev/fuz_util/function.js';
4
+ import type {PathFilter, PathId} from '@fuzdev/fuz_util/path.js';
5
+ import {json_stringify_deterministic} from '@fuzdev/fuz_util/json.js';
6
+ import {hash_secure} from '@fuzdev/fuz_util/hash.js';
7
+
8
+ import {GRO_DIST_DIR, IS_THIS_GRO, paths} from './paths.ts';
9
+ import {
10
+ GRO_CONFIG_FILENAME,
11
+ JS_CLI_DEFAULT,
12
+ NODE_MODULES_DIRNAME,
13
+ PM_CLI_DEFAULT,
14
+ SERVER_DIST_PATH,
15
+ SVELTEKIT_BUILD_DIRNAME,
16
+ SVELTEKIT_DIST_DIRNAME,
17
+ } from './constants.ts';
18
+ import create_default_config from './gro.config.default.ts';
19
+ import type {PluginsCreateConfig} from './plugin.ts';
20
+ import type {PackageJsonMapper} from './package_json.ts';
21
+ import type {ParsedSvelteConfig} from './svelte_config.ts';
22
+ import type {FilerOptions} from './filer.ts';
23
+
24
+ /**
25
+ * SHA-256 hash of empty string, used for configs without build_cache_config.
26
+ * This ensures consistent cache behavior when no custom config is provided.
27
+ */
28
+ export const EMPTY_BUILD_CACHE_CONFIG_HASH =
29
+ 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
30
+
31
+ /**
32
+ * The config that users can extend via `gro.config.ts`.
33
+ * This is exposed to users in places like tasks and genfiles.
34
+ * @see https://github.com/fuzdev/gro/blob/main/src/docs/config.md
35
+ */
36
+ export interface GroConfig extends RawGroConfig {
37
+ /**
38
+ * @see https://github.com/fuzdev/gro/blob/main/src/docs/plugin.md
39
+ */
40
+ plugins: PluginsCreateConfig;
41
+ /**
42
+ * Maps the project's `package.json` before writing it to the filesystem.
43
+ * The `package_json` argument may be mutated, but the return value is what's used by the caller.
44
+ * Returning `null` is a no-op for the caller.
45
+ */
46
+ map_package_json: PackageJsonMapper | null;
47
+ /**
48
+ * The root directories to search for tasks given implicit relative input paths.
49
+ * Defaults to `./src/lib`, then the cwd, then the Gro package dist.
50
+ */
51
+ task_root_dirs: Array<PathId>;
52
+ /**
53
+ * When searching the filsystem for tasks and genfiles,
54
+ * directories and files are included if they pass all of these filters.
55
+ */
56
+ search_filters: Array<PathFilter>;
57
+ /**
58
+ * The CLI to use that's compatible with `node`.
59
+ */
60
+ js_cli: string;
61
+ /**
62
+ * The CLI to use that's compatible with `npm install` and `npm link`. Defaults to `'npm'`.
63
+ */
64
+ pm_cli: string;
65
+ /** @default SVELTE_CONFIG_FILENAME */
66
+ svelte_config_filename?: string;
67
+ /**
68
+ * SHA-256 hash of the user's `build_cache_config` from `gro.config.ts`.
69
+ * This is computed during config normalization and the raw value is immediately deleted.
70
+ * If no `build_cache_config` was provided, this is the hash of an empty string.
71
+ * @see RawGroConfig.build_cache_config
72
+ */
73
+ build_cache_config_hash: string;
74
+ /**
75
+ * Options passed to the `Filer` for file watching and import resolution.
76
+ * @see FilerOptions
77
+ */
78
+ filer_options: Partial<FilerOptions> | null;
79
+ }
80
+
81
+ /**
82
+ * The relaxed variant of `GroConfig` that users can provide via `gro.config.ts`.
83
+ * Superset of `GroConfig`.
84
+ * @see https://github.com/fuzdev/gro/blob/main/src/docs/config.md
85
+ */
86
+ export interface RawGroConfig {
87
+ plugins?: PluginsCreateConfig;
88
+ map_package_json?: PackageJsonMapper | null;
89
+ task_root_dirs?: Array<string>;
90
+ search_filters?: PathFilter | Array<PathFilter> | null;
91
+ js_cli?: string;
92
+ pm_cli?: string;
93
+ /**
94
+ * Optional object defining custom build inputs for cache invalidation.
95
+ * This value is hashed during config normalization and used to detect
96
+ * when builds need to be regenerated due to non-source changes.
97
+ *
98
+ * Use cases:
99
+ * - Environment variables baked into build: `{api_url: process.env.PUBLIC_API_URL}`
100
+ * - External data files: `{data: fs.readFileSync('data.json', 'utf-8')}`
101
+ * - Build feature flags: `{enable_analytics: true}`
102
+ *
103
+ * Can be a static object or an async function that returns an object.
104
+ *
105
+ * IMPORTANT: It's safe to include secrets here because they are hashed and `delete`d
106
+ * during config normalization. The raw value is never logged or persisted.
107
+ */
108
+ build_cache_config?:
109
+ | Record<string, unknown>
110
+ | (() => Record<string, unknown> | Promise<Record<string, unknown>>);
111
+ /**
112
+ * Options passed to the `Filer` for file watching and import resolution.
113
+ * @see FilerOptions
114
+ */
115
+ filer_options?: Partial<FilerOptions> | null;
116
+ }
117
+
118
+ export type CreateGroConfig = (
119
+ base_config: GroConfig,
120
+ svelte_config?: ParsedSvelteConfig,
121
+ ) => RawGroConfig | Promise<RawGroConfig>;
122
+
123
+ export const create_empty_gro_config = (): GroConfig => ({
124
+ plugins: () => [],
125
+ map_package_json: identity,
126
+ task_root_dirs: [
127
+ // TODO maybe disable if no SvelteKit `lib` directory? or other detection to improve defaults
128
+ paths.lib,
129
+ IS_THIS_GRO ? null : paths.root,
130
+ IS_THIS_GRO ? null : GRO_DIST_DIR,
131
+ ].filter((v) => v !== null),
132
+ search_filters: [(id) => !SEARCH_EXCLUDER_DEFAULT.test(id)],
133
+ js_cli: JS_CLI_DEFAULT,
134
+ pm_cli: PM_CLI_DEFAULT,
135
+ build_cache_config_hash: EMPTY_BUILD_CACHE_CONFIG_HASH,
136
+ filer_options: null,
137
+ });
138
+
139
+ /**
140
+ * The regexp used by default to exclude directories and files
141
+ * when searching the filesystem for tasks and genfiles.
142
+ * Customize via `search_filters` in the `GroConfig`.
143
+ * See the test cases for the exact behavior.
144
+ */
145
+ export const SEARCH_EXCLUDER_DEFAULT = new RegExp(
146
+ `(${
147
+ '(^|/)\\.[^/]+' + // exclude all `.`-prefixed directories
148
+ // TODO probably change to `pkg.name` instead of this catch-all (also `gro` below)
149
+ `|(^|/)${NODE_MODULES_DIRNAME}(?!/(@[^/]+/)?gro/${SVELTEKIT_DIST_DIRNAME})` + // exclude `node_modules` unless it's to the Gro directory
150
+ `|(^|/)${SVELTEKIT_BUILD_DIRNAME}` + // exclude the SvelteKit build directory
151
+ `|(^|/)(?<!(^|/)gro/)${SVELTEKIT_DIST_DIRNAME}` + // exclude the SvelteKit dist directory unless it's in the Gro directory
152
+ `|(^|/)${SERVER_DIST_PATH}` + // exclude the Gro server plugin dist directory
153
+ '|(^|/)test' + // exclude test directories
154
+ '|(^|/)benchmark' // exclude benchmark directories
155
+ })($|/)`,
156
+ 'u',
157
+ );
158
+
159
+ export const EXPORTS_EXCLUDER_DEFAULT = /(\.md|\.(test|ignore)\.|\/(test|ignore)\/)/;
160
+
161
+ /**
162
+ * Transforms a `RawGroConfig` to the more strict `GroConfig`.
163
+ * This allows users to provide a more relaxed config.
164
+ * Hashes the `build_cache_config` and deletes the raw value for security.
165
+ */
166
+ export const cook_gro_config = async (raw_config: RawGroConfig): Promise<GroConfig> => {
167
+ const empty_config = create_empty_gro_config();
168
+
169
+ // All of the raw config properties are optional,
170
+ // so fall back to the empty values when `undefined`.
171
+ const {
172
+ plugins = empty_config.plugins,
173
+ map_package_json = empty_config.map_package_json,
174
+ task_root_dirs = empty_config.task_root_dirs,
175
+ search_filters = empty_config.search_filters,
176
+ js_cli = empty_config.js_cli,
177
+ pm_cli = empty_config.pm_cli,
178
+ build_cache_config,
179
+ filer_options = empty_config.filer_options,
180
+ } = raw_config;
181
+
182
+ // Hash build_cache_config and delete the raw value
183
+ // IMPORTANT: Raw value may contain secrets - hash it and delete immediately
184
+ let build_cache_config_hash: string;
185
+ if (!build_cache_config) {
186
+ build_cache_config_hash = EMPTY_BUILD_CACHE_CONFIG_HASH;
187
+ } else {
188
+ // Resolve if it's a function
189
+ const resolved =
190
+ typeof build_cache_config === 'function' ? await build_cache_config() : build_cache_config;
191
+
192
+ // Hash the JSON representation with deterministic key ordering
193
+ build_cache_config_hash = await hash_secure(json_stringify_deterministic(resolved));
194
+ }
195
+
196
+ // Delete the raw value to ensure it doesn't persist in memory
197
+ delete (raw_config as any).build_cache_config;
198
+
199
+ return {
200
+ plugins,
201
+ map_package_json,
202
+ task_root_dirs: task_root_dirs.map((p) => resolve(p)),
203
+ search_filters: Array.isArray(search_filters)
204
+ ? search_filters
205
+ : search_filters
206
+ ? [search_filters]
207
+ : [],
208
+ js_cli,
209
+ pm_cli,
210
+ build_cache_config_hash,
211
+ filer_options: filer_options ?? null,
212
+ };
213
+ };
214
+
215
+ export interface GroConfigModule {
216
+ readonly default: RawGroConfig | CreateGroConfig;
217
+ }
218
+
219
+ export const load_gro_config = async (dir = paths.root): Promise<GroConfig> => {
220
+ const default_config = await cook_gro_config(
221
+ await create_default_config(create_empty_gro_config()),
222
+ );
223
+
224
+ const config_path = join(dir, GRO_CONFIG_FILENAME);
225
+ if (!(await fs_exists(config_path))) {
226
+ // No user config file found, so return the default.
227
+ return default_config;
228
+ }
229
+
230
+ // Import the user's `gro.config.ts`.
231
+ const config_module = await import(config_path);
232
+
233
+ validate_gro_config_module(config_module, config_path);
234
+
235
+ return await cook_gro_config(
236
+ typeof config_module.default === 'function'
237
+ ? await config_module.default(default_config)
238
+ : config_module.default,
239
+ );
240
+ };
241
+
242
+ export const validate_gro_config_module: (
243
+ config_module: any,
244
+ config_path: string,
245
+ ) => asserts config_module is GroConfigModule = (config_module, config_path) => {
246
+ const config = config_module.default;
247
+ if (!config) {
248
+ throw Error(`Invalid Gro config module at ${config_path}: expected a default export`);
249
+ } else if (!(typeof config === 'function' || typeof config === 'object')) {
250
+ throw Error(
251
+ `Invalid Gro config module at ${config_path}: the default export must be a function or object`,
252
+ );
253
+ }
254
+ };
@@ -0,0 +1,108 @@
1
+ import {realpathSync, existsSync} from 'node:fs';
2
+ import {join, resolve} from 'node:path';
3
+ import {fileURLToPath} from 'node:url';
4
+ import {spawn, type SpawnResult} from '@fuzdev/fuz_util/process.js';
5
+
6
+ import {JS_CLI_DEFAULT, NODE_MODULES_DIRNAME, SVELTEKIT_DIST_DIRNAME} from './constants.ts';
7
+
8
+ /*
9
+
10
+ This module is intended to have minimal dependencies to avoid over-imports in the CLI.
11
+
12
+ */
13
+
14
+ /**
15
+ * Resolves a path to an internal Gro file.
16
+ * Prefers any local installation of Gro and falls back to the current CLI context.
17
+ *
18
+ * Uses heuristics to find `path`, so may fail in some rare corner cases.
19
+ * Currently looks for `gro.js` as a sibling to the `path` arg for detection.
20
+ * If this fails for your usecases, rename `gro.js` or open an issue/PR!
21
+ *
22
+ * Used by the CLI and `gro run`.
23
+ *
24
+ * case 1:
25
+ *
26
+ * We're in a directory that has a local installation of Gro at `node_modules/.bin/gro`.
27
+ * Use this local version instead of the global.
28
+ *
29
+ * case 2:
30
+ *
31
+ * We're running Gro inside the Gro repo itself.
32
+ *
33
+ * In this case, we use the build directory instead of dist.
34
+ * There's a paradox here for using Gro inside itself -
35
+ * ideally we use the dist directory because that's what's shipped,
36
+ * but the build directory has all of the tests,
37
+ * and loading two instances of its modules causes problems
38
+ * like `instanceof` checks failing.
39
+ * For now we'll just run from build and see if it causes any problems.
40
+ * There's probably a better design in here somewhere.
41
+ *
42
+ * case 3:
43
+ *
44
+ * Fall back to invoking Gro from wherever the CLI is being executed.
45
+ * When using the global CLI, this uses the global Gro installation.
46
+ *
47
+ */
48
+ export const resolve_gro_module_path = (path = ''): string => {
49
+ const gro_bin_path = resolve(NODE_MODULES_DIRNAME, '.bin/gro');
50
+ // case 1
51
+ // Prefer any locally installed version of Gro.
52
+ // This is really confusing if Gro is installed inside Gro itself,
53
+ // so avoid that when developing Gro.
54
+ if (existsSync(gro_bin_path)) {
55
+ return join(realpathSync(gro_bin_path), '..', path);
56
+ }
57
+ // case 2
58
+ // If running Gro inside its own repo, require the local dist.
59
+ // If the local dist is not yet built it will fall back to the global.
60
+ if (
61
+ existsSync(join(SVELTEKIT_DIST_DIRNAME, 'gro.js')) &&
62
+ existsSync(join(SVELTEKIT_DIST_DIRNAME, path))
63
+ ) {
64
+ return resolve(SVELTEKIT_DIST_DIRNAME, path);
65
+ }
66
+ // case 3
67
+ // Fall back to the version associated with the running CLI.
68
+ const file_path = fileURLToPath(import.meta.url);
69
+ return join(file_path, '..', path);
70
+ };
71
+
72
+ /**
73
+ * Runs a file using the Gro loader.
74
+ *
75
+ * Uses conditional exports to correctly set up `esm-env` as development by default,
76
+ * so if you want production set `NODE_ENV=production`.
77
+ *
78
+ * @see https://nodejs.org/api/packages.html#conditional-exports
79
+ *
80
+ * @param loader_path path to loader
81
+ * @param invoke_path path to file to spawn with `node`
82
+ */
83
+ export const spawn_with_loader = (
84
+ loader_path: string,
85
+ invoke_path: string,
86
+ argv: Array<string>,
87
+ js_cli = JS_CLI_DEFAULT, // TODO source from config when possible
88
+ ): Promise<SpawnResult> => {
89
+ const args = [
90
+ '--import',
91
+ // This does the same as `$lib/register.ts` but without the cost of importing another file.
92
+ `data:text/javascript,
93
+ import {register} from "node:module";
94
+ import {pathToFileURL} from "node:url";
95
+ register("${loader_path}", pathToFileURL("./"));`,
96
+ // @sync Node options to `$lib/gro.ts`
97
+ '--experimental-import-meta-resolve', // for `import.meta.resolve`
98
+ '--experimental-strip-types',
99
+ '--disable-warning',
100
+ 'ExperimentalWarning',
101
+ ];
102
+ // In almost all cases we want the exports condition to be `"development"`. Needed for `esm-env`.
103
+ if (process.env.NODE_ENV !== 'production') {
104
+ args.push('-C', 'development'); // same as `--conditions`
105
+ }
106
+ args.push(invoke_path, ...argv);
107
+ return spawn(js_cli, args);
108
+ };