@fuzdev/gro 0.192.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.
- package/LICENSE +21 -0
- package/README.md +283 -0
- package/dist/args.d.ts +37 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +102 -0
- package/dist/build.task.d.ts +20 -0
- package/dist/build.task.d.ts.map +1 -0
- package/dist/build.task.js +119 -0
- package/dist/build_cache.d.ts +100 -0
- package/dist/build_cache.d.ts.map +1 -0
- package/dist/build_cache.js +299 -0
- package/dist/changelog.d.ts +11 -0
- package/dist/changelog.d.ts.map +1 -0
- package/dist/changelog.js +47 -0
- package/dist/changeset.task.d.ts +35 -0
- package/dist/changeset.task.d.ts.map +1 -0
- package/dist/changeset.task.js +151 -0
- package/dist/changeset_helpers.d.ts +17 -0
- package/dist/changeset_helpers.d.ts.map +1 -0
- package/dist/changeset_helpers.js +7 -0
- package/dist/check.task.d.ts +28 -0
- package/dist/check.task.d.ts.map +1 -0
- package/dist/check.task.js +104 -0
- package/dist/child_process_logging.d.ts +10 -0
- package/dist/child_process_logging.d.ts.map +1 -0
- package/dist/child_process_logging.js +26 -0
- package/dist/clean.task.d.ts +15 -0
- package/dist/clean.task.d.ts.map +1 -0
- package/dist/clean.task.js +40 -0
- package/dist/clean_fs.d.ts +9 -0
- package/dist/clean_fs.d.ts.map +1 -0
- package/dist/clean_fs.js +28 -0
- package/dist/cli.d.ts +34 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +61 -0
- package/dist/commit.task.d.ts +11 -0
- package/dist/commit.task.d.ts.map +1 -0
- package/dist/commit.task.js +24 -0
- package/dist/constants.d.ts +46 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +52 -0
- package/dist/deploy.task.d.ts +29 -0
- package/dist/deploy.task.d.ts.map +1 -0
- package/dist/deploy.task.js +217 -0
- package/dist/dev.task.d.ts +16 -0
- package/dist/dev.task.d.ts.map +1 -0
- package/dist/dev.task.js +44 -0
- package/dist/disknode.d.ts +23 -0
- package/dist/disknode.d.ts.map +1 -0
- package/dist/disknode.js +1 -0
- package/dist/env.d.ts +11 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +49 -0
- package/dist/esbuild_helpers.d.ts +16 -0
- package/dist/esbuild_helpers.d.ts.map +1 -0
- package/dist/esbuild_helpers.js +36 -0
- package/dist/esbuild_plugin_external_worker.d.ts +23 -0
- package/dist/esbuild_plugin_external_worker.d.ts.map +1 -0
- package/dist/esbuild_plugin_external_worker.js +55 -0
- package/dist/esbuild_plugin_svelte.d.ts +15 -0
- package/dist/esbuild_plugin_svelte.d.ts.map +1 -0
- package/dist/esbuild_plugin_svelte.js +83 -0
- package/dist/esbuild_plugin_sveltekit_local_imports.d.ts +8 -0
- package/dist/esbuild_plugin_sveltekit_local_imports.d.ts.map +1 -0
- package/dist/esbuild_plugin_sveltekit_local_imports.js +30 -0
- package/dist/esbuild_plugin_sveltekit_shim_alias.d.ts +7 -0
- package/dist/esbuild_plugin_sveltekit_shim_alias.d.ts.map +1 -0
- package/dist/esbuild_plugin_sveltekit_shim_alias.js +18 -0
- package/dist/esbuild_plugin_sveltekit_shim_app.d.ts +9 -0
- package/dist/esbuild_plugin_sveltekit_shim_app.d.ts.map +1 -0
- package/dist/esbuild_plugin_sveltekit_shim_app.js +22 -0
- package/dist/esbuild_plugin_sveltekit_shim_env.d.ts +11 -0
- package/dist/esbuild_plugin_sveltekit_shim_env.d.ts.map +1 -0
- package/dist/esbuild_plugin_sveltekit_shim_env.js +18 -0
- package/dist/filer.d.ts +33 -0
- package/dist/filer.d.ts.map +1 -0
- package/dist/filer.js +385 -0
- package/dist/format.task.d.ts +11 -0
- package/dist/format.task.d.ts.map +1 -0
- package/dist/format.task.js +27 -0
- package/dist/format_directory.d.ts +13 -0
- package/dist/format_directory.d.ts.map +1 -0
- package/dist/format_directory.js +40 -0
- package/dist/format_file.d.ts +9 -0
- package/dist/format_file.d.ts.map +1 -0
- package/dist/format_file.js +42 -0
- package/dist/gen.d.ts +142 -0
- package/dist/gen.d.ts.map +1 -0
- package/dist/gen.js +199 -0
- package/dist/gen.task.d.ts +12 -0
- package/dist/gen.task.d.ts.map +1 -0
- package/dist/gen.task.js +149 -0
- package/dist/gen_helpers.d.ts +11 -0
- package/dist/gen_helpers.d.ts.map +1 -0
- package/dist/gen_helpers.js +76 -0
- package/dist/github.d.ts +19 -0
- package/dist/github.d.ts.map +1 -0
- package/dist/github.js +33 -0
- package/dist/gro.config.default.d.ts +13 -0
- package/dist/gro.config.default.d.ts.map +1 -0
- package/dist/gro.config.default.js +33 -0
- package/dist/gro.d.ts +3 -0
- package/dist/gro.d.ts.map +1 -0
- package/dist/gro.js +21 -0
- package/dist/gro_config.d.ts +115 -0
- package/dist/gro_config.d.ts.map +1 -0
- package/dist/gro_config.js +114 -0
- package/dist/gro_helpers.d.ts +49 -0
- package/dist/gro_helpers.d.ts.map +1 -0
- package/dist/gro_helpers.js +97 -0
- package/dist/gro_plugin_gen.d.ts +12 -0
- package/dist/gro_plugin_gen.d.ts.map +1 -0
- package/dist/gro_plugin_gen.js +101 -0
- package/dist/gro_plugin_server.d.ts +80 -0
- package/dist/gro_plugin_server.d.ts.map +1 -0
- package/dist/gro_plugin_server.js +167 -0
- package/dist/gro_plugin_sveltekit_app.d.ts +9 -0
- package/dist/gro_plugin_sveltekit_app.d.ts.map +1 -0
- package/dist/gro_plugin_sveltekit_app.js +42 -0
- package/dist/gro_plugin_sveltekit_library.d.ts +16 -0
- package/dist/gro_plugin_sveltekit_library.d.ts.map +1 -0
- package/dist/gro_plugin_sveltekit_library.js +34 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/input_path.d.ts +64 -0
- package/dist/input_path.d.ts.map +1 -0
- package/dist/input_path.js +199 -0
- package/dist/invoke.d.ts +2 -0
- package/dist/invoke.d.ts.map +1 -0
- package/dist/invoke.js +28 -0
- package/dist/invoke_task.d.ts +30 -0
- package/dist/invoke_task.d.ts.map +1 -0
- package/dist/invoke_task.js +104 -0
- package/dist/lint.task.d.ts +11 -0
- package/dist/lint.task.d.ts.map +1 -0
- package/dist/lint.task.js +32 -0
- package/dist/loader.d.ts +6 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +192 -0
- package/dist/module.d.ts +4 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +6 -0
- package/dist/modules.d.ts +36 -0
- package/dist/modules.d.ts.map +1 -0
- package/dist/modules.js +71 -0
- package/dist/package_json.d.ts +32 -0
- package/dist/package_json.d.ts.map +1 -0
- package/dist/package_json.js +178 -0
- package/dist/parse_exports.d.ts +20 -0
- package/dist/parse_exports.d.ts.map +1 -0
- package/dist/parse_exports.js +65 -0
- package/dist/parse_exports_context.d.ts +21 -0
- package/dist/parse_exports_context.d.ts.map +1 -0
- package/dist/parse_exports_context.js +332 -0
- package/dist/parse_imports.d.ts +5 -0
- package/dist/parse_imports.d.ts.map +1 -0
- package/dist/parse_imports.js +140 -0
- package/dist/paths.d.ts +41 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +69 -0
- package/dist/plugin.d.ts +36 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +78 -0
- package/dist/publish.task.d.ts +26 -0
- package/dist/publish.task.d.ts.map +1 -0
- package/dist/publish.task.js +176 -0
- package/dist/register.d.ts +2 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +2 -0
- package/dist/reinstall.task.d.ts +8 -0
- package/dist/reinstall.task.d.ts.map +1 -0
- package/dist/reinstall.task.js +35 -0
- package/dist/release.task.d.ts +8 -0
- package/dist/release.task.d.ts.map +1 -0
- package/dist/release.task.js +20 -0
- package/dist/resolve.task.d.ts +11 -0
- package/dist/resolve.task.d.ts.map +1 -0
- package/dist/resolve.task.js +38 -0
- package/dist/resolve_specifier.d.ts +22 -0
- package/dist/resolve_specifier.d.ts.map +1 -0
- package/dist/resolve_specifier.js +57 -0
- package/dist/run.task.d.ts +16 -0
- package/dist/run.task.d.ts.map +1 -0
- package/dist/run.task.js +52 -0
- package/dist/run_gen.d.ts +10 -0
- package/dist/run_gen.d.ts.map +1 -0
- package/dist/run_gen.js +73 -0
- package/dist/run_task.d.ts +17 -0
- package/dist/run_task.d.ts.map +1 -0
- package/dist/run_task.js +45 -0
- package/dist/source_json.d.ts +7 -0
- package/dist/source_json.d.ts.map +1 -0
- package/dist/source_json.js +145 -0
- package/dist/svelte_config.d.ts +57 -0
- package/dist/svelte_config.d.ts.map +1 -0
- package/dist/svelte_config.js +81 -0
- package/dist/sveltekit_helpers.d.ts +75 -0
- package/dist/sveltekit_helpers.d.ts.map +1 -0
- package/dist/sveltekit_helpers.js +94 -0
- package/dist/sveltekit_shim_app.d.ts +11 -0
- package/dist/sveltekit_shim_app.d.ts.map +1 -0
- package/dist/sveltekit_shim_app.js +31 -0
- package/dist/sveltekit_shim_app_environment.d.ts +13 -0
- package/dist/sveltekit_shim_app_environment.d.ts.map +1 -0
- package/dist/sveltekit_shim_app_environment.js +14 -0
- package/dist/sveltekit_shim_app_forms.d.ts +5 -0
- package/dist/sveltekit_shim_app_forms.d.ts.map +1 -0
- package/dist/sveltekit_shim_app_forms.js +6 -0
- package/dist/sveltekit_shim_app_navigation.d.ts +10 -0
- package/dist/sveltekit_shim_app_navigation.d.ts.map +1 -0
- package/dist/sveltekit_shim_app_navigation.js +11 -0
- package/dist/sveltekit_shim_app_paths.d.ts +17 -0
- package/dist/sveltekit_shim_app_paths.d.ts.map +1 -0
- package/dist/sveltekit_shim_app_paths.js +10 -0
- package/dist/sveltekit_shim_app_state.d.ts +5 -0
- package/dist/sveltekit_shim_app_state.d.ts.map +1 -0
- package/dist/sveltekit_shim_app_state.js +26 -0
- package/dist/sveltekit_shim_env.d.ts +5 -0
- package/dist/sveltekit_shim_env.d.ts.map +1 -0
- package/dist/sveltekit_shim_env.js +23 -0
- package/dist/sync.task.d.ts +16 -0
- package/dist/sync.task.d.ts.map +1 -0
- package/dist/sync.task.js +39 -0
- package/dist/task.d.ts +98 -0
- package/dist/task.d.ts.map +1 -0
- package/dist/task.js +109 -0
- package/dist/task_logging.d.ts +6 -0
- package/dist/task_logging.d.ts.map +1 -0
- package/dist/task_logging.js +201 -0
- package/dist/test.task.d.ts +13 -0
- package/dist/test.task.d.ts.map +1 -0
- package/dist/test.task.js +53 -0
- package/dist/typecheck.task.d.ts +13 -0
- package/dist/typecheck.task.d.ts.map +1 -0
- package/dist/typecheck.task.js +68 -0
- package/dist/upgrade.task.d.ts +20 -0
- package/dist/upgrade.task.d.ts.map +1 -0
- package/dist/upgrade.task.js +111 -0
- package/dist/watch_dir.d.ts +36 -0
- package/dist/watch_dir.d.ts.map +1 -0
- package/dist/watch_dir.js +69 -0
- package/package.json +149 -0
- package/src/lib/args.ts +115 -0
- package/src/lib/build.task.ts +151 -0
- package/src/lib/build_cache.ts +378 -0
- package/src/lib/changelog.ts +69 -0
- package/src/lib/changeset.task.ts +228 -0
- package/src/lib/changeset_helpers.ts +14 -0
- package/src/lib/check.task.ts +132 -0
- package/src/lib/child_process_logging.ts +38 -0
- package/src/lib/clean.task.ts +48 -0
- package/src/lib/clean_fs.ts +54 -0
- package/src/lib/cli.ts +98 -0
- package/src/lib/commit.task.ts +34 -0
- package/src/lib/constants.ts +56 -0
- package/src/lib/deploy.task.ts +287 -0
- package/src/lib/dev.task.ts +52 -0
- package/src/lib/disknode.ts +26 -0
- package/src/lib/env.ts +78 -0
- package/src/lib/esbuild_helpers.ts +49 -0
- package/src/lib/esbuild_plugin_external_worker.ts +94 -0
- package/src/lib/esbuild_plugin_svelte.ts +134 -0
- package/src/lib/esbuild_plugin_sveltekit_local_imports.ts +38 -0
- package/src/lib/esbuild_plugin_sveltekit_shim_alias.ts +27 -0
- package/src/lib/esbuild_plugin_sveltekit_shim_app.ts +42 -0
- package/src/lib/esbuild_plugin_sveltekit_shim_env.ts +47 -0
- package/src/lib/filer.ts +458 -0
- package/src/lib/format.task.ts +44 -0
- package/src/lib/format_directory.ts +65 -0
- package/src/lib/format_file.ts +49 -0
- package/src/lib/gen.task.ts +206 -0
- package/src/lib/gen.ts +406 -0
- package/src/lib/gen_helpers.ts +131 -0
- package/src/lib/github.ts +46 -0
- package/src/lib/gro.config.default.ts +42 -0
- package/src/lib/gro.ts +29 -0
- package/src/lib/gro_config.ts +254 -0
- package/src/lib/gro_helpers.ts +108 -0
- package/src/lib/gro_plugin_gen.ts +149 -0
- package/src/lib/gro_plugin_server.ts +288 -0
- package/src/lib/gro_plugin_sveltekit_app.ts +58 -0
- package/src/lib/gro_plugin_sveltekit_library.ts +63 -0
- package/src/lib/index.ts +8 -0
- package/src/lib/input_path.ts +254 -0
- package/src/lib/invoke.ts +34 -0
- package/src/lib/invoke_task.ts +139 -0
- package/src/lib/lint.task.ts +39 -0
- package/src/lib/loader.ts +229 -0
- package/src/lib/module.ts +13 -0
- package/src/lib/modules.ts +117 -0
- package/src/lib/package_json.ts +255 -0
- package/src/lib/parse_exports.ts +100 -0
- package/src/lib/parse_exports_context.ts +395 -0
- package/src/lib/parse_imports.ts +180 -0
- package/src/lib/paths.ts +111 -0
- package/src/lib/plugin.ts +106 -0
- package/src/lib/publish.task.ts +228 -0
- package/src/lib/register.ts +3 -0
- package/src/lib/reinstall.task.ts +45 -0
- package/src/lib/release.task.ts +26 -0
- package/src/lib/resolve.task.ts +43 -0
- package/src/lib/resolve_specifier.ts +81 -0
- package/src/lib/run.task.ts +65 -0
- package/src/lib/run_gen.ts +110 -0
- package/src/lib/run_task.ts +82 -0
- package/src/lib/source_json.ts +183 -0
- package/src/lib/svelte_config.ts +140 -0
- package/src/lib/sveltekit_helpers.ts +193 -0
- package/src/lib/sveltekit_shim_app.ts +41 -0
- package/src/lib/sveltekit_shim_app_environment.ts +16 -0
- package/src/lib/sveltekit_shim_app_forms.ts +13 -0
- package/src/lib/sveltekit_shim_app_navigation.ts +23 -0
- package/src/lib/sveltekit_shim_app_paths.ts +26 -0
- package/src/lib/sveltekit_shim_app_state.ts +35 -0
- package/src/lib/sveltekit_shim_env.ts +45 -0
- package/src/lib/sync.task.ts +47 -0
- package/src/lib/task.ts +245 -0
- package/src/lib/task_logging.ts +255 -0
- package/src/lib/test.task.ts +63 -0
- package/src/lib/typecheck.task.ts +81 -0
- package/src/lib/upgrade.task.ts +148 -0
- package/src/lib/watch_dir.ts +115 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import {mkdir, readdir, readFile, rm, stat, writeFile} from 'node:fs/promises';
|
|
2
|
+
import {join} from 'node:path';
|
|
3
|
+
import {styleText as st} from 'node:util';
|
|
4
|
+
import {z} from 'zod';
|
|
5
|
+
import type {Logger} from '@fuzdev/fuz_util/log.js';
|
|
6
|
+
import {git_current_commit_hash} from '@fuzdev/fuz_util/git.js';
|
|
7
|
+
import {fs_exists} from '@fuzdev/fuz_util/fs.js';
|
|
8
|
+
import {map_concurrent} from '@fuzdev/fuz_util/async.js';
|
|
9
|
+
import {hash_secure} from '@fuzdev/fuz_util/hash.js';
|
|
10
|
+
|
|
11
|
+
import type {GroConfig} from './gro_config.ts';
|
|
12
|
+
import {paths} from './paths.ts';
|
|
13
|
+
import {SVELTEKIT_BUILD_DIRNAME, SVELTEKIT_DIST_DIRNAME, GRO_DIST_PREFIX} from './constants.ts';
|
|
14
|
+
|
|
15
|
+
export const BUILD_CACHE_METADATA_FILENAME = 'build.json';
|
|
16
|
+
export const BUILD_CACHE_VERSION = '1';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Metadata about a single build output file.
|
|
20
|
+
* Includes cryptographic hash for validation plus filesystem stats for debugging and optimization.
|
|
21
|
+
*/
|
|
22
|
+
export const BuildOutputEntry = z.strictObject({
|
|
23
|
+
path: z
|
|
24
|
+
.string()
|
|
25
|
+
.meta({description: "relative path from project root (e.g., 'build/index.html')."}),
|
|
26
|
+
hash: z.string().meta({description: 'SHA-256 hash of file contents'}),
|
|
27
|
+
size: z.number().meta({description: 'file size in bytes'}),
|
|
28
|
+
mtime: z.number().meta({description: 'modification time in milliseconds since epoch'}),
|
|
29
|
+
ctime: z.number().meta({
|
|
30
|
+
description: 'POSIX change time in milliseconds since epoch',
|
|
31
|
+
}),
|
|
32
|
+
mode: z.number().meta({description: 'unix file permission mode (e.g., 33188 = 0644)'}),
|
|
33
|
+
});
|
|
34
|
+
export type BuildOutputEntry = z.infer<typeof BuildOutputEntry>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Metadata stored in .gro/ directory to track build cache validity.
|
|
38
|
+
* Schema validates structure at load time to catch corrupted cache files.
|
|
39
|
+
*/
|
|
40
|
+
export const BuildCacheMetadata = z.strictObject({
|
|
41
|
+
version: z.string().meta({description: 'schema version for future compatibility'}),
|
|
42
|
+
git_commit: z.string().nullable().meta({description: 'git commit hash at time of build'}),
|
|
43
|
+
build_cache_config_hash: z
|
|
44
|
+
.string()
|
|
45
|
+
.meta({description: "hash of user's custom build_cache_config from gro.config.ts."}),
|
|
46
|
+
timestamp: z.string().meta({description: 'timestamp when build completed'}),
|
|
47
|
+
outputs: z
|
|
48
|
+
.array(BuildOutputEntry)
|
|
49
|
+
.meta({description: 'build output files with hashes and filesystem stats'}),
|
|
50
|
+
});
|
|
51
|
+
export type BuildCacheMetadata = z.infer<typeof BuildCacheMetadata>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Computes the cache key components for a build.
|
|
55
|
+
* This determines whether a cached build can be reused.
|
|
56
|
+
*
|
|
57
|
+
* @param config Gro config (build_cache_config_hash is already computed during config load)
|
|
58
|
+
* @param log Logger
|
|
59
|
+
* @param git_commit Optional pre-computed git commit hash (optimization to avoid re-reading)
|
|
60
|
+
*/
|
|
61
|
+
export const compute_build_cache_key = async (
|
|
62
|
+
config: GroConfig,
|
|
63
|
+
log: Logger,
|
|
64
|
+
git_commit?: string | null,
|
|
65
|
+
): Promise<{
|
|
66
|
+
git_commit: string | null;
|
|
67
|
+
build_cache_config_hash: string;
|
|
68
|
+
}> => {
|
|
69
|
+
// 1. Git commit hash - primary cache key
|
|
70
|
+
const commit = git_commit !== undefined ? git_commit : await git_current_commit_hash();
|
|
71
|
+
if (!commit) {
|
|
72
|
+
log.warn('Not in a git repository - build cache will use null git commit');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 2. Build cache config hash - already computed during config normalization
|
|
76
|
+
return {
|
|
77
|
+
git_commit: commit,
|
|
78
|
+
build_cache_config_hash: config.build_cache_config_hash,
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Loads build cache metadata from .gro/ directory.
|
|
84
|
+
* Invalid or corrupted cache files are automatically deleted.
|
|
85
|
+
*/
|
|
86
|
+
export const load_build_cache_metadata = async (): Promise<BuildCacheMetadata | null> => {
|
|
87
|
+
const metadata_path = join(paths.build, BUILD_CACHE_METADATA_FILENAME);
|
|
88
|
+
|
|
89
|
+
if (!(await fs_exists(metadata_path))) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const contents = await readFile(metadata_path, 'utf-8');
|
|
95
|
+
const parsed = JSON.parse(contents);
|
|
96
|
+
|
|
97
|
+
// Validate structure with Zod
|
|
98
|
+
const metadata = BuildCacheMetadata.parse(parsed);
|
|
99
|
+
|
|
100
|
+
// Validate version
|
|
101
|
+
if (metadata.version !== BUILD_CACHE_VERSION) {
|
|
102
|
+
// Clean up stale cache with old schema version
|
|
103
|
+
try {
|
|
104
|
+
await rm(metadata_path, {force: true});
|
|
105
|
+
} catch {
|
|
106
|
+
// Ignore cleanup errors
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return metadata;
|
|
112
|
+
} catch {
|
|
113
|
+
// Clean up corrupted/invalid cache file
|
|
114
|
+
// (catches JSON.parse, Zod validation, and version errors)
|
|
115
|
+
try {
|
|
116
|
+
await rm(metadata_path, {force: true});
|
|
117
|
+
} catch {
|
|
118
|
+
// Ignore cleanup errors
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Saves build cache metadata to .gro/ directory.
|
|
126
|
+
* Errors are logged but don't fail the build (cache is optional).
|
|
127
|
+
*/
|
|
128
|
+
export const save_build_cache_metadata = async (
|
|
129
|
+
metadata: BuildCacheMetadata,
|
|
130
|
+
log?: Logger,
|
|
131
|
+
): Promise<void> => {
|
|
132
|
+
try {
|
|
133
|
+
// Ensure .gro directory exists
|
|
134
|
+
await mkdir(paths.build, {recursive: true});
|
|
135
|
+
|
|
136
|
+
const metadata_path = join(paths.build, BUILD_CACHE_METADATA_FILENAME);
|
|
137
|
+
await writeFile(metadata_path, JSON.stringify(metadata, null, '\t'), 'utf-8');
|
|
138
|
+
} catch (error) {
|
|
139
|
+
// Cache writes are optional - log warning but don't fail the build
|
|
140
|
+
log?.warn(
|
|
141
|
+
st('yellow', 'Failed to save build cache'),
|
|
142
|
+
st('dim', `(${error instanceof Error ? error.message : String(error)})`),
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Validates that a cached build is still valid by checking stats and hashing outputs.
|
|
149
|
+
* Uses size as a fast negative check before expensive hashing.
|
|
150
|
+
* This is comprehensive validation to catch manual tampering or corruption.
|
|
151
|
+
*/
|
|
152
|
+
export const validate_build_cache = async (metadata: BuildCacheMetadata): Promise<boolean> => {
|
|
153
|
+
// Verify all tracked output files exist and have matching size
|
|
154
|
+
// Sequential checks with early return for performance
|
|
155
|
+
for (const output of metadata.outputs) {
|
|
156
|
+
// eslint-disable-next-line no-await-in-loop
|
|
157
|
+
if (!(await fs_exists(output.path))) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Fast negative check: size mismatch = definitely invalid
|
|
162
|
+
// This avoids expensive file reads and hashing for files that have clearly changed
|
|
163
|
+
// eslint-disable-next-line no-await-in-loop
|
|
164
|
+
const stats = await stat(output.path);
|
|
165
|
+
if (stats.size !== output.size) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Size matches for all files - now verify content with cryptographic hashing
|
|
171
|
+
// Hash files with controlled concurrency (could be 10k+ files)
|
|
172
|
+
const results = await map_concurrent(
|
|
173
|
+
metadata.outputs,
|
|
174
|
+
async (output) => {
|
|
175
|
+
try {
|
|
176
|
+
const contents = await readFile(output.path);
|
|
177
|
+
const actual_hash = await hash_secure(contents);
|
|
178
|
+
return actual_hash === output.hash;
|
|
179
|
+
} catch {
|
|
180
|
+
// File deleted/inaccessible between checks = cache invalid
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
20,
|
|
185
|
+
);
|
|
186
|
+
return results.every((valid) => valid);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Main function to check if the build cache is valid.
|
|
191
|
+
* Returns true if the cached build can be used, false if a fresh build is needed.
|
|
192
|
+
*
|
|
193
|
+
* @param config Gro config
|
|
194
|
+
* @param log Logger
|
|
195
|
+
* @param git_commit Optional pre-computed git commit hash (optimization)
|
|
196
|
+
*/
|
|
197
|
+
export const is_build_cache_valid = async (
|
|
198
|
+
config: GroConfig,
|
|
199
|
+
log: Logger,
|
|
200
|
+
git_commit?: string | null,
|
|
201
|
+
): Promise<boolean> => {
|
|
202
|
+
// Load existing metadata
|
|
203
|
+
const metadata = await load_build_cache_metadata();
|
|
204
|
+
if (!metadata) {
|
|
205
|
+
log.debug('No build cache metadata found');
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Compute current cache key
|
|
210
|
+
const current = await compute_build_cache_key(config, log, git_commit);
|
|
211
|
+
|
|
212
|
+
// Check if cache keys have changed
|
|
213
|
+
if (metadata.git_commit !== current.git_commit) {
|
|
214
|
+
log.debug('Build cache invalid: git commit changed');
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (metadata.build_cache_config_hash !== current.build_cache_config_hash) {
|
|
219
|
+
log.debug('Build cache invalid: build_cache_config changed');
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Comprehensive validation: verify output files
|
|
224
|
+
const outputs_valid = await validate_build_cache(metadata);
|
|
225
|
+
if (!outputs_valid) {
|
|
226
|
+
log.debug('Build cache invalid: output files missing or corrupted');
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
log.info(st('green', 'Build cache valid'), st('dim', `(from ${metadata.timestamp})`));
|
|
231
|
+
return true;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Collects information about all files in build output directories.
|
|
236
|
+
* Returns an array of entries with path, hash, size, mtime, ctime, and mode.
|
|
237
|
+
*
|
|
238
|
+
* Files are hashed in parallel for performance. For very large builds (10k+ files),
|
|
239
|
+
* this may take several seconds but ensures complete cache validation.
|
|
240
|
+
*
|
|
241
|
+
* @param build_dirs Array of output directories to scan (e.g., ['build', 'dist', 'dist_server'])
|
|
242
|
+
*/
|
|
243
|
+
export const collect_build_outputs = async (
|
|
244
|
+
build_dirs: Array<string>,
|
|
245
|
+
): Promise<Array<BuildOutputEntry>> => {
|
|
246
|
+
// Collect all files to hash first
|
|
247
|
+
interface FileEntry {
|
|
248
|
+
full_path: string;
|
|
249
|
+
cache_key: string;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const files_hash_secure: Array<FileEntry> = [];
|
|
253
|
+
|
|
254
|
+
// Recursively collect files
|
|
255
|
+
const collect_files = async (
|
|
256
|
+
dir: string,
|
|
257
|
+
relative_base: string,
|
|
258
|
+
dir_prefix: string,
|
|
259
|
+
): Promise<void> => {
|
|
260
|
+
const entries = await readdir(dir, {withFileTypes: true});
|
|
261
|
+
|
|
262
|
+
for (const entry of entries) {
|
|
263
|
+
// Skip metadata file itself
|
|
264
|
+
if (entry.name === BUILD_CACHE_METADATA_FILENAME) {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const full_path = join(dir, entry.name);
|
|
269
|
+
const relative_path = relative_base ? join(relative_base, entry.name) : entry.name;
|
|
270
|
+
const cache_key = join(dir_prefix, relative_path);
|
|
271
|
+
|
|
272
|
+
if (entry.isDirectory()) {
|
|
273
|
+
// eslint-disable-next-line no-await-in-loop
|
|
274
|
+
await collect_files(full_path, relative_path, dir_prefix);
|
|
275
|
+
} else if (entry.isFile()) {
|
|
276
|
+
files_hash_secure.push({full_path, cache_key});
|
|
277
|
+
}
|
|
278
|
+
// Symlinks are intentionally ignored - we only hash regular files
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// Collect files from all build directories sequentially
|
|
283
|
+
for (const build_dir of build_dirs) {
|
|
284
|
+
// eslint-disable-next-line no-await-in-loop
|
|
285
|
+
if (!(await fs_exists(build_dir))) {
|
|
286
|
+
continue; // Skip non-existent directories
|
|
287
|
+
}
|
|
288
|
+
// eslint-disable-next-line no-await-in-loop
|
|
289
|
+
await collect_files(build_dir, '', build_dir);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Hash files with controlled concurrency and collect stats (could be 10k+ files)
|
|
293
|
+
return map_concurrent(
|
|
294
|
+
files_hash_secure,
|
|
295
|
+
async ({full_path, cache_key}): Promise<BuildOutputEntry> => {
|
|
296
|
+
const stats = await stat(full_path);
|
|
297
|
+
const contents = await readFile(full_path);
|
|
298
|
+
const hash = await hash_secure(contents);
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
path: cache_key,
|
|
302
|
+
hash,
|
|
303
|
+
size: stats.size,
|
|
304
|
+
mtime: stats.mtimeMs,
|
|
305
|
+
ctime: stats.ctimeMs,
|
|
306
|
+
mode: stats.mode,
|
|
307
|
+
};
|
|
308
|
+
},
|
|
309
|
+
20,
|
|
310
|
+
);
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Discovers all build output directories in the current working directory.
|
|
315
|
+
* Returns an array of directory names that exist: build/, dist/, dist_*
|
|
316
|
+
*/
|
|
317
|
+
export const discover_build_output_dirs = async (): Promise<Array<string>> => {
|
|
318
|
+
const build_dirs: Array<string> = [];
|
|
319
|
+
|
|
320
|
+
// Check for SvelteKit app output (build/) and library output (dist/) in parallel
|
|
321
|
+
const [build_exists, dist_exists] = await Promise.all([
|
|
322
|
+
fs_exists(SVELTEKIT_BUILD_DIRNAME),
|
|
323
|
+
fs_exists(SVELTEKIT_DIST_DIRNAME),
|
|
324
|
+
]);
|
|
325
|
+
|
|
326
|
+
if (build_exists) {
|
|
327
|
+
build_dirs.push(SVELTEKIT_BUILD_DIRNAME);
|
|
328
|
+
}
|
|
329
|
+
if (dist_exists) {
|
|
330
|
+
build_dirs.push(SVELTEKIT_DIST_DIRNAME);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Check for server and other plugin outputs (dist_*)
|
|
334
|
+
const root_entries = await readdir('.');
|
|
335
|
+
const dist_dir_checks = await Promise.all(
|
|
336
|
+
root_entries
|
|
337
|
+
.filter((p) => p.startsWith(GRO_DIST_PREFIX))
|
|
338
|
+
.map(async (p) => {
|
|
339
|
+
try {
|
|
340
|
+
const s = await stat(p);
|
|
341
|
+
return s.isDirectory() ? p : null;
|
|
342
|
+
} catch {
|
|
343
|
+
// File was deleted/moved during iteration - skip it
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
}),
|
|
347
|
+
);
|
|
348
|
+
build_dirs.push(...dist_dir_checks.filter((p): p is string => p !== null));
|
|
349
|
+
|
|
350
|
+
return build_dirs;
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Creates build cache metadata after a successful build.
|
|
355
|
+
* Automatically discovers all build output directories (build/, dist/, dist_*).
|
|
356
|
+
*
|
|
357
|
+
* @param config Gro config
|
|
358
|
+
* @param log Logger
|
|
359
|
+
* @param git_commit Optional pre-computed git commit hash (optimization)
|
|
360
|
+
* @param build_dirs Optional pre-discovered build directories (optimization to avoid redundant filesystem scans)
|
|
361
|
+
*/
|
|
362
|
+
export const create_build_cache_metadata = async (
|
|
363
|
+
config: GroConfig,
|
|
364
|
+
log: Logger,
|
|
365
|
+
git_commit?: string | null,
|
|
366
|
+
build_dirs?: Array<string>,
|
|
367
|
+
): Promise<BuildCacheMetadata> => {
|
|
368
|
+
const cache_key = await compute_build_cache_key(config, log, git_commit);
|
|
369
|
+
const dirs = build_dirs ?? (await discover_build_output_dirs());
|
|
370
|
+
const outputs = await collect_build_outputs(dirs);
|
|
371
|
+
|
|
372
|
+
return {
|
|
373
|
+
version: BUILD_CACHE_VERSION,
|
|
374
|
+
...cache_key,
|
|
375
|
+
timestamp: new Date().toISOString(),
|
|
376
|
+
outputs,
|
|
377
|
+
};
|
|
378
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {readFile, writeFile} from 'node:fs/promises';
|
|
2
|
+
import {z} from 'zod';
|
|
3
|
+
import type {Logger} from '@fuzdev/fuz_util/log.js';
|
|
4
|
+
import type {FetchValueCache} from '@fuzdev/fuz_util/fetch.js';
|
|
5
|
+
|
|
6
|
+
import {github_fetch_commit_prs} from './github.ts';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Updates a changelog produced by `@changesets/changelog-git` with better links and formatting.
|
|
10
|
+
* It's similar to `@changesets/changelog-github` but doesn't require a token for light usage.
|
|
11
|
+
* This may be better implemented as a standalone dependency
|
|
12
|
+
* as an alternative to `@changesets/changelog-git`.
|
|
13
|
+
* @returns boolean indicating if the changelog changed
|
|
14
|
+
*/
|
|
15
|
+
export const update_changelog = async (
|
|
16
|
+
owner: string,
|
|
17
|
+
repo: string,
|
|
18
|
+
path = 'CHANGELOG.md',
|
|
19
|
+
token?: string,
|
|
20
|
+
log?: Logger,
|
|
21
|
+
cache: FetchValueCache = new Map(), // include a default cache to efficiently handle multiple changesets per commit
|
|
22
|
+
): Promise<boolean> => {
|
|
23
|
+
const contents = await readFile(path, 'utf8');
|
|
24
|
+
const parsed = parse_changelog(contents);
|
|
25
|
+
const mapped = await map_changelog(parsed, owner, repo, token, log, cache);
|
|
26
|
+
const updated = serialize_changelog(mapped);
|
|
27
|
+
if (contents === updated) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
await writeFile(path, updated, 'utf8');
|
|
31
|
+
return true;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// keeping this really simple for now, no need to parse further for our current usecases
|
|
35
|
+
const ParsedChangelog = z.array(z.string());
|
|
36
|
+
type ParsedChangelog = z.infer<typeof ParsedChangelog>;
|
|
37
|
+
const parse_changelog = (contents: string): ParsedChangelog => contents.split('\n');
|
|
38
|
+
const serialize_changelog = (parsed: ParsedChangelog): string => parsed.join('\n');
|
|
39
|
+
|
|
40
|
+
const LINE_WITH_SHA_MATCHER = /^- ([a-z0-9]{7,8}): /;
|
|
41
|
+
|
|
42
|
+
const map_changelog = async (
|
|
43
|
+
parsed: ParsedChangelog,
|
|
44
|
+
owner: string,
|
|
45
|
+
repo: string,
|
|
46
|
+
token?: string,
|
|
47
|
+
log?: Logger,
|
|
48
|
+
cache?: FetchValueCache,
|
|
49
|
+
): Promise<ParsedChangelog> => {
|
|
50
|
+
const mapped: ParsedChangelog = [];
|
|
51
|
+
for (const line of parsed) {
|
|
52
|
+
const matches = LINE_WITH_SHA_MATCHER.exec(line);
|
|
53
|
+
if (matches) {
|
|
54
|
+
const commit_sha = matches[1]!;
|
|
55
|
+
const l = '- ' + line.substring(commit_sha.length + 4);
|
|
56
|
+
const prs = await github_fetch_commit_prs(owner, repo, commit_sha, token, log, cache); // eslint-disable-line no-await-in-loop
|
|
57
|
+
if (prs?.length) {
|
|
58
|
+
mapped.push(`${l} (${prs.map((p) => `[#${p.number}](${p.html_url})`).join(', ')})`);
|
|
59
|
+
} else {
|
|
60
|
+
mapped.push(
|
|
61
|
+
`${l} ([${commit_sha}](https://github.com/${owner}/${repo}/commit/${commit_sha}))`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
mapped.push(line);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return mapped;
|
|
69
|
+
};
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import {z} from 'zod';
|
|
2
|
+
import {spawn} from '@fuzdev/fuz_util/process.js';
|
|
3
|
+
import {styleText as st} from 'node:util';
|
|
4
|
+
import type {WrittenConfig} from '@changesets/types';
|
|
5
|
+
import {readdir, readFile, writeFile} from 'node:fs/promises';
|
|
6
|
+
import {join} from 'node:path';
|
|
7
|
+
import {fs_exists} from '@fuzdev/fuz_util/fs.js';
|
|
8
|
+
import {
|
|
9
|
+
GitOrigin,
|
|
10
|
+
git_check_fully_staged_workspace,
|
|
11
|
+
git_push_to_create,
|
|
12
|
+
} from '@fuzdev/fuz_util/git.js';
|
|
13
|
+
|
|
14
|
+
import {TaskError, type Task} from './task.ts';
|
|
15
|
+
import {find_cli, spawn_cli} from './cli.ts';
|
|
16
|
+
import {has_sveltekit_library} from './sveltekit_helpers.ts';
|
|
17
|
+
import {
|
|
18
|
+
CHANGESET_CLI,
|
|
19
|
+
CHANGESET_DIR,
|
|
20
|
+
ChangesetAccess,
|
|
21
|
+
ChangesetBump,
|
|
22
|
+
CHANGESET_PUBLIC_ACCESS,
|
|
23
|
+
CHANGESET_RESTRICTED_ACCESS,
|
|
24
|
+
} from './changeset_helpers.ts';
|
|
25
|
+
import {package_json_load} from './package_json.ts';
|
|
26
|
+
|
|
27
|
+
/** @nodocs */
|
|
28
|
+
export const Args = z.strictObject({
|
|
29
|
+
/**
|
|
30
|
+
* The optional rest args get joined with a space to form the `message`.
|
|
31
|
+
*/
|
|
32
|
+
_: z
|
|
33
|
+
.array(z.string())
|
|
34
|
+
.meta({description: 'the message for the changeset and commit'})
|
|
35
|
+
.max(1)
|
|
36
|
+
.default([]),
|
|
37
|
+
minor: z.boolean().meta({description: 'bump the minor version'}).default(false),
|
|
38
|
+
major: z.boolean().meta({description: 'bump the major version'}).default(false),
|
|
39
|
+
dir: z.string().meta({description: 'changeset dir'}).default(CHANGESET_DIR),
|
|
40
|
+
access: ChangesetAccess.describe(
|
|
41
|
+
"changeset 'access' config value, the default depends on package.json#private",
|
|
42
|
+
).optional(),
|
|
43
|
+
changelog: z
|
|
44
|
+
.string()
|
|
45
|
+
.meta({description: 'changelog dep package name, used as changeset\'s "changelog" config'})
|
|
46
|
+
.default('@changesets/changelog-git'),
|
|
47
|
+
dep: z.boolean().meta({description: 'dual of no-dep'}).default(true),
|
|
48
|
+
'no-dep': z
|
|
49
|
+
.boolean()
|
|
50
|
+
.meta({description: 'opt out of installing the changelog package'})
|
|
51
|
+
.default(false),
|
|
52
|
+
origin: GitOrigin.describe('git origin to deploy to').default('origin'),
|
|
53
|
+
changeset_cli: z.string().meta({description: 'the changeset CLI to use'}).default(CHANGESET_CLI),
|
|
54
|
+
});
|
|
55
|
+
export type Args = z.infer<typeof Args>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Calls the `changeset` CLI with some simple automations.
|
|
59
|
+
* This API is designed for convenient manual usage, not clarity or normality.
|
|
60
|
+
*
|
|
61
|
+
* Usage:
|
|
62
|
+
* - gro changeset some commit message
|
|
63
|
+
* - gro changeset some commit message --minor
|
|
64
|
+
* - gro changeset "some commit message" --minor
|
|
65
|
+
*
|
|
66
|
+
* @nodocs
|
|
67
|
+
*/
|
|
68
|
+
export const task: Task<Args> = {
|
|
69
|
+
summary: 'call changeset with gro patterns',
|
|
70
|
+
Args,
|
|
71
|
+
run: async (ctx): Promise<void> => {
|
|
72
|
+
const {
|
|
73
|
+
invoke_task,
|
|
74
|
+
args: {
|
|
75
|
+
_: [message],
|
|
76
|
+
minor,
|
|
77
|
+
major,
|
|
78
|
+
dir,
|
|
79
|
+
access: access_arg,
|
|
80
|
+
changelog,
|
|
81
|
+
dep,
|
|
82
|
+
origin,
|
|
83
|
+
changeset_cli,
|
|
84
|
+
},
|
|
85
|
+
log,
|
|
86
|
+
svelte_config,
|
|
87
|
+
config,
|
|
88
|
+
} = ctx;
|
|
89
|
+
|
|
90
|
+
if (!message && (minor || major)) throw new TaskError('cannot bump version without a message');
|
|
91
|
+
if (minor && major) throw new TaskError('cannot bump both minor and major');
|
|
92
|
+
|
|
93
|
+
const bump: ChangesetBump = minor ? 'minor' : major ? 'major' : 'patch';
|
|
94
|
+
|
|
95
|
+
const found_changeset_cli = await find_cli(changeset_cli);
|
|
96
|
+
if (!found_changeset_cli) {
|
|
97
|
+
throw new TaskError(
|
|
98
|
+
'changeset command not found: install @changesets/cli locally or globally',
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const package_json = await package_json_load();
|
|
103
|
+
|
|
104
|
+
const has_sveltekit_library_result = await has_sveltekit_library(package_json, svelte_config);
|
|
105
|
+
if (!has_sveltekit_library_result.ok) {
|
|
106
|
+
throw new TaskError(
|
|
107
|
+
'Failed to find SvelteKit library: ' + has_sveltekit_library_result.message,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const path = join(dir, 'config.json');
|
|
112
|
+
|
|
113
|
+
const inited = await fs_exists(path);
|
|
114
|
+
|
|
115
|
+
if (!inited) {
|
|
116
|
+
await spawn_cli(found_changeset_cli, ['init'], log);
|
|
117
|
+
|
|
118
|
+
const access =
|
|
119
|
+
(access_arg ?? package_json.private)
|
|
120
|
+
? CHANGESET_RESTRICTED_ACCESS
|
|
121
|
+
: CHANGESET_PUBLIC_ACCESS;
|
|
122
|
+
|
|
123
|
+
const access_color = access === CHANGESET_RESTRICTED_ACCESS ? 'blue' : 'red';
|
|
124
|
+
log.info('initing changeset with ' + st(access_color, access) + ' access');
|
|
125
|
+
if (access !== CHANGESET_RESTRICTED_ACCESS) {
|
|
126
|
+
await update_changeset_config(path, (config) => {
|
|
127
|
+
const updated = {...config};
|
|
128
|
+
updated.access = access;
|
|
129
|
+
updated.changelog = changelog;
|
|
130
|
+
return updated;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
await spawn('git', ['add', dir]);
|
|
135
|
+
|
|
136
|
+
if (dep) {
|
|
137
|
+
await spawn(config.pm_cli, ['install', '-D', changelog]);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// TODO small problem here where generated files don't get committed
|
|
142
|
+
await invoke_task('sync', {install: inited || !dep}); // after installing above, and in all cases
|
|
143
|
+
|
|
144
|
+
if (message) {
|
|
145
|
+
// TODO see the helper below, simplify this to CLI flags when support is added to Changesets
|
|
146
|
+
const changeset_adder = await create_changeset_adder(package_json.name, dir, message, bump);
|
|
147
|
+
await spawn_cli(found_changeset_cli, ['add', '--empty'], log);
|
|
148
|
+
await changeset_adder();
|
|
149
|
+
if (!(await git_check_fully_staged_workspace())) {
|
|
150
|
+
await spawn('git', ['commit', '-m', message]);
|
|
151
|
+
await git_push_to_create(origin);
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
await spawn_cli(found_changeset_cli, [], log);
|
|
155
|
+
await spawn('git', ['add', dir]);
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* TODO ideally this wouldn't exist and we'd use CLI flags, but they doesn't exist yet
|
|
162
|
+
* @see https://github.com/changesets/changesets/pull/1121
|
|
163
|
+
*/
|
|
164
|
+
const create_changeset_adder = async (
|
|
165
|
+
repo_name: string,
|
|
166
|
+
dir: string,
|
|
167
|
+
message: string,
|
|
168
|
+
bump: ChangesetBump,
|
|
169
|
+
): Promise<() => Promise<void>> => {
|
|
170
|
+
const filenames_before = await readdir(dir);
|
|
171
|
+
return async () => {
|
|
172
|
+
const filenames_after = await readdir(dir);
|
|
173
|
+
const filenames_added = filenames_after.filter((p) => !filenames_before.includes(p));
|
|
174
|
+
if (!filenames_added.length) {
|
|
175
|
+
throw Error('expected to find a new changeset file');
|
|
176
|
+
}
|
|
177
|
+
if (filenames_added.length !== 1) {
|
|
178
|
+
throw Error('expected to find exactly one new changeset file');
|
|
179
|
+
}
|
|
180
|
+
const path = join(dir, filenames_added[0]!);
|
|
181
|
+
const contents = create_new_changeset(repo_name, message, bump);
|
|
182
|
+
await writeFile(path, contents, 'utf8');
|
|
183
|
+
await spawn('git', ['add', path]);
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const create_new_changeset = (
|
|
188
|
+
repo_name: string,
|
|
189
|
+
message: string,
|
|
190
|
+
bump: ChangesetBump,
|
|
191
|
+
): string => `---
|
|
192
|
+
"${repo_name}": ${bump}
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
${message}
|
|
196
|
+
`;
|
|
197
|
+
|
|
198
|
+
type ChangesetCallback = (config: WrittenConfig) => WrittenConfig | Promise<WrittenConfig>;
|
|
199
|
+
|
|
200
|
+
type UpdateWrittenConfig = (path: string, cb: ChangesetCallback) => Promise<boolean>;
|
|
201
|
+
|
|
202
|
+
// TODO refactor all of this with zod and package_json helpers - util file helper? JSON parse pluggable
|
|
203
|
+
|
|
204
|
+
const update_changeset_config: UpdateWrittenConfig = async (path, cb) => {
|
|
205
|
+
const config_contents = await load_changeset_config_contents(path);
|
|
206
|
+
const config = parse_changeset_config(config_contents);
|
|
207
|
+
|
|
208
|
+
const updated = await cb(config);
|
|
209
|
+
|
|
210
|
+
const serialized = serialize_changeset_config(updated);
|
|
211
|
+
|
|
212
|
+
if (serialized === config_contents) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
await write_changeset_config(path, serialized);
|
|
217
|
+
return true;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const load_changeset_config_contents = (path: string): Promise<string> => readFile(path, 'utf8');
|
|
221
|
+
|
|
222
|
+
const write_changeset_config = (path: string, serialized: string): Promise<void> =>
|
|
223
|
+
writeFile(path, serialized);
|
|
224
|
+
|
|
225
|
+
const serialize_changeset_config = (config: WrittenConfig): string =>
|
|
226
|
+
JSON.stringify(config, null, '\t') + '\n';
|
|
227
|
+
|
|
228
|
+
const parse_changeset_config = (contents: string): WrittenConfig => JSON.parse(contents);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {z} from 'zod';
|
|
2
|
+
|
|
3
|
+
export const CHANGESET_RESTRICTED_ACCESS = 'restricted';
|
|
4
|
+
export const CHANGESET_PUBLIC_ACCESS = 'public';
|
|
5
|
+
|
|
6
|
+
export const ChangesetAccess = z.enum([CHANGESET_RESTRICTED_ACCESS, CHANGESET_PUBLIC_ACCESS]);
|
|
7
|
+
export type ChangesetAccess = z.infer<typeof ChangesetAccess>;
|
|
8
|
+
|
|
9
|
+
export const CHANGESET_CLI = 'changeset';
|
|
10
|
+
|
|
11
|
+
export const CHANGESET_DIR = '.changeset';
|
|
12
|
+
|
|
13
|
+
export const ChangesetBump = z.enum(['patch', 'minor', 'major']);
|
|
14
|
+
export type ChangesetBump = z.infer<typeof ChangesetBump>;
|