@ryanatkn/gro 0.129.3 → 0.129.5

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 +4 -4
  2. package/package.json +4 -3
  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,41 @@
1
+ import type * as esbuild from 'esbuild';
2
+
3
+ import {
4
+ render_sveltekit_shim_app_environment,
5
+ render_sveltekit_shim_app_paths,
6
+ sveltekit_shim_app_specifiers,
7
+ } from './sveltekit_shim_app.js';
8
+ import type {Parsed_Sveltekit_Config} from './sveltekit_config.js';
9
+
10
+ export interface Options {
11
+ dev: boolean;
12
+ base_url: Parsed_Sveltekit_Config['base_url'];
13
+ assets_url: Parsed_Sveltekit_Config['assets_url'];
14
+ }
15
+
16
+ export const esbuild_plugin_sveltekit_shim_app = ({
17
+ dev,
18
+ base_url,
19
+ assets_url,
20
+ }: Options): esbuild.Plugin => ({
21
+ name: 'sveltekit_shim_app',
22
+ setup: (build) => {
23
+ build.onResolve({filter: /^\$app\/(forms|navigation|stores)$/u}, ({path, ...rest}) =>
24
+ build.resolve(sveltekit_shim_app_specifiers.get(path)!, rest),
25
+ );
26
+ build.onResolve({filter: /^\$app\/paths$/u}, ({path}) => ({
27
+ path: sveltekit_shim_app_specifiers.get(path)!,
28
+ namespace: 'sveltekit_shim_app_paths',
29
+ }));
30
+ build.onLoad({filter: /.*/u, namespace: 'sveltekit_shim_app_paths'}, () => ({
31
+ contents: render_sveltekit_shim_app_paths(base_url, assets_url),
32
+ }));
33
+ build.onResolve({filter: /^\$app\/environment$/u}, ({path}) => ({
34
+ path: sveltekit_shim_app_specifiers.get(path)!,
35
+ namespace: 'sveltekit_shim_app_environment',
36
+ }));
37
+ build.onLoad({filter: /.*/u, namespace: 'sveltekit_shim_app_environment'}, () => ({
38
+ contents: render_sveltekit_shim_app_environment(dev),
39
+ }));
40
+ },
41
+ });
@@ -0,0 +1,46 @@
1
+ import type * as esbuild from 'esbuild';
2
+
3
+ import {render_env_shim_module} from './sveltekit_shim_env.js';
4
+
5
+ export interface Options {
6
+ dev: boolean;
7
+ public_prefix?: string;
8
+ private_prefix?: string;
9
+ env_dir?: string;
10
+ env_files?: string[];
11
+ ambient_env?: Record<string, string>;
12
+ }
13
+
14
+ export const esbuild_plugin_sveltekit_shim_env = ({
15
+ dev,
16
+ public_prefix,
17
+ private_prefix,
18
+ env_dir,
19
+ env_files,
20
+ ambient_env,
21
+ }: Options): esbuild.Plugin => ({
22
+ name: 'sveltekit_shim_env',
23
+ setup: (build) => {
24
+ const namespace = 'sveltekit_shim_env';
25
+ const filter = /^\$env\/(static|dynamic)\/(public|private)$/u;
26
+ build.onResolve({filter}, ({path}) => ({path, namespace}));
27
+ build.onLoad({filter: /.*/u, namespace}, ({path}) => {
28
+ const matches = filter.exec(path);
29
+ const mode = matches![1] as 'static' | 'dynamic';
30
+ const visibility = matches![2] as 'public' | 'private';
31
+ return {
32
+ loader: 'ts',
33
+ contents: render_env_shim_module(
34
+ dev,
35
+ mode,
36
+ visibility,
37
+ public_prefix,
38
+ private_prefix,
39
+ env_dir,
40
+ env_files,
41
+ ambient_env,
42
+ ),
43
+ };
44
+ });
45
+ },
46
+ });
@@ -0,0 +1,30 @@
1
+ import {print_spawn_result} from '@ryanatkn/belt/process.js';
2
+ import {z} from 'zod';
3
+
4
+ import {Task_Error, type Task} from './task.js';
5
+ import {format_directory} from './format_directory.js';
6
+ import {paths} from './paths.js';
7
+
8
+ export const Args = z
9
+ .object({
10
+ check: z
11
+ .boolean({description: 'exit with a nonzero code if any files are unformatted'})
12
+ .default(false),
13
+ })
14
+ .strict();
15
+ export type Args = z.infer<typeof Args>;
16
+
17
+ export const task: Task<Args> = {
18
+ summary: 'format source files',
19
+ Args,
20
+ run: async ({args, log}) => {
21
+ const {check} = args;
22
+ // TODO forward prettier args
23
+ const format_result = await format_directory(log, paths.source, check);
24
+ if (!format_result.ok) {
25
+ throw new Task_Error(
26
+ `Failed ${check ? 'formatting check' : 'to format'}. ${print_spawn_result(format_result)}`,
27
+ );
28
+ }
29
+ },
30
+ };
@@ -0,0 +1,55 @@
1
+ import type {Spawn_Result} from '@ryanatkn/belt/process.js';
2
+ import type {Logger} from '@ryanatkn/belt/log.js';
3
+
4
+ import {paths} from './paths.js';
5
+ import {
6
+ GITHUB_DIRNAME,
7
+ README_FILENAME,
8
+ SVELTEKIT_CONFIG_FILENAME,
9
+ VITE_CONFIG_FILENAME,
10
+ TSCONFIG_FILENAME,
11
+ GRO_CONFIG_PATH,
12
+ } from './path_constants.js';
13
+ import {serialize_args, to_forwarded_args} from './args.js';
14
+ import {spawn_cli, to_cli_name, type Cli} from './cli.js';
15
+
16
+ const PRETTIER_CLI = 'prettier';
17
+
18
+ const DEFAULT_EXTENSIONS = 'ts,js,json,svelte,html,css,md,yml';
19
+ const DEFAULT_ROOT_PATHS = `${[
20
+ README_FILENAME,
21
+ GRO_CONFIG_PATH,
22
+ SVELTEKIT_CONFIG_FILENAME,
23
+ VITE_CONFIG_FILENAME,
24
+ TSCONFIG_FILENAME,
25
+ GITHUB_DIRNAME,
26
+ ].join(',')}/**/*`;
27
+
28
+ /**
29
+ * Formats a directory on the filesystem.
30
+ * If the source directory is given, it also formats all of the root directory files.
31
+ * This is separated from `./format_file` to avoid importing all of the `prettier` code
32
+ * inside modules that import this one. (which has a nontrivial cost)
33
+ */
34
+ export const format_directory = async (
35
+ log: Logger,
36
+ dir: string,
37
+ check = false,
38
+ extensions = DEFAULT_EXTENSIONS,
39
+ root_paths = DEFAULT_ROOT_PATHS,
40
+ prettier_cli: string | Cli = PRETTIER_CLI,
41
+ ): Promise<Spawn_Result> => {
42
+ const forwarded_args = to_forwarded_args(to_cli_name(prettier_cli));
43
+ forwarded_args[check ? 'check' : 'write'] = true;
44
+ const serialized_args = serialize_args(forwarded_args);
45
+ serialized_args.push(`${dir}**/*.{${extensions}}`);
46
+ if (dir === paths.source) {
47
+ serialized_args.push(`${paths.root}{${root_paths}}`);
48
+ }
49
+ const spawned = await spawn_cli(prettier_cli, serialized_args, log);
50
+ if (!spawned)
51
+ throw new Error(
52
+ `failed to find \`${to_cli_name(prettier_cli)}\` CLI locally or globally, do you need to run \`npm i\`?`,
53
+ );
54
+ return spawned;
55
+ };
@@ -0,0 +1,20 @@
1
+ import {test} from 'uvu';
2
+ import * as assert from 'uvu/assert';
3
+
4
+ import {format_file} from './format_file.js';
5
+
6
+ test('format ts', async () => {
7
+ const ts_unformatted = 'hey (1)';
8
+ const ts_formatted = 'hey(1);\n';
9
+ assert.is(await format_file(ts_unformatted, {filepath: 'foo.ts'}), ts_formatted);
10
+ assert.is(await format_file(ts_unformatted, {parser: 'typescript'}), ts_formatted);
11
+ });
12
+
13
+ test('format svelte', async () => {
14
+ const svelte_unformatted = '<style>a{color: red}</style>';
15
+ const svelte_formatted = '<style>\n\ta {\n\t\tcolor: red;\n\t}\n</style>\n';
16
+ assert.is(await format_file(svelte_unformatted, {filepath: 'foo.svelte'}), svelte_formatted);
17
+ assert.is(await format_file(svelte_unformatted, {parser: 'svelte'}), svelte_formatted);
18
+ });
19
+
20
+ test.run();
@@ -0,0 +1,49 @@
1
+ import prettier from 'prettier';
2
+ import {extname} from 'node:path';
3
+
4
+ import {load_package_json} from './package_json.js';
5
+
6
+ let cached_base_options: prettier.Options | undefined;
7
+
8
+ /**
9
+ * Formats a file with Prettier.
10
+ * @param content
11
+ * @param options
12
+ * @param base_options - defaults to the the cwd's package.json `prettier` value
13
+ */
14
+ export const format_file = async (
15
+ content: string,
16
+ options: prettier.Options,
17
+ base_options: prettier.Options | null | undefined = cached_base_options,
18
+ ): Promise<string> => {
19
+ const final_base_options =
20
+ base_options !== undefined
21
+ ? base_options
22
+ : (cached_base_options = load_package_json().prettier as any);
23
+ let final_options = options;
24
+ if (options.filepath && !options.parser) {
25
+ const {filepath, ...rest} = options;
26
+ const parser = infer_parser(filepath);
27
+ if (parser) final_options = {...rest, parser};
28
+ }
29
+ try {
30
+ return await prettier.format(content, {...final_base_options, ...final_options});
31
+ } catch (_err) {
32
+ return content;
33
+ }
34
+ };
35
+
36
+ // This is just a simple convenience for callers so they can pass a file path.
37
+ // They can provide the Prettier `options.parser` for custom extensions.
38
+ const infer_parser = (path: string): string | null => {
39
+ const extension = extname(path).substring(1);
40
+ switch (extension) {
41
+ case 'svelte':
42
+ case 'xml': {
43
+ return extension;
44
+ }
45
+ default: {
46
+ return null;
47
+ }
48
+ }
49
+ };
package/src/lib/fs.ts ADDED
@@ -0,0 +1,18 @@
1
+ import {rm} from 'node:fs/promises';
2
+ import {readdirSync, type RmOptions} from 'node:fs';
3
+ import {join} from 'node:path';
4
+
5
+ /**
6
+ * Empties a directory with an optional `filter`.
7
+ */
8
+ export const empty_dir = async (
9
+ dir: string,
10
+ filter?: (path: string) => boolean,
11
+ options?: RmOptions,
12
+ ): Promise<void> => {
13
+ await Promise.all(
14
+ readdirSync(dir).map((path) =>
15
+ filter && !filter(path) ? null : rm(join(dir, path), {...options, recursive: true}),
16
+ ),
17
+ );
18
+ };
@@ -0,0 +1,134 @@
1
+ import {red, green, gray} from '@ryanatkn/belt/styletext.js';
2
+ import {print_ms, print_error} from '@ryanatkn/belt/print.js';
3
+ import {plural} from '@ryanatkn/belt/string.js';
4
+ import {z} from 'zod';
5
+
6
+ import {Task_Error, type Task} from './task.js';
7
+ import {run_gen} from './run_gen.js';
8
+ import {Raw_Input_Path, to_input_paths} from './input_path.js';
9
+ import {format_file} from './format_file.js';
10
+ import {print_path} from './paths.js';
11
+ import {log_error_reasons} from './task_logging.js';
12
+ import {write_gen_results, analyze_gen_results, find_genfiles, load_genfiles} from './gen.js';
13
+ import {SOURCE_DIRNAME} from './path_constants.js';
14
+
15
+ export const Args = z
16
+ .object({
17
+ _: z.array(Raw_Input_Path, {description: 'input paths to generate'}).default([SOURCE_DIRNAME]),
18
+ root_dirs: z
19
+ .array(z.string(), {description: 'root directories to resolve input paths against'}) // TODO `Path_Id` schema
20
+ .default([process.cwd()]),
21
+ check: z
22
+ .boolean({description: 'exit with a nonzero code if any files need to be generated'})
23
+ .default(false),
24
+ })
25
+ .strict();
26
+ export type Args = z.infer<typeof Args>;
27
+
28
+ // TODO test - especially making sure nothing gets genned
29
+ // if there's any validation or import errors
30
+ export const task: Task<Args> = {
31
+ summary: 'run code generation scripts',
32
+ Args,
33
+ run: async ({args, log, timings, config}): Promise<void> => {
34
+ const {_: raw_input_paths, root_dirs, check} = args;
35
+
36
+ const input_paths = to_input_paths(raw_input_paths);
37
+
38
+ // load all of the gen modules
39
+ const found = find_genfiles(input_paths, root_dirs, config, timings);
40
+ if (!found.ok) {
41
+ if (found.type === 'input_directories_with_no_files') {
42
+ // TODO maybe let this error like the normal case, but only call `gro gen` if we find gen files? problem is the args would need to be hoisted to callers like `gro sync`
43
+ log.info('no gen modules found in ' + input_paths.join(', '));
44
+ return;
45
+ } else {
46
+ log_error_reasons(log, found.reasons);
47
+ throw new Task_Error('Failed to find gen modules.');
48
+ }
49
+ }
50
+ const found_genfiles = found.value;
51
+ log.info(
52
+ 'gen files',
53
+ found_genfiles.resolved_input_files.map((f) => f.id),
54
+ );
55
+ const loaded = await load_genfiles(found_genfiles, timings);
56
+ if (!loaded.ok) {
57
+ log_error_reasons(log, loaded.reasons);
58
+ throw new Task_Error('Failed to load gen modules.');
59
+ }
60
+ const loaded_genfiles = loaded.value;
61
+
62
+ // run `gen` on each of the modules
63
+ const timing_to_generate_code = timings.start('generate code'); // TODO this ignores `gen_results.elapsed` - should it return `Timings` instead?
64
+ const gen_results = await run_gen(loaded_genfiles.modules, config, log, timings, format_file);
65
+ timing_to_generate_code();
66
+
67
+ const fail_count = gen_results.failures.length;
68
+ const analyzed_gen_results = await analyze_gen_results(gen_results);
69
+ if (check) {
70
+ // check if any files changed, and if so, throw errors,
71
+ // but if there are gen failures, skip the check and defer to their errors
72
+ if (!fail_count) {
73
+ log.info('checking generated files for changes');
74
+ const timing_to_check_results = timings.start('check results for changes');
75
+ timing_to_check_results();
76
+
77
+ let has_unexpected_changes = false;
78
+ for (const analyzed of analyzed_gen_results) {
79
+ if (!analyzed.has_changed) continue;
80
+ has_unexpected_changes = true;
81
+ log.error(
82
+ red(
83
+ `Generated file ${print_path(analyzed.file.id)} via ${print_path(
84
+ analyzed.file.origin_id,
85
+ )} ${analyzed.is_new ? 'is new' : 'has changed'}.`,
86
+ ),
87
+ );
88
+ }
89
+ if (has_unexpected_changes) {
90
+ throw new Task_Error(
91
+ 'Failed gen check. Some generated files have unexpectedly changed.' +
92
+ ' Run `gro gen` and try again.',
93
+ );
94
+ }
95
+ log.info('check passed, no files have changed');
96
+ }
97
+ } else {
98
+ // write generated files to disk
99
+ log.info('writing generated files to disk');
100
+ const timing_to_output_results = timings.start('output results');
101
+ await write_gen_results(gen_results, analyzed_gen_results, log);
102
+ timing_to_output_results();
103
+ }
104
+
105
+ // TODO these final printed results could be improved showing a breakdown per file id
106
+ const new_count = analyzed_gen_results.filter((r) => r.is_new).length;
107
+ const changed_count = analyzed_gen_results.filter((r) => r.has_changed).length;
108
+ const unchanged_count = analyzed_gen_results.filter((r) => !r.is_new && !r.has_changed).length;
109
+ let log_result = green('gen results:');
110
+ log_result += `\n\t${new_count} ` + gray('new');
111
+ log_result += `\n\t${changed_count} ` + gray('changed');
112
+ log_result += `\n\t${unchanged_count} ` + gray('unchanged');
113
+ for (const result of gen_results.results) {
114
+ log_result += `\n\t${result.ok ? green('✓') : red('🞩')} ${
115
+ result.ok ? result.files.length : 0
116
+ } ${gray('in')} ${print_ms(result.elapsed)} ${gray('←')} ${print_path(result.id)}`;
117
+ }
118
+ log.info(log_result);
119
+ log.info(
120
+ green(
121
+ `generated ${gen_results.output_count} file${plural(gen_results.output_count)} from ${
122
+ gen_results.successes.length
123
+ } input file${plural(gen_results.successes.length)}`,
124
+ ),
125
+ );
126
+
127
+ if (fail_count) {
128
+ for (const result of gen_results.failures) {
129
+ log.error(result.reason, '\n', print_error(result.error));
130
+ }
131
+ throw new Task_Error(`Failed to generate ${fail_count} file${plural(fail_count)}.`);
132
+ }
133
+ },
134
+ };