@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,90 @@
|
|
|
1
|
+
import {dirname, relative, basename} from 'node:path';
|
|
2
|
+
import {parse_path_parts, parse_path_segments} from '@ryanatkn/belt/path.js';
|
|
3
|
+
import {strip_start} from '@ryanatkn/belt/string.js';
|
|
4
|
+
|
|
5
|
+
import {type Gen, to_output_file_name} from '../gen.js';
|
|
6
|
+
import {paths, base_path_to_path_id} from '../paths.js';
|
|
7
|
+
import {log_error_reasons} from '../task_logging.js';
|
|
8
|
+
import {find_tasks, load_tasks, Task_Error} from '../task.js';
|
|
9
|
+
|
|
10
|
+
// This is the first simple implementation of Gro's automated docs.
|
|
11
|
+
// It combines Gro's gen and task systems
|
|
12
|
+
// to generate a markdown file with a summary of all of Gro's tasks.
|
|
13
|
+
// Other projects that use Gro should be able to import this module
|
|
14
|
+
// or other otherwise get frictionless access to this specific use case,
|
|
15
|
+
// and they should be able to extend or customize it to any degree.
|
|
16
|
+
|
|
17
|
+
// TODO display more info about each task, including a summary and params
|
|
18
|
+
// TODO needs some cleanup and better APIs - paths are confusing and verbose!
|
|
19
|
+
// TODO add backlinks to every document that links to this one
|
|
20
|
+
|
|
21
|
+
export const gen: Gen = async ({origin_id, log, config}) => {
|
|
22
|
+
const found = find_tasks(['.'], [paths.lib], config);
|
|
23
|
+
if (!found.ok) {
|
|
24
|
+
log_error_reasons(log, found.reasons);
|
|
25
|
+
throw new Task_Error(`Failed to generate task docs: ${found.type}`);
|
|
26
|
+
}
|
|
27
|
+
const found_tasks = found.value;
|
|
28
|
+
|
|
29
|
+
const loaded = await load_tasks(found_tasks);
|
|
30
|
+
if (!loaded.ok) {
|
|
31
|
+
log_error_reasons(log, loaded.reasons);
|
|
32
|
+
throw new Task_Error(`Failed to generate task docs: ${loaded.type}`);
|
|
33
|
+
}
|
|
34
|
+
const loaded_tasks = loaded.value;
|
|
35
|
+
const tasks = loaded_tasks.modules;
|
|
36
|
+
|
|
37
|
+
const root_path = parse_path_segments(paths.root).at(-1);
|
|
38
|
+
|
|
39
|
+
const origin_dir = dirname(origin_id);
|
|
40
|
+
const origin_base = basename(origin_id);
|
|
41
|
+
|
|
42
|
+
const base_dir = paths.source;
|
|
43
|
+
const relative_path = strip_start(origin_id, base_dir);
|
|
44
|
+
const relative_dir = dirname(relative_path);
|
|
45
|
+
|
|
46
|
+
// TODO should this be passed in the context, like `defaultOutputFileName`?
|
|
47
|
+
const output_file_name = to_output_file_name(origin_base);
|
|
48
|
+
|
|
49
|
+
// TODO this is GitHub-specific
|
|
50
|
+
const root_link = `[${root_path}](/../..)`;
|
|
51
|
+
|
|
52
|
+
// TODO do we want to use absolute paths instead of relative paths,
|
|
53
|
+
// because GitHub works with them and it simplifies the code?
|
|
54
|
+
const path_parts = parse_path_parts(relative_dir).map(
|
|
55
|
+
(relative_path_part) =>
|
|
56
|
+
`[${parse_path_segments(relative_path_part).at(-1)}](${
|
|
57
|
+
relative(origin_dir, base_path_to_path_id(relative_path_part)) || './'
|
|
58
|
+
})`,
|
|
59
|
+
);
|
|
60
|
+
const breadcrumbs =
|
|
61
|
+
'> <sub>' + [root_link, ...path_parts, output_file_name].join(' / ') + '</sub>';
|
|
62
|
+
|
|
63
|
+
// TODO render the footer with the origin_id
|
|
64
|
+
return `# tasks
|
|
65
|
+
|
|
66
|
+
${breadcrumbs}
|
|
67
|
+
|
|
68
|
+
What is a \`Task\`? See [\`task.md\`](./task.md).
|
|
69
|
+
|
|
70
|
+
## all tasks
|
|
71
|
+
|
|
72
|
+
${tasks.reduce(
|
|
73
|
+
(taskList, task) =>
|
|
74
|
+
taskList +
|
|
75
|
+
`- [${task.name}](${relative(origin_dir, task.id)})${
|
|
76
|
+
task.mod.task.summary ? ` - ${task.mod.task.summary}` : ''
|
|
77
|
+
}\n`,
|
|
78
|
+
'',
|
|
79
|
+
)}
|
|
80
|
+
## usage
|
|
81
|
+
|
|
82
|
+
\`\`\`bash
|
|
83
|
+
$ gro some/name
|
|
84
|
+
\`\`\`
|
|
85
|
+
|
|
86
|
+
${breadcrumbs}
|
|
87
|
+
|
|
88
|
+
> <sub>generated by [${origin_base}](${origin_base})</sub>
|
|
89
|
+
`;
|
|
90
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# tasks
|
|
2
|
+
|
|
3
|
+
> <sub>[gro](/../..) / [lib](..) / [docs](./) / tasks.md</sub>
|
|
4
|
+
|
|
5
|
+
What is a `Task`? See [`task.md`](./task.md).
|
|
6
|
+
|
|
7
|
+
## all tasks
|
|
8
|
+
|
|
9
|
+
- [build](../build.task.ts) - build the project
|
|
10
|
+
- [changeset](../changeset.task.ts) - call changeset with gro patterns
|
|
11
|
+
- [check](../check.task.ts) - check that everything is ready to commit
|
|
12
|
+
- [clean](../clean.task.ts) - remove temporary dev and build files, and optionally prune git branches
|
|
13
|
+
- [commit](../commit.task.ts) - commit and push to a new branch
|
|
14
|
+
- [deploy](../deploy.task.ts) - deploy to a branch
|
|
15
|
+
- [dev](../dev.task.ts) - start SvelteKit and other dev plugins
|
|
16
|
+
- [format](../format.task.ts) - format source files
|
|
17
|
+
- [gen](../gen.task.ts) - run code generation scripts
|
|
18
|
+
- [lint](../lint.task.ts) - run eslint
|
|
19
|
+
- [publish](../publish.task.ts) - bump version, publish to npm, and git push
|
|
20
|
+
- [reinstall](../reinstall.task.ts) - refreshes package-lock.json with the latest and cleanest deps
|
|
21
|
+
- [release](../release.task.ts) - publish and deploy
|
|
22
|
+
- [resolve](../resolve.task.ts) - diagnostic that logs resolved filesystem info for the given input paths
|
|
23
|
+
- [run](../run.task.ts) - execute a file with the loader, like `node` but works for TypeScript
|
|
24
|
+
- [sync](../sync.task.ts) - run `gro gen`, update `package.json`, and optionally `npm i` to sync up
|
|
25
|
+
- [test](../test.task.ts) - run tests with uvu
|
|
26
|
+
- [typecheck](../typecheck.task.ts) - run tsc on the project without emitting any files
|
|
27
|
+
- [upgrade](../upgrade.task.ts) - upgrade deps
|
|
28
|
+
|
|
29
|
+
## usage
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
$ gro some/name
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
> <sub>[gro](/../..) / [lib](..) / [docs](./) / tasks.md</sub>
|
|
36
|
+
|
|
37
|
+
> <sub>generated by [tasks.gen.md.ts](tasks.gen.md.ts)</sub>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# test
|
|
2
|
+
|
|
3
|
+
Gro integrates [`uvu`](https://github.com/lukeed/uvu) for tests:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
gro test # run all tests with Gro's default `*.test.ts` pattern
|
|
7
|
+
gro test thing.test somedir test/a.+b # run tests matching regexp patterns
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
> Running `gro test [...args]` calls `uvu`'s `parse` and `run` helpers
|
|
11
|
+
> inside Gro's normal [task context](/src/lib/docs/task.md) instead of using the `uvu` CLI.
|
|
12
|
+
> Gro typically defers to a tool's CLI, so it can transparently forward args without wrapping,
|
|
13
|
+
> but in this case `uvu` doesn't support [loaders](https://nodejs.org/api/esm.html#loaders)
|
|
14
|
+
> for running TypeScript files directly.
|
|
15
|
+
> `uvu` does support require hooks, but Gro prefers the loader API.
|
|
16
|
+
|
|
17
|
+
Like other tasks, use `--help` to see the args info:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
gro test --help
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
outputs:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
gro test: run tests
|
|
27
|
+
[...args] string[] ["\\.test\\.ts$"] file patterns to test
|
|
28
|
+
bail boolean false the bail option to uvu run, exit immediately on failure
|
|
29
|
+
cwd string undefined the cwd option to uvu parse
|
|
30
|
+
ignore string | string[] undefined the ignore option to uvu parse
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
[`gro test`](/src/lib/test.task.ts) runs all `*.test.ts`
|
|
34
|
+
files in your project by default using the regexp `"\\.test\\.ts$"`.
|
|
35
|
+
So to add a new test, create a new file:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
// by convention, create `src/lib/thing.ts`
|
|
39
|
+
// to test `src/lib/thing.test.ts`
|
|
40
|
+
import {test} from 'uvu';
|
|
41
|
+
import * as assert from 'uvu/assert';
|
|
42
|
+
|
|
43
|
+
import {thing} from '$lib/thing.js';
|
|
44
|
+
|
|
45
|
+
test('the thing', async () => {
|
|
46
|
+
assert.equal(thing, {expected: true});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test.run();
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
See [the `uvu` docs](https://github.com/lukeed/uvu) for more.
|
package/src/lib/env.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import dotenv from 'dotenv';
|
|
2
|
+
import {resolve} from 'node:path';
|
|
3
|
+
import {existsSync, readFileSync} from 'node:fs';
|
|
4
|
+
|
|
5
|
+
export const load_env = (
|
|
6
|
+
dev: boolean,
|
|
7
|
+
visibility: 'public' | 'private',
|
|
8
|
+
public_prefix: string,
|
|
9
|
+
private_prefix: string,
|
|
10
|
+
env_dir?: string,
|
|
11
|
+
env_files = ['.env', '.env.' + (dev ? 'development' : 'production')],
|
|
12
|
+
ambient_env = process.env,
|
|
13
|
+
): Record<string, string> => {
|
|
14
|
+
const envs: Array<Record<string, string | undefined>> = env_files
|
|
15
|
+
.map((path) => load(env_dir === undefined ? path : resolve(env_dir, path)))
|
|
16
|
+
.filter((v) => v !== undefined);
|
|
17
|
+
envs.push(ambient_env);
|
|
18
|
+
return merge_envs(envs, visibility, public_prefix, private_prefix);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const load = (path: string): Record<string, string> | undefined => {
|
|
22
|
+
if (!existsSync(path)) return;
|
|
23
|
+
const loaded = readFileSync(path, 'utf8');
|
|
24
|
+
return dotenv.parse(loaded);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const merge_envs = (
|
|
28
|
+
envs: Array<Record<string, string | undefined>>,
|
|
29
|
+
visibility: 'public' | 'private',
|
|
30
|
+
public_prefix: string,
|
|
31
|
+
private_prefix: string,
|
|
32
|
+
): Record<string, string> => {
|
|
33
|
+
const env: Record<string, string> = {};
|
|
34
|
+
|
|
35
|
+
for (const e of envs) {
|
|
36
|
+
for (const key in e) {
|
|
37
|
+
if (
|
|
38
|
+
(visibility === 'private' && is_private_env(key, public_prefix, private_prefix)) ||
|
|
39
|
+
(visibility === 'public' && is_public_env(key, public_prefix, private_prefix))
|
|
40
|
+
) {
|
|
41
|
+
const value = e[key];
|
|
42
|
+
if (value !== undefined) env[key] = value;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return env;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const is_private_env = (
|
|
51
|
+
key: string,
|
|
52
|
+
public_prefix: string,
|
|
53
|
+
private_prefix: string,
|
|
54
|
+
): boolean =>
|
|
55
|
+
key.startsWith(private_prefix) && (public_prefix === '' || !key.startsWith(public_prefix));
|
|
56
|
+
|
|
57
|
+
export const is_public_env = (
|
|
58
|
+
key: string,
|
|
59
|
+
public_prefix: string,
|
|
60
|
+
private_prefix: string,
|
|
61
|
+
): boolean =>
|
|
62
|
+
key.startsWith(public_prefix) && (private_prefix === '' || !key.startsWith(private_prefix));
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Loads a single env value without merging it into `process.env`.
|
|
66
|
+
* By default searches process.env, then a local `.env` if one exists, then `../.env` if it exists.
|
|
67
|
+
*/
|
|
68
|
+
export const load_from_env = (key: string, paths = ['.env', '../.env']): string | undefined => {
|
|
69
|
+
if (process.env[key]) return process.env[key];
|
|
70
|
+
for (const path of paths) {
|
|
71
|
+
const env = load(path);
|
|
72
|
+
if (env?.[key]) return env[key];
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {yellow, red} from '@ryanatkn/belt/styletext.js';
|
|
2
|
+
import type {Logger} from '@ryanatkn/belt/log.js';
|
|
3
|
+
import type * as esbuild from 'esbuild';
|
|
4
|
+
|
|
5
|
+
import type {Parsed_Sveltekit_Config} from './sveltekit_config.js';
|
|
6
|
+
|
|
7
|
+
export const print_build_result = (log: Logger, build_result: esbuild.BuildResult): void => {
|
|
8
|
+
for (const error of build_result.errors) {
|
|
9
|
+
log.error(red('esbuild error'), error);
|
|
10
|
+
}
|
|
11
|
+
for (const warning of build_result.warnings) {
|
|
12
|
+
log.warn(yellow('esbuild warning'), warning);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// This concatenates weirdly to avoid a SvelteKit warning,
|
|
17
|
+
// because SvelteKit detects usage as a string and not the AST.
|
|
18
|
+
const import_meta_env = 'import.' + 'meta.env.'; // eslint-disable-line no-useless-concat
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Creates an esbuild `define` shim for Vite's `import.meta\.env`.
|
|
22
|
+
* @see https://esbuild.github.io/api/#define
|
|
23
|
+
* @param dev
|
|
24
|
+
* @param base_url - best-effort shim from SvelteKit's `base` to Vite's `import.meta\.env.BASE_URL`
|
|
25
|
+
* @param ssr
|
|
26
|
+
* @param mode
|
|
27
|
+
* @returns
|
|
28
|
+
*/
|
|
29
|
+
export const to_define_import_meta_env = (
|
|
30
|
+
dev: boolean,
|
|
31
|
+
base_url: Parsed_Sveltekit_Config['base_url'],
|
|
32
|
+
ssr = true,
|
|
33
|
+
mode = dev ? 'development' : 'production',
|
|
34
|
+
): Record<string, string> => ({
|
|
35
|
+
// see `import_meta_env` for why this is defined weirdly instead of statically
|
|
36
|
+
[import_meta_env + 'DEV']: JSON.stringify(dev),
|
|
37
|
+
[import_meta_env + 'PROD']: JSON.stringify(!dev),
|
|
38
|
+
[import_meta_env + 'SSR']: JSON.stringify(ssr),
|
|
39
|
+
[import_meta_env + 'MODE']: JSON.stringify(mode),
|
|
40
|
+
// it appears SvelteKit's `''` translates to Vite's `'/'`, so this intentionally falls back for falsy values, not just undefined
|
|
41
|
+
[import_meta_env + 'BASE_URL']: JSON.stringify(base_url || '/'), // eslint-disable-line @typescript-eslint/prefer-nullish-coalescing
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export const ts_transform_options: esbuild.TransformOptions = {
|
|
45
|
+
target: 'esnext',
|
|
46
|
+
format: 'esm',
|
|
47
|
+
loader: 'ts',
|
|
48
|
+
charset: 'utf8',
|
|
49
|
+
// TODO load local tsconfig
|
|
50
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as esbuild from 'esbuild';
|
|
2
|
+
import type {Logger} from '@ryanatkn/belt/log.js';
|
|
3
|
+
import {basename} from 'node:path';
|
|
4
|
+
import type {CompileOptions, PreprocessorGroup, ModuleCompileOptions} from 'svelte/compiler';
|
|
5
|
+
|
|
6
|
+
import {print_build_result, to_define_import_meta_env} from './esbuild_helpers.js';
|
|
7
|
+
import {resolve_specifier} from './resolve_specifier.js';
|
|
8
|
+
import {esbuild_plugin_sveltekit_shim_alias} from './esbuild_plugin_sveltekit_shim_alias.js';
|
|
9
|
+
import {esbuild_plugin_sveltekit_shim_env} from './esbuild_plugin_sveltekit_shim_env.js';
|
|
10
|
+
import {esbuild_plugin_sveltekit_shim_app} from './esbuild_plugin_sveltekit_shim_app.js';
|
|
11
|
+
import {esbuild_plugin_sveltekit_local_imports} from './esbuild_plugin_sveltekit_local_imports.js';
|
|
12
|
+
import {esbuild_plugin_svelte} from './esbuild_plugin_svelte.js';
|
|
13
|
+
import type {Parsed_Sveltekit_Config} from './sveltekit_config.js';
|
|
14
|
+
import type {Path_Id} from './path.js';
|
|
15
|
+
|
|
16
|
+
export interface Options {
|
|
17
|
+
dev: boolean;
|
|
18
|
+
build_options: esbuild.BuildOptions;
|
|
19
|
+
dir?: string;
|
|
20
|
+
svelte_compile_options?: CompileOptions;
|
|
21
|
+
svelte_compile_module_options?: ModuleCompileOptions;
|
|
22
|
+
svelte_preprocessors?: PreprocessorGroup | PreprocessorGroup[];
|
|
23
|
+
alias?: Record<string, string>;
|
|
24
|
+
base_url?: Parsed_Sveltekit_Config['base_url'];
|
|
25
|
+
assets_url?: Parsed_Sveltekit_Config['assets_url'];
|
|
26
|
+
public_prefix?: string;
|
|
27
|
+
private_prefix?: string;
|
|
28
|
+
env_dir?: string;
|
|
29
|
+
env_files?: string[];
|
|
30
|
+
ambient_env?: Record<string, string>;
|
|
31
|
+
log?: Logger;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const esbuild_plugin_external_worker = ({
|
|
35
|
+
dev,
|
|
36
|
+
build_options,
|
|
37
|
+
dir = process.cwd(),
|
|
38
|
+
svelte_compile_options,
|
|
39
|
+
svelte_preprocessors,
|
|
40
|
+
svelte_compile_module_options,
|
|
41
|
+
alias,
|
|
42
|
+
base_url,
|
|
43
|
+
assets_url,
|
|
44
|
+
public_prefix,
|
|
45
|
+
private_prefix,
|
|
46
|
+
env_dir,
|
|
47
|
+
env_files,
|
|
48
|
+
ambient_env,
|
|
49
|
+
log,
|
|
50
|
+
}: Options): esbuild.Plugin => ({
|
|
51
|
+
name: 'external_worker',
|
|
52
|
+
setup: (build) => {
|
|
53
|
+
const builds: Map<string, Promise<esbuild.BuildResult>> = new Map();
|
|
54
|
+
const build_worker = async (path_id: Path_Id): Promise<esbuild.BuildResult> => {
|
|
55
|
+
if (builds.has(path_id)) return builds.get(path_id)!;
|
|
56
|
+
const building = esbuild.build({
|
|
57
|
+
entryPoints: [path_id],
|
|
58
|
+
plugins: [
|
|
59
|
+
esbuild_plugin_sveltekit_shim_app({dev, base_url, assets_url}),
|
|
60
|
+
esbuild_plugin_sveltekit_shim_env({
|
|
61
|
+
dev,
|
|
62
|
+
public_prefix,
|
|
63
|
+
private_prefix,
|
|
64
|
+
env_dir,
|
|
65
|
+
env_files,
|
|
66
|
+
ambient_env,
|
|
67
|
+
}),
|
|
68
|
+
esbuild_plugin_sveltekit_shim_alias({dir, alias}),
|
|
69
|
+
esbuild_plugin_svelte({
|
|
70
|
+
dir,
|
|
71
|
+
svelte_compile_options,
|
|
72
|
+
svelte_compile_module_options,
|
|
73
|
+
svelte_preprocessors,
|
|
74
|
+
}),
|
|
75
|
+
esbuild_plugin_sveltekit_local_imports(),
|
|
76
|
+
],
|
|
77
|
+
define: to_define_import_meta_env(dev, base_url),
|
|
78
|
+
...build_options,
|
|
79
|
+
});
|
|
80
|
+
builds.set(path_id, building);
|
|
81
|
+
return building;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
build.onResolve({filter: /\.worker(|\.js|\.ts)$/u}, async ({path, resolveDir}) => {
|
|
85
|
+
const parsed = resolve_specifier(path, resolveDir);
|
|
86
|
+
const {specifier, path_id, namespace} = parsed;
|
|
87
|
+
const build_result = await build_worker(path_id);
|
|
88
|
+
if (log) print_build_result(log, build_result);
|
|
89
|
+
return {path: './' + basename(specifier), external: true, namespace};
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {test} from 'uvu';
|
|
2
|
+
import * as assert from 'uvu/assert';
|
|
3
|
+
import * as esbuild from 'esbuild';
|
|
4
|
+
import {readFile, rm} from 'node:fs/promises';
|
|
5
|
+
|
|
6
|
+
import {esbuild_plugin_svelte} from './esbuild_plugin_svelte.js';
|
|
7
|
+
|
|
8
|
+
test('build for the client', async () => {
|
|
9
|
+
const outfile = './src/fixtures/modules/some_test_server_bundle_DELETEME.js';
|
|
10
|
+
const built = await esbuild.build({
|
|
11
|
+
entryPoints: ['./src/fixtures/modules/some_test_server.ts'],
|
|
12
|
+
plugins: [esbuild_plugin_svelte()],
|
|
13
|
+
outfile,
|
|
14
|
+
format: 'esm',
|
|
15
|
+
platform: 'node',
|
|
16
|
+
packages: 'external',
|
|
17
|
+
bundle: true,
|
|
18
|
+
target: 'esnext',
|
|
19
|
+
});
|
|
20
|
+
assert.is(built.errors.length, 0);
|
|
21
|
+
assert.is(built.warnings.length, 0);
|
|
22
|
+
|
|
23
|
+
const built_output = await readFile(outfile, 'utf8');
|
|
24
|
+
assert.is(
|
|
25
|
+
built_output,
|
|
26
|
+
`// src/fixtures/modules/some_test_svelte_ts.svelte.ts
|
|
27
|
+
import * as $ from "svelte/internal/client";
|
|
28
|
+
var Some_Test_Svelte_Ts = class {
|
|
29
|
+
#a = $.source("ok");
|
|
30
|
+
get a() {
|
|
31
|
+
return $.get(this.#a);
|
|
32
|
+
}
|
|
33
|
+
set a(value) {
|
|
34
|
+
$.set(this.#a, $.proxy(value));
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// src/fixtures/modules/some_test_server.ts
|
|
39
|
+
var some_test_server = "some_test_server";
|
|
40
|
+
var Rexported_Some_Test_Svelte_Ts = Some_Test_Svelte_Ts;
|
|
41
|
+
export {
|
|
42
|
+
Rexported_Some_Test_Svelte_Ts,
|
|
43
|
+
some_test_server
|
|
44
|
+
};
|
|
45
|
+
`,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
await rm(outfile); // TODO could be cleaner
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('build for the server', async () => {
|
|
52
|
+
const outfile = './src/fixtures/modules/some_test_client_bundle_DELETEME.js';
|
|
53
|
+
const built = await esbuild.build({
|
|
54
|
+
entryPoints: ['./src/fixtures/modules/some_test_server.ts'],
|
|
55
|
+
plugins: [esbuild_plugin_svelte({svelte_compile_module_options: {generate: 'server'}})],
|
|
56
|
+
outfile,
|
|
57
|
+
format: 'esm',
|
|
58
|
+
platform: 'node',
|
|
59
|
+
packages: 'external',
|
|
60
|
+
bundle: true,
|
|
61
|
+
target: 'esnext',
|
|
62
|
+
});
|
|
63
|
+
assert.is(built.errors.length, 0);
|
|
64
|
+
assert.is(built.warnings.length, 0);
|
|
65
|
+
|
|
66
|
+
const built_output = await readFile(outfile, 'utf8');
|
|
67
|
+
assert.is(
|
|
68
|
+
built_output,
|
|
69
|
+
`// src/fixtures/modules/some_test_svelte_ts.svelte.ts
|
|
70
|
+
import * as $ from "svelte/internal/server";
|
|
71
|
+
var Some_Test_Svelte_Ts = class {
|
|
72
|
+
a = "ok";
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// src/fixtures/modules/some_test_server.ts
|
|
76
|
+
var some_test_server = "some_test_server";
|
|
77
|
+
var Rexported_Some_Test_Svelte_Ts = Some_Test_Svelte_Ts;
|
|
78
|
+
export {
|
|
79
|
+
Rexported_Some_Test_Svelte_Ts,
|
|
80
|
+
some_test_server
|
|
81
|
+
};
|
|
82
|
+
`,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
await rm(outfile); // TODO could be cleaner
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test.run();
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type * as esbuild from 'esbuild';
|
|
2
|
+
import {
|
|
3
|
+
compile,
|
|
4
|
+
compileModule,
|
|
5
|
+
preprocess,
|
|
6
|
+
type CompileOptions,
|
|
7
|
+
type ModuleCompileOptions,
|
|
8
|
+
type PreprocessorGroup,
|
|
9
|
+
} from 'svelte/compiler';
|
|
10
|
+
import {readFile} from 'node:fs/promises';
|
|
11
|
+
import {relative} from 'node:path';
|
|
12
|
+
|
|
13
|
+
import {SVELTE_MATCHER, SVELTE_RUNES_MATCHER} from './svelte_helpers.js';
|
|
14
|
+
|
|
15
|
+
export interface Options {
|
|
16
|
+
dir?: string;
|
|
17
|
+
svelte_compile_options?: CompileOptions;
|
|
18
|
+
svelte_compile_module_options?: ModuleCompileOptions;
|
|
19
|
+
svelte_preprocessors?: PreprocessorGroup | PreprocessorGroup[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const esbuild_plugin_svelte = (options: Options = {}): esbuild.Plugin => {
|
|
23
|
+
const {
|
|
24
|
+
dir = process.cwd(),
|
|
25
|
+
svelte_compile_options = {},
|
|
26
|
+
svelte_compile_module_options = {},
|
|
27
|
+
svelte_preprocessors,
|
|
28
|
+
} = options;
|
|
29
|
+
return {
|
|
30
|
+
name: 'svelte',
|
|
31
|
+
setup: (build) => {
|
|
32
|
+
build.onLoad({filter: SVELTE_RUNES_MATCHER}, async ({path}) => {
|
|
33
|
+
const source = await readFile(path, 'utf8');
|
|
34
|
+
try {
|
|
35
|
+
const filename = relative(dir, path);
|
|
36
|
+
const {js, warnings} = compileModule(source, {
|
|
37
|
+
filename,
|
|
38
|
+
...svelte_compile_module_options,
|
|
39
|
+
});
|
|
40
|
+
const contents = js.code + '//# sourceMappingURL=' + js.map.toUrl();
|
|
41
|
+
return {
|
|
42
|
+
contents,
|
|
43
|
+
warnings: warnings.map((w) => convert_svelte_message_to_esbuild(filename, source, w)),
|
|
44
|
+
};
|
|
45
|
+
} catch (err) {
|
|
46
|
+
return {errors: [convert_svelte_message_to_esbuild(path, source, err)]};
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
build.onLoad({filter: SVELTE_MATCHER}, async ({path}) => {
|
|
50
|
+
let source = await readFile(path, 'utf8');
|
|
51
|
+
try {
|
|
52
|
+
const filename = relative(dir, path);
|
|
53
|
+
const preprocessed = svelte_preprocessors
|
|
54
|
+
? await preprocess(source, svelte_preprocessors, {filename})
|
|
55
|
+
: null;
|
|
56
|
+
// TODO handle preprocessor sourcemaps, same as in loader - merge?
|
|
57
|
+
if (preprocessed?.code) source = preprocessed.code;
|
|
58
|
+
const {js, warnings} = compile(source, {
|
|
59
|
+
filename,
|
|
60
|
+
...svelte_compile_options,
|
|
61
|
+
});
|
|
62
|
+
const contents = js.code + '//# sourceMappingURL=' + js.map.toUrl();
|
|
63
|
+
return {
|
|
64
|
+
contents,
|
|
65
|
+
warnings: warnings.map((w) => convert_svelte_message_to_esbuild(filename, source, w)),
|
|
66
|
+
};
|
|
67
|
+
} catch (err) {
|
|
68
|
+
return {errors: [convert_svelte_message_to_esbuild(path, source, err)]};
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Following the example in the esbuild docs:
|
|
77
|
+
* https://esbuild.github.io/plugins/#svelte-plugin
|
|
78
|
+
*/
|
|
79
|
+
const convert_svelte_message_to_esbuild = (
|
|
80
|
+
path: string,
|
|
81
|
+
source: string,
|
|
82
|
+
{message, start, end}: SvelteError,
|
|
83
|
+
): esbuild.PartialMessage => {
|
|
84
|
+
let location: esbuild.PartialMessage['location'] = null;
|
|
85
|
+
if (start && end) {
|
|
86
|
+
const lineText = source.split(/\r\n|\r|\n/gu)[start.line - 1];
|
|
87
|
+
const lineEnd = start.line === end.line ? end.column : lineText.length;
|
|
88
|
+
location = {
|
|
89
|
+
file: path,
|
|
90
|
+
line: start.line,
|
|
91
|
+
lineText,
|
|
92
|
+
column: start.column,
|
|
93
|
+
length: lineEnd - start.column,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return {text: message, location};
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// these are not exported by Svelte
|
|
100
|
+
interface SvelteError {
|
|
101
|
+
message: string;
|
|
102
|
+
start?: LineInfo;
|
|
103
|
+
end?: LineInfo;
|
|
104
|
+
}
|
|
105
|
+
interface LineInfo {
|
|
106
|
+
line: number;
|
|
107
|
+
column: number;
|
|
108
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type * as esbuild from 'esbuild';
|
|
2
|
+
import {readFile} from 'node:fs/promises';
|
|
3
|
+
import {dirname} from 'node:path';
|
|
4
|
+
|
|
5
|
+
import {resolve_specifier} from './resolve_specifier.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Adds support for imports to both `.ts` and `.js`,
|
|
9
|
+
* as well as imports without extensions that resolve to `.js` or `.ts`.
|
|
10
|
+
* Prefers `.ts` over any `.js`, and falls back to `.ts` if no file is found.
|
|
11
|
+
*/
|
|
12
|
+
export const esbuild_plugin_sveltekit_local_imports = (): esbuild.Plugin => ({
|
|
13
|
+
name: 'sveltekit_local_imports',
|
|
14
|
+
setup: (build) => {
|
|
15
|
+
build.onResolve({filter: /^(\/|\.)/u}, (args) => {
|
|
16
|
+
const {path, importer} = args;
|
|
17
|
+
if (!importer) return {path};
|
|
18
|
+
const {path_id, namespace} = resolve_specifier(path, dirname(importer));
|
|
19
|
+
return {path: path_id, namespace}; // `namespace` may be `undefined`, but esbuild needs the absolute path for json etc
|
|
20
|
+
});
|
|
21
|
+
build.onLoad({filter: /.*/u, namespace: 'sveltekit_local_imports_ts'}, async ({path}) => ({
|
|
22
|
+
contents: await readFile(path),
|
|
23
|
+
loader: 'ts',
|
|
24
|
+
resolveDir: dirname(path),
|
|
25
|
+
}));
|
|
26
|
+
build.onLoad({filter: /.*/u, namespace: 'sveltekit_local_imports_js'}, async ({path}) => ({
|
|
27
|
+
contents: await readFile(path),
|
|
28
|
+
resolveDir: dirname(path),
|
|
29
|
+
}));
|
|
30
|
+
},
|
|
31
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type * as esbuild from 'esbuild';
|
|
2
|
+
import {escape_regexp} from '@ryanatkn/belt/regexp.js';
|
|
3
|
+
import {join} from 'node:path';
|
|
4
|
+
|
|
5
|
+
export interface Options {
|
|
6
|
+
dir?: string;
|
|
7
|
+
alias?: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const esbuild_plugin_sveltekit_shim_alias = ({
|
|
11
|
+
dir = process.cwd(),
|
|
12
|
+
alias,
|
|
13
|
+
}: Options): esbuild.Plugin => ({
|
|
14
|
+
name: 'sveltekit_shim_alias',
|
|
15
|
+
setup: (build) => {
|
|
16
|
+
const aliases: Record<string, string> = {$lib: 'src/lib', ...alias};
|
|
17
|
+
const alias_regexp_prefixes = Object.keys(aliases).map((a) => escape_regexp(a));
|
|
18
|
+
const filter = new RegExp('^(' + alias_regexp_prefixes.join('|') + ')', 'u');
|
|
19
|
+
build.onResolve({filter}, async (args) => {
|
|
20
|
+
const {path, ...rest} = args;
|
|
21
|
+
const prefix = filter.exec(path)![1];
|
|
22
|
+
return build.resolve(join(dir, aliases[prefix] + path.substring(prefix.length)), rest);
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
});
|