@fuzdev/fuz_gitops 0.68.0 → 0.69.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/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 +6 -6
- 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/gitops_plan.task.d.ts +3 -3
- package/dist/gitops_plan.task.js +3 -3
- package/dist/gitops_run.task.js +1 -1
- 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.js +12 -9
- 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 +3 -3
- 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 +12 -12
- 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 -1
- package/src/lib/gitops_plan.task.ts +3 -3
- package/src/lib/gitops_run.task.ts +1 -1
- package/src/lib/gitops_task_helpers.ts +5 -5
- package/src/lib/graph_validation.ts +5 -5
- package/src/lib/local_repo.ts +18 -11
- 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 +3 -3
- package/src/lib/repo_ops.ts +7 -7
- package/src/lib/version_utils.ts +2 -2
|
@@ -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;
|
|
@@ -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
|
*/
|
|
@@ -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
|
@@ -5,7 +5,7 @@ import {existsSync} from 'node:fs';
|
|
|
5
5
|
import {join} from 'node:path';
|
|
6
6
|
import {TaskError} from '@fuzdev/gro';
|
|
7
7
|
import type {Logger} from '@fuzdev/fuz_util/log.js';
|
|
8
|
-
import {
|
|
8
|
+
import {spawn_out} from '@fuzdev/fuz_util/process.js';
|
|
9
9
|
import {map_concurrent_settled} from '@fuzdev/fuz_util/async.js';
|
|
10
10
|
import type {GitOperations, NpmOperations} from './operations.js';
|
|
11
11
|
import {default_git_operations, default_npm_operations} from './operations_defaults.js';
|
|
@@ -15,8 +15,8 @@ import type {ResolvedGitopsConfig} from './resolved_gitops_config.js';
|
|
|
15
15
|
import {GITOPS_CONCURRENCY_DEFAULT} from './gitops_constants.js';
|
|
16
16
|
|
|
17
17
|
/**
|
|
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.
|
|
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.
|
|
20
20
|
*/
|
|
21
21
|
export interface LocalRepo {
|
|
22
22
|
library: Library;
|
|
@@ -61,14 +61,14 @@ export interface LocalRepoMissing {
|
|
|
61
61
|
* 2. Switches to target branch if needed (requires clean workspace)
|
|
62
62
|
* 3. Pulls latest changes from remote (skipped for local-only repos)
|
|
63
63
|
* 4. Validates workspace is clean after pull
|
|
64
|
-
* 5. Auto-installs dependencies if package.json changed
|
|
65
|
-
* 6. Imports library_json from src/routes/library.ts
|
|
66
|
-
* 7. Creates Library and extracts dependency maps
|
|
64
|
+
* 5. Auto-installs dependencies if `package.json` changed
|
|
65
|
+
* 6. Imports `library_json` from `src/routes/library.ts`
|
|
66
|
+
* 7. Creates `Library` and extracts dependency maps
|
|
67
67
|
*
|
|
68
68
|
* This ensures repos are always in sync with their configured branch
|
|
69
69
|
* before being used by gitops commands.
|
|
70
70
|
*
|
|
71
|
-
* @throws {TaskError} if workspace dirty, branch switch fails, install fails, or library.ts missing
|
|
71
|
+
* @throws {TaskError} if workspace dirty, branch switch fails, install fails, or `library.ts` missing
|
|
72
72
|
*/
|
|
73
73
|
export const local_repo_load = async ({
|
|
74
74
|
local_repo_path,
|
|
@@ -296,7 +296,7 @@ export const local_repos_load = async ({
|
|
|
296
296
|
// Sequential loading (original behavior)
|
|
297
297
|
const loaded: Array<LocalRepo> = [];
|
|
298
298
|
for (const local_repo_path of local_repo_paths) {
|
|
299
|
-
loaded.push(await local_repo_load({local_repo_path, log, git_ops, npm_ops}));
|
|
299
|
+
loaded.push(await local_repo_load({local_repo_path, log, git_ops, npm_ops}));
|
|
300
300
|
}
|
|
301
301
|
return loaded;
|
|
302
302
|
}
|
|
@@ -383,14 +383,21 @@ const download_repos = async ({
|
|
|
383
383
|
const resolved: Array<LocalRepoPath> = [];
|
|
384
384
|
for (const {repo_config, repo_git_ssh_url} of local_repos_missing) {
|
|
385
385
|
log?.info(`cloning repo ${repo_git_ssh_url} to ${repos_dir}`);
|
|
386
|
-
await
|
|
386
|
+
const clone_result = await spawn_out('git', ['clone', repo_git_ssh_url], {cwd: repos_dir});
|
|
387
|
+
if (!clone_result.result.ok) {
|
|
388
|
+
throw new TaskError(
|
|
389
|
+
`Failed to clone repo ${repo_git_ssh_url} to ${repos_dir}${clone_result.stderr ? ': ' + clone_result.stderr.trim() : ''}`,
|
|
390
|
+
);
|
|
391
|
+
}
|
|
387
392
|
const local_repo = local_repo_locate({repo_config, repos_dir});
|
|
388
393
|
if (local_repo.type === 'local_repo_missing') {
|
|
389
|
-
throw new TaskError(
|
|
394
|
+
throw new TaskError(
|
|
395
|
+
`Failed to clone repo ${repo_git_ssh_url} to ${repos_dir}: directory not found after clone`,
|
|
396
|
+
);
|
|
390
397
|
}
|
|
391
398
|
// Always install dependencies after cloning
|
|
392
399
|
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});
|
|
400
|
+
const install_result = await npm_ops.install({cwd: local_repo.repo_dir});
|
|
394
401
|
if (!install_result.ok) {
|
|
395
402
|
throw new TaskError(
|
|
396
403
|
`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);
|
package/src/lib/operations.ts
CHANGED
|
@@ -29,14 +29,14 @@
|
|
|
29
29
|
* ```
|
|
30
30
|
*
|
|
31
31
|
* See `operations_defaults.ts` for real implementations.
|
|
32
|
-
* See test files (
|
|
32
|
+
* See test files (`*.test.ts`) for mock implementations.
|
|
33
33
|
*
|
|
34
34
|
* @module
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
37
|
import type {Result} from '@fuzdev/fuz_util/result.js';
|
|
38
|
+
import type {FsError} from '@fuzdev/fuz_util/fs.js';
|
|
38
39
|
import type {Logger} from '@fuzdev/fuz_util/log.js';
|
|
39
|
-
import type {SpawnOptions} from 'node:child_process';
|
|
40
40
|
import type {LocalRepo} from './local_repo.js';
|
|
41
41
|
import type {ChangesetInfo} from './changeset_reader.js';
|
|
42
42
|
import type {BumpType} from './semver.js';
|
|
@@ -67,7 +67,7 @@ export interface ChangesetOperations {
|
|
|
67
67
|
/**
|
|
68
68
|
* Predicts the next version based on changesets.
|
|
69
69
|
* Returns null if no changesets found (expected, not an error).
|
|
70
|
-
* Returns error Result if changesets exist but can't be read/parsed.
|
|
70
|
+
* Returns error `Result` if changesets exist but can't be read/parsed.
|
|
71
71
|
*/
|
|
72
72
|
predict_next_version: (options: {
|
|
73
73
|
repo: LocalRepo;
|
|
@@ -161,9 +161,13 @@ export interface GitOperations {
|
|
|
161
161
|
has_changes: (options?: {cwd?: string}) => Promise<Result<{value: boolean}, {message: string}>>;
|
|
162
162
|
|
|
163
163
|
/**
|
|
164
|
-
*
|
|
164
|
+
* Lists uncommitted files in the working tree (`git diff --name-only HEAD`).
|
|
165
|
+
*
|
|
166
|
+
* Renamed from `get_changed_files` in 2026-04 because "changed files" collided
|
|
167
|
+
* with mageguild's `get_changed_files` which diffs two refs. This one reports
|
|
168
|
+
* uncommitted working-tree changes relative to HEAD.
|
|
165
169
|
*/
|
|
166
|
-
|
|
170
|
+
list_uncommitted_files: (options?: {
|
|
167
171
|
cwd?: string;
|
|
168
172
|
}) => Promise<Result<{value: Array<string>}, {message: string}>>;
|
|
169
173
|
|
|
@@ -216,7 +220,7 @@ export interface ProcessOperations {
|
|
|
216
220
|
spawn: (options: {
|
|
217
221
|
cmd: string;
|
|
218
222
|
args: Array<string>;
|
|
219
|
-
|
|
223
|
+
cwd?: string;
|
|
220
224
|
}) => Promise<Result<{stdout?: string; stderr?: string}, {message: string; stderr?: string}>>;
|
|
221
225
|
}
|
|
222
226
|
|
|
@@ -225,7 +229,7 @@ export interface ProcessOperations {
|
|
|
225
229
|
*/
|
|
226
230
|
export interface BuildOperations {
|
|
227
231
|
/**
|
|
228
|
-
* Builds a package using gro build
|
|
232
|
+
* Builds a package using `gro build`.
|
|
229
233
|
*/
|
|
230
234
|
build_package: (options: {
|
|
231
235
|
repo: LocalRepo;
|
|
@@ -302,6 +306,10 @@ export interface PreflightOperations {
|
|
|
302
306
|
|
|
303
307
|
/**
|
|
304
308
|
* File system operations for reading and writing files.
|
|
309
|
+
*
|
|
310
|
+
* Errors are typed via `FsError` (`not_found | permission_denied |
|
|
311
|
+
* already_exists | io_error`) so callers can branch on `kind` instead of
|
|
312
|
+
* regex-matching `message`. See `@fuzdev/fuz_util/fs.js`.
|
|
305
313
|
*/
|
|
306
314
|
export interface FsOperations {
|
|
307
315
|
/**
|
|
@@ -310,28 +318,22 @@ export interface FsOperations {
|
|
|
310
318
|
readFile: (options: {
|
|
311
319
|
path: string;
|
|
312
320
|
encoding: BufferEncoding;
|
|
313
|
-
}) => Promise<Result<{value: string},
|
|
321
|
+
}) => Promise<Result<{value: string}, FsError>>;
|
|
314
322
|
|
|
315
323
|
/**
|
|
316
324
|
* Writes a file to the file system.
|
|
317
325
|
*/
|
|
318
|
-
writeFile: (options: {
|
|
319
|
-
path: string;
|
|
320
|
-
content: string;
|
|
321
|
-
}) => Promise<Result<object, {message: string}>>;
|
|
326
|
+
writeFile: (options: {path: string; content: string}) => Promise<Result<object, FsError>>;
|
|
322
327
|
|
|
323
328
|
/**
|
|
324
329
|
* Creates a directory, optionally with recursive creation.
|
|
325
330
|
*/
|
|
326
|
-
mkdir: (options: {
|
|
327
|
-
path: string;
|
|
328
|
-
recursive?: boolean;
|
|
329
|
-
}) => Promise<Result<object, {message: string}>>;
|
|
331
|
+
mkdir: (options: {path: string; recursive?: boolean}) => Promise<Result<object, FsError>>;
|
|
330
332
|
|
|
331
333
|
/**
|
|
332
334
|
* Checks if a path exists on the file system.
|
|
333
335
|
*/
|
|
334
|
-
exists: (options: {path: string}) => boolean
|
|
336
|
+
exists: (options: {path: string}) => Promise<boolean>;
|
|
335
337
|
}
|
|
336
338
|
|
|
337
339
|
/**
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
import {readFile, writeFile, mkdir} from 'node:fs/promises';
|
|
12
|
-
import {existsSync} from 'node:fs';
|
|
10
|
+
import {spawn_out} from '@fuzdev/fuz_util/process.js';
|
|
11
|
+
import {readFile, writeFile, mkdir, stat} from 'node:fs/promises';
|
|
13
12
|
import {git_checkout, type GitBranch, type GitOrigin} from '@fuzdev/fuz_util/git.js';
|
|
13
|
+
import {fs_classify_error} from '@fuzdev/fuz_util/fs.js';
|
|
14
14
|
import {EMPTY_OBJECT} from '@fuzdev/fuz_util/object.js';
|
|
15
15
|
|
|
16
16
|
import {has_changesets, read_changesets, predict_next_version} from './changeset_reader.js';
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
git_tag,
|
|
24
24
|
git_push_tag,
|
|
25
25
|
git_has_changes,
|
|
26
|
-
|
|
26
|
+
git_list_uncommitted_files,
|
|
27
27
|
git_has_file_changed,
|
|
28
28
|
git_stash,
|
|
29
29
|
git_stash_pop,
|
|
@@ -118,9 +118,20 @@ export const default_git_operations: GitOperations = {
|
|
|
118
118
|
|
|
119
119
|
pull: async (options) => {
|
|
120
120
|
const {origin, branch, cwd} = options ?? EMPTY_OBJECT;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
try {
|
|
122
|
+
const spawned = await spawn_out(
|
|
123
|
+
'git',
|
|
124
|
+
['pull', origin || 'origin', branch || ''],
|
|
125
|
+
cwd ? {cwd} : undefined,
|
|
126
|
+
);
|
|
127
|
+
if (spawned.result.ok) {
|
|
128
|
+
return {ok: true};
|
|
129
|
+
} else {
|
|
130
|
+
return {ok: false, message: spawned.stderr || 'Pull failed'};
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
return {ok: false, message: String(error)};
|
|
134
|
+
}
|
|
124
135
|
},
|
|
125
136
|
|
|
126
137
|
switch_branch: async (options) => {
|
|
@@ -154,9 +165,9 @@ export const default_git_operations: GitOperations = {
|
|
|
154
165
|
return wrap_with_value(() => git_has_changes(cwd ? {cwd} : undefined));
|
|
155
166
|
},
|
|
156
167
|
|
|
157
|
-
|
|
168
|
+
list_uncommitted_files: async (options) => {
|
|
158
169
|
const {cwd} = options ?? EMPTY_OBJECT;
|
|
159
|
-
return wrap_with_value(() =>
|
|
170
|
+
return wrap_with_value(() => git_list_uncommitted_files(cwd ? {cwd} : undefined));
|
|
160
171
|
},
|
|
161
172
|
|
|
162
173
|
// Tagging
|
|
@@ -192,9 +203,9 @@ export const default_git_operations: GitOperations = {
|
|
|
192
203
|
|
|
193
204
|
export const default_process_operations: ProcessOperations = {
|
|
194
205
|
spawn: async (options) => {
|
|
195
|
-
const {cmd, args,
|
|
206
|
+
const {cmd, args, cwd} = options;
|
|
196
207
|
try {
|
|
197
|
-
const spawned = await spawn_out(cmd, args,
|
|
208
|
+
const spawned = await spawn_out(cmd, args, cwd ? {cwd} : undefined);
|
|
198
209
|
if (spawned.result.ok) {
|
|
199
210
|
return {
|
|
200
211
|
ok: true,
|
|
@@ -294,21 +305,41 @@ export const default_preflight_operations: PreflightOperations = {
|
|
|
294
305
|
export const default_fs_operations: FsOperations = {
|
|
295
306
|
readFile: async (options) => {
|
|
296
307
|
const {path, encoding} = options;
|
|
297
|
-
|
|
308
|
+
try {
|
|
309
|
+
const value = await readFile(path, encoding);
|
|
310
|
+
return {ok: true, value};
|
|
311
|
+
} catch (error) {
|
|
312
|
+
return {ok: false, ...fs_classify_error(error)};
|
|
313
|
+
}
|
|
298
314
|
},
|
|
299
315
|
|
|
300
316
|
writeFile: async (options) => {
|
|
301
317
|
const {path, content} = options;
|
|
302
|
-
|
|
318
|
+
try {
|
|
319
|
+
await writeFile(path, content);
|
|
320
|
+
return {ok: true};
|
|
321
|
+
} catch (error) {
|
|
322
|
+
return {ok: false, ...fs_classify_error(error)};
|
|
323
|
+
}
|
|
303
324
|
},
|
|
304
325
|
|
|
305
326
|
mkdir: async (options) => {
|
|
306
327
|
const {path, recursive} = options;
|
|
307
|
-
|
|
328
|
+
try {
|
|
329
|
+
await mkdir(path, {recursive});
|
|
330
|
+
return {ok: true};
|
|
331
|
+
} catch (error) {
|
|
332
|
+
return {ok: false, ...fs_classify_error(error)};
|
|
333
|
+
}
|
|
308
334
|
},
|
|
309
335
|
|
|
310
|
-
exists: (options) => {
|
|
311
|
-
|
|
336
|
+
exists: async (options) => {
|
|
337
|
+
try {
|
|
338
|
+
await stat(options.path);
|
|
339
|
+
return true;
|
|
340
|
+
} catch {
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
312
343
|
},
|
|
313
344
|
};
|
|
314
345
|
|
|
@@ -22,11 +22,11 @@ export interface OutputFormatters<T> {
|
|
|
22
22
|
* Formats data and outputs to file or stdout based on options.
|
|
23
23
|
*
|
|
24
24
|
* Supports three formats:
|
|
25
|
-
* - stdout: Uses logger for colored/styled output (cannot use with
|
|
25
|
+
* - stdout: Uses logger for colored/styled output (cannot use with `--outfile`)
|
|
26
26
|
* - json: Stringified JSON
|
|
27
27
|
* - markdown: Formatted markdown text
|
|
28
28
|
*
|
|
29
|
-
* @throws {Error} if stdout format used with outfile
|
|
29
|
+
* @throws {Error} if stdout format used with `outfile`, or if logger missing for stdout
|
|
30
30
|
*/
|
|
31
31
|
export const format_and_output = async <T>(
|
|
32
32
|
data: T,
|
package/src/lib/paths.ts
CHANGED
|
@@ -6,6 +6,6 @@ export const GITOPS_OUTPUT_DIR = '.gro/fuz_gitops';
|
|
|
6
6
|
/**
|
|
7
7
|
* Default repos directory relative to gitops config file.
|
|
8
8
|
* Resolves to the parent of the directory with the config
|
|
9
|
-
* (e.g.,
|
|
9
|
+
* (e.g., `~/dev/repo/gitops.config.ts` resolves to `~/dev/`).
|
|
10
10
|
*/
|
|
11
11
|
export const DEFAULT_REPOS_DIR = '..';
|