@ryanatkn/gro 0.129.4 → 0.129.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package.js +3 -3
- package/package.json +3 -2
- package/src/lib/args.test.ts +59 -0
- package/src/lib/args.ts +169 -0
- package/src/lib/build.task.ts +37 -0
- package/src/lib/changelog.test.ts +138 -0
- package/src/lib/changelog.ts +69 -0
- package/src/lib/changeset.task.ts +206 -0
- package/src/lib/changeset_helpers.ts +13 -0
- package/src/lib/check.task.ts +90 -0
- package/src/lib/clean.task.ts +46 -0
- package/src/lib/clean_fs.ts +54 -0
- package/src/lib/cli.ts +97 -0
- package/src/lib/commit.task.ts +33 -0
- package/src/lib/config.test.ts +71 -0
- package/src/lib/config.ts +161 -0
- package/src/lib/deploy.task.ts +243 -0
- package/src/lib/dev.task.ts +43 -0
- package/src/lib/docs/README.gen.md.ts +63 -0
- package/src/lib/docs/README.md +20 -0
- package/src/lib/docs/build.md +41 -0
- package/src/lib/docs/config.md +213 -0
- package/src/lib/docs/deploy.md +32 -0
- package/src/lib/docs/dev.md +40 -0
- package/src/lib/docs/gen.md +269 -0
- package/src/lib/docs/gro_plugin_sveltekit_app.md +113 -0
- package/src/lib/docs/package_json.md +33 -0
- package/src/lib/docs/plugin.md +50 -0
- package/src/lib/docs/publish.md +137 -0
- package/src/lib/docs/task.md +391 -0
- package/src/lib/docs/tasks.gen.md.ts +90 -0
- package/src/lib/docs/tasks.md +37 -0
- package/src/lib/docs/test.md +52 -0
- package/src/lib/env.ts +75 -0
- package/src/lib/esbuild_helpers.ts +50 -0
- package/src/lib/esbuild_plugin_external_worker.ts +92 -0
- package/src/lib/esbuild_plugin_svelte.test.ts +88 -0
- package/src/lib/esbuild_plugin_svelte.ts +108 -0
- package/src/lib/esbuild_plugin_sveltekit_local_imports.ts +31 -0
- package/src/lib/esbuild_plugin_sveltekit_shim_alias.ts +25 -0
- package/src/lib/esbuild_plugin_sveltekit_shim_app.ts +41 -0
- package/src/lib/esbuild_plugin_sveltekit_shim_env.ts +46 -0
- package/src/lib/format.task.ts +30 -0
- package/src/lib/format_directory.ts +55 -0
- package/src/lib/format_file.test.ts +20 -0
- package/src/lib/format_file.ts +49 -0
- package/src/lib/fs.ts +18 -0
- package/src/lib/gen.task.ts +134 -0
- package/src/lib/gen.test.ts +306 -0
- package/src/lib/gen.ts +360 -0
- package/src/lib/git.test.ts +34 -0
- package/src/lib/git.ts +297 -0
- package/src/lib/github.ts +46 -0
- package/src/lib/gro.config.default.ts +34 -0
- package/src/lib/gro.ts +25 -0
- package/src/lib/gro_helpers.ts +101 -0
- package/src/lib/gro_plugin_gen.ts +95 -0
- package/src/lib/gro_plugin_server.ts +288 -0
- package/src/lib/gro_plugin_sveltekit_app.ts +257 -0
- package/src/lib/gro_plugin_sveltekit_library.ts +74 -0
- package/src/lib/hash.test.ts +33 -0
- package/src/lib/hash.ts +19 -0
- package/src/lib/index.ts +4 -0
- package/src/lib/input_path.test.ts +230 -0
- package/src/lib/input_path.ts +255 -0
- package/src/lib/invoke.ts +27 -0
- package/src/lib/invoke_task.ts +116 -0
- package/src/lib/lint.task.ts +38 -0
- package/src/lib/loader.test.ts +49 -0
- package/src/lib/loader.ts +226 -0
- package/src/lib/module.test.ts +46 -0
- package/src/lib/module.ts +13 -0
- package/src/lib/modules.test.ts +63 -0
- package/src/lib/modules.ts +112 -0
- package/src/lib/package.gen.ts +33 -0
- package/src/lib/package.ts +998 -0
- package/src/lib/package_json.test.ts +101 -0
- package/src/lib/package_json.ts +330 -0
- package/src/lib/package_meta.ts +86 -0
- package/src/lib/path.ts +23 -0
- package/src/lib/path_constants.ts +30 -0
- package/src/lib/paths.test.ts +77 -0
- package/src/lib/paths.ts +101 -0
- package/src/lib/plugin.test.ts +57 -0
- package/src/lib/plugin.ts +113 -0
- package/src/lib/publish.task.ts +194 -0
- package/src/lib/register.ts +3 -0
- package/src/lib/reinstall.task.ts +42 -0
- package/src/lib/release.task.ts +21 -0
- package/src/lib/resolve.task.ts +43 -0
- package/src/lib/resolve_node_specifier.test.ts +31 -0
- package/src/lib/resolve_node_specifier.ts +55 -0
- package/src/lib/resolve_specifier.test.ts +76 -0
- package/src/lib/resolve_specifier.ts +61 -0
- package/src/lib/run.task.ts +41 -0
- package/src/lib/run_gen.test.ts +196 -0
- package/src/lib/run_gen.ts +95 -0
- package/src/lib/run_task.test.ts +86 -0
- package/src/lib/run_task.ts +75 -0
- package/src/lib/search_fs.test.ts +56 -0
- package/src/lib/search_fs.ts +93 -0
- package/src/lib/src_json.test.ts +49 -0
- package/src/lib/src_json.ts +153 -0
- package/src/lib/svelte_helpers.ts +2 -0
- package/src/lib/sveltekit_config.ts +101 -0
- package/src/lib/sveltekit_config_global.ts +6 -0
- package/src/lib/sveltekit_helpers.ts +132 -0
- package/src/lib/sveltekit_shim_app.ts +42 -0
- package/src/lib/sveltekit_shim_app_environment.ts +14 -0
- package/src/lib/sveltekit_shim_app_forms.ts +20 -0
- package/src/lib/sveltekit_shim_app_navigation.ts +23 -0
- package/src/lib/sveltekit_shim_app_paths.ts +16 -0
- package/src/lib/sveltekit_shim_app_stores.ts +25 -0
- package/src/lib/sveltekit_shim_env.ts +45 -0
- package/src/lib/sync.task.ts +47 -0
- package/src/lib/task.test.ts +84 -0
- package/src/lib/task.ts +235 -0
- package/src/lib/task_logging.ts +180 -0
- package/src/lib/test.task.ts +50 -0
- package/src/lib/throttle.test.ts +52 -0
- package/src/lib/throttle.ts +63 -0
- package/src/lib/typecheck.task.ts +57 -0
- package/src/lib/upgrade.task.ts +108 -0
- package/src/lib/watch_dir.ts +88 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import {join, resolve} from 'node:path';
|
|
2
|
+
import {existsSync} from 'node:fs';
|
|
3
|
+
|
|
4
|
+
import {GRO_DIST_DIR, IS_THIS_GRO, paths} from './paths.js';
|
|
5
|
+
import {
|
|
6
|
+
GRO_CONFIG_PATH,
|
|
7
|
+
NODE_MODULES_DIRNAME,
|
|
8
|
+
SERVER_DIST_PATH,
|
|
9
|
+
SVELTEKIT_BUILD_DIRNAME,
|
|
10
|
+
SVELTEKIT_DIST_DIRNAME,
|
|
11
|
+
} from './path_constants.js';
|
|
12
|
+
import create_default_config from './gro.config.default.js';
|
|
13
|
+
import type {Create_Config_Plugins} from './plugin.js';
|
|
14
|
+
import type {Map_Package_Json} from './package_json.js';
|
|
15
|
+
import type {Path_Filter, Path_Id} from './path.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The config that users can extend via `gro.config.ts`.
|
|
19
|
+
* This is exposed to users in places like tasks and genfiles.
|
|
20
|
+
* @see https://github.com/ryanatkn/gro/blob/main/src/lib/docs/config.md
|
|
21
|
+
*/
|
|
22
|
+
export interface Gro_Config {
|
|
23
|
+
/**
|
|
24
|
+
* @see https://github.com/ryanatkn/gro/blob/main/src/lib/docs/plugin.md
|
|
25
|
+
*/
|
|
26
|
+
plugins: Create_Config_Plugins;
|
|
27
|
+
/**
|
|
28
|
+
* Maps the project's `package.json` before writing it to the filesystem.
|
|
29
|
+
* The `package_json` argument may be mutated, but the return value is what's used by the caller.
|
|
30
|
+
* Returning `null` is a no-op for the caller.
|
|
31
|
+
*/
|
|
32
|
+
map_package_json: Map_Package_Json | null;
|
|
33
|
+
/**
|
|
34
|
+
* The root directories to search for tasks given implicit relative input paths.
|
|
35
|
+
* Defaults to `./src/lib`, then the cwd, then the Gro package dist.
|
|
36
|
+
*/
|
|
37
|
+
task_root_dirs: Path_Id[];
|
|
38
|
+
/**
|
|
39
|
+
* When searching the filsystem for tasks and genfiles,
|
|
40
|
+
* directories and files are included if they pass all of these filters.
|
|
41
|
+
*/
|
|
42
|
+
search_filters: Path_Filter[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The relaxed variant of `Gro_Config` that users can provide via `gro.config.ts`.
|
|
47
|
+
* Superset of `Gro_Config`.
|
|
48
|
+
* @see https://github.com/ryanatkn/gro/blob/main/src/lib/docs/config.md
|
|
49
|
+
*/
|
|
50
|
+
export interface Raw_Gro_Config {
|
|
51
|
+
plugins?: Create_Config_Plugins;
|
|
52
|
+
map_package_json?: Map_Package_Json | null;
|
|
53
|
+
task_root_dirs?: string[];
|
|
54
|
+
search_filters?: Path_Filter | Path_Filter[] | null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type Create_Gro_Config = (
|
|
58
|
+
base_config: Gro_Config,
|
|
59
|
+
) => Raw_Gro_Config | Promise<Raw_Gro_Config>;
|
|
60
|
+
|
|
61
|
+
export const create_empty_config = (): Gro_Config => ({
|
|
62
|
+
plugins: () => [],
|
|
63
|
+
map_package_json: default_map_package_json,
|
|
64
|
+
task_root_dirs: [
|
|
65
|
+
// TODO maybe disable if no SvelteKit `lib` directory? or other detection to improve defaults
|
|
66
|
+
paths.lib,
|
|
67
|
+
IS_THIS_GRO ? null : paths.root,
|
|
68
|
+
IS_THIS_GRO ? null : GRO_DIST_DIR,
|
|
69
|
+
].filter((v) => v !== null),
|
|
70
|
+
search_filters: [(id) => !DEFAULT_SEARCH_EXCLUDER.test(id)],
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The regexp used by default to exclude directories and files
|
|
75
|
+
* when searching the filesystem for tasks and genfiles.
|
|
76
|
+
* Customize via `search_filters` in the `Gro_Config`.
|
|
77
|
+
* See the test cases for the exact behavior.
|
|
78
|
+
*/
|
|
79
|
+
export const DEFAULT_SEARCH_EXCLUDER = new RegExp(
|
|
80
|
+
`(${
|
|
81
|
+
'(^|/)\\.[^/]+' + // exclude all `.`-prefixed directories
|
|
82
|
+
// TODO probably change to `pkg.name` instead of this catch-all (also `gro` below)
|
|
83
|
+
`|(^|/)${NODE_MODULES_DIRNAME}(?!/(@[^/]+/)?gro/${SVELTEKIT_DIST_DIRNAME})` + // exclude `node_modules` unless it's to the Gro directory
|
|
84
|
+
`|(^|/)${SVELTEKIT_BUILD_DIRNAME}` + // exclude the SvelteKit build directory
|
|
85
|
+
`|(^|/)(?<!(^|/)gro/)${SVELTEKIT_DIST_DIRNAME}` + // exclude the SvelteKit dist directory unless it's in the Gro directory
|
|
86
|
+
`|(^|/)${SERVER_DIST_PATH}` // exclude the Gro server plugin dist directory
|
|
87
|
+
})($|/)`,
|
|
88
|
+
'u',
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const default_map_package_json: Map_Package_Json = (package_json) => {
|
|
92
|
+
if (package_json.exports) {
|
|
93
|
+
package_json.exports = Object.fromEntries(
|
|
94
|
+
Object.entries(package_json.exports).filter(([k]) => !DEFAULT_EXPORTS_EXCLUDER.test(k)),
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
return package_json;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const DEFAULT_EXPORTS_EXCLUDER = /(\.md|\.(test|ignore)\.|\/(test|fixtures|ignore)\/)/u;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Transforms a `Raw_Gro_Config` to the more strict `Gro_Config`.
|
|
104
|
+
* This allows users to provide a more relaxed config.
|
|
105
|
+
*/
|
|
106
|
+
export const normalize_config = (raw_config: Raw_Gro_Config): Gro_Config => {
|
|
107
|
+
const empty_config = create_empty_config();
|
|
108
|
+
// All of the raw config properties are optional,
|
|
109
|
+
// so fall back to the empty values when `undefined`.
|
|
110
|
+
const {
|
|
111
|
+
plugins = empty_config.plugins,
|
|
112
|
+
map_package_json = empty_config.map_package_json,
|
|
113
|
+
task_root_dirs = empty_config.task_root_dirs,
|
|
114
|
+
search_filters = empty_config.search_filters,
|
|
115
|
+
} = raw_config;
|
|
116
|
+
return {
|
|
117
|
+
plugins,
|
|
118
|
+
map_package_json,
|
|
119
|
+
task_root_dirs: task_root_dirs.map((p) => resolve(p)),
|
|
120
|
+
search_filters: Array.isArray(search_filters)
|
|
121
|
+
? search_filters
|
|
122
|
+
: search_filters
|
|
123
|
+
? [search_filters]
|
|
124
|
+
: [],
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export interface Gro_Config_Module {
|
|
129
|
+
readonly default: Raw_Gro_Config | Create_Gro_Config;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const load_config = async (dir = paths.root): Promise<Gro_Config> => {
|
|
133
|
+
const default_config = normalize_config(await create_default_config(create_empty_config()));
|
|
134
|
+
const config_path = join(dir, GRO_CONFIG_PATH);
|
|
135
|
+
if (!existsSync(config_path)) {
|
|
136
|
+
// No user config file found, so return the default.
|
|
137
|
+
return default_config;
|
|
138
|
+
}
|
|
139
|
+
// Import the user's `gro.config.ts`.
|
|
140
|
+
const config_module = await import(config_path);
|
|
141
|
+
validate_config_module(config_module, config_path);
|
|
142
|
+
return normalize_config(
|
|
143
|
+
typeof config_module.default === 'function'
|
|
144
|
+
? await config_module.default(default_config)
|
|
145
|
+
: config_module.default,
|
|
146
|
+
);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export const validate_config_module: (
|
|
150
|
+
config_module: any,
|
|
151
|
+
config_path: string,
|
|
152
|
+
) => asserts config_module is Gro_Config_Module = (config_module, config_path) => {
|
|
153
|
+
const config = config_module.default;
|
|
154
|
+
if (!config) {
|
|
155
|
+
throw Error(`Invalid Gro config module at ${config_path}: expected a default export`);
|
|
156
|
+
} else if (!(typeof config === 'function' || typeof config === 'object')) {
|
|
157
|
+
throw Error(
|
|
158
|
+
`Invalid Gro config module at ${config_path}: the default export must be a function or object`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import {spawn} from '@ryanatkn/belt/process.js';
|
|
2
|
+
import {print_error} from '@ryanatkn/belt/print.js';
|
|
3
|
+
import {green, red} from '@ryanatkn/belt/styletext.js';
|
|
4
|
+
import {z} from 'zod';
|
|
5
|
+
import {cp, mkdir, rm} from 'node:fs/promises';
|
|
6
|
+
import {join, resolve} from 'node:path';
|
|
7
|
+
import {existsSync, readdirSync} from 'node:fs';
|
|
8
|
+
|
|
9
|
+
import {Task_Error, type Task} from './task.js';
|
|
10
|
+
import {print_path} from './paths.js';
|
|
11
|
+
import {GRO_DIRNAME, GIT_DIRNAME, SVELTEKIT_BUILD_DIRNAME} from './path_constants.js';
|
|
12
|
+
import {empty_dir} from './fs.js';
|
|
13
|
+
import {
|
|
14
|
+
git_check_clean_workspace,
|
|
15
|
+
git_checkout,
|
|
16
|
+
git_local_branch_exists,
|
|
17
|
+
git_remote_branch_exists,
|
|
18
|
+
Git_Origin,
|
|
19
|
+
Git_Branch,
|
|
20
|
+
git_delete_local_branch,
|
|
21
|
+
git_push_to_create,
|
|
22
|
+
git_reset_branch_to_first_commit,
|
|
23
|
+
git_pull,
|
|
24
|
+
git_fetch,
|
|
25
|
+
git_check_setting_pull_rebase,
|
|
26
|
+
git_clone_locally,
|
|
27
|
+
git_current_branch_name,
|
|
28
|
+
} from './git.js';
|
|
29
|
+
|
|
30
|
+
// docs at ./docs/deploy.md
|
|
31
|
+
|
|
32
|
+
// terminal command for testing:
|
|
33
|
+
// npm run bootstrap && rm -rf .gro && clear && gro deploy --source no-git-workspace --no-build --dry
|
|
34
|
+
|
|
35
|
+
// TODO customize
|
|
36
|
+
const dir = process.cwd();
|
|
37
|
+
const INITIAL_FILE_PATH = '.gitkeep';
|
|
38
|
+
const DEPLOY_DIR = GRO_DIRNAME + '/deploy';
|
|
39
|
+
const SOURCE_BRANCH = 'main';
|
|
40
|
+
const TARGET_BRANCH = 'deploy';
|
|
41
|
+
const DANGEROUS_BRANCHES = [SOURCE_BRANCH, 'master'];
|
|
42
|
+
|
|
43
|
+
export const Args = z
|
|
44
|
+
.object({
|
|
45
|
+
source: Git_Branch.describe('git source branch to build and deploy from').default(
|
|
46
|
+
SOURCE_BRANCH,
|
|
47
|
+
),
|
|
48
|
+
target: Git_Branch.describe('git target branch to deploy to').default(TARGET_BRANCH),
|
|
49
|
+
origin: Git_Origin.describe('git origin to deploy to').default('origin'),
|
|
50
|
+
deploy_dir: z.string({description: 'the deploy output directory'}).default(DEPLOY_DIR),
|
|
51
|
+
build_dir: z
|
|
52
|
+
.string({description: 'the SvelteKit build directory'})
|
|
53
|
+
.default(SVELTEKIT_BUILD_DIRNAME),
|
|
54
|
+
dry: z
|
|
55
|
+
.boolean({
|
|
56
|
+
description: 'build and prepare to deploy without actually deploying',
|
|
57
|
+
})
|
|
58
|
+
.default(false),
|
|
59
|
+
force: z
|
|
60
|
+
.boolean({description: 'caution!! destroys the target branch both locally and remotely'})
|
|
61
|
+
.default(false),
|
|
62
|
+
dangerous: z
|
|
63
|
+
.boolean({description: 'caution!! enables destruction of branches like main and master'})
|
|
64
|
+
.default(false),
|
|
65
|
+
reset: z
|
|
66
|
+
.boolean({
|
|
67
|
+
description: 'if true, resets the target branch back to the first commit before deploying',
|
|
68
|
+
})
|
|
69
|
+
.default(false),
|
|
70
|
+
build: z.boolean({description: 'dual of no-build'}).default(true),
|
|
71
|
+
'no-build': z.boolean({description: 'opt out of building'}).default(false),
|
|
72
|
+
})
|
|
73
|
+
.strict();
|
|
74
|
+
export type Args = z.infer<typeof Args>;
|
|
75
|
+
|
|
76
|
+
export const task: Task<Args> = {
|
|
77
|
+
summary: 'deploy to a branch',
|
|
78
|
+
Args,
|
|
79
|
+
run: async ({args, log, invoke_task}): Promise<void> => {
|
|
80
|
+
const {source, target, origin, build_dir, deploy_dir, dry, force, dangerous, reset, build} =
|
|
81
|
+
args;
|
|
82
|
+
|
|
83
|
+
// Checks
|
|
84
|
+
if (!force && target !== TARGET_BRANCH) {
|
|
85
|
+
throw new Task_Error(
|
|
86
|
+
`Warning! You are deploying to a custom target branch '${target}',` +
|
|
87
|
+
` instead of the default '${TARGET_BRANCH}' branch.` +
|
|
88
|
+
` This is destructive to your '${target}' branch!` +
|
|
89
|
+
` If you understand and are OK with deleting your branch '${target}',` +
|
|
90
|
+
` both locally and remotely, pass --force to suppress this error.`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (!dangerous && DANGEROUS_BRANCHES.includes(target)) {
|
|
94
|
+
throw new Task_Error(
|
|
95
|
+
`Warning! You are deploying to a custom target branch '${target}'` +
|
|
96
|
+
` and that appears very dangerous: it is destructive to your '${target}' branch!` +
|
|
97
|
+
` If you understand and are OK with deleting your branch '${target}',` +
|
|
98
|
+
` both locally and remotely, pass --dangerous to suppress this error.`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
const clean_error_message = await git_check_clean_workspace();
|
|
102
|
+
if (clean_error_message) {
|
|
103
|
+
throw new Task_Error(
|
|
104
|
+
'Deploy failed because the git workspace has uncommitted changes: ' + clean_error_message,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
if (!(await git_check_setting_pull_rebase())) {
|
|
108
|
+
throw new Task_Error(
|
|
109
|
+
'Deploying currently requires `git config --global pull.rebase true`,' +
|
|
110
|
+
' but this restriction could be lifted with more work',
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Fetch the source branch in the cwd if it's not there
|
|
115
|
+
if (!(await git_local_branch_exists(source))) {
|
|
116
|
+
await git_fetch(origin, source);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Prepare the source branch in the cwd
|
|
120
|
+
await git_checkout(source);
|
|
121
|
+
await git_pull(origin, source);
|
|
122
|
+
if (await git_check_clean_workspace()) {
|
|
123
|
+
throw new Task_Error(
|
|
124
|
+
'Deploy failed because the local source branch is out of sync with the remote one,' +
|
|
125
|
+
' finish rebasing manually or reset with `git rebase --abort`',
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Prepare the target branch remotely and locally
|
|
130
|
+
const resolved_deploy_dir = resolve(deploy_dir);
|
|
131
|
+
const target_spawn_options = {cwd: resolved_deploy_dir};
|
|
132
|
+
const remote_target_exists = await git_remote_branch_exists(origin, target);
|
|
133
|
+
if (remote_target_exists) {
|
|
134
|
+
// Remote target branch already exists, so sync up efficiently
|
|
135
|
+
|
|
136
|
+
// First, check if the deploy dir exists, and if so, attempt to sync it.
|
|
137
|
+
// If anything goes wrong, delete the directory and we'll initialize it
|
|
138
|
+
// using the same code path as if it didn't exist in the first place.
|
|
139
|
+
if (existsSync(resolved_deploy_dir)) {
|
|
140
|
+
if (target !== (await git_current_branch_name(target_spawn_options))) {
|
|
141
|
+
// We're in a bad state because the target branch has changed,
|
|
142
|
+
// so delete the directory and continue as if it wasn't there.
|
|
143
|
+
await rm(resolved_deploy_dir, {recursive: true});
|
|
144
|
+
} else {
|
|
145
|
+
await spawn('git', ['reset', '--hard'], target_spawn_options); // in case it's dirty
|
|
146
|
+
await git_pull(origin, target, target_spawn_options);
|
|
147
|
+
if (await git_check_clean_workspace(target_spawn_options)) {
|
|
148
|
+
// We're in a bad state because the local branch lost continuity with the remote,
|
|
149
|
+
// so delete the directory and continue as if it wasn't there.
|
|
150
|
+
await rm(resolved_deploy_dir, {recursive: true});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Second, initialize the deploy dir if needed.
|
|
156
|
+
// It may not exist, or it may have been deleted after failing to sync above.
|
|
157
|
+
if (!existsSync(resolved_deploy_dir)) {
|
|
158
|
+
const local_deploy_branch_exists = await git_local_branch_exists(target);
|
|
159
|
+
await git_fetch(origin, ('+' + target + ':' + target) as Git_Branch); // fetch+merge and allow non-fastforward updates with the +
|
|
160
|
+
await git_clone_locally(origin, target, dir, resolved_deploy_dir);
|
|
161
|
+
// Clean up if we created the target branch in the cwd
|
|
162
|
+
if (!local_deploy_branch_exists) {
|
|
163
|
+
await git_delete_local_branch(target);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Local target branch is now synced with remote, but do we need to reset?
|
|
168
|
+
if (reset) {
|
|
169
|
+
await git_reset_branch_to_first_commit(origin, target, target_spawn_options);
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
// Remote target branch does not exist, so start from scratch
|
|
173
|
+
|
|
174
|
+
// Delete the deploy dir and recreate it
|
|
175
|
+
if (existsSync(resolved_deploy_dir)) {
|
|
176
|
+
await rm(resolved_deploy_dir, {recursive: true});
|
|
177
|
+
await mkdir(resolved_deploy_dir, {recursive: true});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Delete the target branch locally in the cwd if it exists
|
|
181
|
+
if (await git_local_branch_exists(target)) {
|
|
182
|
+
await git_delete_local_branch(target);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Create the target branch locally and remotely.
|
|
186
|
+
// This is more complex to avoid churning the cwd.
|
|
187
|
+
await git_clone_locally(origin, source, dir, resolved_deploy_dir);
|
|
188
|
+
await spawn('git', ['checkout', '--orphan', target], target_spawn_options);
|
|
189
|
+
// TODO there's definitely a better way to do this
|
|
190
|
+
await spawn('git', ['rm', '-rf', '.'], target_spawn_options);
|
|
191
|
+
await spawn('touch', [INITIAL_FILE_PATH], target_spawn_options);
|
|
192
|
+
await spawn('git', ['add', INITIAL_FILE_PATH], target_spawn_options);
|
|
193
|
+
await spawn('git', ['commit', '-m', 'init'], target_spawn_options);
|
|
194
|
+
await git_push_to_create(origin, target, target_spawn_options);
|
|
195
|
+
await git_delete_local_branch(source, target_spawn_options);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Remove everything except .git from the deploy directory to avoid stale files
|
|
199
|
+
await empty_dir(resolved_deploy_dir, (path) => path !== GIT_DIRNAME);
|
|
200
|
+
|
|
201
|
+
// Build
|
|
202
|
+
try {
|
|
203
|
+
if (build) {
|
|
204
|
+
await invoke_task('build');
|
|
205
|
+
}
|
|
206
|
+
if (!existsSync(build_dir)) {
|
|
207
|
+
log.error(red('directory to deploy does not exist after building:'), build_dir);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
} catch (err) {
|
|
211
|
+
log.error(red('build failed'), 'but', green('no changes were made to git'), print_error(err));
|
|
212
|
+
if (dry) {
|
|
213
|
+
log.info(red('dry deploy failed'));
|
|
214
|
+
}
|
|
215
|
+
throw new Task_Error(`Deploy safely canceled due to build failure. See the error above.`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Copy the build
|
|
219
|
+
await Promise.all(
|
|
220
|
+
readdirSync(build_dir).map((path) =>
|
|
221
|
+
cp(join(build_dir, path), join(resolved_deploy_dir, path), {recursive: true}),
|
|
222
|
+
),
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
// At this point, `dist/` is ready to be committed and deployed!
|
|
226
|
+
if (dry) {
|
|
227
|
+
log.info(green('dry deploy complete:'), 'files at', print_path(resolved_deploy_dir));
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Commit and push
|
|
232
|
+
try {
|
|
233
|
+
await spawn('git', ['add', '.', '-f'], target_spawn_options);
|
|
234
|
+
await spawn('git', ['commit', '-m', 'deployment'], target_spawn_options);
|
|
235
|
+
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
|
|
236
|
+
} catch (err) {
|
|
237
|
+
log.error(red('updating git failed:'), print_error(err));
|
|
238
|
+
throw new Task_Error(`Deploy failed in a bad state: built but not pushed, see error above.`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
log.info(green('deployed')); // TODO log a different message if "Everything up-to-date"
|
|
242
|
+
},
|
|
243
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {z} from 'zod';
|
|
2
|
+
|
|
3
|
+
import type {Task} from './task.js';
|
|
4
|
+
import {Plugins, type Plugin_Context} from './plugin.js';
|
|
5
|
+
import {clean_fs} from './clean_fs.js';
|
|
6
|
+
|
|
7
|
+
export const Args = z
|
|
8
|
+
.object({
|
|
9
|
+
watch: z.boolean({description: 'dual of no-watch'}).default(true),
|
|
10
|
+
'no-watch': z
|
|
11
|
+
.boolean({
|
|
12
|
+
description:
|
|
13
|
+
'opt out of running a long-lived process to watch files and rebuild on changes',
|
|
14
|
+
})
|
|
15
|
+
.default(false),
|
|
16
|
+
sync: z.boolean({description: 'dual of no-sync'}).default(true),
|
|
17
|
+
'no-sync': z.boolean({description: 'opt out of gro sync'}).default(false),
|
|
18
|
+
})
|
|
19
|
+
.strict();
|
|
20
|
+
export type Args = z.infer<typeof Args>;
|
|
21
|
+
|
|
22
|
+
export type DevTask_Context = Plugin_Context<Args>;
|
|
23
|
+
|
|
24
|
+
export const task: Task<Args> = {
|
|
25
|
+
summary: 'start SvelteKit and other dev plugins',
|
|
26
|
+
Args,
|
|
27
|
+
run: async (ctx) => {
|
|
28
|
+
const {args, invoke_task} = ctx;
|
|
29
|
+
const {watch, sync} = args;
|
|
30
|
+
|
|
31
|
+
await clean_fs({build_dev: true});
|
|
32
|
+
|
|
33
|
+
if (sync) {
|
|
34
|
+
await invoke_task('sync');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const plugins = await Plugins.create({...ctx, dev: true, watch});
|
|
38
|
+
await plugins.setup();
|
|
39
|
+
if (!watch) {
|
|
40
|
+
await plugins.teardown();
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
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 {search_fs} from '../search_fs.js';
|
|
8
|
+
|
|
9
|
+
// TODO look at `tasks.gen.md.ts` to refactor and generalize
|
|
10
|
+
// TODO show nested structure, not a flat list
|
|
11
|
+
// TODO work with file types beyond markdown
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Renders a simple index of a possibly nested directory of files.
|
|
15
|
+
*/
|
|
16
|
+
export const gen: Gen = ({origin_id}) => {
|
|
17
|
+
// TODO need to get this from project config or something
|
|
18
|
+
const root_path = parse_path_segments(paths.root).at(-1);
|
|
19
|
+
|
|
20
|
+
const origin_dir = dirname(origin_id);
|
|
21
|
+
const origin_base = basename(origin_id);
|
|
22
|
+
|
|
23
|
+
const base_dir = paths.source;
|
|
24
|
+
const relative_path = strip_start(origin_id, base_dir);
|
|
25
|
+
const relative_dir = dirname(relative_path);
|
|
26
|
+
|
|
27
|
+
// TODO should this be passed in the context, like `defaultOutputFileName`?
|
|
28
|
+
const output_file_name = to_output_file_name(origin_base);
|
|
29
|
+
|
|
30
|
+
// TODO this is GitHub-specific
|
|
31
|
+
const root_link = `[${root_path}](/../..)`;
|
|
32
|
+
const doc_files = search_fs(origin_dir);
|
|
33
|
+
const doc_paths: string[] = [];
|
|
34
|
+
for (const {path} of doc_files) {
|
|
35
|
+
if (path === output_file_name || !path.endsWith('.md')) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
doc_paths.push(path);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// TODO do we want to use absolute paths instead of relative paths,
|
|
42
|
+
// because GitHub works with them and it simplifies the code?
|
|
43
|
+
const is_index_file = output_file_name === 'README.md';
|
|
44
|
+
const path_parts = parse_path_parts(relative_dir).map((relative_path_part) => {
|
|
45
|
+
const segment = parse_path_segments(relative_path_part).at(-1);
|
|
46
|
+
return is_index_file && relative_path_part === relative_dir
|
|
47
|
+
? segment
|
|
48
|
+
: `[${segment}](${relative(origin_dir, base_path_to_path_id(relative_path_part)) || './'})`;
|
|
49
|
+
});
|
|
50
|
+
const breadcrumbs =
|
|
51
|
+
'> <sub>' + [root_link, ...path_parts, output_file_name].join(' / ') + '</sub>';
|
|
52
|
+
|
|
53
|
+
// TODO render the footer with the origin_id
|
|
54
|
+
return `# docs
|
|
55
|
+
|
|
56
|
+
${breadcrumbs}
|
|
57
|
+
|
|
58
|
+
${doc_paths.reduce((docList, doc) => docList + `- [${basename(doc, '.md')}](${doc})\n`, '')}
|
|
59
|
+
${breadcrumbs}
|
|
60
|
+
|
|
61
|
+
> <sub>generated by [${origin_base}](${origin_base})</sub>
|
|
62
|
+
`;
|
|
63
|
+
};
|
|
@@ -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_app](gro_plugin_sveltekit_app.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.
|