@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,288 @@
1
+ import {spawn_restartable_process, type Restartable_Process} from '@ryanatkn/belt/process.js';
2
+ import * as esbuild from 'esbuild';
3
+ import type {Config as SvelteKitConfig} from '@sveltejs/kit';
4
+ import {join, resolve} from 'node:path';
5
+ import {identity} from '@ryanatkn/belt/function.js';
6
+ import {strip_before, strip_end} from '@ryanatkn/belt/string.js';
7
+ import type {Result} from '@ryanatkn/belt/result.js';
8
+ import {existsSync} from 'node:fs';
9
+
10
+ import type {Plugin} from './plugin.js';
11
+ import {base_path_to_path_id, LIB_DIRNAME, paths} from './paths.js';
12
+ import type {Path_Id} from './path.js';
13
+ import {GRO_DEV_DIRNAME, SERVER_DIST_PATH} from './path_constants.js';
14
+ import {watch_dir, type Watch_Node_Fs} from './watch_dir.js';
15
+ import {init_sveltekit_config} from './sveltekit_config.js';
16
+ import {esbuild_plugin_sveltekit_shim_app} from './esbuild_plugin_sveltekit_shim_app.js';
17
+ import {esbuild_plugin_sveltekit_shim_env} from './esbuild_plugin_sveltekit_shim_env.js';
18
+ import {print_build_result, to_define_import_meta_env} from './esbuild_helpers.js';
19
+ import {esbuild_plugin_sveltekit_shim_alias} from './esbuild_plugin_sveltekit_shim_alias.js';
20
+ import {esbuild_plugin_external_worker} from './esbuild_plugin_external_worker.js';
21
+ import {esbuild_plugin_sveltekit_local_imports} from './esbuild_plugin_sveltekit_local_imports.js';
22
+ import {esbuild_plugin_svelte} from './esbuild_plugin_svelte.js';
23
+ import {throttle} from './throttle.js';
24
+ import {sveltekit_config_global} from './sveltekit_config_global.js';
25
+
26
+ // TODO sourcemap as a hoisted option? disable for production by default - or like `outpaths`, passed a `dev` param
27
+
28
+ export const SERVER_SOURCE_ID = base_path_to_path_id(LIB_DIRNAME + '/server/server.ts');
29
+
30
+ export const has_server = (path = SERVER_SOURCE_ID): Result<object, {message: string}> => {
31
+ if (!existsSync(path)) {
32
+ return {ok: false, message: `no server file found at ${path}`};
33
+ }
34
+ return {ok: true};
35
+ };
36
+
37
+ export interface Options {
38
+ /**
39
+ * same as esbuild's `entryPoints`
40
+ */
41
+ entry_points?: string[];
42
+ /**
43
+ * @default cwd
44
+ */
45
+ dir?: string;
46
+ /**
47
+ * Returns the `Outpaths` given a `dev` param.
48
+ * Decoupling this from plugin creation allows it to be created generically,
49
+ * so the build and dev tasks can be the source of truth for `dev`.
50
+ */
51
+ outpaths?: Create_Outpaths;
52
+ /**
53
+ * @default SvelteKit's `.env`, `.env.development`, and `.env.production`
54
+ */
55
+ env_files?: string[];
56
+ /**
57
+ * @default process.env
58
+ */
59
+ ambient_env?: Record<string, string>;
60
+ /**
61
+ * @default loaded from `${cwd}/${SVELTEKIT_CONFIG_FILENAME}`
62
+ */
63
+ sveltekit_config?: SvelteKitConfig;
64
+ /**
65
+ * @default 'esnext'
66
+ */
67
+ target?: string;
68
+ /**
69
+ * Optionally map the esbuild options.
70
+ * @default identity
71
+ */
72
+ esbuild_build_options?: (base_options: esbuild.BuildOptions) => esbuild.BuildOptions;
73
+ /**
74
+ * Milliseconds to throttle rebuilds.
75
+ * Should be longer than it takes to build to avoid backpressure.
76
+ * @default 1000
77
+ */
78
+ rebuild_throttle_delay?: number; // TODO could detect the backpressure problem and at least warn, shouldn't be a big deal
79
+ /**
80
+ * The CLI command to run the server, like `'node'` or `'bun'` or `'deno'`.
81
+ * Receives the path to the server js file as its argument.
82
+ * @default 'node'
83
+ */
84
+ cli_command?: string;
85
+ /**
86
+ * Whether to run the server or not after building.
87
+ * @default dev
88
+ */
89
+ run?: boolean;
90
+ }
91
+
92
+ export interface Outpaths {
93
+ /**
94
+ * @default '.gro/dev' or 'dist_server'
95
+ */
96
+ outdir: string;
97
+ /**
98
+ * @default 'src/lib'
99
+ */
100
+ outbase: string;
101
+ /**
102
+ * @default 'server.js'
103
+ */
104
+ outname: string;
105
+ }
106
+
107
+ export type Create_Outpaths = (dev: boolean) => Outpaths;
108
+
109
+ export const gro_plugin_server = ({
110
+ entry_points = [SERVER_SOURCE_ID],
111
+ dir = process.cwd(),
112
+ outpaths = (dev) => ({
113
+ outdir: join(dir, dev ? GRO_DEV_DIRNAME : SERVER_DIST_PATH),
114
+ outbase: paths.lib,
115
+ outname: 'server/server.js',
116
+ }),
117
+ env_files,
118
+ ambient_env,
119
+ sveltekit_config,
120
+ target = 'esnext',
121
+ esbuild_build_options = identity,
122
+ rebuild_throttle_delay = 1000,
123
+ cli_command = 'node',
124
+ run, // `dev` default is not available in this scope
125
+ }: Options = {}): Plugin => {
126
+ let build_ctx: esbuild.BuildContext | null = null;
127
+ let watcher: Watch_Node_Fs | null = null;
128
+ let server_process: Restartable_Process | null = null;
129
+ let deps: Set<Path_Id> | null = null;
130
+
131
+ return {
132
+ name: 'gro_plugin_server',
133
+ setup: async ({dev, watch, timings, log}) => {
134
+ const parsed_sveltekit_config =
135
+ !sveltekit_config && strip_end(dir, '/') === process.cwd()
136
+ ? sveltekit_config_global
137
+ : await init_sveltekit_config(sveltekit_config ?? dir);
138
+ const {
139
+ alias,
140
+ base_url,
141
+ assets_url,
142
+ env_dir,
143
+ private_prefix,
144
+ public_prefix,
145
+ svelte_compile_options,
146
+ svelte_compile_module_options,
147
+ svelte_preprocessors,
148
+ } = parsed_sveltekit_config;
149
+
150
+ // TODO hacky
151
+ if (svelte_compile_options.generate === undefined) {
152
+ svelte_compile_options.generate = 'server';
153
+ }
154
+ if (svelte_compile_module_options.generate === undefined) {
155
+ svelte_compile_module_options.generate = 'server';
156
+ }
157
+
158
+ const {outbase, outdir, outname} = outpaths(dev);
159
+
160
+ const server_outpath = join(outdir, outname);
161
+
162
+ const timing_to_esbuild_create_context = timings.start('create build context');
163
+
164
+ const build_options = esbuild_build_options({
165
+ outdir,
166
+ outbase,
167
+ format: 'esm',
168
+ platform: 'node',
169
+ packages: 'external',
170
+ bundle: true,
171
+ target,
172
+ metafile: watch,
173
+ });
174
+
175
+ build_ctx = await esbuild.context({
176
+ entryPoints: entry_points.map((path) => resolve(dir, path)),
177
+ plugins: [
178
+ esbuild_plugin_sveltekit_shim_app({dev, base_url, assets_url}),
179
+ esbuild_plugin_sveltekit_shim_env({
180
+ dev,
181
+ public_prefix,
182
+ private_prefix,
183
+ env_dir,
184
+ env_files,
185
+ ambient_env,
186
+ }),
187
+ esbuild_plugin_sveltekit_shim_alias({dir, alias}),
188
+ esbuild_plugin_external_worker({
189
+ dev,
190
+ build_options,
191
+ dir,
192
+ svelte_compile_options,
193
+ svelte_compile_module_options,
194
+ svelte_preprocessors,
195
+ alias,
196
+ base_url,
197
+ public_prefix,
198
+ private_prefix,
199
+ env_dir,
200
+ env_files,
201
+ ambient_env,
202
+ log,
203
+ }),
204
+ esbuild_plugin_svelte({
205
+ dir,
206
+ svelte_compile_options,
207
+ svelte_compile_module_options,
208
+ svelte_preprocessors,
209
+ }),
210
+ esbuild_plugin_sveltekit_local_imports(),
211
+ ],
212
+ define: to_define_import_meta_env(dev, base_url),
213
+ ...build_options,
214
+ });
215
+
216
+ timing_to_esbuild_create_context();
217
+
218
+ const rebuild = throttle(async () => {
219
+ let build_result;
220
+ try {
221
+ build_result = await build_ctx!.rebuild();
222
+ } catch (err) {
223
+ log.error('[gro_plugin_server] build failed', err);
224
+ return;
225
+ }
226
+ const {metafile} = build_result;
227
+ if (!metafile) return;
228
+ print_build_result(log, build_result);
229
+ deps = parse_deps(metafile.inputs, dir);
230
+ server_process?.restart();
231
+ }, rebuild_throttle_delay);
232
+
233
+ await rebuild();
234
+
235
+ // uses chokidar instead of esbuild's watcher for efficiency
236
+ if (watch) {
237
+ let watcher_ready = false;
238
+ // TODO maybe reuse this watcher globally via an option,
239
+ // because it watches all of `$lib`, and that means it excludes `$routes`
240
+ // while also including a lot of client files we don't care about,
241
+ // but we can't discern which of `$lib` to watch ahead of time
242
+ watcher = watch_dir({
243
+ dir: paths.lib,
244
+ on_change: (change) => {
245
+ if (!watcher_ready || !deps?.has(change.path)) return;
246
+ void rebuild();
247
+ },
248
+ });
249
+ await watcher.init();
250
+ watcher_ready = true;
251
+ }
252
+
253
+ if (!existsSync(server_outpath)) {
254
+ throw Error(`Node server failed to start due to missing file: ${server_outpath}`);
255
+ }
256
+
257
+ if (run ?? dev) {
258
+ server_process = spawn_restartable_process(cli_command, [server_outpath]);
259
+ }
260
+ },
261
+ teardown: async () => {
262
+ if (server_process) {
263
+ const s = server_process; // avoid possible issue where a build is in progress, don't want to issue a restart, could be fixed upstream in `spawn_restartable_process`
264
+ server_process = null;
265
+ await s.kill();
266
+ }
267
+ if (watcher) {
268
+ await watcher.close();
269
+ }
270
+ if (build_ctx) {
271
+ await build_ctx.dispose();
272
+ }
273
+ },
274
+ };
275
+ };
276
+
277
+ /**
278
+ * The esbuild metafile contains the paths in `entryPoints` relative to the `dir`
279
+ * even though we're resolving them to absolute paths before passing them to esbuild,
280
+ * so we resolve them here relative to the `dir`.
281
+ */
282
+ const parse_deps = (metafile_inputs: Record<string, unknown>, dir: string): Set<string> => {
283
+ const deps: Set<string> = new Set();
284
+ for (const key in metafile_inputs) {
285
+ deps.add(resolve(dir, strip_before(key, ':')));
286
+ }
287
+ return deps;
288
+ };
@@ -0,0 +1,257 @@
1
+ import type {Spawned_Process} from '@ryanatkn/belt/process.js';
2
+ import {cpSync, mkdirSync, rmSync, writeFileSync, existsSync} from 'node:fs';
3
+ import {dirname, join} from 'node:path';
4
+
5
+ import type {Plugin} from './plugin.js';
6
+ import {serialize_args, to_forwarded_args} from './args.js';
7
+ import {serialize_package_json, type Map_Package_Json, load_package_json} from './package_json.js';
8
+ import {Task_Error} from './task.js';
9
+ import {find_cli, spawn_cli, spawn_cli_process} from './cli.js';
10
+ import {type Map_Src_Json, serialize_src_json, create_src_json} from './src_json.js';
11
+ import {DEFAULT_EXPORTS_EXCLUDER} from './config.js';
12
+ import {sveltekit_config_global} from './sveltekit_config_global.js';
13
+ import {SOURCE_DIRNAME} from './path_constants.js';
14
+ import {VITE_CLI} from './sveltekit_helpers.js';
15
+
16
+ export interface Options {
17
+ /**
18
+ * Used for finalizing a SvelteKit build like adding a `.nojekyll` file for GitHub Pages.
19
+ * @default 'github_pages'
20
+ */
21
+ host_target?: Host_Target;
22
+
23
+ /**
24
+ * If truthy, adds `/.well-known/package.json` to the static output.
25
+ * If a function, maps the value.
26
+ */
27
+ well_known_package_json?: boolean | Map_Package_Json;
28
+
29
+ /**
30
+ * If truthy, adds `/.well-known/src.json` and `/.well-known/src/` to the static output.
31
+ * If a function, maps the value.
32
+ */
33
+ well_known_src_json?: boolean | Map_Src_Json;
34
+
35
+ /**
36
+ * If truthy, copies `src/` to `/.well-known/src/` to the static output.
37
+ * Pass a function to customize which files get copied.
38
+ */
39
+ well_known_src_files?: boolean | Copy_File_Filter;
40
+ /**
41
+ * The Vite CLI to use.
42
+ */
43
+ vite_cli?: string;
44
+ }
45
+
46
+ export type Host_Target = 'github_pages' | 'static' | 'node';
47
+
48
+ export type Copy_File_Filter = (file_path: string) => boolean;
49
+
50
+ export const gro_plugin_sveltekit_app = ({
51
+ host_target = 'github_pages',
52
+ well_known_package_json,
53
+ well_known_src_json,
54
+ well_known_src_files,
55
+ vite_cli = VITE_CLI,
56
+ }: Options = {}): Plugin => {
57
+ let sveltekit_process: Spawned_Process | undefined = undefined;
58
+ return {
59
+ name: 'gro_plugin_sveltekit_app',
60
+ setup: async ({dev, watch, log}) => {
61
+ const found_vite_cli = find_cli(vite_cli);
62
+ if (!found_vite_cli)
63
+ throw new Error(`Failed to find Vite CLI \`${vite_cli}\`, do you need to run \`npm i\`?`);
64
+ if (dev) {
65
+ // `vite dev` in development mode
66
+ if (watch) {
67
+ const serialized_args = ['dev', ...serialize_args(to_forwarded_args(vite_cli))];
68
+ sveltekit_process = spawn_cli_process(found_vite_cli, serialized_args, log);
69
+ } else {
70
+ log.debug(
71
+ `the SvelteKit app plugin is loaded but will not output anything` +
72
+ ' because `dev` is true and `watch` is false',
73
+ );
74
+ }
75
+ } else {
76
+ // `vite build` in production mode
77
+
78
+ // `.well-known/package.json`
79
+ const package_json = load_package_json(); // TODO put in plugin context? same with sveltekit config?
80
+ if (well_known_package_json === undefined) {
81
+ well_known_package_json = package_json.public; // eslint-disable-line no-param-reassign
82
+ }
83
+ const mapped_package_json = !well_known_package_json
84
+ ? null
85
+ : well_known_package_json === true
86
+ ? package_json
87
+ : await well_known_package_json(package_json);
88
+ const serialized_package_json =
89
+ mapped_package_json && serialize_package_json(mapped_package_json);
90
+
91
+ // `.well-known/src.json` and `.well-known/src/`
92
+ const final_package_json = mapped_package_json ?? package_json;
93
+ const src_json = create_src_json(final_package_json);
94
+ if (well_known_src_json === undefined) {
95
+ well_known_src_json = final_package_json.public; // eslint-disable-line no-param-reassign
96
+ }
97
+ const mapped_src_json = !well_known_src_json
98
+ ? null
99
+ : well_known_src_json === true
100
+ ? src_json
101
+ : await well_known_src_json(src_json);
102
+ const serialized_src_json = mapped_src_json && serialize_src_json(mapped_src_json);
103
+
104
+ // TODO this strategy means the files aren't available during development --
105
+ // maybe a Vite middleware is best? what if this plugin added its plugin to your `vite.config.ts`?
106
+
107
+ // copy files to `static` before building, in such a way
108
+ // that's non-destructive to existing files and dirs and easy to clean up
109
+ const {assets_path} = sveltekit_config_global;
110
+ const cleanups: Cleanup[] = [
111
+ serialized_package_json
112
+ ? create_temporarily(
113
+ join(assets_path, '.well-known/package.json'),
114
+ serialized_package_json,
115
+ )
116
+ : null,
117
+ serialized_src_json
118
+ ? create_temporarily(join(assets_path, '.well-known/src.json'), serialized_src_json)
119
+ : null,
120
+ serialized_src_json && well_known_src_files
121
+ ? copy_temporarily(
122
+ SOURCE_DIRNAME,
123
+ assets_path,
124
+ '.well-known',
125
+ well_known_src_files === true
126
+ ? (file_path) => !DEFAULT_EXPORTS_EXCLUDER.test(file_path)
127
+ : well_known_src_files,
128
+ )
129
+ : null,
130
+ /**
131
+ * GitHub pages processes everything with Jekyll by default,
132
+ * breaking things like files and dirs prefixed with an underscore.
133
+ * This adds a `.nojekyll` file to the root of the output
134
+ * to tell GitHub Pages to treat the outputs as plain static files.
135
+ */
136
+ host_target === 'github_pages'
137
+ ? create_temporarily(join(assets_path, '.nojekyll'), '')
138
+ : null,
139
+ ].filter((v) => v != null);
140
+ const cleanup = () => {
141
+ for (const c of cleanups) c();
142
+ };
143
+ try {
144
+ const serialized_args = ['build', ...serialize_args(to_forwarded_args(vite_cli))];
145
+ const spawned = await spawn_cli(found_vite_cli, serialized_args, log);
146
+ if (!spawned?.ok) {
147
+ throw new Task_Error(`${vite_cli} build failed with exit code ${spawned?.code}`);
148
+ }
149
+ } catch (err) {
150
+ cleanup();
151
+ throw err;
152
+ }
153
+ cleanup();
154
+ }
155
+ },
156
+ teardown: async () => {
157
+ if (sveltekit_process) {
158
+ sveltekit_process.child.kill();
159
+ await sveltekit_process.closed;
160
+ }
161
+ },
162
+ };
163
+ };
164
+
165
+ type Cleanup = () => void;
166
+
167
+ // TODO probably extract these, and create a common helper or merge them
168
+
169
+ const copy_temporarily = (
170
+ source_path: string,
171
+ dest_dir: string,
172
+ dest_base_dir = '',
173
+ filter?: Copy_File_Filter,
174
+ ): Cleanup => {
175
+ const path = join(dest_dir, dest_base_dir, source_path);
176
+ const dir = dirname(path);
177
+
178
+ const dir_already_exists = existsSync(dir);
179
+ let root_created_dir: string | undefined;
180
+ if (!dir_already_exists) {
181
+ root_created_dir = to_root_dir_that_doesnt_exist(dir);
182
+ if (!root_created_dir) throw Error();
183
+ mkdirSync(dir, {recursive: true});
184
+ }
185
+
186
+ const path_already_exists = existsSync(path);
187
+ if (!path_already_exists) {
188
+ cpSync(source_path, path, {recursive: true, filter});
189
+ }
190
+
191
+ return () => {
192
+ if (!dir_already_exists) {
193
+ if (!root_created_dir) throw Error();
194
+ if (existsSync(root_created_dir)) {
195
+ rmSync(root_created_dir, {recursive: true});
196
+ }
197
+ } else if (!path_already_exists) {
198
+ if (existsSync(path)) {
199
+ rmSync(path, {recursive: true});
200
+ }
201
+ }
202
+ };
203
+ };
204
+
205
+ /**
206
+ * Creates a file at `path` with `contents` if it doesn't already exist,
207
+ * and returns a function that deletes the file and any created directories.
208
+ * @param path
209
+ * @param contents
210
+ * @returns cleanup function that deletes the file and any created dirs
211
+ */
212
+ const create_temporarily = (path: string, contents: string): Cleanup => {
213
+ const dir = dirname(path);
214
+
215
+ const dir_already_exists = existsSync(dir);
216
+ let root_created_dir: string | undefined;
217
+ if (!dir_already_exists) {
218
+ root_created_dir = to_root_dir_that_doesnt_exist(dir);
219
+ if (!root_created_dir) throw Error();
220
+ mkdirSync(dir, {recursive: true});
221
+ }
222
+
223
+ const path_already_exists = existsSync(path);
224
+ if (!path_already_exists) {
225
+ writeFileSync(path, contents, 'utf8');
226
+ }
227
+
228
+ return () => {
229
+ if (!dir_already_exists) {
230
+ if (!root_created_dir) throw Error();
231
+ if (existsSync(root_created_dir)) {
232
+ rmSync(root_created_dir, {recursive: true});
233
+ }
234
+ } else if (!path_already_exists) {
235
+ if (existsSync(path)) {
236
+ rmSync(path);
237
+ }
238
+ }
239
+ };
240
+ };
241
+
242
+ /**
243
+ * Niche and probably needs refactoring,
244
+ * for `/a/b/DOESNT_EXIST/NOR_THIS/ETC` returns `/a/b/DOESNT_EXIST`
245
+ * where `/a/b` does exist on the filesystem and `DOESNT_EXIST` is not one of its subdirectories.
246
+ */
247
+ const to_root_dir_that_doesnt_exist = (dir: string): string | undefined => {
248
+ let prev: string | undefined;
249
+ let d = dir;
250
+ do {
251
+ if (existsSync(d)) {
252
+ return prev;
253
+ }
254
+ prev = d;
255
+ } while ((d = dirname(d)));
256
+ throw Error('no dirs exist for ' + dir);
257
+ };
@@ -0,0 +1,74 @@
1
+ import {print_spawn_result, spawn} from '@ryanatkn/belt/process.js';
2
+
3
+ import type {Plugin} from './plugin.js';
4
+ import {Task_Error} from './task.js';
5
+ import {load_package_json} from './package_json.js';
6
+ import {serialize_args, to_forwarded_args} from './args.js';
7
+ import {find_cli, spawn_cli} from './cli.js';
8
+ import {
9
+ SVELTE_PACKAGE_CLI,
10
+ has_sveltekit_library,
11
+ type Svelte_Package_Options,
12
+ } from './sveltekit_helpers.js';
13
+
14
+ export interface Options {
15
+ /**
16
+ * The options passed to the SvelteKit packaging CLI.
17
+ * @see https://kit.svelte.dev/docs/packaging#options
18
+ */
19
+ svelte_package_options?: Svelte_Package_Options;
20
+ /**
21
+ * The SvelteKit packaging CLI to use. Defaults to `svelte-package`.
22
+ * @see https://kit.svelte.dev/docs/packaging
23
+ */
24
+ svelte_package_cli?: string;
25
+ }
26
+
27
+ export const gro_plugin_sveltekit_library = ({
28
+ svelte_package_options,
29
+ svelte_package_cli = SVELTE_PACKAGE_CLI,
30
+ }: Options = {}): Plugin => {
31
+ return {
32
+ name: 'gro_plugin_sveltekit_library',
33
+ setup: async ({log}) => {
34
+ const has_sveltekit_library_result = has_sveltekit_library();
35
+ if (!has_sveltekit_library_result.ok) {
36
+ throw new Task_Error(
37
+ 'Failed to find SvelteKit library: ' + has_sveltekit_library_result.message,
38
+ );
39
+ }
40
+ const found_svelte_package_cli = find_cli(svelte_package_cli);
41
+ if (found_svelte_package_cli?.kind !== 'local') {
42
+ throw new Task_Error(
43
+ `Failed to find SvelteKit packaging CLI \`${svelte_package_cli}\`, do you need to run \`npm i\`?`,
44
+ );
45
+ }
46
+ const serialized_args = serialize_args({
47
+ ...svelte_package_options,
48
+ ...to_forwarded_args(svelte_package_cli),
49
+ });
50
+ await spawn_cli(found_svelte_package_cli, serialized_args, log);
51
+ },
52
+ adapt: async ({log, timings}) => {
53
+ const package_json = load_package_json();
54
+
55
+ // `npm link`
56
+ if (package_json.bin) {
57
+ const timing_to_npm_link = timings.start('npm link');
58
+ await Promise.all(
59
+ Object.values(package_json.bin).map(async (bin_path) => {
60
+ const chmod_result = await spawn('chmod', ['+x', bin_path]);
61
+ if (!chmod_result.ok)
62
+ log.error(`chmod on bin path ${bin_path} failed with code ${chmod_result.code}`);
63
+ }),
64
+ );
65
+ log.info(`linking`);
66
+ const link_result = await spawn('npm', ['link', '-f']); // TODO don't use `-f` unless necessary or at all?
67
+ if (!link_result.ok) {
68
+ throw new Task_Error(`Failed to link. ${print_spawn_result(link_result)}`);
69
+ }
70
+ timing_to_npm_link();
71
+ }
72
+ },
73
+ };
74
+ };
@@ -0,0 +1,33 @@
1
+ import {suite} from 'uvu';
2
+ import * as assert from 'uvu/assert';
3
+ import {webcrypto} from 'node:crypto';
4
+
5
+ import {to_hash} from './hash.js';
6
+
7
+ /* test__to_hash */
8
+ const test__to_hash = suite('to_hash');
9
+
10
+ test__to_hash('turns a buffer into a string', async () => {
11
+ assert.type(await to_hash(Buffer.from('hey')), 'string');
12
+ });
13
+
14
+ test__to_hash('returns the same value given the same input', async () => {
15
+ assert.is(await to_hash(Buffer.from('hey')), await to_hash(Buffer.from('hey')));
16
+ });
17
+
18
+ test__to_hash('checks against an implementation copied from MDN', async () => {
19
+ const data = Buffer.from('some_test_string');
20
+ assert.is(await to_hash_from_mdn_example(data), await to_hash(data));
21
+ });
22
+
23
+ test__to_hash.run();
24
+ /* test__to_hash */
25
+
26
+ /**
27
+ * Copied from https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
28
+ * and compared against our implementation for extra assurances, because cryptography.
29
+ */
30
+ const to_hash_from_mdn_example = async (data: Buffer): Promise<string> =>
31
+ Array.from(new Uint8Array(await webcrypto.subtle.digest('SHA-256', data)))
32
+ .map((h) => h.toString(16).padStart(2, '0'))
33
+ .join('');
@@ -0,0 +1,19 @@
1
+ import {webcrypto} from 'node:crypto';
2
+
3
+ const {subtle} = webcrypto;
4
+
5
+ /**
6
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto
7
+ */
8
+ export const to_hash = async (
9
+ data: Buffer,
10
+ algorithm: 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512' = 'SHA-256',
11
+ ): Promise<string> => {
12
+ const digested = await subtle.digest(algorithm, data);
13
+ const bytes = Array.from(new Uint8Array(digested));
14
+ let hex = '';
15
+ for (const h of bytes) {
16
+ hex += h.toString(16).padStart(2, '0');
17
+ }
18
+ return hex;
19
+ };
@@ -0,0 +1,4 @@
1
+ export type {Gro_Config, Create_Gro_Config, Raw_Gro_Config} from './config.js';
2
+ export {type Plugin, replace_plugin} from './plugin.js';
3
+ export type {Gen, Gen_Context} from './gen.js';
4
+ export {type Task, type Task_Context, Task_Error} from './task.js';