@fuzdev/fuz_gitops 0.68.0 → 0.70.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/README.md +3 -3
- package/dist/ModulesDetail.svelte +14 -14
- package/dist/ModulesNav.svelte +2 -2
- package/dist/PageFooter.svelte +1 -1
- package/dist/ReposTable.svelte +1 -1
- package/dist/ReposTree.svelte +7 -7
- package/dist/TablePage.svelte +1 -7
- package/dist/TreeItemPage.svelte +3 -3
- package/dist/TreePage.svelte +3 -3
- package/dist/changeset_generator.d.ts +4 -4
- package/dist/changeset_generator.js +5 -5
- package/dist/changeset_reader.d.ts +6 -4
- package/dist/changeset_reader.d.ts.map +1 -1
- package/dist/changeset_reader.js +7 -5
- package/dist/dependency_graph.d.ts +3 -3
- package/dist/dependency_graph.js +3 -3
- package/dist/dependency_updater.d.ts +4 -4
- package/dist/dependency_updater.js +5 -5
- package/dist/fetch_repo_data.d.ts +4 -4
- package/dist/fetch_repo_data.d.ts.map +1 -1
- package/dist/fetch_repo_data.js +4 -5
- package/dist/fs_fetch_value_cache.d.ts +4 -4
- package/dist/fs_fetch_value_cache.js +4 -4
- package/dist/git_operations.d.ts +5 -5
- package/dist/git_operations.d.ts.map +1 -1
- package/dist/git_operations.js +18 -18
- package/dist/github.d.ts +1 -1
- package/dist/github.js +0 -1
- package/dist/gitops_analyze.task.d.ts +1 -1
- package/dist/gitops_plan.task.d.ts +4 -4
- package/dist/gitops_plan.task.js +3 -3
- package/dist/gitops_publish.task.d.ts +1 -1
- package/dist/gitops_run.task.d.ts +1 -1
- package/dist/gitops_run.task.js +2 -2
- package/dist/gitops_task_helpers.d.ts +5 -5
- package/dist/gitops_task_helpers.js +5 -5
- package/dist/graph_validation.d.ts +5 -5
- package/dist/graph_validation.js +5 -5
- package/dist/local_repo.d.ts +6 -6
- package/dist/local_repo.d.ts.map +1 -1
- package/dist/local_repo.js +22 -26
- package/dist/multi_repo_publisher.d.ts.map +1 -1
- package/dist/multi_repo_publisher.js +4 -4
- package/dist/npm_install_helpers.d.ts +3 -3
- package/dist/npm_install_helpers.js +3 -3
- package/dist/npm_registry.d.ts +4 -4
- package/dist/npm_registry.js +5 -6
- package/dist/operations.d.ts +19 -17
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +1 -1
- package/dist/operations_defaults.d.ts.map +1 -1
- package/dist/operations_defaults.js +49 -14
- package/dist/output_helpers.d.ts +2 -2
- package/dist/output_helpers.js +2 -2
- package/dist/paths.d.ts +1 -1
- package/dist/paths.js +1 -1
- package/dist/preflight_checks.d.ts +2 -2
- package/dist/preflight_checks.js +7 -7
- package/dist/publishing_plan.js +4 -4
- package/dist/publishing_plan_helpers.d.ts +1 -1
- package/dist/publishing_plan_helpers.js +1 -1
- package/dist/repo.svelte.d.ts +4 -5
- package/dist/repo.svelte.d.ts.map +1 -1
- package/dist/repo.svelte.js +2 -2
- package/dist/repo_ops.d.ts +6 -6
- package/dist/repo_ops.js +7 -7
- package/dist/version_utils.d.ts +2 -2
- package/dist/version_utils.js +2 -2
- package/package.json +18 -16
- package/src/lib/changeset_generator.ts +5 -5
- package/src/lib/changeset_reader.ts +7 -5
- package/src/lib/dependency_graph.ts +3 -3
- package/src/lib/dependency_updater.ts +5 -5
- package/src/lib/fetch_repo_data.ts +4 -6
- package/src/lib/fs_fetch_value_cache.ts +4 -4
- package/src/lib/git_operations.ts +32 -18
- package/src/lib/github.ts +1 -2
- package/src/lib/gitops_plan.task.ts +3 -3
- package/src/lib/gitops_run.task.ts +2 -2
- package/src/lib/gitops_task_helpers.ts +5 -5
- package/src/lib/graph_validation.ts +5 -5
- package/src/lib/local_repo.ts +28 -30
- package/src/lib/multi_repo_publisher.ts +4 -6
- package/src/lib/npm_install_helpers.ts +3 -3
- package/src/lib/npm_registry.ts +6 -6
- package/src/lib/operations.ts +19 -17
- package/src/lib/operations_defaults.ts +47 -16
- package/src/lib/output_helpers.ts +2 -2
- package/src/lib/paths.ts +1 -1
- package/src/lib/preflight_checks.ts +7 -7
- package/src/lib/publishing_plan.ts +4 -4
- package/src/lib/publishing_plan_helpers.ts +1 -1
- package/src/lib/repo.svelte.ts +4 -5
- package/src/lib/repo_ops.ts +7 -7
- package/src/lib/version_utils.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fuzdev/fuz_gitops",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.70.0",
|
|
4
4
|
"description": "a tool for managing many repos",
|
|
5
5
|
"glyph": "🪄",
|
|
6
6
|
"logo": "logo.svg",
|
|
@@ -26,29 +26,30 @@
|
|
|
26
26
|
},
|
|
27
27
|
"type": "module",
|
|
28
28
|
"engines": {
|
|
29
|
-
"node": ">=
|
|
29
|
+
"node": ">=24.14"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
|
-
"@fuzdev/fuz_css": ">=0.
|
|
33
|
-
"@fuzdev/fuz_ui": ">=0.
|
|
34
|
-
"@fuzdev/fuz_util": ">=0.
|
|
35
|
-
"@fuzdev/gro": ">=0.
|
|
32
|
+
"@fuzdev/fuz_css": ">=0.61.0",
|
|
33
|
+
"@fuzdev/fuz_ui": ">=0.196.0",
|
|
34
|
+
"@fuzdev/fuz_util": ">=0.63.0",
|
|
35
|
+
"@fuzdev/gro": ">=0.200.0",
|
|
36
36
|
"@sveltejs/kit": "^2",
|
|
37
37
|
"svelte": "^5",
|
|
38
38
|
"zod": "^4.3.6"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@changesets/changelog-git": "^0.2.1",
|
|
42
|
+
"@fuzdev/blake3_wasm": "^0.1.1",
|
|
42
43
|
"@fuzdev/fuz_code": "^0.45.1",
|
|
43
|
-
"@fuzdev/fuz_css": "^0.
|
|
44
|
-
"@fuzdev/fuz_ui": "^0.
|
|
45
|
-
"@fuzdev/fuz_util": "^0.
|
|
46
|
-
"@fuzdev/gro": "^0.
|
|
44
|
+
"@fuzdev/fuz_css": "^0.61.0",
|
|
45
|
+
"@fuzdev/fuz_ui": "^0.196.0",
|
|
46
|
+
"@fuzdev/fuz_util": "^0.63.0",
|
|
47
|
+
"@fuzdev/gro": "^0.200.0",
|
|
47
48
|
"@jridgewell/trace-mapping": "^0.3.31",
|
|
48
|
-
"@ryanatkn/eslint-config": "^0.
|
|
49
|
+
"@ryanatkn/eslint-config": "^0.12.1",
|
|
49
50
|
"@sveltejs/acorn-typescript": "^1.0.9",
|
|
50
51
|
"@sveltejs/adapter-static": "^3.0.10",
|
|
51
|
-
"@sveltejs/kit": "^2.
|
|
52
|
+
"@sveltejs/kit": "^2.61.1",
|
|
52
53
|
"@sveltejs/package": "^2.5.7",
|
|
53
54
|
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
54
55
|
"@types/estree": "^1.0.8",
|
|
@@ -59,10 +60,11 @@
|
|
|
59
60
|
"esm-env": "^1.2.2",
|
|
60
61
|
"magic-string": "^0.30.21",
|
|
61
62
|
"prettier": "^3.7.4",
|
|
62
|
-
"prettier-plugin-svelte": "^3.
|
|
63
|
-
"svelte": "^5.
|
|
64
|
-
"svelte-check": "^4.
|
|
65
|
-
"
|
|
63
|
+
"prettier-plugin-svelte": "^3.5.1",
|
|
64
|
+
"svelte": "^5.56.0",
|
|
65
|
+
"svelte-check": "^4.4.6",
|
|
66
|
+
"svelte-docinfo": "^0.2.0",
|
|
67
|
+
"svelte2tsx": "^0.7.52",
|
|
66
68
|
"tslib": "^2.8.1",
|
|
67
69
|
"typescript": "^5.9.3",
|
|
68
70
|
"typescript-eslint": "^8.48.1",
|
|
@@ -36,7 +36,7 @@ export const create_changeset_for_dependency_updates = async (
|
|
|
36
36
|
const changesets_dir = join(repo.repo_dir, '.changeset');
|
|
37
37
|
|
|
38
38
|
// Ensure .changeset directory exists
|
|
39
|
-
if (!fs_ops.exists({path: changesets_dir})) {
|
|
39
|
+
if (!(await fs_ops.exists({path: changesets_dir}))) {
|
|
40
40
|
const mkdir_result = await fs_ops.mkdir({path: changesets_dir, recursive: true});
|
|
41
41
|
if (!mkdir_result.ok) {
|
|
42
42
|
throw new Error(`Failed to create .changeset directory: ${mkdir_result.message}`);
|
|
@@ -95,10 +95,10 @@ const calculate_required_bump = (
|
|
|
95
95
|
* and categorized list of breaking vs regular updates. Output format
|
|
96
96
|
* matches changesets CLI for consistency.
|
|
97
97
|
*
|
|
98
|
-
* @param package_name package receiving the dependency updates
|
|
99
|
-
* @param updates list of dependency changes with version info
|
|
100
|
-
* @param bump_type required bump type (calculated from breaking changes)
|
|
101
|
-
* @returns markdown content ready to write to
|
|
98
|
+
* @param package_name - package receiving the dependency updates
|
|
99
|
+
* @param updates - list of dependency changes with version info
|
|
100
|
+
* @param bump_type - required bump type (calculated from breaking changes)
|
|
101
|
+
* @returns markdown content ready to write to `.changeset/*.md` file
|
|
102
102
|
*/
|
|
103
103
|
export const generate_changeset_content = (
|
|
104
104
|
package_name: string,
|
|
@@ -30,15 +30,17 @@ export interface ChangesetInfo {
|
|
|
30
30
|
* Returns null if format is invalid or no packages found.
|
|
31
31
|
*
|
|
32
32
|
* Expected format:
|
|
33
|
+
* ```
|
|
33
34
|
* ---
|
|
34
35
|
* "package-name": patch
|
|
35
36
|
* "@scope/package": minor
|
|
36
37
|
* ---
|
|
37
38
|
*
|
|
38
39
|
* Summary of changes
|
|
40
|
+
* ```
|
|
39
41
|
*
|
|
40
|
-
* @param content changeset markdown with YAML frontmatter
|
|
41
|
-
* @param filename optional filename for error reporting context
|
|
42
|
+
* @param content - changeset markdown with YAML frontmatter
|
|
43
|
+
* @param filename - optional filename for error reporting context
|
|
42
44
|
* @returns parsed changeset info or null if invalid format
|
|
43
45
|
*/
|
|
44
46
|
export const parse_changeset_content = (
|
|
@@ -116,7 +118,7 @@ export const read_changesets = async (
|
|
|
116
118
|
|
|
117
119
|
for (const file of changeset_files) {
|
|
118
120
|
const filepath = join(changesets_dir, file);
|
|
119
|
-
const changeset = await parse_changeset_file(filepath, log);
|
|
121
|
+
const changeset = await parse_changeset_file(filepath, log);
|
|
120
122
|
if (changeset) {
|
|
121
123
|
changesets.push(changeset);
|
|
122
124
|
}
|
|
@@ -161,8 +163,8 @@ export const determine_bump_from_changesets = (
|
|
|
161
163
|
* Checks if a repo has any changeset files (excluding README.md).
|
|
162
164
|
*
|
|
163
165
|
* Used by preflight checks and publishing workflow to determine which packages
|
|
164
|
-
* need to be published. Returns false if
|
|
165
|
-
* or contains only README.md
|
|
166
|
+
* need to be published. Returns false if `.changeset` directory doesn't exist
|
|
167
|
+
* or contains only `README.md`.
|
|
166
168
|
*
|
|
167
169
|
* @returns true if repo has unpublished changesets
|
|
168
170
|
*/
|
|
@@ -127,8 +127,8 @@ export class DependencyGraph {
|
|
|
127
127
|
* Delegates to `@fuzdev/fuz_util/sort.js` for the sorting algorithm.
|
|
128
128
|
* Throws if cycles detected.
|
|
129
129
|
*
|
|
130
|
-
* @param exclude_dev if true, excludes dev dependencies to break cycles
|
|
131
|
-
* Publishing uses exclude_dev
|
|
130
|
+
* @param exclude_dev - if true, excludes dev dependencies to break cycles
|
|
131
|
+
* Publishing uses `exclude_dev`=true to handle circular dev deps.
|
|
132
132
|
* @returns array of package names in dependency order (dependencies before dependents)
|
|
133
133
|
* @throws {Error} if circular dependencies detected in included dependency types
|
|
134
134
|
*/
|
|
@@ -158,7 +158,7 @@ export class DependencyGraph {
|
|
|
158
158
|
* Uses DFS traversal with recursion stack to identify back edges.
|
|
159
159
|
* Deduplicates cycles using sorted cycle keys.
|
|
160
160
|
*
|
|
161
|
-
* @returns object with production_cycles (errors) and dev_cycles (info)
|
|
161
|
+
* @returns object with `production_cycles` (errors) and `dev_cycles` (info)
|
|
162
162
|
*/
|
|
163
163
|
detect_cycles_by_type(): {
|
|
164
164
|
production_cycles: Array<Array<string>>;
|
|
@@ -22,13 +22,13 @@ export interface UpdatePackageJsonOptions {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
* Updates package.json dependencies and creates changeset if needed.
|
|
25
|
+
* Updates `package.json` dependencies and creates changeset if needed.
|
|
26
26
|
*
|
|
27
27
|
* Workflow:
|
|
28
28
|
* 1. Updates all dependency types (dependencies, devDependencies, peerDependencies)
|
|
29
|
-
* 2. Writes updated package.json with tabs formatting
|
|
30
|
-
* 3. Creates auto-changeset if published_versions provided (for transitive updates)
|
|
31
|
-
* 4. Commits both package.json and changeset with standard message
|
|
29
|
+
* 2. Writes updated `package.json` with tabs formatting
|
|
30
|
+
* 3. Creates auto-changeset if `published_versions` provided (for transitive updates)
|
|
31
|
+
* 4. Commits both `package.json` and changeset with standard message
|
|
32
32
|
*
|
|
33
33
|
* Uses version strategy to determine prefix (exact, caret, tilde) while preserving
|
|
34
34
|
* existing prefixes when possible.
|
|
@@ -226,7 +226,7 @@ export const update_all_repos = async (
|
|
|
226
226
|
if (updates.size === 0) continue;
|
|
227
227
|
|
|
228
228
|
try {
|
|
229
|
-
await update_package_json(repo, updates, {strategy, log, git_ops, fs_ops});
|
|
229
|
+
await update_package_json(repo, updates, {strategy, log, git_ops, fs_ops});
|
|
230
230
|
updated_count++;
|
|
231
231
|
log?.info(` Updated ${updates.size} dependencies in ${repo.library.name}`);
|
|
232
232
|
} catch (error) {
|
|
@@ -6,8 +6,6 @@ import {fetch_github_check_runs, fetch_github_pull_requests} from './github.js';
|
|
|
6
6
|
import type {RepoJson} from './repo.svelte.js';
|
|
7
7
|
import type {LocalRepo} from './local_repo.js';
|
|
8
8
|
|
|
9
|
-
/* eslint-disable no-await-in-loop */
|
|
10
|
-
|
|
11
9
|
/**
|
|
12
10
|
* Fetches GitHub metadata (CI status, PRs) for all repos.
|
|
13
11
|
*
|
|
@@ -15,11 +13,11 @@ import type {LocalRepo} from './local_repo.js';
|
|
|
15
13
|
* Uses `await_in_loop` intentionally to avoid parallel requests overwhelming the API.
|
|
16
14
|
*
|
|
17
15
|
* Error handling: Logs fetch failures but continues processing remaining repos.
|
|
18
|
-
* Repos with failed fetches will have `null` for check_runs or pull_requests
|
|
16
|
+
* Repos with failed fetches will have `null` for `check_runs` or `pull_requests`.
|
|
19
17
|
*
|
|
20
|
-
* @param delay milliseconds between API requests (default: 33ms)
|
|
21
|
-
* @param cache optional cache from fuz_util's fetch.js for response memoization
|
|
22
|
-
* @returns array of Repo objects with GitHub metadata attached
|
|
18
|
+
* @param delay - milliseconds between API requests (default: 33ms)
|
|
19
|
+
* @param cache - optional cache from `fuz_util`'s `fetch.js` for response memoization
|
|
20
|
+
* @returns array of `Repo` objects with GitHub metadata attached
|
|
23
21
|
*/
|
|
24
22
|
export const fetch_repo_data = async (
|
|
25
23
|
resolved_repos: Array<LocalRepo>,
|
|
@@ -17,7 +17,7 @@ export interface FetchCache {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* Creates file-system backed cache for fuz_util's fetch.js API responses.
|
|
20
|
+
* Creates file-system backed cache for `fuz_util`'s `fetch.js` API responses.
|
|
21
21
|
*
|
|
22
22
|
* Cache invalidation strategy: If cache file can't be read or parsed, entire
|
|
23
23
|
* cache is cleared (delete file) and starts fresh. This handles format changes.
|
|
@@ -25,9 +25,9 @@ export interface FetchCache {
|
|
|
25
25
|
* Uses `structuredClone` to track changes - only writes to disk if data modified.
|
|
26
26
|
* Formatted with Prettier before writing for version control friendliness.
|
|
27
27
|
*
|
|
28
|
-
* @param name cache filename (without .json extension)
|
|
29
|
-
* @param dir cache directory (defaults to `.gro/build/fetch/`)
|
|
30
|
-
* @returns cache object with Map-based data and save() method
|
|
28
|
+
* @param name - cache filename (without .json extension)
|
|
29
|
+
* @param dir - cache directory (defaults to `.gro/build/fetch/`)
|
|
30
|
+
* @returns cache object with Map-based data and `save()` method
|
|
31
31
|
*/
|
|
32
32
|
export const create_fs_fetch_value_cache = async (
|
|
33
33
|
name: string,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {spawn_out, spawn_result_to_message} from '@fuzdev/fuz_util/process.js';
|
|
2
2
|
import type {SpawnOptions} from 'node:child_process';
|
|
3
3
|
import {
|
|
4
4
|
git_check_clean_workspace as gro_git_check_clean_workspace,
|
|
@@ -18,9 +18,11 @@ export const git_add = async (
|
|
|
18
18
|
options?: SpawnOptions,
|
|
19
19
|
): Promise<void> => {
|
|
20
20
|
const file_list = Array.isArray(files) ? files : [files];
|
|
21
|
-
const result = await
|
|
21
|
+
const {result, stderr} = await spawn_out('git', ['add', ...file_list], options);
|
|
22
22
|
if (!result.ok) {
|
|
23
|
-
throw Error(
|
|
23
|
+
throw Error(
|
|
24
|
+
`git_add failed with ${spawn_result_to_message(result)}${stderr ? ': ' + stderr.trim() : ''}`,
|
|
25
|
+
);
|
|
24
26
|
}
|
|
25
27
|
};
|
|
26
28
|
|
|
@@ -28,9 +30,11 @@ export const git_add = async (
|
|
|
28
30
|
* Commits staged changes with a message and throws if anything goes wrong.
|
|
29
31
|
*/
|
|
30
32
|
export const git_commit = async (message: string, options?: SpawnOptions): Promise<void> => {
|
|
31
|
-
const result = await
|
|
33
|
+
const {result, stderr} = await spawn_out('git', ['commit', '-m', message], options);
|
|
32
34
|
if (!result.ok) {
|
|
33
|
-
throw Error(
|
|
35
|
+
throw Error(
|
|
36
|
+
`git_commit failed with ${spawn_result_to_message(result)}${stderr ? ': ' + stderr.trim() : ''}`,
|
|
37
|
+
);
|
|
34
38
|
}
|
|
35
39
|
};
|
|
36
40
|
|
|
@@ -56,9 +60,11 @@ export const git_tag = async (
|
|
|
56
60
|
): Promise<void> => {
|
|
57
61
|
const args = message ? ['tag', '-a', tag_name, '-m', message] : ['tag', tag_name];
|
|
58
62
|
|
|
59
|
-
const result = await
|
|
63
|
+
const {result, stderr} = await spawn_out('git', args, options);
|
|
60
64
|
if (!result.ok) {
|
|
61
|
-
throw Error(
|
|
65
|
+
throw Error(
|
|
66
|
+
`git_tag failed for tag '${tag_name}' with ${spawn_result_to_message(result)}${stderr ? ': ' + stderr.trim() : ''}`,
|
|
67
|
+
);
|
|
62
68
|
}
|
|
63
69
|
};
|
|
64
70
|
|
|
@@ -70,9 +76,11 @@ export const git_push_tag = async (
|
|
|
70
76
|
origin: GitOrigin = 'origin' as GitOrigin,
|
|
71
77
|
options?: SpawnOptions,
|
|
72
78
|
): Promise<void> => {
|
|
73
|
-
const result = await
|
|
79
|
+
const {result, stderr} = await spawn_out('git', ['push', origin, tag_name], options);
|
|
74
80
|
if (!result.ok) {
|
|
75
|
-
throw Error(
|
|
81
|
+
throw Error(
|
|
82
|
+
`git_push_tag failed for tag '${tag_name}' with ${spawn_result_to_message(result)}${stderr ? ': ' + stderr.trim() : ''}`,
|
|
83
|
+
);
|
|
76
84
|
}
|
|
77
85
|
};
|
|
78
86
|
|
|
@@ -82,9 +90,11 @@ export const git_has_changes = async (options?: SpawnOptions): Promise<boolean>
|
|
|
82
90
|
};
|
|
83
91
|
|
|
84
92
|
/**
|
|
85
|
-
*
|
|
93
|
+
* Lists uncommitted files in the working tree (`git diff --name-only HEAD`).
|
|
86
94
|
*/
|
|
87
|
-
export const
|
|
95
|
+
export const git_list_uncommitted_files = async (
|
|
96
|
+
options?: SpawnOptions,
|
|
97
|
+
): Promise<Array<string>> => {
|
|
88
98
|
const {stdout} = await spawn_out('git', ['diff', '--name-only', 'HEAD'], options);
|
|
89
99
|
if (!stdout) return [];
|
|
90
100
|
|
|
@@ -114,9 +124,11 @@ export const git_has_file_changed = async (
|
|
|
114
124
|
export const git_stash = async (message?: string, options?: SpawnOptions): Promise<void> => {
|
|
115
125
|
const args = message ? ['stash', 'push', '-m', message] : ['stash', 'push'];
|
|
116
126
|
|
|
117
|
-
const result = await
|
|
127
|
+
const {result, stderr} = await spawn_out('git', args, options);
|
|
118
128
|
if (!result.ok) {
|
|
119
|
-
throw Error(
|
|
129
|
+
throw Error(
|
|
130
|
+
`git_stash failed with ${spawn_result_to_message(result)}${stderr ? ': ' + stderr.trim() : ''}`,
|
|
131
|
+
);
|
|
120
132
|
}
|
|
121
133
|
};
|
|
122
134
|
|
|
@@ -124,9 +136,11 @@ export const git_stash = async (message?: string, options?: SpawnOptions): Promi
|
|
|
124
136
|
* Applies stashed changes and throws if anything goes wrong.
|
|
125
137
|
*/
|
|
126
138
|
export const git_stash_pop = async (options?: SpawnOptions): Promise<void> => {
|
|
127
|
-
const result = await
|
|
139
|
+
const {result, stderr} = await spawn_out('git', ['stash', 'pop'], options);
|
|
128
140
|
if (!result.ok) {
|
|
129
|
-
throw Error(
|
|
141
|
+
throw Error(
|
|
142
|
+
`git_stash_pop failed with ${spawn_result_to_message(result)}${stderr ? ': ' + stderr.trim() : ''}`,
|
|
143
|
+
);
|
|
130
144
|
}
|
|
131
145
|
};
|
|
132
146
|
|
|
@@ -160,7 +174,7 @@ export const git_switch_branch = async (
|
|
|
160
174
|
};
|
|
161
175
|
|
|
162
176
|
/**
|
|
163
|
-
* Wrapper for gro's git_current_branch_name that throws if null.
|
|
177
|
+
* Wrapper for gro's `git_current_branch_name` that throws if null.
|
|
164
178
|
*/
|
|
165
179
|
export const git_current_branch_name_required = async (options?: SpawnOptions): Promise<string> => {
|
|
166
180
|
const branch = await gro_git_current_branch_name(options);
|
|
@@ -171,7 +185,7 @@ export const git_current_branch_name_required = async (options?: SpawnOptions):
|
|
|
171
185
|
};
|
|
172
186
|
|
|
173
187
|
/**
|
|
174
|
-
* Wrapper for gro's git_current_commit_hash that throws if null.
|
|
188
|
+
* Wrapper for gro's `git_current_commit_hash` that throws if null.
|
|
175
189
|
*/
|
|
176
190
|
export const git_current_commit_hash_required = async (
|
|
177
191
|
branch?: string,
|
|
@@ -185,7 +199,7 @@ export const git_current_commit_hash_required = async (
|
|
|
185
199
|
};
|
|
186
200
|
|
|
187
201
|
/**
|
|
188
|
-
* Wrapper for gro's git_check_clean_workspace that returns a boolean.
|
|
202
|
+
* Wrapper for gro's `git_check_clean_workspace` that returns a boolean.
|
|
189
203
|
*/
|
|
190
204
|
export const git_check_clean_workspace_as_boolean = async (
|
|
191
205
|
options?: SpawnOptions,
|
package/src/lib/github.ts
CHANGED
|
@@ -3,7 +3,7 @@ import {z} from 'zod';
|
|
|
3
3
|
import {fetch_value, type FetchValueCache} from '@fuzdev/fuz_util/fetch.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Minimal interface for GitHub API calls - works with both Pkg and Repo
|
|
6
|
+
* Minimal interface for GitHub API calls - works with both `Pkg` and `Repo`.
|
|
7
7
|
*/
|
|
8
8
|
export interface GithubRepoInfo {
|
|
9
9
|
owner_name: string | null;
|
|
@@ -116,7 +116,6 @@ const reduce_check_runs = (check_runs: Array<GithubCheckRunsItem>): GithubCheckR
|
|
|
116
116
|
let status!: GithubCheckRunsItem['status'];
|
|
117
117
|
let conclusion!: GithubCheckRunsItem['conclusion'];
|
|
118
118
|
for (const check_run of check_runs) {
|
|
119
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
120
119
|
if (!status || status === 'completed') {
|
|
121
120
|
status = check_run.status;
|
|
122
121
|
}
|
|
@@ -36,9 +36,9 @@ export type Args = z.infer<typeof Args>;
|
|
|
36
36
|
* Shows version changes, dependency updates, and breaking change cascades.
|
|
37
37
|
*
|
|
38
38
|
* Usage:
|
|
39
|
-
* gro gitops_plan
|
|
40
|
-
* gro gitops_plan --dir ../repos
|
|
41
|
-
* gro gitops_plan --config ./custom.config.ts
|
|
39
|
+
* `gro gitops_plan`
|
|
40
|
+
* `gro gitops_plan --dir ../repos`
|
|
41
|
+
* `gro gitops_plan --config ./custom.config.ts`
|
|
42
42
|
*
|
|
43
43
|
* @nodocs
|
|
44
44
|
*/
|
|
@@ -41,7 +41,7 @@ export const task: Task<Args> = {
|
|
|
41
41
|
run: async ({args, log}) => {
|
|
42
42
|
const {command, config, concurrency, format} = args;
|
|
43
43
|
|
|
44
|
-
// Get repo paths (lightweight, no library
|
|
44
|
+
// Get repo paths (lightweight, no library-metadata loading needed)
|
|
45
45
|
const config_path = resolve(config);
|
|
46
46
|
const repos = await get_repo_paths(config_path);
|
|
47
47
|
|
|
@@ -75,7 +75,7 @@ export const task: Task<Args> = {
|
|
|
75
75
|
repo_name,
|
|
76
76
|
repo_dir,
|
|
77
77
|
status: success ? 'success' : 'failure',
|
|
78
|
-
exit_code: spawned.result.code
|
|
78
|
+
exit_code: spawned.result.kind === 'exited' ? spawned.result.code : 0,
|
|
79
79
|
stdout: spawned.stdout || '',
|
|
80
80
|
stderr: spawned.stderr || '',
|
|
81
81
|
duration_ms,
|
|
@@ -46,17 +46,17 @@ export interface GetGitopsReadyOptions {
|
|
|
46
46
|
* 1. Loads and normalizes config from `gitops.config.ts`
|
|
47
47
|
* 2. Resolves local repo paths (creates missing with `--download`)
|
|
48
48
|
* 3. Switches branches and pulls latest changes (in parallel by default)
|
|
49
|
-
* 4. Auto-installs deps if package.json changed during pull
|
|
49
|
+
* 4. Auto-installs deps if `package.json` changed during pull
|
|
50
50
|
*
|
|
51
51
|
* Priority for path resolution:
|
|
52
52
|
* - `dir` argument (explicit override)
|
|
53
53
|
* - Config `repos_dir` setting
|
|
54
54
|
* - `DEFAULT_REPOS_DIR` constant
|
|
55
55
|
*
|
|
56
|
-
* @param options.git_ops for testing (defaults to real git operations)
|
|
57
|
-
* @param options.npm_ops for testing (defaults to real npm operations)
|
|
58
|
-
* @param options.parallel whether to load repos in parallel (default: true)
|
|
59
|
-
* @param options.concurrency max concurrent repo loads (default: 5)
|
|
56
|
+
* @param options.git_ops - for testing (defaults to real git operations)
|
|
57
|
+
* @param options.npm_ops - for testing (defaults to real npm operations)
|
|
58
|
+
* @param options.parallel - whether to load repos in parallel (default: true)
|
|
59
|
+
* @param options.concurrency - max concurrent repo loads (default: 5)
|
|
60
60
|
* @returns initialized config and fully loaded repos ready for operations
|
|
61
61
|
* @throws {TaskError} if config loading or repo resolution fails
|
|
62
62
|
*/
|
|
@@ -30,13 +30,13 @@ export interface GraphValidationResult {
|
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Shared utility for building dependency graph, detecting cycles, and computing publishing order.
|
|
33
|
-
* This centralizes logic that was duplicated across multi_repo_publisher
|
|
33
|
+
* This centralizes logic that was duplicated across `multi_repo_publisher`, `publishing_plan`, and `gitops_analyze`.
|
|
34
34
|
*
|
|
35
|
-
* @param options.throw_on_prod_cycles whether to throw an error if production cycles are detected (default: true)
|
|
36
|
-
* @param options.log_cycles whether to log cycle information (default: true)
|
|
37
|
-
* @param options.log_order whether to log publishing order (default: true)
|
|
35
|
+
* @param options.throw_on_prod_cycles - whether to throw an error if production cycles are detected (default: true)
|
|
36
|
+
* @param options.log_cycles - whether to log cycle information (default: true)
|
|
37
|
+
* @param options.log_order - whether to log publishing order (default: true)
|
|
38
38
|
* @returns graph validation result with graph, publishing order, and detected cycles
|
|
39
|
-
* @throws {TaskError} if production cycles detected and throw_on_prod_cycles is true
|
|
39
|
+
* @throws {TaskError} if production cycles detected and `throw_on_prod_cycles` is true
|
|
40
40
|
*/
|
|
41
41
|
export const validate_dependency_graph = (
|
|
42
42
|
repos: Array<LocalRepo>,
|
package/src/lib/local_repo.ts
CHANGED
|
@@ -4,8 +4,9 @@ import {Library} from '@fuzdev/fuz_ui/library.svelte.js';
|
|
|
4
4
|
import {existsSync} from 'node:fs';
|
|
5
5
|
import {join} from 'node:path';
|
|
6
6
|
import {TaskError} from '@fuzdev/gro';
|
|
7
|
+
import {library_load_from_repo} from '@fuzdev/gro/library_load.js';
|
|
7
8
|
import type {Logger} from '@fuzdev/fuz_util/log.js';
|
|
8
|
-
import {
|
|
9
|
+
import {spawn_out} from '@fuzdev/fuz_util/process.js';
|
|
9
10
|
import {map_concurrent_settled} from '@fuzdev/fuz_util/async.js';
|
|
10
11
|
import type {GitOperations, NpmOperations} from './operations.js';
|
|
11
12
|
import {default_git_operations, default_npm_operations} from './operations_defaults.js';
|
|
@@ -15,8 +16,8 @@ import type {ResolvedGitopsConfig} from './resolved_gitops_config.js';
|
|
|
15
16
|
import {GITOPS_CONCURRENCY_DEFAULT} from './gitops_constants.js';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
|
-
* Fully loaded local repo with Library and extracted dependency data.
|
|
19
|
-
* Does not extend LocalRepoPath - Library is source of truth for name/repo_url/etc.
|
|
19
|
+
* Fully loaded local repo with `Library` and extracted dependency data.
|
|
20
|
+
* Does not extend `LocalRepoPath` - `Library` is source of truth for name/repo_url/etc.
|
|
20
21
|
*/
|
|
21
22
|
export interface LocalRepo {
|
|
22
23
|
library: Library;
|
|
@@ -61,14 +62,14 @@ export interface LocalRepoMissing {
|
|
|
61
62
|
* 2. Switches to target branch if needed (requires clean workspace)
|
|
62
63
|
* 3. Pulls latest changes from remote (skipped for local-only repos)
|
|
63
64
|
* 4. Validates workspace is clean after pull
|
|
64
|
-
* 5. Auto-installs dependencies if package.json changed
|
|
65
|
-
* 6.
|
|
66
|
-
* 7. Creates Library and extracts dependency maps
|
|
65
|
+
* 5. Auto-installs dependencies if `package.json` changed
|
|
66
|
+
* 6. Loads `library_json` via `library_load_from_repo` (svelte-docinfo analysis)
|
|
67
|
+
* 7. Creates `Library` and extracts dependency maps
|
|
67
68
|
*
|
|
68
69
|
* This ensures repos are always in sync with their configured branch
|
|
69
70
|
* before being used by gitops commands.
|
|
70
71
|
*
|
|
71
|
-
* @throws {TaskError} if workspace dirty, branch switch fails, install fails, or
|
|
72
|
+
* @throws {TaskError} if workspace dirty, branch switch fails, install fails, or analysis fails
|
|
72
73
|
*/
|
|
73
74
|
export const local_repo_load = async ({
|
|
74
75
|
local_repo_path,
|
|
@@ -179,27 +180,17 @@ export const local_repo_load = async ({
|
|
|
179
180
|
}
|
|
180
181
|
}
|
|
181
182
|
|
|
182
|
-
//
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
` export const gen = library_gen();\n` +
|
|
191
|
-
` 2. Run: cd ${repo_dir} && gro gen`,
|
|
183
|
+
// Load library metadata via svelte-docinfo analysis (cached under `.gro/library.json`).
|
|
184
|
+
let library_json: LibraryJson;
|
|
185
|
+
try {
|
|
186
|
+
library_json = await library_load_from_repo(repo_dir, {log: _log});
|
|
187
|
+
} catch (err) {
|
|
188
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
189
|
+
_log?.warn(
|
|
190
|
+
`Failed to load library metadata for repo "${repo_name}" in ${repo_dir}: ${message}`,
|
|
192
191
|
);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const library_module = await import(library_path);
|
|
196
|
-
const {library_json} = library_module as {library_json: LibraryJson | undefined};
|
|
197
|
-
if (!library_json) {
|
|
198
192
|
throw new TaskError(
|
|
199
|
-
`
|
|
200
|
-
`The file must export a library_json object. To fix:\n` +
|
|
201
|
-
` 1. Ensure src/routes/library.gen.ts uses library_gen from @fuzdev/fuz_ui\n` +
|
|
202
|
-
` 2. Run: cd ${repo_dir} && gro gen`,
|
|
193
|
+
`Failed to load library metadata for repo "${repo_name}" in ${repo_dir}: ${message}`,
|
|
203
194
|
);
|
|
204
195
|
}
|
|
205
196
|
const library = new Library(library_json);
|
|
@@ -296,7 +287,7 @@ export const local_repos_load = async ({
|
|
|
296
287
|
// Sequential loading (original behavior)
|
|
297
288
|
const loaded: Array<LocalRepo> = [];
|
|
298
289
|
for (const local_repo_path of local_repo_paths) {
|
|
299
|
-
loaded.push(await local_repo_load({local_repo_path, log, git_ops, npm_ops}));
|
|
290
|
+
loaded.push(await local_repo_load({local_repo_path, log, git_ops, npm_ops}));
|
|
300
291
|
}
|
|
301
292
|
return loaded;
|
|
302
293
|
}
|
|
@@ -383,14 +374,21 @@ const download_repos = async ({
|
|
|
383
374
|
const resolved: Array<LocalRepoPath> = [];
|
|
384
375
|
for (const {repo_config, repo_git_ssh_url} of local_repos_missing) {
|
|
385
376
|
log?.info(`cloning repo ${repo_git_ssh_url} to ${repos_dir}`);
|
|
386
|
-
await
|
|
377
|
+
const clone_result = await spawn_out('git', ['clone', repo_git_ssh_url], {cwd: repos_dir});
|
|
378
|
+
if (!clone_result.result.ok) {
|
|
379
|
+
throw new TaskError(
|
|
380
|
+
`Failed to clone repo ${repo_git_ssh_url} to ${repos_dir}${clone_result.stderr ? ': ' + clone_result.stderr.trim() : ''}`,
|
|
381
|
+
);
|
|
382
|
+
}
|
|
387
383
|
const local_repo = local_repo_locate({repo_config, repos_dir});
|
|
388
384
|
if (local_repo.type === 'local_repo_missing') {
|
|
389
|
-
throw new TaskError(
|
|
385
|
+
throw new TaskError(
|
|
386
|
+
`Failed to clone repo ${repo_git_ssh_url} to ${repos_dir}: directory not found after clone`,
|
|
387
|
+
);
|
|
390
388
|
}
|
|
391
389
|
// Always install dependencies after cloning
|
|
392
390
|
log?.info(`installing dependencies for newly cloned repo ${local_repo.repo_dir}`);
|
|
393
|
-
const install_result = await npm_ops.install({cwd: local_repo.repo_dir});
|
|
391
|
+
const install_result = await npm_ops.install({cwd: local_repo.repo_dir});
|
|
394
392
|
if (!install_result.ok) {
|
|
395
393
|
throw new TaskError(
|
|
396
394
|
`Failed to install dependencies in ${local_repo.repo_dir}: ${install_result.message}${install_result.stderr ? `\n${install_result.stderr}` : ''}`,
|
|
@@ -16,8 +16,6 @@ import {
|
|
|
16
16
|
} from './gitops_constants.js';
|
|
17
17
|
import {install_with_cache_healing} from './npm_install_helpers.js';
|
|
18
18
|
|
|
19
|
-
/* eslint-disable no-await-in-loop */
|
|
20
|
-
|
|
21
19
|
export interface PublishingOptions {
|
|
22
20
|
wetrun: boolean;
|
|
23
21
|
update_deps: boolean;
|
|
@@ -337,7 +335,7 @@ export const publish_repos = async (
|
|
|
337
335
|
const deploy_result = await ops.process.spawn({
|
|
338
336
|
cmd: 'gro',
|
|
339
337
|
args: ['deploy', '--no-build'],
|
|
340
|
-
|
|
338
|
+
cwd: repo.repo_dir,
|
|
341
339
|
});
|
|
342
340
|
|
|
343
341
|
if (deploy_result.ok) {
|
|
@@ -377,11 +375,11 @@ export const publish_repos = async (
|
|
|
377
375
|
};
|
|
378
376
|
|
|
379
377
|
/**
|
|
380
|
-
* Publishes a single repo using gro publish
|
|
378
|
+
* Publishes a single repo using `gro publish`.
|
|
381
379
|
*
|
|
382
380
|
* Dry run mode: Predicts version from changesets without side effects.
|
|
383
381
|
* Real mode: Runs `gro publish --no-build` (builds already validated in preflight),
|
|
384
|
-
* reads new version from package.json
|
|
382
|
+
* reads new version from `package.json`, and returns metadata.
|
|
385
383
|
*
|
|
386
384
|
* @throws {Error} if changeset prediction fails (dry run) or publish fails (real)
|
|
387
385
|
*/
|
|
@@ -426,7 +424,7 @@ const publish_single_repo = async (
|
|
|
426
424
|
const publish_result = await ops.process.spawn({
|
|
427
425
|
cmd: 'gro',
|
|
428
426
|
args: ['publish', '--no-build'],
|
|
429
|
-
|
|
427
|
+
cwd: repo.repo_dir,
|
|
430
428
|
});
|
|
431
429
|
|
|
432
430
|
if (!publish_result.ok) {
|
|
@@ -34,9 +34,9 @@ const is_etarget_error = (message: string, stderr: string): boolean => {
|
|
|
34
34
|
* npm's local cache may still have stale "404" metadata. This healing
|
|
35
35
|
* strategy clears the cache to force fresh metadata fetch.
|
|
36
36
|
*
|
|
37
|
-
* @param repo -
|
|
38
|
-
* @param ops -
|
|
39
|
-
* @param log -
|
|
37
|
+
* @param repo - the repository to install dependencies for
|
|
38
|
+
* @param ops - gitops operations (for dependency injection)
|
|
39
|
+
* @param log - optional logger
|
|
40
40
|
* @throws Error if install fails (with details about cache healing attempts)
|
|
41
41
|
*/
|
|
42
42
|
export const install_with_cache_healing = async (
|
package/src/lib/npm_registry.ts
CHANGED
|
@@ -48,10 +48,10 @@ export const check_package_available = async (
|
|
|
48
48
|
* Critical for multi-repo publishing: ensures published packages are available
|
|
49
49
|
* before updating dependent packages.
|
|
50
50
|
*
|
|
51
|
-
* @param options.max_attempts max poll attempts (default 30)
|
|
52
|
-
* @param options.initial_delay starting delay in ms (default 1000)
|
|
53
|
-
* @param options.max_delay max delay between attempts (default 60000)
|
|
54
|
-
* @param options.timeout total timeout in ms (default 300000 = 5min)
|
|
51
|
+
* @param options.max_attempts - max poll attempts (default 30)
|
|
52
|
+
* @param options.initial_delay - starting delay in ms (default 1000)
|
|
53
|
+
* @param options.max_delay - max delay between attempts (default 60000)
|
|
54
|
+
* @param options.timeout - total timeout in ms (default 300000 = 5min)
|
|
55
55
|
* @throws {Error} if timeout reached or max attempts exceeded
|
|
56
56
|
*/
|
|
57
57
|
export const wait_for_package = async (
|
|
@@ -80,7 +80,7 @@ export const wait_for_package = async (
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
// Check if package is available
|
|
83
|
-
|
|
83
|
+
|
|
84
84
|
if (await check_package_available(pkg, version, {log})) {
|
|
85
85
|
log?.info(st('green', ` ✓ ${pkg}@${version} is now available on NPM`));
|
|
86
86
|
return;
|
|
@@ -94,7 +94,7 @@ export const wait_for_package = async (
|
|
|
94
94
|
// Wait with exponential backoff + jitter
|
|
95
95
|
const jitter = Math.random() * delay * 0.1; // 10% jitter
|
|
96
96
|
const actual_delay = Math.min(delay + jitter, max_delay);
|
|
97
|
-
await wait(actual_delay);
|
|
97
|
+
await wait(actual_delay);
|
|
98
98
|
|
|
99
99
|
// Exponential backoff
|
|
100
100
|
delay = Math.min(delay * 1.5, max_delay);
|