@fuzdev/fuz_gitops 0.78.1 → 0.79.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 +78 -24
- package/dist/ModulesDetail.svelte +15 -22
- package/dist/ModulesDetail.svelte.d.ts.map +1 -1
- package/dist/ModulesNav.svelte +4 -1
- package/dist/ModulesPage.svelte +1 -3
- package/dist/ModulesPage.svelte.d.ts.map +1 -1
- package/dist/PageHeader.svelte +2 -2
- package/dist/PageHeader.svelte.d.ts +0 -3
- package/dist/PageHeader.svelte.d.ts.map +1 -1
- package/dist/PullRequestsDetail.svelte +10 -9
- package/dist/PullRequestsDetail.svelte.d.ts.map +1 -1
- package/dist/PullRequestsPage.svelte +1 -3
- package/dist/PullRequestsPage.svelte.d.ts.map +1 -1
- package/dist/ReposTable.svelte +27 -48
- package/dist/ReposTable.svelte.d.ts.map +1 -1
- package/dist/ReposTree.svelte +5 -19
- package/dist/ReposTree.svelte.d.ts.map +1 -1
- package/dist/ReposTreeNav.svelte +5 -7
- package/dist/ReposTreeNav.svelte.d.ts.map +1 -1
- package/dist/TablePage.svelte +1 -3
- package/dist/TablePage.svelte.d.ts.map +1 -1
- package/dist/TreeItemPage.svelte +1 -3
- package/dist/TreeItemPage.svelte.d.ts.map +1 -1
- package/dist/TreePage.svelte +1 -3
- package/dist/TreePage.svelte.d.ts.map +1 -1
- package/dist/ci_reconcile.d.ts +6 -10
- package/dist/ci_reconcile.d.ts.map +1 -1
- package/dist/ci_reconcile.js +6 -8
- package/dist/dependency_graph.d.ts +26 -49
- package/dist/dependency_graph.d.ts.map +1 -1
- package/dist/dependency_graph.js +37 -80
- package/dist/dependency_updater.d.ts +1 -1
- package/dist/dependency_updater.d.ts.map +1 -1
- package/dist/dependency_updater.js +6 -1
- package/dist/fetch_repo_data.d.ts +34 -8
- package/dist/fetch_repo_data.d.ts.map +1 -1
- package/dist/fetch_repo_data.js +44 -22
- package/dist/fs_fetch_value_cache.d.ts +1 -1
- package/dist/fs_fetch_value_cache.js +1 -1
- package/dist/git_operations.d.ts +6 -49
- package/dist/git_operations.d.ts.map +1 -1
- package/dist/git_operations.js +13 -126
- package/dist/github.d.ts +17 -2
- package/dist/github.d.ts.map +1 -1
- package/dist/github.js +15 -6
- package/dist/github_helpers.d.ts.map +1 -1
- package/dist/github_helpers.js +3 -11
- package/dist/gitops_analyze.task.d.ts +24 -2
- package/dist/gitops_analyze.task.d.ts.map +1 -1
- package/dist/gitops_analyze.task.js +43 -30
- package/dist/gitops_config.d.ts +34 -79
- package/dist/gitops_config.d.ts.map +1 -1
- package/dist/gitops_config.js +76 -53
- package/dist/gitops_constants.d.ts +6 -4
- package/dist/gitops_constants.d.ts.map +1 -1
- package/dist/gitops_constants.js +6 -4
- package/dist/gitops_plan.task.d.ts +29 -3
- package/dist/gitops_plan.task.d.ts.map +1 -1
- package/dist/gitops_plan.task.js +80 -58
- package/dist/gitops_publish.task.d.ts +62 -2
- package/dist/gitops_publish.task.d.ts.map +1 -1
- package/dist/gitops_publish.task.js +187 -102
- package/dist/gitops_run.task.d.ts +74 -0
- package/dist/gitops_run.task.d.ts.map +1 -1
- package/dist/gitops_run.task.js +174 -160
- package/dist/gitops_sync.task.d.ts +65 -3
- package/dist/gitops_sync.task.d.ts.map +1 -1
- package/dist/gitops_sync.task.js +154 -30
- package/dist/gitops_task_helpers.d.ts +86 -59
- package/dist/gitops_task_helpers.d.ts.map +1 -1
- package/dist/gitops_task_helpers.js +120 -79
- package/dist/gitops_validate.task.d.ts +1 -2
- package/dist/gitops_validate.task.d.ts.map +1 -1
- package/dist/gitops_validate.task.js +15 -22
- package/dist/graph_validation.d.ts +16 -27
- package/dist/graph_validation.d.ts.map +1 -1
- package/dist/graph_validation.js +23 -64
- package/dist/local_repo.d.ts +50 -62
- package/dist/local_repo.d.ts.map +1 -1
- package/dist/local_repo.js +80 -193
- package/dist/log_helpers.d.ts +8 -8
- package/dist/log_helpers.d.ts.map +1 -1
- package/dist/multi_repo_publisher.d.ts +10 -0
- package/dist/multi_repo_publisher.d.ts.map +1 -1
- package/dist/multi_repo_publisher.js +115 -51
- package/dist/npm_registry.d.ts +23 -19
- package/dist/npm_registry.d.ts.map +1 -1
- package/dist/npm_registry.js +20 -47
- package/dist/operations.d.ts +75 -180
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +15 -7
- package/dist/operations_defaults.d.ts +17 -2
- package/dist/operations_defaults.d.ts.map +1 -1
- package/dist/operations_defaults.js +104 -121
- package/dist/output_helpers.d.ts +36 -0
- package/dist/output_helpers.d.ts.map +1 -1
- package/dist/output_helpers.js +37 -3
- package/dist/preflight_checks.d.ts +20 -27
- package/dist/preflight_checks.d.ts.map +1 -1
- package/dist/preflight_checks.js +37 -126
- package/dist/publish_steps.d.ts +2 -4
- package/dist/publish_steps.d.ts.map +1 -1
- package/dist/publish_steps.js +2 -6
- package/dist/publishing_event.d.ts +2 -8
- package/dist/publishing_event.d.ts.map +1 -1
- package/dist/publishing_event.js +5 -4
- package/dist/publishing_event_handler.d.ts +10 -8
- package/dist/publishing_event_handler.d.ts.map +1 -1
- package/dist/publishing_event_handler.js +14 -12
- package/dist/publishing_plan.d.ts +16 -1
- package/dist/publishing_plan.d.ts.map +1 -1
- package/dist/publishing_plan.js +328 -250
- package/dist/publishing_plan_logging.d.ts +1 -1
- package/dist/publishing_plan_logging.d.ts.map +1 -1
- package/dist/publishing_plan_logging.js +21 -14
- package/dist/repo.svelte.d.ts +7 -0
- package/dist/repo.svelte.d.ts.map +1 -1
- package/dist/repo.svelte.js +3 -0
- package/dist/repo_readiness.d.ts +210 -0
- package/dist/repo_readiness.d.ts.map +1 -0
- package/dist/repo_readiness.js +516 -0
- package/dist/repos_status.d.ts +3340 -0
- package/dist/repos_status.d.ts.map +1 -0
- package/dist/repos_status.js +555 -0
- package/dist/repos_status_load.d.ts +65 -0
- package/dist/repos_status_load.d.ts.map +1 -0
- package/dist/repos_status_load.js +131 -0
- package/package.json +7 -27
- package/src/lib/ci_reconcile.ts +7 -11
- package/src/lib/dependency_graph.ts +45 -97
- package/src/lib/dependency_updater.ts +7 -1
- package/src/lib/fetch_repo_data.ts +66 -29
- package/src/lib/fs_fetch_value_cache.ts +1 -1
- package/src/lib/git_operations.ts +15 -187
- package/src/lib/github.ts +20 -8
- package/src/lib/github_helpers.ts +5 -12
- package/src/lib/gitops_analyze.task.ts +79 -41
- package/src/lib/gitops_config.ts +88 -137
- package/src/lib/gitops_constants.ts +6 -4
- package/src/lib/gitops_plan.task.ts +113 -61
- package/src/lib/gitops_publish.task.ts +255 -122
- package/src/lib/gitops_run.task.ts +250 -178
- package/src/lib/gitops_sync.task.ts +208 -33
- package/src/lib/gitops_task_helpers.ts +166 -110
- package/src/lib/gitops_validate.task.ts +17 -27
- package/src/lib/graph_validation.ts +25 -86
- package/src/lib/local_repo.ts +109 -316
- package/src/lib/log_helpers.ts +8 -18
- package/src/lib/multi_repo_publisher.ts +138 -53
- package/src/lib/npm_registry.ts +37 -62
- package/src/lib/operations.ts +78 -150
- package/src/lib/operations_defaults.ts +108 -154
- package/src/lib/output_helpers.ts +55 -3
- package/src/lib/preflight_checks.ts +47 -183
- package/src/lib/publish_steps.ts +15 -13
- package/src/lib/publishing_event.ts +5 -4
- package/src/lib/publishing_event_handler.ts +18 -16
- package/src/lib/publishing_plan.ts +451 -287
- package/src/lib/publishing_plan_logging.ts +32 -23
- package/src/lib/repo.svelte.ts +8 -0
- package/src/lib/repo_readiness.ts +647 -0
- package/src/lib/repos_status.ts +671 -0
- package/src/lib/repos_status_load.ts +173 -0
- package/dist/config_reconcile.d.ts +0 -46
- package/dist/config_reconcile.d.ts.map +0 -1
- package/dist/config_reconcile.js +0 -64
- package/dist/paths.d.ts +0 -7
- package/dist/paths.d.ts.map +0 -1
- package/dist/paths.js +0 -6
- package/dist/repo_ops.d.ts +0 -59
- package/dist/repo_ops.d.ts.map +0 -1
- package/dist/repo_ops.js +0 -170
- package/dist/resolved_gitops_config.d.ts +0 -9
- package/dist/resolved_gitops_config.d.ts.map +0 -1
- package/dist/resolved_gitops_config.js +0 -12
- package/src/lib/config_reconcile.ts +0 -98
- package/src/lib/paths.ts +0 -6
- package/src/lib/repo_ops.ts +0 -216
- package/src/lib/resolved_gitops_config.ts +0 -27
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { TaskError, type Task } from '@fuzdev/gro';
|
|
2
|
+
import type { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import { map_concurrent_settled } from '@fuzdev/fuz_util/async.ts';
|
|
4
5
|
import { spawn_out } from '@fuzdev/fuz_util/process.ts';
|
|
6
|
+
import { to_error_message } from '@fuzdev/fuz_util/error.ts';
|
|
5
7
|
import { writeFile } from 'node:fs/promises';
|
|
6
8
|
import { styleText as st } from 'node:util';
|
|
7
|
-
import { resolve } from 'node:path';
|
|
8
9
|
|
|
9
|
-
import {
|
|
10
|
+
import { resolve_gitops_repos } from './gitops_task_helpers.ts';
|
|
10
11
|
import { GITOPS_CONCURRENCY_DEFAULT, GITOPS_CONFIG_PATH_DEFAULT } from './gitops_constants.ts';
|
|
12
|
+
import type { ReposOperations } from './operations.ts';
|
|
13
|
+
import { default_repos_operations } from './operations_defaults.ts';
|
|
14
|
+
import { output_is_machine, route_human_output } from './output_helpers.ts';
|
|
11
15
|
|
|
16
|
+
/** @nodocs */
|
|
12
17
|
export const Args = z.strictObject({
|
|
13
18
|
// Positional rest args (gro convention) so `gro gitops_run "npm test"` works;
|
|
14
19
|
// joined with spaces and passed to `sh -c`, so quote commands that contain flags.
|
|
@@ -17,6 +22,13 @@ export const Args = z.strictObject({
|
|
|
17
22
|
.string()
|
|
18
23
|
.meta({ description: 'path to the gitops config file' })
|
|
19
24
|
.default(GITOPS_CONFIG_PATH_DEFAULT),
|
|
25
|
+
registry: z
|
|
26
|
+
.string()
|
|
27
|
+
.meta({
|
|
28
|
+
description:
|
|
29
|
+
'path to the repos.toml registry, when `repos` would not find it walking up from the cwd'
|
|
30
|
+
})
|
|
31
|
+
.optional(),
|
|
20
32
|
concurrency: z
|
|
21
33
|
.number()
|
|
22
34
|
.int()
|
|
@@ -31,216 +43,276 @@ export const Args = z.strictObject({
|
|
|
31
43
|
});
|
|
32
44
|
export type Args = z.infer<typeof Args>;
|
|
33
45
|
|
|
34
|
-
|
|
46
|
+
/**
|
|
47
|
+
* How a command run in one repo ended: it exited with a code, a signal killed
|
|
48
|
+
* it, or it never started.
|
|
49
|
+
*
|
|
50
|
+
* @nodocs
|
|
51
|
+
*/
|
|
52
|
+
export type GitopsRunOutcome =
|
|
53
|
+
| { kind: 'exited'; code: number }
|
|
54
|
+
| { kind: 'signaled'; signal: string }
|
|
55
|
+
| { kind: 'error'; message: string };
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* What a command run in one repo printed, and how it ended.
|
|
59
|
+
*
|
|
60
|
+
* @nodocs
|
|
61
|
+
*/
|
|
62
|
+
export interface GitopsRunCommandOutput {
|
|
63
|
+
outcome: GitopsRunOutcome;
|
|
64
|
+
stdout: string;
|
|
65
|
+
stderr: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* One repo's result, as the JSON output carries it.
|
|
70
|
+
*
|
|
71
|
+
* @nodocs
|
|
72
|
+
*/
|
|
73
|
+
export interface GitopsRunResult {
|
|
35
74
|
repo_name: string;
|
|
36
75
|
repo_dir: string;
|
|
37
76
|
status: 'success' | 'failure';
|
|
38
|
-
|
|
77
|
+
/** The command's exit code; `null` when a signal killed it or it never started. */
|
|
78
|
+
exit_code: number | null;
|
|
79
|
+
/** The signal that killed the command, else `null`. */
|
|
80
|
+
signal: string | null;
|
|
39
81
|
stdout: string;
|
|
40
82
|
stderr: string;
|
|
41
83
|
duration_ms: number;
|
|
84
|
+
/** Why the command never started. */
|
|
42
85
|
error?: string;
|
|
43
86
|
}
|
|
44
87
|
|
|
88
|
+
/**
|
|
89
|
+
* The side effects `run_gitops_run` reaches through, injectable for tests.
|
|
90
|
+
*
|
|
91
|
+
* @nodocs
|
|
92
|
+
*/
|
|
93
|
+
export interface GitopsRunDeps {
|
|
94
|
+
/** Runs `repos status` to resolve the config's repos. */
|
|
95
|
+
repos_ops: ReposOperations;
|
|
96
|
+
/** Runs `command` through `sh -c` in `cwd`, capturing its output. */
|
|
97
|
+
run_command: (options: { command: string; cwd: string }) => Promise<GitopsRunCommandOutput>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const default_gitops_run_deps: GitopsRunDeps = {
|
|
101
|
+
repos_ops: default_repos_operations,
|
|
102
|
+
run_command: async ({ command, cwd }) => {
|
|
103
|
+
// through a shell, so pipes, redirects, and globs work
|
|
104
|
+
const { result, stdout, stderr } = await spawn_out('sh', ['-c', command], { cwd });
|
|
105
|
+
const outcome: GitopsRunOutcome =
|
|
106
|
+
result.kind === 'exited'
|
|
107
|
+
? { kind: 'exited', code: result.code }
|
|
108
|
+
: result.kind === 'signaled'
|
|
109
|
+
? { kind: 'signaled', signal: result.signal }
|
|
110
|
+
: { kind: 'error', message: to_error_message(result.error) };
|
|
111
|
+
return { outcome, stdout: stdout ?? '', stderr: stderr ?? '' };
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/** @nodocs */
|
|
45
116
|
export const task: Task<Args> = {
|
|
46
117
|
Args,
|
|
47
118
|
summary: 'run a shell command across all repos in parallel',
|
|
48
119
|
run: async ({ args, log }) => {
|
|
49
|
-
|
|
120
|
+
await run_gitops_run(args, log);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
50
123
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Runs `gro gitops_run`: resolves the config's repos through `repos status`,
|
|
126
|
+
* refusing a missing one, runs the command in each, `concurrency` at a time,
|
|
127
|
+
* and reports every result. Under `--format json` without `--outfile`, the log
|
|
128
|
+
* goes to stderr and stdout carries the JSON alone (`route_human_output`).
|
|
129
|
+
*
|
|
130
|
+
* @throws {TaskError} when no command is given, resolving the repos fails, or the command fails in any repo (after reporting)
|
|
131
|
+
* @nodocs
|
|
132
|
+
*/
|
|
133
|
+
export const run_gitops_run = async (
|
|
134
|
+
args: Args,
|
|
135
|
+
log: Logger,
|
|
136
|
+
deps: Partial<GitopsRunDeps> = {}
|
|
137
|
+
): Promise<void> => {
|
|
138
|
+
const { repos_ops, run_command } = { ...default_gitops_run_deps, ...deps };
|
|
139
|
+
const { _, config, registry, concurrency, format, outfile } = args;
|
|
140
|
+
const write_stdout = route_human_output(log, output_is_machine(format, outfile));
|
|
55
141
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
142
|
+
const command = _.join(' ').trim();
|
|
143
|
+
if (!command) {
|
|
144
|
+
throw new TaskError('No command provided, e.g. `gro gitops_run "npm test"`');
|
|
145
|
+
}
|
|
59
146
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
147
|
+
// Resolve repo paths through `repos status` (no library-metadata loading needed);
|
|
148
|
+
// a configured repo that's missing or not a repo fails the run, naming it
|
|
149
|
+
const { local_repo_paths: repos } = await resolve_gitops_repos({
|
|
150
|
+
config,
|
|
151
|
+
registry,
|
|
152
|
+
log,
|
|
153
|
+
repos_ops
|
|
154
|
+
});
|
|
63
155
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
156
|
+
log.info(
|
|
157
|
+
`Running ${st('cyan', command)} across ${repos.length} repos (concurrency: ${concurrency})`
|
|
158
|
+
);
|
|
67
159
|
|
|
68
|
-
|
|
160
|
+
const start_time = performance.now();
|
|
69
161
|
|
|
70
|
-
|
|
71
|
-
|
|
162
|
+
const settled = await map_concurrent_settled(
|
|
163
|
+
repos,
|
|
164
|
+
concurrency,
|
|
165
|
+
async ({ repo_name, repo_dir }): Promise<GitopsRunResult> => {
|
|
72
166
|
const repo_start = performance.now();
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
repo_name,
|
|
88
|
-
repo_dir,
|
|
89
|
-
status: success ? 'success' : 'failure',
|
|
90
|
-
exit_code: spawned.result.kind === 'exited' ? spawned.result.code : 0,
|
|
91
|
-
stdout: spawned.stdout || '',
|
|
92
|
-
stderr: spawned.stderr || '',
|
|
93
|
-
duration_ms
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
return result;
|
|
97
|
-
} catch (error) {
|
|
98
|
-
const duration_ms = performance.now() - repo_start;
|
|
99
|
-
return {
|
|
100
|
-
repo_name,
|
|
101
|
-
repo_dir,
|
|
102
|
-
status: 'failure' as const,
|
|
103
|
-
exit_code: -1,
|
|
104
|
-
stdout: '',
|
|
105
|
-
stderr: '',
|
|
106
|
-
duration_ms,
|
|
107
|
-
error: String(error)
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
});
|
|
167
|
+
const { outcome, stdout, stderr } = await run_command({ command, cwd: repo_dir });
|
|
168
|
+
return {
|
|
169
|
+
repo_name,
|
|
170
|
+
repo_dir,
|
|
171
|
+
status: outcome.kind === 'exited' && outcome.code === 0 ? 'success' : 'failure',
|
|
172
|
+
exit_code: outcome.kind === 'exited' ? outcome.code : null,
|
|
173
|
+
signal: outcome.kind === 'signaled' ? outcome.signal : null,
|
|
174
|
+
stdout,
|
|
175
|
+
stderr,
|
|
176
|
+
duration_ms: performance.now() - repo_start,
|
|
177
|
+
...(outcome.kind === 'error' && { error: outcome.message })
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
);
|
|
111
181
|
|
|
112
|
-
|
|
182
|
+
const total_duration_ms = performance.now() - start_time;
|
|
113
183
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
184
|
+
// Process results; a rejection is `run_command` throwing, which the default never does
|
|
185
|
+
const successes: Array<GitopsRunResult> = [];
|
|
186
|
+
const failures: Array<GitopsRunResult> = [];
|
|
187
|
+
settled.forEach((result, i) => {
|
|
188
|
+
if (result.status === 'fulfilled') {
|
|
189
|
+
(result.value.status === 'success' ? successes : failures).push(result.value);
|
|
190
|
+
} else {
|
|
191
|
+
const { repo_name, repo_dir } = repos[i]!;
|
|
192
|
+
failures.push({
|
|
193
|
+
repo_name,
|
|
194
|
+
repo_dir,
|
|
195
|
+
status: 'failure',
|
|
196
|
+
exit_code: null,
|
|
197
|
+
signal: null,
|
|
198
|
+
stdout: '',
|
|
199
|
+
stderr: '',
|
|
200
|
+
duration_ms: 0,
|
|
201
|
+
error: to_error_message(result.reason)
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
});
|
|
117
205
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
failures.push({
|
|
130
|
-
repo_name: 'unknown',
|
|
131
|
-
repo_dir: 'unknown',
|
|
132
|
-
status: 'failure',
|
|
133
|
-
exit_code: -1,
|
|
134
|
-
stdout: '',
|
|
135
|
-
stderr: '',
|
|
136
|
-
duration_ms: 0,
|
|
137
|
-
error: String(result.reason)
|
|
138
|
-
});
|
|
206
|
+
// Output results based on format
|
|
207
|
+
if (format === 'json') {
|
|
208
|
+
const json_output = {
|
|
209
|
+
command,
|
|
210
|
+
concurrency,
|
|
211
|
+
repos: [...successes, ...failures],
|
|
212
|
+
summary: {
|
|
213
|
+
total: repos.length,
|
|
214
|
+
success: successes.length,
|
|
215
|
+
failure: failures.length,
|
|
216
|
+
duration_ms: Math.round(total_duration_ms)
|
|
139
217
|
}
|
|
218
|
+
};
|
|
219
|
+
const json = JSON.stringify(json_output, null, 2);
|
|
220
|
+
if (outfile) {
|
|
221
|
+
// Clean machine-readable output, free of gro's logging prefixes.
|
|
222
|
+
await writeFile(outfile, json);
|
|
223
|
+
log.info(`wrote JSON output to ${outfile}`);
|
|
224
|
+
} else {
|
|
225
|
+
write_stdout(json);
|
|
140
226
|
}
|
|
227
|
+
} else {
|
|
228
|
+
log_text_results({ successes, failures, total: repos.length, total_duration_ms }, log);
|
|
229
|
+
}
|
|
141
230
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
231
|
+
// Exit with error if any failures (so CI fails)
|
|
232
|
+
if (failures.length > 0) {
|
|
233
|
+
throw new TaskError(`${failures.length} repos failed`);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const log_text_results = (
|
|
238
|
+
data: {
|
|
239
|
+
successes: Array<GitopsRunResult>;
|
|
240
|
+
failures: Array<GitopsRunResult>;
|
|
241
|
+
total: number;
|
|
242
|
+
total_duration_ms: number;
|
|
243
|
+
},
|
|
244
|
+
log: Logger
|
|
245
|
+
): void => {
|
|
246
|
+
const { successes, failures, total, total_duration_ms } = data;
|
|
247
|
+
log.info(''); // blank line
|
|
248
|
+
|
|
249
|
+
// Show successes, including the command's output so read-only commands
|
|
250
|
+
// (`git rev-parse`, `cat package.json`, …) are useful without `--format json`.
|
|
251
|
+
if (successes.length > 0) {
|
|
252
|
+
log.info(st('green', `✓ ${successes.length} succeeded:`));
|
|
253
|
+
for (const result of successes) {
|
|
254
|
+
const duration = st('blue', `(${Math.round(result.duration_ms)}ms)`);
|
|
255
|
+
const label = st('gray', ` ${result.repo_name}`);
|
|
256
|
+
const out = result.stdout.trim();
|
|
257
|
+
const out_lines = out ? out.split('\n') : [];
|
|
258
|
+
if (out_lines.length <= 1) {
|
|
259
|
+
// single-line (or empty) output inline after the repo name
|
|
260
|
+
log.info(out ? `${label} ${out} ${duration}` : `${label} ${duration}`);
|
|
160
261
|
} else {
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
// Text format
|
|
166
|
-
log.info(''); // blank line
|
|
167
|
-
|
|
168
|
-
// Show successes, including the command's output so read-only commands
|
|
169
|
-
// (`git rev-parse`, `cat package.json`, …) are useful without `--format json`.
|
|
170
|
-
if (successes.length > 0) {
|
|
171
|
-
log.info(st('green', `✓ ${successes.length} succeeded:`));
|
|
172
|
-
for (const result of successes) {
|
|
173
|
-
const duration = st('blue', `(${Math.round(result.duration_ms)}ms)`);
|
|
174
|
-
const label = st('gray', ` ${result.repo_name}`);
|
|
175
|
-
const out = result.stdout.trim();
|
|
176
|
-
const out_lines = out ? out.split('\n') : [];
|
|
177
|
-
if (out_lines.length <= 1) {
|
|
178
|
-
// single-line (or empty) output inline after the repo name
|
|
179
|
-
log.info(out ? `${label} ${out} ${duration}` : `${label} ${duration}`);
|
|
180
|
-
} else {
|
|
181
|
-
// multi-line output indented under the repo
|
|
182
|
-
log.info(`${label} ${duration}`);
|
|
183
|
-
for (const line of out_lines) {
|
|
184
|
-
log.info(st('gray', ` ${line}`));
|
|
185
|
-
}
|
|
186
|
-
}
|
|
262
|
+
// multi-line output indented under the repo
|
|
263
|
+
log.info(`${label} ${duration}`);
|
|
264
|
+
for (const line of out_lines) {
|
|
265
|
+
log.info(st('gray', ` ${line}`));
|
|
187
266
|
}
|
|
188
267
|
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
189
270
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (result.stderr) {
|
|
205
|
-
// Show first few lines of stderr
|
|
206
|
-
const stderr_lines = result.stderr.trim().split('\n');
|
|
207
|
-
const preview_lines = stderr_lines.slice(0, 3);
|
|
208
|
-
for (const line of preview_lines) {
|
|
209
|
-
log.error(st('gray', ` ${line}`));
|
|
210
|
-
}
|
|
211
|
-
if (stderr_lines.length > 3) {
|
|
212
|
-
log.error(st('gray', ` ... (${stderr_lines.length - 3} more lines)`));
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
271
|
+
// Show failures with details
|
|
272
|
+
if (failures.length > 0) {
|
|
273
|
+
log.info(''); // blank line
|
|
274
|
+
log.error(st('red', `✗ ${failures.length} failed:`));
|
|
275
|
+
for (const result of failures) {
|
|
276
|
+
const duration = `${Math.round(result.duration_ms)}ms`;
|
|
277
|
+
log.error(st('gray', ` ${result.repo_name} ${st('blue', `(${duration})`)}`));
|
|
278
|
+
|
|
279
|
+
if (result.error) {
|
|
280
|
+
log.error(st('gray', ` Error: ${result.error}`));
|
|
281
|
+
} else if (result.signal) {
|
|
282
|
+
log.error(st('gray', ` Killed by ${result.signal}`));
|
|
283
|
+
} else if (result.exit_code !== null) {
|
|
284
|
+
log.error(st('gray', ` Exit code: ${result.exit_code}`));
|
|
216
285
|
}
|
|
217
286
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
st(
|
|
227
|
-
|
|
228
|
-
`✓ All ${total} repos succeeded in ${duration} (${success_rate}% success rate)`
|
|
229
|
-
)
|
|
230
|
-
);
|
|
231
|
-
} else {
|
|
232
|
-
log.info(
|
|
233
|
-
st(
|
|
234
|
-
'yellow',
|
|
235
|
-
`⚠ ${successes.length}/${total} repos succeeded in ${duration} (${success_rate}% success rate)`
|
|
236
|
-
)
|
|
237
|
-
);
|
|
287
|
+
if (result.stderr) {
|
|
288
|
+
// Show first few lines of stderr
|
|
289
|
+
const stderr_lines = result.stderr.trim().split('\n');
|
|
290
|
+
const preview_lines = stderr_lines.slice(0, 3);
|
|
291
|
+
for (const line of preview_lines) {
|
|
292
|
+
log.error(st('gray', ` ${line}`));
|
|
293
|
+
}
|
|
294
|
+
if (stderr_lines.length > 3) {
|
|
295
|
+
log.error(st('gray', ` ... (${stderr_lines.length - 3} more lines)`));
|
|
296
|
+
}
|
|
238
297
|
}
|
|
239
298
|
}
|
|
299
|
+
}
|
|
240
300
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
301
|
+
// Summary
|
|
302
|
+
log.info(''); // blank line
|
|
303
|
+
const success_rate = ((successes.length / total) * 100).toFixed(0);
|
|
304
|
+
const duration = `${Math.round(total_duration_ms)}ms`;
|
|
305
|
+
|
|
306
|
+
if (failures.length === 0) {
|
|
307
|
+
log.info(
|
|
308
|
+
st('green', `✓ All ${total} repos succeeded in ${duration} (${success_rate}% success rate)`)
|
|
309
|
+
);
|
|
310
|
+
} else {
|
|
311
|
+
log.info(
|
|
312
|
+
st(
|
|
313
|
+
'yellow',
|
|
314
|
+
`⚠ ${successes.length}/${total} repos succeeded in ${duration} (${success_rate}% success rate)`
|
|
315
|
+
)
|
|
316
|
+
);
|
|
245
317
|
}
|
|
246
318
|
};
|