@ryanatkn/gro 0.129.4 → 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 +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,101 @@
1
+ import {join, extname, relative, basename} from 'node:path';
2
+ import {fileURLToPath} from 'node:url';
3
+ import {strip_end} from '@ryanatkn/belt/string.js';
4
+ import {gray} from '@ryanatkn/belt/styletext.js';
5
+
6
+ import {
7
+ GRO_CONFIG_PATH,
8
+ GRO_DEV_DIR,
9
+ GRO_DIR,
10
+ SOURCE_DIR,
11
+ SVELTEKIT_DIST_DIRNAME,
12
+ } from './path_constants.js';
13
+ import {sveltekit_config_global} from './sveltekit_config_global.js';
14
+ import type {Path_Id} from './path.js';
15
+
16
+ /*
17
+
18
+ A path `id` is an absolute path to the source/.gro/dist directory.
19
+ It's the same name that Rollup uses.
20
+
21
+ */
22
+
23
+ export const LIB_DIRNAME = basename(sveltekit_config_global.lib_path);
24
+ export const LIB_PATH = SOURCE_DIR + LIB_DIRNAME;
25
+ export const LIB_DIR = LIB_PATH + '/';
26
+ export const ROUTES_DIRNAME = basename(sveltekit_config_global.routes_path);
27
+
28
+ export interface Paths {
29
+ root: string;
30
+ source: string;
31
+ lib: string;
32
+ build: string;
33
+ build_dev: string;
34
+ config: string;
35
+ }
36
+
37
+ export const create_paths = (root_dir: string): Paths => {
38
+ // TODO remove reliance on trailing slash towards windows support
39
+ const root = strip_end(root_dir, '/') + '/';
40
+ return {
41
+ root,
42
+ source: root + SOURCE_DIR,
43
+ lib: root + LIB_DIR,
44
+ build: root + GRO_DIR,
45
+ build_dev: root + GRO_DEV_DIR,
46
+ config: root + GRO_CONFIG_PATH,
47
+ };
48
+ };
49
+
50
+ export const infer_paths = (id: Path_Id): Paths => (is_gro_id(id) ? gro_paths : paths);
51
+ export const is_gro_id = (id: Path_Id): boolean => id.startsWith(strip_end(gro_paths.root, '/')); // strip `/` in case we're looking at the Gro root without a trailing slash
52
+
53
+ // '/home/me/app/src/foo/bar/baz.ts' → 'src/foo/bar/baz.ts'
54
+ export const to_root_path = (id: Path_Id, p = infer_paths(id)): string =>
55
+ relative(p.root, id) || './';
56
+
57
+ // '/home/me/app/src/foo/bar/baz.ts' → 'foo/bar/baz.ts'
58
+ export const path_id_to_base_path = (path_id: Path_Id, p = infer_paths(path_id)): string =>
59
+ relative(p.source, path_id);
60
+
61
+ // TODO base_path is an obsolete concept, it was a remnant from forcing `src/`
62
+ // 'foo/bar/baz.ts' → '/home/me/app/src/foo/bar/baz.ts'
63
+ export const base_path_to_path_id = (base_path: string, p = infer_paths(base_path)): Path_Id =>
64
+ join(p.source, base_path);
65
+
66
+ export const print_path = (path: string, p = infer_paths(path)): string => {
67
+ let final_path =
68
+ strip_end(path, '/') === strip_end(GRO_DIST_DIR, '/') ? 'gro' : to_root_path(path, p);
69
+ final_path =
70
+ final_path === 'gro' ? final_path : final_path[0] === '.' ? final_path : './' + final_path;
71
+ return gray(final_path);
72
+ };
73
+
74
+ export const replace_extension = (path: string, new_extension: string): string => {
75
+ const {length} = extname(path);
76
+ return (length === 0 ? path : path.substring(0, path.length - length)) + new_extension;
77
+ };
78
+
79
+ /**
80
+ * Paths for the user repo.
81
+ */
82
+ export const paths = create_paths(process.cwd());
83
+
84
+ export const GRO_PACKAGE_DIR = 'gro/';
85
+ // TODO document these conditions with comments
86
+ // TODO there's probably a more robust way to do this
87
+ const filename = fileURLToPath(import.meta.url);
88
+ const gro_package_dir_path = join(
89
+ filename,
90
+ filename.includes('/gro/src/lib/')
91
+ ? '../../../'
92
+ : filename.includes('/gro/dist/')
93
+ ? '../../'
94
+ : '../',
95
+ );
96
+ export const IS_THIS_GRO = gro_package_dir_path === paths.root;
97
+ /**
98
+ * Paths for the Gro package being used by the user repo.
99
+ */
100
+ export const gro_paths = IS_THIS_GRO ? paths : create_paths(gro_package_dir_path);
101
+ export const GRO_DIST_DIR = gro_paths.root + SVELTEKIT_DIST_DIRNAME + '/';
@@ -0,0 +1,57 @@
1
+ import {test} from 'uvu';
2
+ import * as assert from 'uvu/assert';
3
+
4
+ import {replace_plugin} from './plugin.js';
5
+
6
+ test('replace_plugin', () => {
7
+ const a = {name: 'a'};
8
+ const b = {name: 'b'};
9
+ const c = {name: 'c'};
10
+ const plugins = [a, b, c];
11
+ const a2 = {name: 'a'};
12
+ const b2 = {name: 'b'};
13
+ const c2 = {name: 'c'};
14
+ let p = plugins;
15
+ p = replace_plugin(p, a2);
16
+ assert.is(p[0], a2);
17
+ assert.is(p[1], b);
18
+ assert.is(p[2], c);
19
+ p = replace_plugin(p, b2);
20
+ assert.is(p[0], a2);
21
+ assert.is(p[1], b2);
22
+ assert.is(p[2], c);
23
+ // allows duplicate names in the array
24
+ p = replace_plugin(p, c2, 'a');
25
+ assert.is(p[0], c2);
26
+ assert.is(p[1], b2);
27
+ assert.is(p[2], c);
28
+ p = replace_plugin(p, a2, 'c');
29
+ assert.is(p[0], a2);
30
+ assert.is(p[1], b2);
31
+ assert.is(p[2], c);
32
+ p = replace_plugin(p, c2);
33
+ assert.is(p[0], a2);
34
+ assert.is(p[1], b2);
35
+ assert.is(p[2], c2);
36
+ });
37
+
38
+ test('replace_plugin without an array', () => {
39
+ const a = {name: 'a'};
40
+ const a2 = {name: 'a'};
41
+ const p = replace_plugin(a, a2);
42
+ assert.is(p[0], a2);
43
+ });
44
+
45
+ test('replace_plugin throws if it cannot find the given name', () => {
46
+ const a = {name: 'a'};
47
+ const plugins = [a];
48
+ let err;
49
+ try {
50
+ replace_plugin(plugins, {name: 'b'});
51
+ } catch (_err) {
52
+ err = _err;
53
+ }
54
+ if (!err) assert.unreachable('should have failed');
55
+ });
56
+
57
+ test.run();
@@ -0,0 +1,113 @@
1
+ import {to_array} from '@ryanatkn/belt/array.js';
2
+
3
+ import type {Task_Context} from './task.js';
4
+
5
+ /**
6
+ * Gro `Plugin`s enable custom behavior during `gro dev` and `gro build`.
7
+ * In contrast, `Adapter`s use the results of `gro build` to produce final artifacts.
8
+ */
9
+ export interface Plugin<T_Plugin_Context extends Plugin_Context = Plugin_Context> {
10
+ name: string;
11
+ setup?: (ctx: T_Plugin_Context) => void | Promise<void>;
12
+ adapt?: (ctx: T_Plugin_Context) => void | Promise<void>;
13
+ teardown?: (ctx: T_Plugin_Context) => void | Promise<void>;
14
+ }
15
+
16
+ export type Create_Config_Plugins<T_Plugin_Context extends Plugin_Context = Plugin_Context> = (
17
+ ctx: T_Plugin_Context,
18
+ ) =>
19
+ | (Plugin<T_Plugin_Context> | null | Array<Plugin<T_Plugin_Context> | null>)
20
+ | Promise<Plugin<T_Plugin_Context> | null | Array<Plugin<T_Plugin_Context> | null>>;
21
+
22
+ export interface Plugin_Context<T_Args = object> extends Task_Context<T_Args> {
23
+ dev: boolean;
24
+ watch: boolean;
25
+ }
26
+
27
+ export class Plugins<T_Plugin_Context extends Plugin_Context> {
28
+ /* prefer `Plugins.create` to the constructor */
29
+ constructor(
30
+ private ctx: T_Plugin_Context,
31
+ private instances: Plugin[],
32
+ ) {}
33
+
34
+ static async create<T_Plugin_Context extends Plugin_Context>(
35
+ ctx: T_Plugin_Context,
36
+ ): Promise<Plugins<T_Plugin_Context>> {
37
+ const {timings} = ctx;
38
+ const timing_to_create = timings.start('plugins.create');
39
+ const instances: Plugin[] = to_array(await ctx.config.plugins(ctx)).filter(
40
+ (v) => v !== null,
41
+ ) as Plugin[]; // TODO remove cast, should infer the type predicate? `Type '(Plugin<Plugin_Context<object>> | null)[]' is not assignable to type 'Plugin<Plugin_Context<object>>[]'.`
42
+ const plugins = new Plugins(ctx, instances);
43
+ timing_to_create();
44
+ return plugins;
45
+ }
46
+
47
+ async setup(): Promise<void> {
48
+ const {ctx, instances} = this;
49
+ if (!instances.length) return;
50
+ const {timings, log} = ctx;
51
+ const timing_to_setup = timings.start('plugins.setup');
52
+ for (const plugin of instances) {
53
+ if (!plugin.setup) continue;
54
+ log.debug('setup plugin', plugin.name);
55
+ const timing = timings.start(`setup:${plugin.name}`);
56
+ await plugin.setup(ctx); // eslint-disable-line no-await-in-loop
57
+ timing();
58
+ }
59
+ timing_to_setup();
60
+ }
61
+
62
+ async adapt(): Promise<void> {
63
+ const {ctx, instances} = this;
64
+ const {timings} = ctx;
65
+ const timing_to_run_adapters = timings.start('plugins.adapt');
66
+ for (const plugin of instances) {
67
+ if (!plugin.adapt) continue;
68
+ const timing = timings.start(`adapt:${plugin.name}`);
69
+ await plugin.adapt(ctx); // eslint-disable-line no-await-in-loop
70
+ timing();
71
+ }
72
+ timing_to_run_adapters();
73
+ }
74
+
75
+ async teardown(): Promise<void> {
76
+ const {ctx, instances} = this;
77
+ if (!instances.length) return;
78
+ const {timings, log} = ctx;
79
+ const timing_to_teardown = timings.start('plugins.teardown');
80
+ for (const plugin of instances) {
81
+ if (!plugin.teardown) continue;
82
+ log.debug('teardown plugin', plugin.name);
83
+ const timing = timings.start(`teardown:${plugin.name}`);
84
+ await plugin.teardown(ctx); // eslint-disable-line no-await-in-loop
85
+ timing();
86
+ }
87
+ timing_to_teardown();
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Replaces a plugin by name in `plugins` without mutating the param.
93
+ * Throws if the plugin name cannot be found.
94
+ * @param plugins - accepts the same types as the return value of `Create_Config_Plugins`
95
+ * @param new_plugin
96
+ * @param name - @default new_plugin.name
97
+ * @returns `plugins` with `new_plugin` at the index of the plugin with `name`
98
+ */
99
+ export const replace_plugin = <
100
+ T_Plugins extends T_Plugin | null | Array<T_Plugin | null>,
101
+ T_Plugin extends Plugin,
102
+ >(
103
+ plugins: T_Plugins,
104
+ new_plugin: Plugin,
105
+ name = new_plugin.name,
106
+ ): T_Plugin[] => {
107
+ const array = to_array(plugins).filter((v) => v !== null);
108
+ const index = array.findIndex((p) => p.name === name);
109
+ if (index === -1) throw Error('Failed to find plugin to replace: ' + name);
110
+ const replaced = array.slice();
111
+ replaced[index] = new_plugin;
112
+ return replaced as T_Plugin[];
113
+ };
@@ -0,0 +1,194 @@
1
+ import {spawn} from '@ryanatkn/belt/process.js';
2
+ import {z} from 'zod';
3
+ import {green, cyan} from '@ryanatkn/belt/styletext.js';
4
+ import {existsSync} from 'node:fs';
5
+
6
+ import {Task_Error, type Task} from './task.js';
7
+ import {load_package_json, parse_repo_url} from './package_json.js';
8
+ import {find_cli, spawn_cli} from './cli.js';
9
+ import {IS_THIS_GRO} from './paths.js';
10
+ import {has_sveltekit_library} from './sveltekit_helpers.js';
11
+ import {update_changelog} from './changelog.js';
12
+ import {load_from_env} from './env.js';
13
+ import {
14
+ Git_Branch,
15
+ Git_Origin,
16
+ git_check_clean_workspace,
17
+ git_checkout,
18
+ git_fetch,
19
+ git_pull,
20
+ } from './git.js';
21
+ import {CHANGESET_CLI} from './changeset_helpers.js';
22
+
23
+ // publish.task.ts
24
+ // - usage: `gro publish patch`
25
+ // - forwards args to `npm version`: https://docs.npmjs.com/v6/commands/npm-version
26
+ // - runs the production build
27
+ // - publishes to npm from the `main` branch, configurable with `--branch`
28
+ // - syncs commits and tags to the configured main branch
29
+
30
+ export const Args = z
31
+ .object({
32
+ branch: Git_Branch.describe('branch to publish from').default('main'),
33
+ origin: Git_Origin.describe('git origin to publish from').default('origin'),
34
+ changelog: z
35
+ .string({description: 'file name and path of the changelog'})
36
+ .default('CHANGELOG.md'),
37
+ preserve_changelog: z
38
+ .boolean({
39
+ description:
40
+ 'opt out of linkifying and formatting the changelog from @changesets/changelog-git',
41
+ })
42
+ .default(false),
43
+ optional: z.boolean({description: 'exit gracefully if there are no changesets'}).default(false),
44
+ dry: z
45
+ .boolean({description: 'build and prepare to publish without actually publishing'})
46
+ .default(false),
47
+ check: z.boolean({description: 'dual of no-check'}).default(true),
48
+ 'no-check': z
49
+ .boolean({description: 'opt out of npm checking before publishing'})
50
+ .default(false),
51
+ build: z.boolean({description: 'dual of no-build'}).default(true),
52
+ 'no-build': z.boolean({description: 'opt out of building'}).default(false),
53
+ pull: z.boolean({description: 'dual of no-pull'}).default(true),
54
+ 'no-pull': z.boolean({description: 'opt out of git pull'}).default(false),
55
+ changeset_cli: z.string({description: 'the changeset CLI to use'}).default(CHANGESET_CLI),
56
+ })
57
+ .strict();
58
+ export type Args = z.infer<typeof Args>;
59
+
60
+ export const task: Task<Args> = {
61
+ summary: 'bump version, publish to npm, and git push',
62
+ Args,
63
+ run: async ({args, log, invoke_task}): Promise<void> => {
64
+ const {
65
+ branch,
66
+ origin,
67
+ changelog,
68
+ preserve_changelog,
69
+ dry,
70
+ check,
71
+ build,
72
+ pull,
73
+ optional,
74
+ changeset_cli,
75
+ } = args;
76
+ if (dry) {
77
+ log.info(green('dry run!'));
78
+ }
79
+
80
+ const has_sveltekit_library_result = has_sveltekit_library();
81
+ if (!has_sveltekit_library_result.ok) {
82
+ throw new Task_Error(
83
+ 'Failed to find SvelteKit library: ' + has_sveltekit_library_result.message,
84
+ );
85
+ }
86
+
87
+ // TODO hacky, ensures Gro bootstraps itself
88
+ if (IS_THIS_GRO) {
89
+ await spawn('npm', ['run', 'build']);
90
+ }
91
+
92
+ const changelog_exists = existsSync(changelog);
93
+
94
+ const found_changeset_cli = find_cli(changeset_cli);
95
+ if (!found_changeset_cli) {
96
+ throw new Task_Error(
97
+ 'changeset command not found, install @changesets/cli locally or globally',
98
+ );
99
+ }
100
+
101
+ // Make sure we're on the right branch:
102
+ await git_fetch(origin, branch);
103
+ await git_checkout(branch);
104
+ if (pull) {
105
+ if (await git_check_clean_workspace()) {
106
+ throw new Task_Error('The git workspace is not clean, pass --no-pull to bypass git pull');
107
+ }
108
+ await git_pull(origin, branch);
109
+ }
110
+
111
+ // Check before proceeding.
112
+ if (check) {
113
+ await invoke_task('check', {workspace: true});
114
+ }
115
+
116
+ let version!: string;
117
+
118
+ // Bump the version so the package.json is updated before building:
119
+ // TODO problem here is build may fail and put us in a bad state,
120
+ // but I don't see how we could do this to robustly
121
+ // have the new version in the build without building twice -
122
+ // maybe the code should catch the error and revert the version and delete the tag?
123
+ if (dry) {
124
+ log.info('dry run, skipping changeset version');
125
+ } else {
126
+ const package_json_before = load_package_json();
127
+ if (typeof package_json_before.version !== 'string') {
128
+ throw new Task_Error('Failed to find package.json version');
129
+ }
130
+ const parsed_repo_url = parse_repo_url(package_json_before);
131
+ if (!parsed_repo_url) {
132
+ throw new Task_Error(
133
+ 'package.json `repository` must contain a repo url (and GitHub only for now, sorry),' +
134
+ ' like `git+https://github.com/ryanatkn/gro.git` or `https://github.com/ryanatkn/gro`' +
135
+ ' or an object with the `url` key',
136
+ );
137
+ }
138
+
139
+ // This is the first line that alters the repo.
140
+
141
+ const npmVersionResult = await spawn_cli(found_changeset_cli, ['version'], log);
142
+ if (!npmVersionResult?.ok) {
143
+ throw Error('npm version failed: no commits were made: see the error above');
144
+ }
145
+
146
+ if (!preserve_changelog) {
147
+ const token = load_from_env('GITHUB_TOKEN_SECRET');
148
+ if (!token) {
149
+ log.warn(
150
+ 'the env var GITHUB_TOKEN_SECRET was not found, so API calls with be unauthorized',
151
+ );
152
+ }
153
+ await update_changelog(parsed_repo_url.owner, parsed_repo_url.repo, changelog, token, log);
154
+ }
155
+
156
+ const package_json_after = load_package_json();
157
+ version = package_json_after.version!;
158
+ if (package_json_before.version === version) {
159
+ // The version didn't change.
160
+ // For now this is the best detection we have for a no-op `changeset version`.
161
+ if (optional) {
162
+ return; // exit gracefully
163
+ } else {
164
+ throw new Task_Error(`\`${changeset_cli} version\` failed: are there any changes?`);
165
+ }
166
+ }
167
+ }
168
+
169
+ if (build) {
170
+ await invoke_task('build');
171
+ }
172
+
173
+ if (dry) {
174
+ log.info('publishing branch ' + branch);
175
+ log.info(green('dry run complete!'));
176
+ return;
177
+ }
178
+
179
+ const npm_publish_result = await spawn_cli(found_changeset_cli, ['publish'], log);
180
+ if (!npm_publish_result?.ok) {
181
+ throw new Task_Error(
182
+ `\`${changeset_cli} publish\` failed - revert the version tag or run it again manually`,
183
+ );
184
+ }
185
+
186
+ if (!changelog_exists && existsSync(changelog)) {
187
+ await spawn('git', ['add', changelog]);
188
+ }
189
+ await spawn('git', ['commit', '-a', '-m', `publish v${version}`]);
190
+ await spawn('git', ['push', '--follow-tags']);
191
+
192
+ log.info(green(`published to branch ${cyan(branch)}!`));
193
+ },
194
+ };
@@ -0,0 +1,3 @@
1
+ import {register} from 'node:module';
2
+
3
+ register('./loader.js', import.meta.url);
@@ -0,0 +1,42 @@
1
+ import {z} from 'zod';
2
+ import {spawn} from '@ryanatkn/belt/process.js';
3
+ import {rm} from 'node:fs/promises';
4
+
5
+ import {Task_Error, type Task} from './task.js';
6
+ import {LOCKFILE_FILENAME, NODE_MODULES_DIRNAME} from './path_constants.js';
7
+
8
+ export const Args = z.object({}).strict();
9
+ export type Args = z.infer<typeof Args>;
10
+
11
+ export const task: Task<Args> = {
12
+ summary: `refreshes ${LOCKFILE_FILENAME} with the latest and cleanest deps`,
13
+ Args,
14
+ run: async ({log}): Promise<void> => {
15
+ log.info('running the initial npm install');
16
+ const initial_install_result = await spawn('npm', ['i']);
17
+ if (!initial_install_result.ok) {
18
+ throw new Task_Error('Failed initial npm install');
19
+ }
20
+
21
+ // Deleting both the lockfile and node_modules upgrades to the latest minor/patch versions.
22
+ await Promise.all([rm(LOCKFILE_FILENAME), rm(NODE_MODULES_DIRNAME, {recursive: true})]);
23
+ log.info(
24
+ `running npm install after deleting ${LOCKFILE_FILENAME} and ${NODE_MODULES_DIRNAME}, this can take a while...`,
25
+ );
26
+ const second_install_result = await spawn('npm', ['i']);
27
+ if (!second_install_result.ok) {
28
+ throw new Task_Error(
29
+ `Failed npm install after deleting ${LOCKFILE_FILENAME} and ${NODE_MODULES_DIRNAME}`,
30
+ );
31
+ }
32
+
33
+ // Deleting the lockfile and reinstalling cleans the lockfile of unnecessary dep noise,
34
+ // like esbuild's many packages for each platform.
35
+ await rm(LOCKFILE_FILENAME);
36
+ log.info(`running npm install one last time to clean ${LOCKFILE_FILENAME}`);
37
+ const final_install_result = await spawn('npm', ['i']);
38
+ if (!final_install_result.ok) {
39
+ throw new Task_Error('Failed npm install');
40
+ }
41
+ },
42
+ };
@@ -0,0 +1,21 @@
1
+ import {z} from 'zod';
2
+
3
+ import type {Task} from './task.js';
4
+ import {has_sveltekit_library, has_sveltekit_app} from './sveltekit_helpers.js';
5
+
6
+ export const Args = z.object({}).strict();
7
+ export type Args = z.infer<typeof Args>;
8
+
9
+ export const task: Task<Args> = {
10
+ summary: 'publish and deploy',
11
+ Args,
12
+ run: async ({invoke_task}) => {
13
+ const publish = has_sveltekit_library().ok;
14
+ if (publish) {
15
+ await invoke_task('publish', {optional: true});
16
+ }
17
+ if (has_sveltekit_app().ok) {
18
+ await invoke_task('deploy', {build: !publish});
19
+ }
20
+ },
21
+ };
@@ -0,0 +1,43 @@
1
+ import {z} from 'zod';
2
+ import {green, yellow} from '@ryanatkn/belt/styletext.js';
3
+
4
+ import {TASK_FILE_SUFFIXES, type Task} from './task.js';
5
+ import {resolve_input_paths, to_input_paths} from './input_path.js';
6
+
7
+ export const Args = z
8
+ .object({
9
+ _: z.array(z.string(), {description: 'the input paths to resolve'}).default(['']),
10
+ verbose: z.boolean({description: 'log diagnostics'}).default(false),
11
+ })
12
+ .strict();
13
+ export type Args = z.infer<typeof Args>;
14
+
15
+ export const task: Task<Args> = {
16
+ summary: 'diagnostic that logs resolved filesystem info for the given input paths',
17
+ Args,
18
+ run: ({args, config, log}): void => {
19
+ const {_, verbose} = args;
20
+
21
+ if (verbose) log.info('raw input paths:', _);
22
+
23
+ const input_paths = to_input_paths(_);
24
+ if (verbose) log.info('input paths:', input_paths);
25
+
26
+ const {task_root_dirs} = config;
27
+ if (verbose) log.info('task root paths:', task_root_dirs);
28
+
29
+ const {resolved_input_paths, possible_paths_by_input_path, unmapped_input_paths} =
30
+ resolve_input_paths(input_paths, task_root_dirs, TASK_FILE_SUFFIXES);
31
+ if (verbose) log.info('resolved_input_paths:', resolved_input_paths);
32
+ if (verbose) log.info('possible_paths_by_input_path:', possible_paths_by_input_path);
33
+ if (verbose) log.info('unmapped_input_paths:', unmapped_input_paths);
34
+
35
+ for (const p of resolved_input_paths) {
36
+ log.info('resolved:', green(p.id));
37
+ }
38
+
39
+ if (!resolved_input_paths.length) {
40
+ log.warn(yellow('no input paths were resolved'));
41
+ }
42
+ },
43
+ };
@@ -0,0 +1,31 @@
1
+ import {test} from 'uvu';
2
+ import * as assert from 'uvu/assert';
3
+
4
+ import {resolve_node_specifier} from './resolve_node_specifier.js';
5
+ import {paths} from './paths.js';
6
+
7
+ test('resolves a JS specifier', () => {
8
+ assert.is(
9
+ resolve_node_specifier('@ryanatkn/fuz/tome.js'),
10
+ paths.root + 'node_modules/@ryanatkn/fuz/dist/tome.js',
11
+ );
12
+ });
13
+
14
+ test('resolves a Svelte specifier', () => {
15
+ assert.is(
16
+ resolve_node_specifier('@ryanatkn/fuz/Library.svelte'),
17
+ paths.root + 'node_modules/@ryanatkn/fuz/dist/Library.svelte',
18
+ );
19
+ });
20
+
21
+ test('throws for a specifier that does not exist', () => {
22
+ let err;
23
+ try {
24
+ resolve_node_specifier('@ryanatkn/fuz/this_does_not_exist');
25
+ } catch (_err) {
26
+ err = _err;
27
+ }
28
+ assert.ok(err);
29
+ });
30
+
31
+ test.run();
@@ -0,0 +1,55 @@
1
+ import {join} from 'node:path';
2
+
3
+ import {Package_Json, load_package_json} from './package_json.js';
4
+ import type {Path_Id} from './path.js';
5
+ import {paths} from './paths.js';
6
+ import {NODE_MODULES_DIRNAME} from './path_constants.js';
7
+
8
+ export const resolve_node_specifier = (
9
+ specifier: string,
10
+ dir = paths.root,
11
+ parent_url?: string,
12
+ cache?: Record<string, Package_Json>,
13
+ exports_key = specifier.endsWith('.svelte') ? 'svelte' : 'default',
14
+ ): Path_Id => {
15
+ const parsed = parse_node_specifier(specifier);
16
+ const subpath = './' + parsed.path;
17
+ const package_dir = join(dir, NODE_MODULES_DIRNAME, parsed.name);
18
+ const package_json = load_package_json(package_dir, cache);
19
+ const exported = package_json.exports?.[subpath];
20
+ if (!exported) {
21
+ // same error message as Node
22
+ throw Error(
23
+ `[ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath '${subpath}' is not defined by "exports" in ${package_dir}/package.json` +
24
+ (parent_url ? ` imported from ${parent_url}` : ''),
25
+ );
26
+ }
27
+ const path_id = join(package_dir, exported[exports_key]);
28
+ return path_id;
29
+ };
30
+
31
+ export interface Parsed_Node_Specifier {
32
+ name: string;
33
+ path: string;
34
+ }
35
+
36
+ export const parse_node_specifier = (specifier: string): Parsed_Node_Specifier => {
37
+ let idx!: number;
38
+ if (specifier[0] === '@') {
39
+ // get the index of the second `/`
40
+ let count = 0;
41
+ for (let i = 0; i < specifier.length; i++) {
42
+ if (specifier[i] === '/') count++;
43
+ if (count === 2) {
44
+ idx = i;
45
+ break;
46
+ }
47
+ }
48
+ } else {
49
+ idx = specifier.indexOf('/');
50
+ }
51
+ return {
52
+ name: specifier.substring(0, idx),
53
+ path: specifier.substring(idx + 1),
54
+ };
55
+ };