@ryanatkn/gro 0.170.0 → 0.172.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/dist/build.task.d.ts +6 -1
- package/dist/build.task.d.ts.map +1 -1
- package/dist/build.task.js +86 -5
- package/dist/build_cache.d.ts +100 -0
- package/dist/build_cache.d.ts.map +1 -0
- package/dist/build_cache.js +289 -0
- package/dist/deploy.task.d.ts.map +1 -1
- package/dist/deploy.task.js +13 -10
- package/dist/esbuild_plugin_svelte.js +1 -1
- package/dist/gen.d.ts.map +1 -1
- package/dist/gro_config.d.ts +30 -1
- package/dist/gro_config.d.ts.map +1 -1
- package/dist/gro_config.js +29 -5
- package/dist/hash.d.ts +1 -1
- package/dist/hash.d.ts.map +1 -1
- package/dist/hash.js +1 -2
- package/dist/invoke_task.d.ts.map +1 -1
- package/dist/invoke_task.js +2 -1
- package/dist/package.d.ts.map +1 -1
- package/dist/package.js +24 -15
- package/dist/package_json.js +1 -1
- package/package.json +4 -4
- package/src/lib/build.task.ts +110 -6
- package/src/lib/build_cache.ts +362 -0
- package/src/lib/changelog.ts +1 -1
- package/src/lib/changeset.task.ts +1 -1
- package/src/lib/commit.task.ts +1 -1
- package/src/lib/deploy.task.ts +14 -10
- package/src/lib/esbuild_plugin_svelte.ts +1 -1
- package/src/lib/gen.ts +2 -1
- package/src/lib/gro_config.ts +63 -4
- package/src/lib/hash.ts +2 -4
- package/src/lib/invoke_task.ts +5 -2
- package/src/lib/package.ts +24 -15
- package/src/lib/package_json.ts +2 -2
- package/src/lib/parse_exports_context.ts +2 -2
- package/src/lib/parse_imports.ts +1 -1
- package/src/lib/upgrade.task.ts +1 -1
- package/dist/test_helpers.d.ts +0 -22
- package/dist/test_helpers.d.ts.map +0 -1
- package/dist/test_helpers.js +0 -123
- package/src/lib/test_helpers.ts +0 -161
package/dist/gro_config.d.ts
CHANGED
|
@@ -2,6 +2,11 @@ import type { Path_Filter, Path_Id } from '@ryanatkn/belt/path.js';
|
|
|
2
2
|
import type { Create_Config_Plugins } from './plugin.ts';
|
|
3
3
|
import type { Map_Package_Json } from './package_json.ts';
|
|
4
4
|
import type { Parsed_Svelte_Config } from './svelte_config.ts';
|
|
5
|
+
/**
|
|
6
|
+
* SHA-256 hash of empty string, used for configs without build_cache_config.
|
|
7
|
+
* This ensures consistent cache behavior when no custom config is provided.
|
|
8
|
+
*/
|
|
9
|
+
export declare const EMPTY_BUILD_CACHE_CONFIG_HASH = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
|
5
10
|
/**
|
|
6
11
|
* The config that users can extend via `gro.config.ts`.
|
|
7
12
|
* This is exposed to users in places like tasks and genfiles.
|
|
@@ -38,6 +43,13 @@ export interface Gro_Config extends Raw_Gro_Config {
|
|
|
38
43
|
pm_cli: string;
|
|
39
44
|
/** @default SVELTE_CONFIG_FILENAME */
|
|
40
45
|
svelte_config_filename?: string;
|
|
46
|
+
/**
|
|
47
|
+
* SHA-256 hash of the user's `build_cache_config` from `gro.config.ts`.
|
|
48
|
+
* This is computed during config normalization and the raw value is immediately deleted.
|
|
49
|
+
* If no `build_cache_config` was provided, this is the hash of an empty string.
|
|
50
|
+
* @see Raw_Gro_Config.build_cache_config
|
|
51
|
+
*/
|
|
52
|
+
build_cache_config_hash: string;
|
|
41
53
|
}
|
|
42
54
|
/**
|
|
43
55
|
* The relaxed variant of `Gro_Config` that users can provide via `gro.config.ts`.
|
|
@@ -51,6 +63,22 @@ export interface Raw_Gro_Config {
|
|
|
51
63
|
search_filters?: Path_Filter | Array<Path_Filter> | null;
|
|
52
64
|
js_cli?: string;
|
|
53
65
|
pm_cli?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Optional object defining custom build inputs for cache invalidation.
|
|
68
|
+
* This value is hashed during config normalization and used to detect
|
|
69
|
+
* when builds need to be regenerated due to non-source changes.
|
|
70
|
+
*
|
|
71
|
+
* Use cases:
|
|
72
|
+
* - Environment variables baked into build: `{api_url: process.env.PUBLIC_API_URL}`
|
|
73
|
+
* - External data files: `{data: fs.readFileSync('data.json', 'utf-8')}`
|
|
74
|
+
* - Build feature flags: `{enable_analytics: true}`
|
|
75
|
+
*
|
|
76
|
+
* Can be a static object or an async function that returns an object.
|
|
77
|
+
*
|
|
78
|
+
* IMPORTANT: It's safe to include secrets here because they are hashed and `delete`d
|
|
79
|
+
* during config normalization. The raw value is never logged or persisted.
|
|
80
|
+
*/
|
|
81
|
+
build_cache_config?: Record<string, unknown> | (() => Record<string, unknown> | Promise<Record<string, unknown>>);
|
|
54
82
|
}
|
|
55
83
|
export type Create_Gro_Config = (base_config: Gro_Config, svelte_config?: Parsed_Svelte_Config) => Raw_Gro_Config | Promise<Raw_Gro_Config>;
|
|
56
84
|
export declare const create_empty_gro_config: () => Gro_Config;
|
|
@@ -65,8 +93,9 @@ export declare const EXPORTS_EXCLUDER_DEFAULT: RegExp;
|
|
|
65
93
|
/**
|
|
66
94
|
* Transforms a `Raw_Gro_Config` to the more strict `Gro_Config`.
|
|
67
95
|
* This allows users to provide a more relaxed config.
|
|
96
|
+
* Hashes the `build_cache_config` and deletes the raw value for security.
|
|
68
97
|
*/
|
|
69
|
-
export declare const cook_gro_config: (raw_config: Raw_Gro_Config) => Gro_Config
|
|
98
|
+
export declare const cook_gro_config: (raw_config: Raw_Gro_Config) => Promise<Gro_Config>;
|
|
70
99
|
export interface Gro_Config_Module {
|
|
71
100
|
readonly default: Raw_Gro_Config | Create_Gro_Config;
|
|
72
101
|
}
|
package/dist/gro_config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gro_config.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gro_config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,WAAW,EAAE,OAAO,EAAC,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"gro_config.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gro_config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,WAAW,EAAE,OAAO,EAAC,MAAM,wBAAwB,CAAC;AAcjE,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,oBAAoB,CAAC;AAG7D;;;GAGG;AACH,eAAO,MAAM,6BAA6B,qEACyB,CAAC;AAEpE;;;;GAIG;AACH,MAAM,WAAW,UAAW,SAAQ,cAAc;IACjD;;OAEG;IACH,OAAO,EAAE,qBAAqB,CAAC;IAC/B;;;;OAIG;IACH,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC1C;;;OAGG;IACH,cAAc,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/B;;;OAGG;IACH,cAAc,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;IACnC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;;;;;OAKG;IACH,uBAAuB,EAAE,MAAM,CAAC;CAChC;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC9B,OAAO,CAAC,EAAE,qBAAqB,CAAC;IAChC,gBAAgB,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC3C,cAAc,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B,cAAc,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC;IACzD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;;;;;;;OAcG;IACH,kBAAkB,CAAC,EAChB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACvB,CAAC,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CACtE;AAED,MAAM,MAAM,iBAAiB,GAAG,CAC/B,WAAW,EAAE,UAAU,EACvB,aAAa,CAAC,EAAE,oBAAoB,KAChC,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;AAE9C,eAAO,MAAM,uBAAuB,QAAO,UAazC,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,QAUnC,CAAC;AAEF,eAAO,MAAM,wBAAwB,QAA+C,CAAC;AAErF;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAU,YAAY,cAAc,KAAG,OAAO,CAAC,UAAU,CA+CpF,CAAC;AAEF,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,OAAO,EAAE,cAAc,GAAG,iBAAiB,CAAC;CACrD;AAED,eAAO,MAAM,eAAe,GAAU,YAAgB,KAAG,OAAO,CAAC,UAAU,CAqB1E,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,CACxC,aAAa,EAAE,GAAG,EAClB,WAAW,EAAE,MAAM,KACf,OAAO,CAAC,aAAa,IAAI,iBAS7B,CAAC"}
|
package/dist/gro_config.js
CHANGED
|
@@ -9,9 +9,16 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
9
9
|
import { join, resolve } from 'node:path';
|
|
10
10
|
import { existsSync } from 'node:fs';
|
|
11
11
|
import { identity } from '@ryanatkn/belt/function.js';
|
|
12
|
+
import { json_stringify_deterministic } from '@ryanatkn/belt/json.js';
|
|
12
13
|
import { GRO_DIST_DIR, IS_THIS_GRO, paths } from "./paths.js";
|
|
13
14
|
import { GRO_CONFIG_FILENAME, JS_CLI_DEFAULT, NODE_MODULES_DIRNAME, PM_CLI_DEFAULT, SERVER_DIST_PATH, SVELTEKIT_BUILD_DIRNAME, SVELTEKIT_DIST_DIRNAME, } from "./constants.js";
|
|
14
15
|
import create_default_config from "./gro.config.default.js";
|
|
16
|
+
import { to_hash } from "./hash.js";
|
|
17
|
+
/**
|
|
18
|
+
* SHA-256 hash of empty string, used for configs without build_cache_config.
|
|
19
|
+
* This ensures consistent cache behavior when no custom config is provided.
|
|
20
|
+
*/
|
|
21
|
+
export const EMPTY_BUILD_CACHE_CONFIG_HASH = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
|
|
15
22
|
export const create_empty_gro_config = () => ({
|
|
16
23
|
plugins: () => [],
|
|
17
24
|
map_package_json: identity,
|
|
@@ -24,6 +31,7 @@ export const create_empty_gro_config = () => ({
|
|
|
24
31
|
search_filters: [(id) => !SEARCH_EXCLUDER_DEFAULT.test(id)],
|
|
25
32
|
js_cli: JS_CLI_DEFAULT,
|
|
26
33
|
pm_cli: PM_CLI_DEFAULT,
|
|
34
|
+
build_cache_config_hash: EMPTY_BUILD_CACHE_CONFIG_HASH,
|
|
27
35
|
});
|
|
28
36
|
/**
|
|
29
37
|
* The regexp used by default to exclude directories and files
|
|
@@ -38,16 +46,31 @@ export const SEARCH_EXCLUDER_DEFAULT = new RegExp(`(${'(^|/)\\.[^/]+' + // exclu
|
|
|
38
46
|
`|(^|/)(?<!(^|/)gro/)${SVELTEKIT_DIST_DIRNAME}` + // exclude the SvelteKit dist directory unless it's in the Gro directory
|
|
39
47
|
`|(^|/)${SERVER_DIST_PATH}` // exclude the Gro server plugin dist directory
|
|
40
48
|
})($|/)`, 'u');
|
|
41
|
-
export const EXPORTS_EXCLUDER_DEFAULT = /(\.md|\.(test|ignore)\.|\/(test|
|
|
49
|
+
export const EXPORTS_EXCLUDER_DEFAULT = /(\.md|\.(test|ignore)\.|\/(test|ignore)\/)/;
|
|
42
50
|
/**
|
|
43
51
|
* Transforms a `Raw_Gro_Config` to the more strict `Gro_Config`.
|
|
44
52
|
* This allows users to provide a more relaxed config.
|
|
53
|
+
* Hashes the `build_cache_config` and deletes the raw value for security.
|
|
45
54
|
*/
|
|
46
|
-
export const cook_gro_config = (raw_config) => {
|
|
55
|
+
export const cook_gro_config = async (raw_config) => {
|
|
47
56
|
const empty_config = create_empty_gro_config();
|
|
48
57
|
// All of the raw config properties are optional,
|
|
49
58
|
// so fall back to the empty values when `undefined`.
|
|
50
|
-
const { plugins = empty_config.plugins, map_package_json = empty_config.map_package_json, task_root_dirs = empty_config.task_root_dirs, search_filters = empty_config.search_filters, js_cli = empty_config.js_cli, pm_cli = empty_config.pm_cli, } = raw_config;
|
|
59
|
+
const { plugins = empty_config.plugins, map_package_json = empty_config.map_package_json, task_root_dirs = empty_config.task_root_dirs, search_filters = empty_config.search_filters, js_cli = empty_config.js_cli, pm_cli = empty_config.pm_cli, build_cache_config, } = raw_config;
|
|
60
|
+
// Hash build_cache_config and delete the raw value
|
|
61
|
+
// IMPORTANT: Raw value may contain secrets - hash it and delete immediately
|
|
62
|
+
let build_cache_config_hash;
|
|
63
|
+
if (!build_cache_config) {
|
|
64
|
+
build_cache_config_hash = EMPTY_BUILD_CACHE_CONFIG_HASH;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// Resolve if it's a function
|
|
68
|
+
const resolved = typeof build_cache_config === 'function' ? await build_cache_config() : build_cache_config;
|
|
69
|
+
// Hash the JSON representation with deterministic key ordering
|
|
70
|
+
build_cache_config_hash = await to_hash(new TextEncoder().encode(json_stringify_deterministic(resolved)));
|
|
71
|
+
}
|
|
72
|
+
// Delete the raw value to ensure it doesn't persist in memory
|
|
73
|
+
delete raw_config.build_cache_config;
|
|
51
74
|
return {
|
|
52
75
|
plugins,
|
|
53
76
|
map_package_json,
|
|
@@ -59,10 +82,11 @@ export const cook_gro_config = (raw_config) => {
|
|
|
59
82
|
: [],
|
|
60
83
|
js_cli,
|
|
61
84
|
pm_cli,
|
|
85
|
+
build_cache_config_hash,
|
|
62
86
|
};
|
|
63
87
|
};
|
|
64
88
|
export const load_gro_config = async (dir = paths.root) => {
|
|
65
|
-
const default_config = cook_gro_config(await create_default_config(create_empty_gro_config()));
|
|
89
|
+
const default_config = await cook_gro_config(await create_default_config(create_empty_gro_config()));
|
|
66
90
|
const config_path = join(dir, GRO_CONFIG_FILENAME);
|
|
67
91
|
if (!existsSync(config_path)) {
|
|
68
92
|
// No user config file found, so return the default.
|
|
@@ -71,7 +95,7 @@ export const load_gro_config = async (dir = paths.root) => {
|
|
|
71
95
|
// Import the user's `gro.config.ts`.
|
|
72
96
|
const config_module = await import(__rewriteRelativeImportExtension(config_path, true));
|
|
73
97
|
validate_gro_config_module(config_module, config_path);
|
|
74
|
-
return cook_gro_config(typeof config_module.default === 'function'
|
|
98
|
+
return await cook_gro_config(typeof config_module.default === 'function'
|
|
75
99
|
? await config_module.default(default_config)
|
|
76
100
|
: config_module.default);
|
|
77
101
|
};
|
package/dist/hash.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto
|
|
3
3
|
*/
|
|
4
|
-
export declare const to_hash: (data:
|
|
4
|
+
export declare const to_hash: (data: BufferSource, algorithm?: "SHA-1" | "SHA-256" | "SHA-384" | "SHA-512") => Promise<string>;
|
|
5
5
|
//# sourceMappingURL=hash.d.ts.map
|
package/dist/hash.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hash.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/hash.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/hash.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,eAAO,MAAM,OAAO,GACnB,MAAM,YAAY,EAClB,YAAW,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,SAAqB,KAChE,OAAO,CAAC,MAAM,CAQhB,CAAC"}
|
package/dist/hash.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invoke_task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/invoke_task.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmB,OAAO,EAAC,MAAM,2BAA2B,CAAC;AAGpE,OAAO,EAAoB,KAAK,IAAI,EAAC,MAAM,WAAW,CAAC;AAEvD,OAAO,EAAgB,cAAc,EAAC,MAAM,iBAAiB,CAAC;AAI9D,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAC,KAAK,EAAC,MAAM,YAAY,CAAC;AAEjC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,WAAW,GACvB,WAAW,cAAc,EACzB,MAAM,IAAI,GAAG,SAAS,EACtB,QAAQ,UAAU,EAClB,gBAAgB,KAAK,EACrB,kBAAkB,OAAO,GAAG,IAAI,KAC9B,OAAO,CAAC,IAAI,
|
|
1
|
+
{"version":3,"file":"invoke_task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/invoke_task.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmB,OAAO,EAAC,MAAM,2BAA2B,CAAC;AAGpE,OAAO,EAAoB,KAAK,IAAI,EAAC,MAAM,WAAW,CAAC;AAEvD,OAAO,EAAgB,cAAc,EAAC,MAAM,iBAAiB,CAAC;AAI9D,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAC,KAAK,EAAC,MAAM,YAAY,CAAC;AAEjC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,WAAW,GACvB,WAAW,cAAc,EACzB,MAAM,IAAI,GAAG,SAAS,EACtB,QAAQ,UAAU,EAClB,gBAAgB,KAAK,EACrB,kBAAkB,OAAO,GAAG,IAAI,KAC9B,OAAO,CAAC,IAAI,CA2Fd,CAAC"}
|
package/dist/invoke_task.js
CHANGED
|
@@ -77,7 +77,8 @@ export const invoke_task = async (task_name, args, config, initial_filer, initia
|
|
|
77
77
|
throw new Silent_Error();
|
|
78
78
|
}
|
|
79
79
|
const loaded_tasks = loaded.value;
|
|
80
|
-
if (resolved_input_files.length > 1 ||
|
|
80
|
+
if (resolved_input_files.length > 1 ||
|
|
81
|
+
resolved_input_files[0].resolved_input_path.is_directory) {
|
|
81
82
|
// The input path matches a directory. Log the tasks but don't run them.
|
|
82
83
|
log_tasks(log, loaded_tasks);
|
|
83
84
|
await finish();
|
package/dist/package.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/package.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gCAAgC,CAAC;AACjE,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,4BAA4B,CAAC;AAEzD,eAAO,MAAM,YAAY,EAAE,YAkGnB,CAAC;AAET,eAAO,MAAM,QAAQ,EAAE,
|
|
1
|
+
{"version":3,"file":"package.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/package.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gCAAgC,CAAC;AACjE,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,4BAA4B,CAAC;AAEzD,eAAO,MAAM,YAAY,EAAE,YAkGnB,CAAC;AAET,eAAO,MAAM,QAAQ,EAAE,QAuwBf,CAAC"}
|
package/dist/package.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// generated by src/lib/package.gen.ts
|
|
2
2
|
export const package_json = {
|
|
3
3
|
name: '@ryanatkn/gro',
|
|
4
|
-
version: '0.
|
|
4
|
+
version: '0.172.0',
|
|
5
5
|
description: 'task runner and toolkit extending SvelteKit',
|
|
6
6
|
motto: 'generate, run, optimize',
|
|
7
7
|
glyph: '🌰',
|
|
@@ -51,7 +51,7 @@ export const package_json = {
|
|
|
51
51
|
zod: '^4.1.12',
|
|
52
52
|
},
|
|
53
53
|
peerDependencies: {
|
|
54
|
-
'@ryanatkn/belt': '^0.
|
|
54
|
+
'@ryanatkn/belt': '^0.36.0',
|
|
55
55
|
'@sveltejs/kit': '^2',
|
|
56
56
|
esbuild: '^0.25',
|
|
57
57
|
svelte: '^5',
|
|
@@ -63,7 +63,7 @@ export const package_json = {
|
|
|
63
63
|
devDependencies: {
|
|
64
64
|
'@changesets/changelog-git': '^0.2.1',
|
|
65
65
|
'@changesets/types': '^6.1.0',
|
|
66
|
-
'@ryanatkn/eslint-config': '^0.8.
|
|
66
|
+
'@ryanatkn/eslint-config': '^0.8.1',
|
|
67
67
|
'@ryanatkn/fuz': '^0.147.0',
|
|
68
68
|
'@ryanatkn/moss': '^0.36.0',
|
|
69
69
|
'@sveltejs/adapter-static': '^3.0.10',
|
|
@@ -78,7 +78,7 @@ export const package_json = {
|
|
|
78
78
|
'svelte-check': '^4.3.3',
|
|
79
79
|
typescript: '^5.9.3',
|
|
80
80
|
'typescript-eslint': '^8.42.0',
|
|
81
|
-
vitest: '^4.0.
|
|
81
|
+
vitest: '^4.0.3',
|
|
82
82
|
},
|
|
83
83
|
prettier: {
|
|
84
84
|
plugins: ['prettier-plugin-svelte'],
|
|
@@ -99,7 +99,7 @@ export const package_json = {
|
|
|
99
99
|
};
|
|
100
100
|
export const src_json = {
|
|
101
101
|
name: '@ryanatkn/gro',
|
|
102
|
-
version: '0.
|
|
102
|
+
version: '0.172.0',
|
|
103
103
|
modules: {
|
|
104
104
|
'.': {
|
|
105
105
|
path: 'index.ts',
|
|
@@ -132,10 +132,28 @@ export const src_json = {
|
|
|
132
132
|
{ name: 'print_command_args', kind: 'function' },
|
|
133
133
|
],
|
|
134
134
|
},
|
|
135
|
+
'./build_cache.js': {
|
|
136
|
+
path: 'build_cache.ts',
|
|
137
|
+
declarations: [
|
|
138
|
+
{ name: 'BUILD_CACHE_METADATA_FILENAME', kind: 'variable' },
|
|
139
|
+
{ name: 'BUILD_CACHE_VERSION', kind: 'variable' },
|
|
140
|
+
{ name: 'Build_Output_Entry', kind: 'variable' },
|
|
141
|
+
{ name: 'Build_Cache_Metadata', kind: 'variable' },
|
|
142
|
+
{ name: 'compute_build_cache_key', kind: 'function' },
|
|
143
|
+
{ name: 'load_build_cache_metadata', kind: 'function' },
|
|
144
|
+
{ name: 'save_build_cache_metadata', kind: 'function' },
|
|
145
|
+
{ name: 'validate_build_cache', kind: 'function' },
|
|
146
|
+
{ name: 'is_build_cache_valid', kind: 'function' },
|
|
147
|
+
{ name: 'collect_build_outputs', kind: 'function' },
|
|
148
|
+
{ name: 'discover_build_output_dirs', kind: 'function' },
|
|
149
|
+
{ name: 'create_build_cache_metadata', kind: 'function' },
|
|
150
|
+
],
|
|
151
|
+
},
|
|
135
152
|
'./build.task.js': {
|
|
136
153
|
path: 'build.task.ts',
|
|
137
154
|
declarations: [
|
|
138
155
|
{ name: 'Args', kind: 'variable' },
|
|
156
|
+
{ name: 'GIT_SHORT_HASH_LENGTH', kind: 'variable' },
|
|
139
157
|
{ name: 'task', kind: 'variable' },
|
|
140
158
|
],
|
|
141
159
|
},
|
|
@@ -408,6 +426,7 @@ export const src_json = {
|
|
|
408
426
|
'./gro_config.js': {
|
|
409
427
|
path: 'gro_config.ts',
|
|
410
428
|
declarations: [
|
|
429
|
+
{ name: 'EMPTY_BUILD_CACHE_CONFIG_HASH', kind: 'variable' },
|
|
411
430
|
{ name: 'Gro_Config', kind: 'type' },
|
|
412
431
|
{ name: 'Raw_Gro_Config', kind: 'type' },
|
|
413
432
|
{ name: 'Create_Gro_Config', kind: 'type' },
|
|
@@ -820,16 +839,6 @@ export const src_json = {
|
|
|
820
839
|
{ name: 'validate_task_module', kind: 'function' },
|
|
821
840
|
],
|
|
822
841
|
},
|
|
823
|
-
'./test_helpers.js': {
|
|
824
|
-
path: 'test_helpers.ts',
|
|
825
|
-
declarations: [
|
|
826
|
-
{ name: 'TEST_TIMEOUT_MD', kind: 'variable' },
|
|
827
|
-
{ name: 'SOME_PUBLIC_ENV_VAR_NAME', kind: 'variable' },
|
|
828
|
-
{ name: 'SOME_PUBLIC_ENV_VAR_VALUE', kind: 'variable' },
|
|
829
|
-
{ name: 'init_test_env', kind: 'function' },
|
|
830
|
-
{ name: 'create_ts_test_env', kind: 'function' },
|
|
831
|
-
],
|
|
832
|
-
},
|
|
833
842
|
'./test.task.js': {
|
|
834
843
|
path: 'test.task.ts',
|
|
835
844
|
declarations: [
|
package/dist/package_json.js
CHANGED
|
@@ -136,7 +136,7 @@ export const parse_repo_url = (package_json) => {
|
|
|
136
136
|
return;
|
|
137
137
|
}
|
|
138
138
|
const [, owner, repo] = parsed_repo_url;
|
|
139
|
-
return { owner, repo };
|
|
139
|
+
return { owner: owner, repo: repo };
|
|
140
140
|
};
|
|
141
141
|
/**
|
|
142
142
|
* Parses a `Package_Json` object but preserves the order of the original keys.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ryanatkn/gro",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.172.0",
|
|
4
4
|
"description": "task runner and toolkit extending SvelteKit",
|
|
5
5
|
"motto": "generate, run, optimize",
|
|
6
6
|
"glyph": "🌰",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"zod": "^4.1.12"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
|
-
"@ryanatkn/belt": "^0.
|
|
64
|
+
"@ryanatkn/belt": "^0.36.0",
|
|
65
65
|
"@sveltejs/kit": "^2",
|
|
66
66
|
"esbuild": "^0.25",
|
|
67
67
|
"svelte": "^5",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"devDependencies": {
|
|
83
83
|
"@changesets/changelog-git": "^0.2.1",
|
|
84
84
|
"@changesets/types": "^6.1.0",
|
|
85
|
-
"@ryanatkn/eslint-config": "^0.8.
|
|
85
|
+
"@ryanatkn/eslint-config": "^0.8.1",
|
|
86
86
|
"@ryanatkn/fuz": "^0.147.0",
|
|
87
87
|
"@ryanatkn/moss": "^0.36.0",
|
|
88
88
|
"@sveltejs/adapter-static": "^3.0.10",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
"svelte-check": "^4.3.3",
|
|
98
98
|
"typescript": "^5.9.3",
|
|
99
99
|
"typescript-eslint": "^8.42.0",
|
|
100
|
-
"vitest": "^4.0.
|
|
100
|
+
"vitest": "^4.0.3"
|
|
101
101
|
},
|
|
102
102
|
"prettier": {
|
|
103
103
|
"plugins": [
|
package/src/lib/build.task.ts
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import {z} from 'zod';
|
|
2
|
+
import {styleText as st} from 'node:util';
|
|
3
|
+
import {git_check_clean_workspace, git_current_commit_hash} from '@ryanatkn/belt/git.js';
|
|
4
|
+
import {rmSync, existsSync} from 'node:fs';
|
|
5
|
+
import {join} from 'node:path';
|
|
2
6
|
|
|
3
|
-
import type
|
|
7
|
+
import {Task_Error, type Task} from './task.ts';
|
|
4
8
|
import {Plugins} from './plugin.ts';
|
|
5
9
|
import {clean_fs} from './clean_fs.ts';
|
|
10
|
+
import {
|
|
11
|
+
is_build_cache_valid,
|
|
12
|
+
create_build_cache_metadata,
|
|
13
|
+
save_build_cache_metadata,
|
|
14
|
+
discover_build_output_dirs,
|
|
15
|
+
} from './build_cache.ts';
|
|
16
|
+
import {paths} from './paths.ts';
|
|
6
17
|
|
|
7
18
|
export const Args = z.strictObject({
|
|
8
19
|
sync: z.boolean().meta({description: 'dual of no-sync'}).default(true),
|
|
@@ -12,29 +23,122 @@ export const Args = z.strictObject({
|
|
|
12
23
|
.boolean()
|
|
13
24
|
.meta({description: 'opt out of installing packages before building'})
|
|
14
25
|
.default(false),
|
|
26
|
+
force_build: z
|
|
27
|
+
.boolean()
|
|
28
|
+
.meta({description: 'force a fresh build, ignoring the cache'})
|
|
29
|
+
.default(false),
|
|
15
30
|
});
|
|
16
31
|
export type Args = z.infer<typeof Args>;
|
|
17
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Length of git commit hash when displayed in logs (standard git convention).
|
|
35
|
+
*/
|
|
36
|
+
export const GIT_SHORT_HASH_LENGTH = 7;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Formats a git commit hash for display in logs.
|
|
40
|
+
* Returns '[none]' if hash is null (e.g., not in a git repo).
|
|
41
|
+
*/
|
|
42
|
+
const format_commit_hash = (hash: string | null): string =>
|
|
43
|
+
hash?.slice(0, GIT_SHORT_HASH_LENGTH) ?? '[none]';
|
|
44
|
+
|
|
18
45
|
export const task: Task<Args> = {
|
|
19
46
|
summary: 'build the project',
|
|
20
47
|
Args,
|
|
21
48
|
run: async (ctx): Promise<void> => {
|
|
22
|
-
const {args, invoke_task, log} = ctx;
|
|
23
|
-
const {sync, install} = args;
|
|
49
|
+
const {args, invoke_task, log, config} = ctx;
|
|
50
|
+
const {sync, install, force_build} = args;
|
|
24
51
|
|
|
25
52
|
if (sync || install) {
|
|
26
53
|
if (!sync) log.warn('sync is false but install is true, so ignoring the sync option');
|
|
27
54
|
await invoke_task('sync', {install});
|
|
28
55
|
}
|
|
29
56
|
|
|
30
|
-
//
|
|
31
|
-
|
|
57
|
+
// Batch git calls upfront for performance (spawning processes is expensive)
|
|
58
|
+
const [workspace_status, initial_commit] = await Promise.all([
|
|
59
|
+
git_check_clean_workspace(),
|
|
60
|
+
git_current_commit_hash(),
|
|
61
|
+
]);
|
|
62
|
+
const workspace_dirty = !!workspace_status;
|
|
32
63
|
|
|
33
|
-
|
|
64
|
+
// Discover build output directories once to avoid redundant filesystem scans
|
|
65
|
+
let build_dirs: Array<string> | undefined;
|
|
66
|
+
|
|
67
|
+
// Check build cache unless force_build is set or workspace is dirty
|
|
68
|
+
if (!workspace_dirty && !force_build) {
|
|
69
|
+
const cache_valid = await is_build_cache_valid(config, log, initial_commit);
|
|
70
|
+
if (cache_valid) {
|
|
71
|
+
log.info(
|
|
72
|
+
st('cyan', 'skipping build, cache is valid'),
|
|
73
|
+
st('dim', '(use --force_build to rebuild)'),
|
|
74
|
+
);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
} else if (workspace_dirty) {
|
|
78
|
+
// IMPORTANT: When workspace is dirty, we delete cache AND all outputs to prevent stale state.
|
|
79
|
+
// Rationale: Uncommitted changes could be reverted, leaving cached outputs from reverted code.
|
|
80
|
+
// This conservative approach prioritizes safety over convenience during development.
|
|
81
|
+
const cache_path = join(paths.build, 'build.json');
|
|
82
|
+
if (existsSync(cache_path)) {
|
|
83
|
+
rmSync(cache_path, {force: true});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Delete all build output directories
|
|
87
|
+
build_dirs = discover_build_output_dirs();
|
|
88
|
+
for (const dir of build_dirs) {
|
|
89
|
+
rmSync(dir, {recursive: true, force: true});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
log.info(st('yellow', 'workspace has uncommitted changes - skipping build cache'));
|
|
93
|
+
// Skip clean_fs - already manually cleaned cache and all build outputs above
|
|
94
|
+
} else {
|
|
95
|
+
log.info(st('yellow', 'forcing fresh build, ignoring cache'));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Clean build outputs (skip if workspace was dirty - already cleaned manually above)
|
|
99
|
+
if (!workspace_dirty) {
|
|
100
|
+
await clean_fs({build_dist: true});
|
|
101
|
+
}
|
|
34
102
|
|
|
35
103
|
const plugins = await Plugins.create({...ctx, dev: false, watch: false});
|
|
36
104
|
await plugins.setup();
|
|
37
105
|
await plugins.adapt();
|
|
38
106
|
await plugins.teardown();
|
|
107
|
+
|
|
108
|
+
// Verify workspace didn't become dirty during build
|
|
109
|
+
const final_workspace_status = await git_check_clean_workspace();
|
|
110
|
+
if (final_workspace_status !== workspace_status) {
|
|
111
|
+
// Workspace state changed during build - this indicates a problem
|
|
112
|
+
throw new Task_Error(
|
|
113
|
+
'Build process modified tracked files or created untracked files.\n\n' +
|
|
114
|
+
'Git status after build:\n' +
|
|
115
|
+
final_workspace_status +
|
|
116
|
+
'\n\n' +
|
|
117
|
+
'Builds should only write to output directories (build/, dist/, etc.).\n' +
|
|
118
|
+
'This usually indicates a plugin or build step is incorrectly modifying source files.',
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Save build cache metadata after successful build (only if workspace is clean)
|
|
123
|
+
if (!workspace_dirty) {
|
|
124
|
+
// Race condition protection: verify git commit didn't change during build
|
|
125
|
+
const current_commit = await git_current_commit_hash();
|
|
126
|
+
|
|
127
|
+
if (current_commit !== initial_commit) {
|
|
128
|
+
log.warn(
|
|
129
|
+
st('yellow', 'git commit changed during build'),
|
|
130
|
+
st(
|
|
131
|
+
'dim',
|
|
132
|
+
`(${format_commit_hash(initial_commit)} → ${format_commit_hash(current_commit)})`,
|
|
133
|
+
),
|
|
134
|
+
'- cache not saved',
|
|
135
|
+
);
|
|
136
|
+
} else {
|
|
137
|
+
// Commit is stable - safe to save cache
|
|
138
|
+
const metadata = await create_build_cache_metadata(config, log, initial_commit, build_dirs);
|
|
139
|
+
save_build_cache_metadata(metadata, log);
|
|
140
|
+
log.debug('Build cache metadata saved');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
39
143
|
},
|
|
40
144
|
};
|