@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,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
+ });