@ryanatkn/gro 0.112.0

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 (222) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +257 -0
  3. package/dist/args.d.ts +59 -0
  4. package/dist/args.js +132 -0
  5. package/dist/args.test.d.ts +1 -0
  6. package/dist/args.test.js +43 -0
  7. package/dist/build.task.d.ts +11 -0
  8. package/dist/build.task.js +24 -0
  9. package/dist/changelog.d.ts +8 -0
  10. package/dist/changelog.js +47 -0
  11. package/dist/changelog.test.d.ts +1 -0
  12. package/dist/changelog.test.js +118 -0
  13. package/dist/changeset.task.d.ts +49 -0
  14. package/dist/changeset.task.js +141 -0
  15. package/dist/check.task.d.ts +47 -0
  16. package/dist/check.task.js +77 -0
  17. package/dist/clean.task.d.ts +26 -0
  18. package/dist/clean.task.js +41 -0
  19. package/dist/clean_fs.d.ts +9 -0
  20. package/dist/clean_fs.js +27 -0
  21. package/dist/cli.d.ts +11 -0
  22. package/dist/cli.js +25 -0
  23. package/dist/commit.task.d.ts +11 -0
  24. package/dist/commit.task.js +22 -0
  25. package/dist/config.d.ts +21 -0
  26. package/dist/config.js +42 -0
  27. package/dist/config.test.d.ts +1 -0
  28. package/dist/config.test.js +8 -0
  29. package/dist/deploy.task.d.ts +47 -0
  30. package/dist/deploy.task.js +198 -0
  31. package/dist/dev.task.d.ts +22 -0
  32. package/dist/dev.task.js +32 -0
  33. package/dist/docs/README.gen.md.d.ts +5 -0
  34. package/dist/docs/README.gen.md.js +53 -0
  35. package/dist/docs/README.md +20 -0
  36. package/dist/docs/build.md +41 -0
  37. package/dist/docs/config.md +162 -0
  38. package/dist/docs/deploy.md +32 -0
  39. package/dist/docs/dev.md +40 -0
  40. package/dist/docs/gen.md +241 -0
  41. package/dist/docs/gro_plugin_sveltekit_frontend.md +97 -0
  42. package/dist/docs/package_json.md +29 -0
  43. package/dist/docs/plugin.md +50 -0
  44. package/dist/docs/publish.md +144 -0
  45. package/dist/docs/task.md +377 -0
  46. package/dist/docs/tasks.gen.md.d.ts +2 -0
  47. package/dist/docs/tasks.gen.md.js +60 -0
  48. package/dist/docs/tasks.md +35 -0
  49. package/dist/docs/test.md +52 -0
  50. package/dist/env.d.ts +10 -0
  51. package/dist/env.js +47 -0
  52. package/dist/esbuild_helpers.d.ts +14 -0
  53. package/dist/esbuild_helpers.js +36 -0
  54. package/dist/esbuild_plugin_external_worker.d.ts +22 -0
  55. package/dist/esbuild_plugin_external_worker.js +49 -0
  56. package/dist/esbuild_plugin_svelte.d.ts +9 -0
  57. package/dist/esbuild_plugin_svelte.js +49 -0
  58. package/dist/esbuild_plugin_sveltekit_local_imports.d.ts +7 -0
  59. package/dist/esbuild_plugin_sveltekit_local_imports.js +30 -0
  60. package/dist/esbuild_plugin_sveltekit_shim_alias.d.ts +6 -0
  61. package/dist/esbuild_plugin_sveltekit_shim_alias.js +16 -0
  62. package/dist/esbuild_plugin_sveltekit_shim_app.d.ts +8 -0
  63. package/dist/esbuild_plugin_sveltekit_shim_app.js +23 -0
  64. package/dist/esbuild_plugin_sveltekit_shim_env.d.ts +10 -0
  65. package/dist/esbuild_plugin_sveltekit_shim_env.js +18 -0
  66. package/dist/format.task.d.ts +11 -0
  67. package/dist/format.task.js +24 -0
  68. package/dist/format_directory.d.ts +2 -0
  69. package/dist/format_directory.js +27 -0
  70. package/dist/format_file.d.ts +8 -0
  71. package/dist/format_file.js +42 -0
  72. package/dist/format_file.test.d.ts +1 -0
  73. package/dist/format_file.test.js +16 -0
  74. package/dist/fs.d.ts +7 -0
  75. package/dist/fs.js +19 -0
  76. package/dist/fs.test.d.ts +1 -0
  77. package/dist/fs.test.js +16 -0
  78. package/dist/gen.d.ts +57 -0
  79. package/dist/gen.js +81 -0
  80. package/dist/gen.task.d.ts +14 -0
  81. package/dist/gen.task.js +103 -0
  82. package/dist/gen.test.d.ts +1 -0
  83. package/dist/gen.test.js +239 -0
  84. package/dist/gen_module.d.ts +46 -0
  85. package/dist/gen_module.js +54 -0
  86. package/dist/gen_module.test.d.ts +1 -0
  87. package/dist/gen_module.test.js +30 -0
  88. package/dist/git.d.ts +76 -0
  89. package/dist/git.js +200 -0
  90. package/dist/git.test.d.ts +1 -0
  91. package/dist/git.test.js +18 -0
  92. package/dist/github.d.ts +35 -0
  93. package/dist/github.js +32 -0
  94. package/dist/gro.config.default.d.ts +12 -0
  95. package/dist/gro.config.default.js +31 -0
  96. package/dist/gro.d.ts +2 -0
  97. package/dist/gro.js +19 -0
  98. package/dist/gro_helpers.d.ts +43 -0
  99. package/dist/gro_helpers.js +79 -0
  100. package/dist/gro_plugin_gen.d.ts +6 -0
  101. package/dist/gro_plugin_gen.js +80 -0
  102. package/dist/gro_plugin_server.d.ts +77 -0
  103. package/dist/gro_plugin_server.js +152 -0
  104. package/dist/gro_plugin_sveltekit_app.d.ts +27 -0
  105. package/dist/gro_plugin_sveltekit_app.js +180 -0
  106. package/dist/gro_plugin_sveltekit_library.d.ts +4 -0
  107. package/dist/gro_plugin_sveltekit_library.js +42 -0
  108. package/dist/hash.d.ts +5 -0
  109. package/dist/hash.js +14 -0
  110. package/dist/hash.test.d.ts +1 -0
  111. package/dist/hash.test.js +25 -0
  112. package/dist/index.d.ts +4 -0
  113. package/dist/index.js +3 -0
  114. package/dist/input_path.d.ts +48 -0
  115. package/dist/input_path.js +161 -0
  116. package/dist/input_path.test.d.ts +1 -0
  117. package/dist/input_path.test.js +106 -0
  118. package/dist/invoke.d.ts +1 -0
  119. package/dist/invoke.js +18 -0
  120. package/dist/invoke_task.d.ts +20 -0
  121. package/dist/invoke_task.js +140 -0
  122. package/dist/lint.task.d.ts +11 -0
  123. package/dist/lint.task.js +29 -0
  124. package/dist/loader.d.ts +4 -0
  125. package/dist/loader.js +153 -0
  126. package/dist/module.d.ts +3 -0
  127. package/dist/module.js +6 -0
  128. package/dist/module.test.d.ts +1 -0
  129. package/dist/module.test.js +41 -0
  130. package/dist/modules.d.ts +60 -0
  131. package/dist/modules.js +103 -0
  132. package/dist/modules.test.d.ts +1 -0
  133. package/dist/modules.test.js +182 -0
  134. package/dist/package.d.ts +939 -0
  135. package/dist/package.gen.d.ts +7 -0
  136. package/dist/package.gen.js +26 -0
  137. package/dist/package.js +887 -0
  138. package/dist/package_json.d.ts +342 -0
  139. package/dist/package_json.js +212 -0
  140. package/dist/package_json.test.d.ts +1 -0
  141. package/dist/package_json.test.js +77 -0
  142. package/dist/path.d.ts +12 -0
  143. package/dist/path.js +8 -0
  144. package/dist/paths.d.ts +60 -0
  145. package/dist/paths.js +128 -0
  146. package/dist/paths.test.d.ts +1 -0
  147. package/dist/paths.test.js +49 -0
  148. package/dist/plugin.d.ts +36 -0
  149. package/dist/plugin.js +80 -0
  150. package/dist/plugin.test.d.ts +1 -0
  151. package/dist/plugin.test.js +54 -0
  152. package/dist/print_task.d.ts +4 -0
  153. package/dist/print_task.js +124 -0
  154. package/dist/publish.task.d.ts +32 -0
  155. package/dist/publish.task.js +125 -0
  156. package/dist/release.task.d.ts +5 -0
  157. package/dist/release.task.js +18 -0
  158. package/dist/resolve_node_specifier.d.ts +8 -0
  159. package/dist/resolve_node_specifier.js +39 -0
  160. package/dist/resolve_node_specifier.test.d.ts +1 -0
  161. package/dist/resolve_node_specifier.test.js +21 -0
  162. package/dist/resolve_specifier.d.ts +15 -0
  163. package/dist/resolve_specifier.js +51 -0
  164. package/dist/resolve_specifier.test.d.ts +1 -0
  165. package/dist/resolve_specifier.test.js +66 -0
  166. package/dist/run.task.d.ts +11 -0
  167. package/dist/run.task.js +31 -0
  168. package/dist/run_gen.d.ts +6 -0
  169. package/dist/run_gen.js +74 -0
  170. package/dist/run_gen.test.d.ts +1 -0
  171. package/dist/run_gen.test.js +182 -0
  172. package/dist/run_task.d.ts +13 -0
  173. package/dist/run_task.js +44 -0
  174. package/dist/run_task.test.d.ts +1 -0
  175. package/dist/run_task.test.js +63 -0
  176. package/dist/search_fs.d.ts +11 -0
  177. package/dist/search_fs.js +22 -0
  178. package/dist/search_fs.test.d.ts +1 -0
  179. package/dist/search_fs.test.js +46 -0
  180. package/dist/src_json.d.ts +256 -0
  181. package/dist/src_json.js +110 -0
  182. package/dist/src_json.test.d.ts +1 -0
  183. package/dist/src_json.test.js +52 -0
  184. package/dist/sveltekit_config.d.ts +36 -0
  185. package/dist/sveltekit_config.js +51 -0
  186. package/dist/sveltekit_shim_app.d.ts +10 -0
  187. package/dist/sveltekit_shim_app.js +31 -0
  188. package/dist/sveltekit_shim_app_environment.d.ts +10 -0
  189. package/dist/sveltekit_shim_app_environment.js +12 -0
  190. package/dist/sveltekit_shim_app_forms.d.ts +5 -0
  191. package/dist/sveltekit_shim_app_forms.js +13 -0
  192. package/dist/sveltekit_shim_app_navigation.d.ts +10 -0
  193. package/dist/sveltekit_shim_app_navigation.js +11 -0
  194. package/dist/sveltekit_shim_app_paths.d.ts +11 -0
  195. package/dist/sveltekit_shim_app_paths.js +6 -0
  196. package/dist/sveltekit_shim_app_stores.d.ts +6 -0
  197. package/dist/sveltekit_shim_app_stores.js +17 -0
  198. package/dist/sveltekit_shim_env.d.ts +4 -0
  199. package/dist/sveltekit_shim_env.js +23 -0
  200. package/dist/sync.task.d.ts +30 -0
  201. package/dist/sync.task.js +45 -0
  202. package/dist/task.d.ts +29 -0
  203. package/dist/task.js +17 -0
  204. package/dist/task.test.d.ts +1 -0
  205. package/dist/task.test.js +22 -0
  206. package/dist/task_module.d.ts +14 -0
  207. package/dist/task_module.js +19 -0
  208. package/dist/task_module.test.d.ts +1 -0
  209. package/dist/task_module.test.js +70 -0
  210. package/dist/test.task.d.ts +20 -0
  211. package/dist/test.task.js +43 -0
  212. package/dist/throttle.d.ts +16 -0
  213. package/dist/throttle.js +59 -0
  214. package/dist/throttle.test.d.ts +1 -0
  215. package/dist/throttle.test.js +49 -0
  216. package/dist/typecheck.task.d.ts +5 -0
  217. package/dist/typecheck.task.js +38 -0
  218. package/dist/upgrade.task.d.ts +14 -0
  219. package/dist/upgrade.task.js +37 -0
  220. package/dist/watch_dir.d.ts +30 -0
  221. package/dist/watch_dir.js +59 -0
  222. package/package.json +422 -0
package/dist/config.js ADDED
@@ -0,0 +1,42 @@
1
+ import { join } from 'node:path';
2
+ import { CONFIG_PATH, paths } from './paths.js';
3
+ import create_default_config from './gro.config.default.js';
4
+ import { exists } from './fs.js';
5
+ export const create_empty_config = () => ({
6
+ plugins: () => [],
7
+ // TODO maybe disable if no SvelteKit `lib` directory? or other detection to improve defaults
8
+ map_package_json: default_map_package_json,
9
+ });
10
+ const default_map_package_json = async (package_json) => {
11
+ if (package_json.exports) {
12
+ package_json.exports = Object.fromEntries(Object.entries(package_json.exports).filter(([k]) => !DEFAULT_EXPORTS_EXCLUDER.test(k)));
13
+ }
14
+ return package_json;
15
+ };
16
+ export const DEFAULT_EXPORTS_EXCLUDER = /(\.md|\.(test|ignore)\.|\/(test|fixtures|ignore)\/)/u;
17
+ export const load_config = async (dir = paths.root) => {
18
+ const default_config = await create_default_config(create_empty_config());
19
+ const config_path = join(dir, CONFIG_PATH);
20
+ let config;
21
+ if (await exists(config_path)) {
22
+ const config_module = await import(config_path);
23
+ validate_config_module(config_module, config_path);
24
+ config =
25
+ typeof config_module.default === 'function'
26
+ ? await config_module.default(default_config)
27
+ : config_module.default;
28
+ }
29
+ else {
30
+ config = default_config;
31
+ }
32
+ return config;
33
+ };
34
+ export const validate_config_module = (config_module, config_path) => {
35
+ const config = config_module.default;
36
+ if (!config) {
37
+ throw Error(`Invalid Gro config module at ${config_path}: expected a default export`);
38
+ }
39
+ else if (!(typeof config === 'function' || typeof config === 'object')) {
40
+ throw Error(`Invalid Gro config module at ${config_path}: the default export must be a function or object`);
41
+ }
42
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import { test } from 'uvu';
2
+ import * as assert from 'uvu/assert';
3
+ import { load_config } from './config.js';
4
+ test('load_config', async () => {
5
+ const config = await load_config();
6
+ assert.ok(config);
7
+ });
8
+ test.run();
@@ -0,0 +1,47 @@
1
+ import { z } from 'zod';
2
+ import { type Task } from './task.js';
3
+ export declare const Args: z.ZodObject<{
4
+ source: z.ZodDefault<z.ZodString>;
5
+ target: z.ZodDefault<z.ZodString>;
6
+ origin: z.ZodDefault<z.ZodString>;
7
+ deploy_dir: z.ZodDefault<z.ZodString>;
8
+ build_dir: z.ZodDefault<z.ZodString>;
9
+ dry: z.ZodDefault<z.ZodBoolean>;
10
+ force: z.ZodDefault<z.ZodBoolean>;
11
+ dangerous: z.ZodDefault<z.ZodBoolean>;
12
+ reset: z.ZodDefault<z.ZodBoolean>;
13
+ install: z.ZodDefault<z.ZodBoolean>;
14
+ 'no-install': z.ZodDefault<z.ZodBoolean>;
15
+ build: z.ZodDefault<z.ZodBoolean>;
16
+ 'no-build': z.ZodDefault<z.ZodBoolean>;
17
+ }, "strict", z.ZodTypeAny, {
18
+ build: boolean;
19
+ target: string;
20
+ install: boolean;
21
+ origin: string;
22
+ reset: boolean;
23
+ 'no-install': boolean;
24
+ source: string;
25
+ deploy_dir: string;
26
+ build_dir: string;
27
+ dry: boolean;
28
+ force: boolean;
29
+ dangerous: boolean;
30
+ 'no-build': boolean;
31
+ }, {
32
+ source?: string | undefined;
33
+ target?: string | undefined;
34
+ origin?: string | undefined;
35
+ deploy_dir?: string | undefined;
36
+ build_dir?: string | undefined;
37
+ dry?: boolean | undefined;
38
+ force?: boolean | undefined;
39
+ dangerous?: boolean | undefined;
40
+ reset?: boolean | undefined;
41
+ install?: boolean | undefined;
42
+ 'no-install'?: boolean | undefined;
43
+ build?: boolean | undefined;
44
+ 'no-build'?: boolean | undefined;
45
+ }>;
46
+ export type Args = z.infer<typeof Args>;
47
+ export declare const task: Task<Args>;
@@ -0,0 +1,198 @@
1
+ import { spawn } from '@ryanatkn/belt/process.js';
2
+ import { print_error } from '@ryanatkn/belt/print.js';
3
+ import { green, red } from 'kleur/colors';
4
+ import { z } from 'zod';
5
+ import { cp, mkdir, readdir, rm } from 'node:fs/promises';
6
+ import { join, resolve } from 'node:path';
7
+ import { Task_Error } from './task.js';
8
+ import { GIT_DIRNAME, GRO_DIRNAME, print_path, SVELTEKIT_BUILD_DIRNAME } from './paths.js';
9
+ import { empty_dir, exists } from './fs.js';
10
+ import { git_check_clean_workspace, git_checkout, git_local_branch_exists, git_remote_branch_exists, Git_Origin, Git_Branch, git_delete_local_branch, git_push_to_create, git_reset_branch_to_first_commit, git_pull, git_fetch, git_check_setting_pull_rebase, git_clone_locally, git_current_branch_name, } from './git.js';
11
+ // docs at ./docs/deploy.md
12
+ // TODO use `to_forwarded_args` and the `gro deploy -- gro build --no-install` pattern to remove the `install`/`no-install` args (also needs testing, maybe a custom override for `gro ` prefixes)
13
+ // terminal command for testing:
14
+ // npm run build && rm -rf .gro && clear && gro deploy --source no-git-workspace --no-build --dry
15
+ // TODO customize
16
+ const cwd = process.cwd();
17
+ const INITIAL_FILE_PATH = '.gitkeep';
18
+ const INITIAL_FILE_CONTENTS = '';
19
+ const DEPLOY_DIR = GRO_DIRNAME + '/deploy';
20
+ const SOURCE_BRANCH = 'main';
21
+ const TARGET_BRANCH = 'deploy';
22
+ const DANGEROUS_BRANCHES = [SOURCE_BRANCH, 'master'];
23
+ export const Args = z
24
+ .object({
25
+ source: Git_Branch.describe('git source branch to build and deploy from').default(SOURCE_BRANCH),
26
+ target: Git_Branch.describe('git target branch to deploy to').default(TARGET_BRANCH),
27
+ origin: Git_Origin.describe('git origin to deploy to').default('origin'),
28
+ deploy_dir: z.string({ description: 'the deploy output directory' }).default(DEPLOY_DIR),
29
+ build_dir: z
30
+ .string({ description: 'the SvelteKit build directory' })
31
+ .default(SVELTEKIT_BUILD_DIRNAME),
32
+ dry: z
33
+ .boolean({
34
+ description: 'build and prepare to deploy without actually deploying',
35
+ })
36
+ .default(false),
37
+ force: z
38
+ .boolean({ description: 'caution!! destroys the target branch both locally and remotely' })
39
+ .default(false),
40
+ dangerous: z
41
+ .boolean({ description: 'caution!! enables destruction of branches like main and master' })
42
+ .default(false),
43
+ reset: z
44
+ .boolean({
45
+ description: 'if true, resets the target branch back to the first commit before deploying',
46
+ })
47
+ .default(false),
48
+ install: z.boolean({ description: 'dual of no-install' }).default(true),
49
+ 'no-install': z
50
+ .boolean({ description: 'opt out of npm installing before building' })
51
+ .default(false),
52
+ build: z.boolean({ description: 'dual of no-build' }).default(true),
53
+ 'no-build': z.boolean({ description: 'opt out of building' }).default(false),
54
+ })
55
+ .strict();
56
+ export const task = {
57
+ summary: 'deploy to a branch',
58
+ Args,
59
+ run: async ({ args, log, invoke_task }) => {
60
+ const { source, target, origin, build_dir, deploy_dir, dry, force, dangerous, reset, install, build, } = args;
61
+ // Checks
62
+ if (!force && target !== TARGET_BRANCH) {
63
+ throw new Task_Error(`Warning! You are deploying to a custom target branch '${target}',` +
64
+ ` instead of the default '${TARGET_BRANCH}' branch.` +
65
+ ` This is destructive to your '${target}' branch!` +
66
+ ` If you understand and are OK with deleting your branch '${target}',` +
67
+ ` both locally and remotely, pass --force to suppress this error.`);
68
+ }
69
+ if (!dangerous && DANGEROUS_BRANCHES.includes(target)) {
70
+ throw new Task_Error(`Warning! You are deploying to a custom target branch '${target}'` +
71
+ ` and that appears very dangerous: it is destructive to your '${target}' branch!` +
72
+ ` If you understand and are OK with deleting your branch '${target}',` +
73
+ ` both locally and remotely, pass --dangerous to suppress this error.`);
74
+ }
75
+ const clean_error_message = await git_check_clean_workspace();
76
+ if (clean_error_message) {
77
+ throw new Task_Error('Deploy failed because the git workspace has uncommitted changes: ' + clean_error_message);
78
+ }
79
+ if (!(await git_check_setting_pull_rebase())) {
80
+ throw new Task_Error('Deploying currently requires `git config --global pull.rebase true`,' +
81
+ ' but this restriction could be lifted with more work');
82
+ }
83
+ // Fetch the source branch in the cwd if it's not there
84
+ if (!(await git_local_branch_exists(source))) {
85
+ await git_fetch(origin, source);
86
+ }
87
+ // Prepare the source branch in the cwd
88
+ await git_checkout(source);
89
+ await git_pull(origin, source);
90
+ if (await git_check_clean_workspace()) {
91
+ throw new Task_Error('Deploy failed because the local source branch is out of sync with the remote one,' +
92
+ ' finish rebasing manually or reset with `git rebase --abort`');
93
+ }
94
+ // Prepare the target branch remotely and locally
95
+ const resolved_deploy_dir = resolve(deploy_dir);
96
+ const target_spawn_options = { cwd: resolved_deploy_dir };
97
+ const remote_target_exists = await git_remote_branch_exists(origin, target);
98
+ if (remote_target_exists) {
99
+ // Remote target branch already exists, so sync up efficiently
100
+ // First, check if the deploy dir exists, and if so, attempt to sync it.
101
+ // If anything goes wrong, delete the directory and we'll initialize it
102
+ // using the same code path as if it didn't exist in the first place.
103
+ if (await exists(resolved_deploy_dir)) {
104
+ if (target !== (await git_current_branch_name(target_spawn_options))) {
105
+ // We're in a bad state because the target branch has changed,
106
+ // so delete the directory and continue as if it wasn't there.
107
+ await rm(resolved_deploy_dir, { recursive: true });
108
+ }
109
+ else {
110
+ await spawn('git', ['reset', '--hard'], target_spawn_options); // in case it's dirty
111
+ await git_pull(origin, target, target_spawn_options);
112
+ if (await git_check_clean_workspace(target_spawn_options)) {
113
+ // We're in a bad state because the local branch lost continuity with the remote,
114
+ // so delete the directory and continue as if it wasn't there.
115
+ await rm(resolved_deploy_dir, { recursive: true });
116
+ }
117
+ }
118
+ }
119
+ // Second, initialize the deploy dir if needed.
120
+ // It may not exist, or it may have been deleted after failing to sync above.
121
+ if (!(await exists(resolved_deploy_dir))) {
122
+ const local_deploy_branch_exists = await git_local_branch_exists(target);
123
+ await git_fetch(origin, '+' + target + ':' + target); // fetch+merge and allow non-fastforward updates with the +
124
+ await git_clone_locally(origin, target, cwd, resolved_deploy_dir);
125
+ // Clean up if we created the target branch in the cwd
126
+ if (!local_deploy_branch_exists) {
127
+ await git_delete_local_branch(target);
128
+ }
129
+ }
130
+ // Local target branch is now synced with remote, but do we need to reset?
131
+ if (reset) {
132
+ await git_reset_branch_to_first_commit(origin, target, target_spawn_options);
133
+ }
134
+ }
135
+ else {
136
+ // Remote target branch does not exist, so start from scratch
137
+ // Delete the deploy dir and recreate it
138
+ if (await exists(resolved_deploy_dir)) {
139
+ await rm(resolved_deploy_dir, { recursive: true });
140
+ await mkdir(resolved_deploy_dir, { recursive: true });
141
+ }
142
+ // Delete the target branch locally in the cwd if it exists
143
+ if (await git_local_branch_exists(target)) {
144
+ await git_delete_local_branch(target);
145
+ }
146
+ // Create the target branch locally and remotely.
147
+ // This is more complex to avoid churning the cwd.
148
+ await git_clone_locally(origin, source, cwd, resolved_deploy_dir);
149
+ await spawn(`git checkout --orphan ${target} && ` +
150
+ // TODO there's definitely a better way to do this
151
+ `git rm -rf . && ` +
152
+ `echo "${INITIAL_FILE_CONTENTS}" >> ${INITIAL_FILE_PATH} && ` +
153
+ `git add ${INITIAL_FILE_PATH} && ` +
154
+ `git commit -m "init"`, [],
155
+ // Use `shell: true` because the above is unwieldy with standard command construction
156
+ { ...target_spawn_options, shell: true });
157
+ await git_push_to_create(origin, target, target_spawn_options);
158
+ await git_delete_local_branch(source, target_spawn_options);
159
+ }
160
+ // Remove everything except .git from the deploy directory to avoid stale files
161
+ await empty_dir(resolved_deploy_dir, (path) => path !== GIT_DIRNAME);
162
+ // Build
163
+ try {
164
+ if (build) {
165
+ await invoke_task('build', { install });
166
+ }
167
+ if (!(await exists(build_dir))) {
168
+ log.error(red('directory to deploy does not exist after building:'), build_dir);
169
+ return;
170
+ }
171
+ }
172
+ catch (err) {
173
+ log.error(red('build failed'), 'but', green('no changes were made to git'), print_error(err));
174
+ if (dry) {
175
+ log.info(red('dry deploy failed'));
176
+ }
177
+ throw new Task_Error(`Deploy safely canceled due to build failure. See the error above.`);
178
+ }
179
+ // Copy the build
180
+ await Promise.all((await readdir(build_dir)).map((path) => cp(join(build_dir, path), join(resolved_deploy_dir, path), { recursive: true })));
181
+ // At this point, `dist/` is ready to be committed and deployed!
182
+ if (dry) {
183
+ log.info(green('dry deploy complete:'), 'files at', print_path(resolved_deploy_dir));
184
+ return;
185
+ }
186
+ // Commit and push
187
+ try {
188
+ await spawn('git', ['add', '.', '-f'], target_spawn_options);
189
+ await spawn('git', ['commit', '-m', 'deployment'], target_spawn_options);
190
+ await spawn('git', ['push', origin, target, '-f'], target_spawn_options); // force push because we may be resetting the branch, see the checks above to make this safer
191
+ }
192
+ catch (err) {
193
+ log.error(red('updating git failed:'), print_error(err));
194
+ throw new Task_Error(`Deploy failed in a bad state: built but not pushed, see error above.`);
195
+ }
196
+ log.info(green('deployed')); // TODO log a different message if "Everything up-to-date"
197
+ },
198
+ };
@@ -0,0 +1,22 @@
1
+ import { z } from 'zod';
2
+ import type { Task } from './task.js';
3
+ import { type Plugin_Context } from './plugin.js';
4
+ export declare const Args: z.ZodObject<{
5
+ watch: z.ZodDefault<z.ZodBoolean>;
6
+ 'no-watch': z.ZodDefault<z.ZodBoolean>;
7
+ sync: z.ZodDefault<z.ZodBoolean>;
8
+ 'no-sync': z.ZodDefault<z.ZodBoolean>;
9
+ }, "strict", z.ZodTypeAny, {
10
+ watch: boolean;
11
+ sync: boolean;
12
+ 'no-watch': boolean;
13
+ 'no-sync': boolean;
14
+ }, {
15
+ watch?: boolean | undefined;
16
+ 'no-watch'?: boolean | undefined;
17
+ sync?: boolean | undefined;
18
+ 'no-sync'?: boolean | undefined;
19
+ }>;
20
+ export type Args = z.infer<typeof Args>;
21
+ export type DevTask_Context = Plugin_Context<Args>;
22
+ export declare const task: Task<Args>;
@@ -0,0 +1,32 @@
1
+ import { z } from 'zod';
2
+ import { Plugins } from './plugin.js';
3
+ import { clean_fs } from './clean_fs.js';
4
+ export const Args = z
5
+ .object({
6
+ watch: z.boolean({ description: 'dual of no-watch' }).default(true),
7
+ 'no-watch': z
8
+ .boolean({
9
+ description: 'opt out of running a long-lived process to watch files and rebuild on changes',
10
+ })
11
+ .default(false),
12
+ sync: z.boolean({ description: 'dual of no-sync' }).default(true),
13
+ 'no-sync': z.boolean({ description: 'opt out of gro sync' }).default(false),
14
+ })
15
+ .strict();
16
+ export const task = {
17
+ summary: 'start SvelteKit and other dev plugins',
18
+ Args,
19
+ run: async (ctx) => {
20
+ const { args, invoke_task } = ctx;
21
+ const { watch, sync } = args;
22
+ await clean_fs({ build_dev: true });
23
+ if (sync) {
24
+ await invoke_task('sync');
25
+ }
26
+ const plugins = await Plugins.create({ ...ctx, dev: true, watch });
27
+ await plugins.setup();
28
+ if (!watch) {
29
+ await plugins.teardown();
30
+ }
31
+ },
32
+ };
@@ -0,0 +1,5 @@
1
+ import { type Gen } from '../gen.js';
2
+ /**
3
+ * Renders a simple index of a possibly nested directory of files.
4
+ */
5
+ export declare const gen: Gen;
@@ -0,0 +1,53 @@
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
+ import { to_output_file_name } from '../gen.js';
5
+ import { paths, base_path_to_source_id } from '../paths.js';
6
+ import { search_fs } from '../search_fs.js';
7
+ // TODO look at `tasks.gen.md.ts` to refactor and generalize
8
+ // TODO show nested structure, not a flat list
9
+ // TODO work with file types beyond markdown
10
+ /**
11
+ * Renders a simple index of a possibly nested directory of files.
12
+ */
13
+ export const gen = async ({ origin_id }) => {
14
+ // TODO need to get this from project config or something
15
+ const root_path = parse_path_segments(paths.root).at(-1);
16
+ const origin_dir = dirname(origin_id);
17
+ const origin_base = basename(origin_id);
18
+ const base_dir = paths.source;
19
+ const relative_path = strip_start(origin_id, base_dir);
20
+ const relative_dir = dirname(relative_path);
21
+ // TODO should this be passed in the context, like `defaultOutputFileName`?
22
+ const output_file_name = to_output_file_name(origin_base);
23
+ // TODO this is GitHub-specific
24
+ const root_link = `[${root_path}](/../..)`;
25
+ const docFiles = await search_fs(origin_dir);
26
+ const docPaths = [];
27
+ for (const path of docFiles.keys()) {
28
+ if (path === output_file_name || !path.endsWith('.md')) {
29
+ continue;
30
+ }
31
+ docPaths.push(path);
32
+ }
33
+ // TODO do we want to use absolute paths instead of relative paths,
34
+ // because GitHub works with them and it simplifies the code?
35
+ const is_index_file = output_file_name === 'README.md';
36
+ const path_parts = parse_path_parts(relative_dir).map((relative_path_part) => {
37
+ const segment = parse_path_segments(relative_path_part).at(-1);
38
+ return is_index_file && relative_path_part === relative_dir
39
+ ? segment
40
+ : `[${segment}](${relative(origin_dir, base_path_to_source_id(relative_path_part)) || './'})`;
41
+ });
42
+ const breadcrumbs = '> <sub>' + [root_link, ...path_parts, output_file_name].join(' / ') + '</sub>';
43
+ // TODO render the footer with the origin_id
44
+ return `# docs
45
+
46
+ ${breadcrumbs}
47
+
48
+ ${docPaths.reduce((docList, doc) => docList + `- [${basename(doc, '.md')}](${doc})\n`, '')}
49
+ ${breadcrumbs}
50
+
51
+ > <sub>generated by [${origin_base}](${origin_base})</sub>
52
+ `;
53
+ };
@@ -0,0 +1,20 @@
1
+ # docs
2
+
3
+ > <sub>[gro](/../..) / [lib](..) / docs / README.md</sub>
4
+
5
+ - [build](build.md)
6
+ - [config](config.md)
7
+ - [deploy](deploy.md)
8
+ - [dev](dev.md)
9
+ - [gen](gen.md)
10
+ - [gro_plugin_sveltekit_frontend](gro_plugin_sveltekit_frontend.md)
11
+ - [package_json](package_json.md)
12
+ - [plugin](plugin.md)
13
+ - [publish](publish.md)
14
+ - [task](task.md)
15
+ - [tasks](tasks.md)
16
+ - [test](test.md)
17
+
18
+ > <sub>[gro](/../..) / [lib](..) / docs / README.md</sub>
19
+
20
+ > <sub>generated by [README.gen.md.ts](README.gen.md.ts)</sub>
@@ -0,0 +1,41 @@
1
+ # build
2
+
3
+ > these docs are for production builds, for development see [dev.md](dev.md)
4
+
5
+ ## usage
6
+
7
+ The `gro build` task produces outputs for production:
8
+
9
+ ```bash
10
+ gro build
11
+ ```
12
+
13
+ This runs the configured Gro plugins, `setup -> adapt -> teardown`, in production mode.
14
+
15
+ If your project has a SvelteKit frontend,
16
+ [the default plugin](../gro_plugin_sveltekit_app.ts) calls `vite build`,
17
+ forwarding any [`-- vite [...]` args](https://vitejs.dev/config/):
18
+
19
+ ```bash
20
+ gro build -- vite --config my-config.js
21
+ ```
22
+
23
+ ## plugins
24
+
25
+ `Plugin`s are objects that customize the behavior of `gro build` and `gro dev`.
26
+ They try to defer to underlying tools as much as possible, and exist to glue everything together.
27
+ For example, the library plugin internally uses
28
+ [`svelte-package`](https://kit.svelte.dev/docs/packaging).
29
+ See [plugin.md](plugin.md) to learn more.
30
+
31
+ ## deploying and publishing
32
+
33
+ Now that we can produce builds, how do we share them with the world?
34
+
35
+ The [`gro deploy`](deploy.md) task outputs builds to a branch,
36
+ like for static publishing to GitHub pages.
37
+
38
+ The [`gro publish`](publish.md) task publishes packages to npm.
39
+
40
+ Both of these tasks call `gro build` internally,
41
+ and you can always run it manually if you're curious.
@@ -0,0 +1,162 @@
1
+ # config
2
+
3
+ Gro supports SvelteKit apps, Node libraries, and Node servers with minimal abstraction
4
+ with the help of an optional config file that lives at the root `gro.config.ts`.
5
+ If a project does not define a config, Gro imports a default config from
6
+ [`src/lib/gro.config.default.ts`](/src/lib/gro.config.default.ts),
7
+ which looks at your project for the familiar patterns and tries to do the right thing.
8
+
9
+ > The [default config](/src/lib/gro.config.default.ts)
10
+ > detects three types of projects that can coexist in one repo:
11
+ > SvelteKit frontends,
12
+ > Node libraries with [`@sveltejs/package`](https://kit.svelte.dev/docs/packaging),
13
+ > and Node servers.
14
+
15
+ See [`src/lib/config.ts`](/src/lib/config.ts) for the config types and implementation.
16
+
17
+ ## examples
18
+
19
+ [The default config](/src/lib/gro.config.default.ts)
20
+ is used for projects that do not define `gro.config.ts`.
21
+ It's also passed as the first argument to `Create_Gro_Config`.
22
+
23
+ A simple config that does nothing:
24
+
25
+ ```ts
26
+ // gro.config.ts
27
+ import type {Create_Gro_Config} from '@ryanatkn/gro';
28
+
29
+ const config: Create_Gro_Config = async (cfg) => {
30
+ // mutate `cfg` or return a new object
31
+ return cfg;
32
+ };
33
+
34
+ export default config;
35
+ ```
36
+
37
+ The default export of a Gro config is `Gro_Config | Create_Gro_Config`:
38
+
39
+ ```ts
40
+ export interface Create_Gro_Config {
41
+ (base_config: Gro_Config): Gro_Config | Promise<Gro_Config>;
42
+ }
43
+
44
+ export interface Gro_Config {
45
+ plugins: Create_Config_Plugins;
46
+ map_package_json: Map_Package_Json | null;
47
+ }
48
+ ```
49
+
50
+ To define a user config that overrides the default plugins:
51
+
52
+ ```ts
53
+ import type {Create_Gro_Config} from '@ryanatkn/gro';
54
+ import {gro_plugin_sveltekit_app} from '@ryanatkn/gro/gro_plugin_sveltekit_app.js';
55
+
56
+ const config: Create_Gro_Config = async (cfg) => {
57
+ // example setting your own plugins:
58
+ cfg.plugins = async () => [
59
+ gro_plugin_sveltekit_app(),
60
+ (await import('./src/custom_plugin.js')).plugin(),
61
+ ];
62
+
63
+ // example extending the default plugins:
64
+ const get_base_plugins = cfg.plugins;
65
+ cfg.plugins = async (ctx) => {
66
+ // replace a base plugin with `import {replace_plugin} from '@ryanatkn/gro';`:
67
+ const updated_plugins = replace_plugin(
68
+ await get_base_plugins(ctx),
69
+ gro_plugin_sveltekit_app({
70
+ // host_target?: Host_Target;
71
+ // well_known_package_json?: boolean | Map_Package_Json;
72
+ }),
73
+ // 'gro_plugin_sveltekit_app', // optional name if they don't match
74
+ );
75
+ return updated_plugins.concat(create_some_custom_plugin());
76
+ };
77
+ return cfg;
78
+ };
79
+
80
+ export default config;
81
+ ```
82
+
83
+ See also [Gro's own internal config](/gro.config.ts).
84
+
85
+ ## `plugins`
86
+
87
+ The `plugins` property is a function that returns an array of `Plugin` instances.
88
+ Read more about plugins and the `Plugin` in
89
+ [plugin.md](plugin.md), [dev.md](dev.md#plugin), and [build.md](build.md#plugin).
90
+
91
+ ```ts
92
+ export interface Create_Config_Plugins<T_Plugin_Context extends Plugin_Context = Plugin_Context> {
93
+ (
94
+ ctx: T_Plugin_Context,
95
+ ):
96
+ | (Plugin<T_Plugin_Context> | null | Array<Plugin<T_Plugin_Context> | null>)
97
+ | Promise<Plugin<T_Plugin_Context> | null | Array<Plugin<T_Plugin_Context> | null>>;
98
+ }
99
+ ```
100
+
101
+ ## `map_package_json`
102
+
103
+ The Gro config option `map_package_json` hooks into Gro's `package.json` automations.
104
+ The `gro sync` task, which is called during the dev and build tasks among others,
105
+ performs several steps to get a project's state ready,
106
+ including `svelte-kit sync` and `package.json` automations.
107
+ When the `map_package_json` config value is truthy,
108
+ Gro outputs a mapped version of the root `package.json`.
109
+
110
+ > The `gro check` task integrates with `map_package_json` to ensure everything is synced.
111
+
112
+ The main purpose of `map_package_json` is to automate
113
+ the `"exports"` property of your root `package.json`.
114
+ The motivation is to streamline package publishing by supplementing
115
+ [`@sveltejs/package`](https://kit.svelte.dev/docs/packaging).
116
+
117
+ By default `package_json.exports` includes everything from `$lib/`
118
+ except for some ignored files like tests and markdown,
119
+ and you can provide your own `map_package_json` hook to
120
+ mutate the `package_json`, return new data, or return `null` to be a no-op.
121
+
122
+ Typical usage modifies `package_json.exports` during this step to define the public API.
123
+
124
+ ### using `map_package_json`
125
+
126
+ ```ts
127
+ // gro.config.ts
128
+ const config: Gro_Config = {
129
+ // ...other config
130
+
131
+ // disable mapping `package.json` with automated `exports`:
132
+ map_package_json: null,
133
+
134
+ // mutate anything and return the final config (can be async):
135
+ map_package_json: (package_json) => {
136
+ // example setting `exports`:
137
+ package_json.exports = {
138
+ '.': {
139
+ default: './dist/index.js',
140
+ types: './dist/index.d.ts',
141
+ },
142
+ './example.js': {
143
+ default: './dist/example.js',
144
+ types: './dist/example.d.ts',
145
+ },
146
+ './Example.svelte': {
147
+ svelte: './dist/Example.svelte',
148
+ types: './dist/Example.svelte.d.ts',
149
+ },
150
+ };
151
+ // example filtering `exports`:
152
+ package_json.exports = Object.fromEntries(
153
+ Object.entries(package_json.exports).filter(/* ... */),
154
+ );
155
+ return package_json; // returning `null` is a no-op
156
+ },
157
+ };
158
+
159
+ export interface Map_Package_Json {
160
+ (package_json: Package_Json): Package_Json | null | Promise<Package_Json | null>;
161
+ }
162
+ ```
@@ -0,0 +1,32 @@
1
+ # deploy
2
+
3
+ The [`gro deploy`](/src/lib/deploy.task.ts)
4
+ task was originally designed to support static deployments to
5
+ [GitHub pages](https://pages.github.com/),
6
+ but what it actually does is just [build](./build.md) and push to a branch.
7
+
8
+ Importantly, Gro **destructively force pushes** to the `--target` branch, `deploy` by default.
9
+ This is because Gro treats your deployment
10
+ branch as disposable, able to be deleted or squashed or whatever whenever.
11
+ Internally, `gro deploy` uses [git worktree](https://git-scm.com/docs/git-worktree)
12
+ for tidiness.
13
+
14
+ ```bash
15
+ gro deploy # prepare build/ and commit it to the `deploy` branch, then push to go live
16
+ gro deploy --source my-branch # deploy from `my-branch` instead of the default `main`
17
+
18
+ # deploy to `custom-deploy-branch` instead of the default `deploy`
19
+ # WARNING! this force pushes to the target branch!
20
+ gro deploy --target custom-deploy-branch
21
+ # the above actually fails because force pushing is destructive, so add `--force` to be extra clear:
22
+ gro deploy --target custom-deploy-branch --force
23
+ # TODO maybe it should be `--dangerous-target-branch` instead of `--target` and `--force`?
24
+
25
+ gro deploy --dry # prepare build/ but don't commit or push
26
+ gro deploy --clean # if something goes wrong, use this to reset git and gro state
27
+ ```
28
+
29
+ Run `gro deploy --help` or see [`src/lib/deploy.task.ts`](/src/lib/deploy.task.ts) for the details.
30
+
31
+ For needs more advanced than pushing to a remote branch,
32
+ projects can implement a custom `src/lib/deploy.task.ts`.