@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.
- package/dist/package.js +3 -3
- package/package.json +3 -2
- package/src/lib/args.test.ts +59 -0
- package/src/lib/args.ts +169 -0
- package/src/lib/build.task.ts +37 -0
- package/src/lib/changelog.test.ts +138 -0
- package/src/lib/changelog.ts +69 -0
- package/src/lib/changeset.task.ts +206 -0
- package/src/lib/changeset_helpers.ts +13 -0
- package/src/lib/check.task.ts +90 -0
- package/src/lib/clean.task.ts +46 -0
- package/src/lib/clean_fs.ts +54 -0
- package/src/lib/cli.ts +97 -0
- package/src/lib/commit.task.ts +33 -0
- package/src/lib/config.test.ts +71 -0
- package/src/lib/config.ts +161 -0
- package/src/lib/deploy.task.ts +243 -0
- package/src/lib/dev.task.ts +43 -0
- package/src/lib/docs/README.gen.md.ts +63 -0
- package/src/lib/docs/README.md +20 -0
- package/src/lib/docs/build.md +41 -0
- package/src/lib/docs/config.md +213 -0
- package/src/lib/docs/deploy.md +32 -0
- package/src/lib/docs/dev.md +40 -0
- package/src/lib/docs/gen.md +269 -0
- package/src/lib/docs/gro_plugin_sveltekit_app.md +113 -0
- package/src/lib/docs/package_json.md +33 -0
- package/src/lib/docs/plugin.md +50 -0
- package/src/lib/docs/publish.md +137 -0
- package/src/lib/docs/task.md +391 -0
- package/src/lib/docs/tasks.gen.md.ts +90 -0
- package/src/lib/docs/tasks.md +37 -0
- package/src/lib/docs/test.md +52 -0
- package/src/lib/env.ts +75 -0
- package/src/lib/esbuild_helpers.ts +50 -0
- package/src/lib/esbuild_plugin_external_worker.ts +92 -0
- package/src/lib/esbuild_plugin_svelte.test.ts +88 -0
- package/src/lib/esbuild_plugin_svelte.ts +108 -0
- package/src/lib/esbuild_plugin_sveltekit_local_imports.ts +31 -0
- package/src/lib/esbuild_plugin_sveltekit_shim_alias.ts +25 -0
- package/src/lib/esbuild_plugin_sveltekit_shim_app.ts +41 -0
- package/src/lib/esbuild_plugin_sveltekit_shim_env.ts +46 -0
- package/src/lib/format.task.ts +30 -0
- package/src/lib/format_directory.ts +55 -0
- package/src/lib/format_file.test.ts +20 -0
- package/src/lib/format_file.ts +49 -0
- package/src/lib/fs.ts +18 -0
- package/src/lib/gen.task.ts +134 -0
- package/src/lib/gen.test.ts +306 -0
- package/src/lib/gen.ts +360 -0
- package/src/lib/git.test.ts +34 -0
- package/src/lib/git.ts +297 -0
- package/src/lib/github.ts +46 -0
- package/src/lib/gro.config.default.ts +34 -0
- package/src/lib/gro.ts +25 -0
- package/src/lib/gro_helpers.ts +101 -0
- package/src/lib/gro_plugin_gen.ts +95 -0
- package/src/lib/gro_plugin_server.ts +288 -0
- package/src/lib/gro_plugin_sveltekit_app.ts +257 -0
- package/src/lib/gro_plugin_sveltekit_library.ts +74 -0
- package/src/lib/hash.test.ts +33 -0
- package/src/lib/hash.ts +19 -0
- package/src/lib/index.ts +4 -0
- package/src/lib/input_path.test.ts +230 -0
- package/src/lib/input_path.ts +255 -0
- package/src/lib/invoke.ts +27 -0
- package/src/lib/invoke_task.ts +116 -0
- package/src/lib/lint.task.ts +38 -0
- package/src/lib/loader.test.ts +49 -0
- package/src/lib/loader.ts +226 -0
- package/src/lib/module.test.ts +46 -0
- package/src/lib/module.ts +13 -0
- package/src/lib/modules.test.ts +63 -0
- package/src/lib/modules.ts +112 -0
- package/src/lib/package.gen.ts +33 -0
- package/src/lib/package.ts +998 -0
- package/src/lib/package_json.test.ts +101 -0
- package/src/lib/package_json.ts +330 -0
- package/src/lib/package_meta.ts +86 -0
- package/src/lib/path.ts +23 -0
- package/src/lib/path_constants.ts +30 -0
- package/src/lib/paths.test.ts +77 -0
- package/src/lib/paths.ts +101 -0
- package/src/lib/plugin.test.ts +57 -0
- package/src/lib/plugin.ts +113 -0
- package/src/lib/publish.task.ts +194 -0
- package/src/lib/register.ts +3 -0
- package/src/lib/reinstall.task.ts +42 -0
- package/src/lib/release.task.ts +21 -0
- package/src/lib/resolve.task.ts +43 -0
- package/src/lib/resolve_node_specifier.test.ts +31 -0
- package/src/lib/resolve_node_specifier.ts +55 -0
- package/src/lib/resolve_specifier.test.ts +76 -0
- package/src/lib/resolve_specifier.ts +61 -0
- package/src/lib/run.task.ts +41 -0
- package/src/lib/run_gen.test.ts +196 -0
- package/src/lib/run_gen.ts +95 -0
- package/src/lib/run_task.test.ts +86 -0
- package/src/lib/run_task.ts +75 -0
- package/src/lib/search_fs.test.ts +56 -0
- package/src/lib/search_fs.ts +93 -0
- package/src/lib/src_json.test.ts +49 -0
- package/src/lib/src_json.ts +153 -0
- package/src/lib/svelte_helpers.ts +2 -0
- package/src/lib/sveltekit_config.ts +101 -0
- package/src/lib/sveltekit_config_global.ts +6 -0
- package/src/lib/sveltekit_helpers.ts +132 -0
- package/src/lib/sveltekit_shim_app.ts +42 -0
- package/src/lib/sveltekit_shim_app_environment.ts +14 -0
- package/src/lib/sveltekit_shim_app_forms.ts +20 -0
- package/src/lib/sveltekit_shim_app_navigation.ts +23 -0
- package/src/lib/sveltekit_shim_app_paths.ts +16 -0
- package/src/lib/sveltekit_shim_app_stores.ts +25 -0
- package/src/lib/sveltekit_shim_env.ts +45 -0
- package/src/lib/sync.task.ts +47 -0
- package/src/lib/task.test.ts +84 -0
- package/src/lib/task.ts +235 -0
- package/src/lib/task_logging.ts +180 -0
- package/src/lib/test.task.ts +50 -0
- package/src/lib/throttle.test.ts +52 -0
- package/src/lib/throttle.ts +63 -0
- package/src/lib/typecheck.task.ts +57 -0
- package/src/lib/upgrade.task.ts +108 -0
- 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('');
|
package/src/lib/hash.ts
ADDED
|
@@ -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
|
+
};
|
package/src/lib/index.ts
ADDED