@fuzdev/fuz_gitops 0.78.0 → 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 +91 -17
- package/dist/ModulesDetail.svelte +22 -27
- package/dist/ModulesDetail.svelte.d.ts.map +1 -1
- package/dist/ModulesNav.svelte +8 -5
- package/dist/ModulesNav.svelte.d.ts.map +1 -1
- package/dist/ModulesPage.svelte +3 -5
- package/dist/ModulesPage.svelte.d.ts.map +1 -1
- package/dist/PageFooter.svelte +2 -2
- package/dist/PageHeader.svelte +6 -6
- package/dist/PageHeader.svelte.d.ts +0 -3
- package/dist/PageHeader.svelte.d.ts.map +1 -1
- package/dist/PullRequestsDetail.svelte +29 -20
- package/dist/PullRequestsDetail.svelte.d.ts.map +1 -1
- package/dist/PullRequestsPage.svelte +4 -6
- package/dist/PullRequestsPage.svelte.d.ts.map +1 -1
- package/dist/ReposTable.svelte +43 -55
- package/dist/ReposTable.svelte.d.ts.map +1 -1
- package/dist/ReposTree.svelte +9 -21
- package/dist/ReposTree.svelte.d.ts.map +1 -1
- package/dist/ReposTreeNav.svelte +9 -12
- package/dist/ReposTreeNav.svelte.d.ts.map +1 -1
- package/dist/TablePage.svelte +3 -5
- package/dist/TablePage.svelte.d.ts.map +1 -1
- package/dist/TreeItemPage.svelte +3 -5
- package/dist/TreeItemPage.svelte.d.ts.map +1 -1
- package/dist/TreePage.svelte +3 -5
- package/dist/TreePage.svelte.d.ts.map +1 -1
- package/dist/changeset_generator.d.ts.map +1 -1
- package/dist/changeset_generator.js +2 -2
- package/dist/changeset_reader.d.ts.map +1 -1
- package/dist/changeset_reader.js +3 -3
- 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 +40 -83
- package/dist/dependency_updater.d.ts +1 -1
- package/dist/dependency_updater.d.ts.map +1 -1
- package/dist/dependency_updater.js +14 -9
- 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.d.ts.map +1 -1
- package/dist/fs_fetch_value_cache.js +3 -3
- 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 +17 -8
- 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 +47 -34
- 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 +83 -61
- 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 +193 -105
- 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 +176 -162
- 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 +163 -37
- 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 +25 -32
- package/dist/graph_validation.d.ts +16 -27
- package/dist/graph_validation.d.ts.map +1 -1
- package/dist/graph_validation.js +24 -65
- package/dist/local_repo.d.ts +50 -62
- package/dist/local_repo.d.ts.map +1 -1
- package/dist/local_repo.js +83 -196
- 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 +125 -61
- package/dist/npm_registry.d.ts +23 -19
- package/dist/npm_registry.d.ts.map +1 -1
- package/dist/npm_registry.js +21 -48
- 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 +111 -128
- 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_gate.d.ts.map +1 -1
- package/dist/publish_steps.d.ts +2 -4
- package/dist/publish_steps.d.ts.map +1 -1
- package/dist/publish_steps.js +4 -8
- package/dist/publishing_event.d.ts +2 -8
- package/dist/publishing_event.d.ts.map +1 -1
- package/dist/publishing_event.js +20 -17
- 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 +19 -17
- package/dist/publishing_plan.d.ts +16 -1
- package/dist/publishing_plan.d.ts.map +1 -1
- package/dist/publishing_plan.js +332 -254
- package/dist/publishing_plan_helpers.d.ts.map +1 -1
- package/dist/publishing_plan_helpers.js +3 -3
- 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 +22 -15
- 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/dist/version_utils.js +1 -1
- package/package.json +15 -34
- package/src/lib/cargo_toml.ts +3 -3
- package/src/lib/changeset_generator.ts +19 -19
- package/src/lib/changeset_reader.ts +18 -18
- package/src/lib/ci_reconcile.ts +12 -16
- package/src/lib/dependency_graph.ts +61 -113
- package/src/lib/dependency_updater.ts +36 -30
- package/src/lib/fetch_repo_data.ts +71 -34
- package/src/lib/fs_fetch_value_cache.ts +15 -11
- package/src/lib/git_operations.ts +22 -194
- package/src/lib/github.ts +33 -21
- package/src/lib/github_helpers.ts +9 -16
- package/src/lib/gitops_analyze.task.ts +104 -66
- package/src/lib/gitops_config.ts +90 -139
- package/src/lib/gitops_constants.ts +6 -4
- package/src/lib/gitops_plan.task.ts +128 -76
- package/src/lib/gitops_publish.task.ts +280 -144
- package/src/lib/gitops_run.task.ts +265 -193
- package/src/lib/gitops_sync.task.ts +236 -52
- package/src/lib/gitops_task_helpers.ts +170 -114
- package/src/lib/gitops_validate.task.ts +44 -54
- package/src/lib/graph_validation.ts +28 -89
- package/src/lib/local_repo.ts +128 -335
- package/src/lib/log_helpers.ts +18 -28
- package/src/lib/multi_repo_publisher.ts +192 -110
- package/src/lib/npm_registry.ts +42 -67
- package/src/lib/operations.ts +99 -160
- package/src/lib/operations_defaults.ts +157 -201
- package/src/lib/output_helpers.ts +58 -6
- package/src/lib/preflight_checks.ts +52 -188
- package/src/lib/publish_gate.ts +7 -9
- package/src/lib/publish_steps.ts +27 -25
- package/src/lib/publishing_event.ts +26 -23
- package/src/lib/publishing_event_handler.ts +24 -22
- package/src/lib/publishing_plan.ts +474 -310
- package/src/lib/publishing_plan_helpers.ts +16 -12
- package/src/lib/publishing_plan_logging.ts +53 -44
- package/src/lib/repo.svelte.ts +16 -8
- 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/src/lib/version_utils.ts +3 -3
- package/dist/config_reconcile.d.ts +0 -46
- package/dist/config_reconcile.d.ts.map +0 -1
- package/dist/config_reconcile.js +0 -60
- 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 -94
- 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,29 +1,32 @@
|
|
|
1
|
-
import type {Logger} from '@fuzdev/fuz_util/log.ts';
|
|
2
|
-
import {TaskError} from '@fuzdev/gro';
|
|
3
|
-
import {join} from 'node:path';
|
|
4
|
-
import {styleText as st} from 'node:util';
|
|
1
|
+
import type { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
2
|
+
import { TaskError } from '@fuzdev/gro';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { styleText as st } from 'node:util';
|
|
5
5
|
|
|
6
|
-
import {repo_is_npm, type LocalRepo} from './local_repo.ts';
|
|
7
|
-
import {update_package_json, type VersionStrategy} from './dependency_updater.ts';
|
|
6
|
+
import { repo_is_npm, type LocalRepo } from './local_repo.ts';
|
|
7
|
+
import { update_package_json, type VersionStrategy } from './dependency_updater.ts';
|
|
8
8
|
import {
|
|
9
9
|
generate_publishing_plan,
|
|
10
10
|
type VersionChange,
|
|
11
11
|
type DependencyUpdate,
|
|
12
|
-
type PublishingPlan
|
|
12
|
+
type PublishingPlan
|
|
13
13
|
} from './publishing_plan.ts';
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
14
|
+
import { load_repos_status, to_repos_command } from './repos_status_load.ts';
|
|
15
|
+
import { check_publish_readiness } from './repo_readiness.ts';
|
|
16
|
+
import type { Result } from '@fuzdev/fuz_util/result.ts';
|
|
17
|
+
import type { GitopsOperations } from './operations.ts';
|
|
18
|
+
import { default_gitops_operations } from './operations_defaults.ts';
|
|
19
|
+
import { GITOPS_NPM_WAIT_TIMEOUT_DEFAULT } from './gitops_constants.ts';
|
|
18
20
|
import {
|
|
19
21
|
type PublishingEvent,
|
|
20
22
|
type PublishingRunSummary,
|
|
21
|
-
summarize_events
|
|
23
|
+
summarize_events
|
|
22
24
|
} from './publishing_event.ts';
|
|
23
25
|
import {
|
|
24
26
|
type PublishingEventHandler,
|
|
25
27
|
capture_handler,
|
|
26
28
|
multi_handler,
|
|
29
|
+
redact_secrets
|
|
27
30
|
} from './publishing_event_handler.ts';
|
|
28
31
|
|
|
29
32
|
export interface PublishingOptions {
|
|
@@ -33,8 +36,18 @@ export interface PublishingOptions {
|
|
|
33
36
|
max_wait?: number;
|
|
34
37
|
log?: Logger;
|
|
35
38
|
ops?: GitopsOperations;
|
|
39
|
+
/** A `repos.toml` for the per-repo readiness re-check, when `repos` wouldn't find it from the cwd. */
|
|
40
|
+
registry?: string;
|
|
36
41
|
/** Structured event sink; defaults to capture-only (events surface on the result). */
|
|
37
42
|
events?: PublishingEventHandler;
|
|
43
|
+
/**
|
|
44
|
+
* Where `gro publish` and `gro deploy` show their stdout: ours, or our stderr
|
|
45
|
+
* when our stdout carries a machine-readable stream (`--emit_json`, or a JSON
|
|
46
|
+
* or markdown report written to stdout).
|
|
47
|
+
*
|
|
48
|
+
* @default 'stdout'
|
|
49
|
+
*/
|
|
50
|
+
child_stdout?: 'stdout' | 'stderr';
|
|
38
51
|
}
|
|
39
52
|
|
|
40
53
|
export interface PublishedVersion {
|
|
@@ -50,7 +63,7 @@ export interface PublishedVersion {
|
|
|
50
63
|
export interface PublishingResult {
|
|
51
64
|
ok: boolean;
|
|
52
65
|
published: Array<PublishedVersion>;
|
|
53
|
-
failed: Array<{name: string; error: Error}>;
|
|
66
|
+
failed: Array<{ name: string; error: Error }>;
|
|
54
67
|
duration: number;
|
|
55
68
|
/** The structured event stream for this run, in emission order. */
|
|
56
69
|
events: Array<PublishingEvent>;
|
|
@@ -64,13 +77,13 @@ export interface PublishingResult {
|
|
|
64
77
|
|
|
65
78
|
export const publish_repos = async (
|
|
66
79
|
repos: Array<LocalRepo>,
|
|
67
|
-
options: PublishingOptions
|
|
80
|
+
options: PublishingOptions
|
|
68
81
|
): Promise<PublishingResult> => {
|
|
69
|
-
const {log, ops = default_gitops_operations} = options;
|
|
82
|
+
const { log, ops = default_gitops_operations } = options;
|
|
70
83
|
// Convenience entry: generate the plan, then execute it. Callers that already hold a
|
|
71
84
|
// plan (e.g. to display and confirm it first) call `execute_publishing_plan` directly,
|
|
72
85
|
// so the plan is generated exactly once per command.
|
|
73
|
-
const plan = await generate_publishing_plan(repos, {log, ops: ops.changeset});
|
|
86
|
+
const plan = await generate_publishing_plan(repos, { log, ops: ops.changeset });
|
|
74
87
|
return execute_publishing_plan(repos, plan, options);
|
|
75
88
|
};
|
|
76
89
|
|
|
@@ -85,10 +98,11 @@ export const publish_repos = async (
|
|
|
85
98
|
export const execute_publishing_plan = async (
|
|
86
99
|
all_repos: Array<LocalRepo>,
|
|
87
100
|
plan: PublishingPlan,
|
|
88
|
-
options: PublishingOptions
|
|
101
|
+
options: PublishingOptions
|
|
89
102
|
): Promise<PublishingResult> => {
|
|
90
103
|
const start_time = Date.now();
|
|
91
|
-
const {wetrun, log, ops = default_gitops_operations} = options;
|
|
104
|
+
const { wetrun, log, ops = default_gitops_operations } = options;
|
|
105
|
+
const version_strategy = options.version_strategy ?? 'caret';
|
|
92
106
|
|
|
93
107
|
// Only npm repos publish; drop any non-npm repos (e.g. cargo) so preflight and the
|
|
94
108
|
// executor's name lookup never touch them. The plan already excludes them too.
|
|
@@ -99,7 +113,7 @@ export const execute_publishing_plan = async (
|
|
|
99
113
|
// (no side effects) but the result is not ok (see the summary below).
|
|
100
114
|
if (wetrun && plan.errors.length > 0) {
|
|
101
115
|
throw new TaskError(
|
|
102
|
-
`Cannot publish — the plan has ${plan.errors.length} error(s):\n ${plan.errors.join('\n ')}
|
|
116
|
+
`Cannot publish — the plan has ${plan.errors.length} error(s):\n ${plan.errors.join('\n ')}`
|
|
103
117
|
);
|
|
104
118
|
}
|
|
105
119
|
|
|
@@ -110,20 +124,16 @@ export const execute_publishing_plan = async (
|
|
|
110
124
|
events_handler.emit(event);
|
|
111
125
|
};
|
|
112
126
|
|
|
113
|
-
// Preflight checks (skip for dry runs since we're not actually publishing)
|
|
127
|
+
// Preflight checks (skip for dry runs since we're not actually publishing): it builds
|
|
128
|
+
// exactly the packages the plan publishes. Repo git state isn't preflight's:
|
|
129
|
+
// `gitops_publish --wetrun` gates on it before its confirmation prompt.
|
|
114
130
|
if (wetrun) {
|
|
115
|
-
const preflight_options: PreflightOptions = {
|
|
116
|
-
skip_changesets: false, // Always check for changesets
|
|
117
|
-
required_branch: 'main',
|
|
118
|
-
log,
|
|
119
|
-
};
|
|
120
131
|
const preflight = await ops.preflight.run_preflight_checks({
|
|
121
132
|
repos,
|
|
122
|
-
|
|
123
|
-
|
|
133
|
+
version_changes: plan.version_changes,
|
|
134
|
+
log,
|
|
124
135
|
npm_ops: ops.npm,
|
|
125
|
-
build_ops: ops.build
|
|
126
|
-
changeset_ops: ops.changeset,
|
|
136
|
+
build_ops: ops.build
|
|
127
137
|
});
|
|
128
138
|
|
|
129
139
|
if (!preflight.ok) {
|
|
@@ -138,13 +148,13 @@ export const execute_publishing_plan = async (
|
|
|
138
148
|
// frozen topological order directly, re-deriving nothing.
|
|
139
149
|
const order = plan.publishing_order;
|
|
140
150
|
const plan_changes: Map<string, VersionChange> = new Map(
|
|
141
|
-
plan.version_changes.map((vc) => [vc.package_name, vc])
|
|
151
|
+
plan.version_changes.map((vc) => [vc.package_name, vc])
|
|
142
152
|
);
|
|
143
153
|
if (order.length > 0) {
|
|
144
154
|
log?.info(` Publishing order: ${order.join(' → ')}`);
|
|
145
155
|
}
|
|
146
156
|
|
|
147
|
-
emit({event: 'run_started', wetrun, total: order.length});
|
|
157
|
+
emit({ event: 'run_started', wetrun, total: order.length });
|
|
148
158
|
|
|
149
159
|
const published: Map<string, PublishedVersion> = new Map();
|
|
150
160
|
const failed: Map<string, Error> = new Map();
|
|
@@ -165,7 +175,7 @@ export const execute_publishing_plan = async (
|
|
|
165
175
|
|
|
166
176
|
// Not in the plan = no changesets and no dependency updates = nothing to publish.
|
|
167
177
|
if (!planned) {
|
|
168
|
-
emit({event: 'package_skipped', name: pkg_name, reason: 'no changesets'});
|
|
178
|
+
emit({ event: 'package_skipped', name: pkg_name, reason: 'no changesets' });
|
|
169
179
|
log?.info(st('yellow', ` ⚠️ Skipping ${pkg_name} - no changesets`));
|
|
170
180
|
continue;
|
|
171
181
|
}
|
|
@@ -173,6 +183,26 @@ export const execute_publishing_plan = async (
|
|
|
173
183
|
const repo = repo_by_name.get(pkg_name);
|
|
174
184
|
if (!repo) continue;
|
|
175
185
|
|
|
186
|
+
// Re-check the repo right before its `gro publish`: the gate ran before the prompt, and a
|
|
187
|
+
// cascade's npm waits leave a long window. `--no-pull` means gro won't notice origin
|
|
188
|
+
// moving until its push is rejected — after `changeset publish` put the version on npm —
|
|
189
|
+
// and its `commit -a` would sweep a tracked edit made since into the release commit.
|
|
190
|
+
// Aborting here, before any npm side effect, leaves the dirty state resumption expects.
|
|
191
|
+
if (wetrun) {
|
|
192
|
+
const recheck = await recheck_publish_readiness(repo, options.registry, ops);
|
|
193
|
+
if (!recheck.ok) {
|
|
194
|
+
const err = new Error(
|
|
195
|
+
`${pkg_name} isn't ready to publish, re-checked right before \`gro publish\`: ` +
|
|
196
|
+
`${recheck.message}. Aborting before it touches npm — fix it, then re-run ` +
|
|
197
|
+
`'gro gitops_publish --wetrun' to re-plan from the current state.`
|
|
198
|
+
);
|
|
199
|
+
failed.set(pkg_name, err);
|
|
200
|
+
emit({ event: 'package_failed', name: pkg_name, error: err.message, code: 'not_ready' });
|
|
201
|
+
log?.error(st('red', ` ❌ ${err.message}`));
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
176
206
|
// An earlier publish may have rewritten this package's dependency ranges. No install is
|
|
177
207
|
// needed here: `gro publish` runs its own install (which self-heals npm's stale-cache
|
|
178
208
|
// ETARGET), so the package's freshly-rewritten deps are installed and healed as part of
|
|
@@ -182,8 +212,8 @@ export const execute_publishing_plan = async (
|
|
|
182
212
|
log?.info(
|
|
183
213
|
st(
|
|
184
214
|
'dim',
|
|
185
|
-
` [${i + 1}/${order.length}] ${wetrun ? 'Publishing' : 'Would publish'} ${pkg_name}
|
|
186
|
-
)
|
|
215
|
+
` [${i + 1}/${order.length}] ${wetrun ? 'Publishing' : 'Would publish'} ${pkg_name}...`
|
|
216
|
+
)
|
|
187
217
|
);
|
|
188
218
|
const version = await publish_single_repo(repo, options, ops, planned);
|
|
189
219
|
|
|
@@ -194,10 +224,10 @@ export const execute_publishing_plan = async (
|
|
|
194
224
|
if (wetrun && version.new_version !== planned.to) {
|
|
195
225
|
const err = new Error(
|
|
196
226
|
`Plan drift for ${pkg_name}: published ${version.new_version} but the plan predicted ${planned.to}. ` +
|
|
197
|
-
`Aborting — re-run 'gro gitops_publish --wetrun' to re-plan from the current state
|
|
227
|
+
`Aborting — re-run 'gro gitops_publish --wetrun' to re-plan from the current state.`
|
|
198
228
|
);
|
|
199
229
|
failed.set(pkg_name, err);
|
|
200
|
-
emit({event: 'package_failed', name: pkg_name, error: err.message, code: 'drift'});
|
|
230
|
+
emit({ event: 'package_failed', name: pkg_name, error: err.message, code: 'drift' });
|
|
201
231
|
log?.error(st('red', ` ❌ ${err.message}`));
|
|
202
232
|
break;
|
|
203
233
|
}
|
|
@@ -212,12 +242,12 @@ export const execute_publishing_plan = async (
|
|
|
212
242
|
bump_type: version.bump_type,
|
|
213
243
|
breaking: version.breaking,
|
|
214
244
|
commit: version.commit,
|
|
215
|
-
tag: version.tag
|
|
245
|
+
tag: version.tag
|
|
216
246
|
});
|
|
217
247
|
log?.info(
|
|
218
248
|
wetrun
|
|
219
249
|
? st('green', ` ✅ Published ${pkg_name}@${version.new_version}`)
|
|
220
|
-
: st('cyan', ` ◇ Would publish ${pkg_name}@${version.new_version}`)
|
|
250
|
+
: st('cyan', ` ◇ Would publish ${pkg_name}@${version.new_version}`)
|
|
221
251
|
);
|
|
222
252
|
|
|
223
253
|
if (wetrun) {
|
|
@@ -230,35 +260,32 @@ export const execute_publishing_plan = async (
|
|
|
230
260
|
max_attempts: 30,
|
|
231
261
|
initial_delay: 1000,
|
|
232
262
|
max_delay: 60000,
|
|
233
|
-
timeout: options.max_wait ?? GITOPS_NPM_WAIT_TIMEOUT_DEFAULT
|
|
263
|
+
timeout: options.max_wait ?? GITOPS_NPM_WAIT_TIMEOUT_DEFAULT
|
|
234
264
|
},
|
|
235
|
-
log
|
|
265
|
+
log
|
|
236
266
|
});
|
|
237
267
|
|
|
238
268
|
if (!wait_result.ok) {
|
|
239
|
-
// Handle inline (don't throw into the generic catch):
|
|
240
|
-
//
|
|
241
|
-
|
|
242
|
-
const err = new Error(
|
|
243
|
-
`Failed to wait for package: ${wait_result.message}${wait_result.timeout ? ' (timeout)' : ''}`,
|
|
244
|
-
);
|
|
269
|
+
// Handle inline (don't throw into the generic catch): a failed npm wait is a
|
|
270
|
+
// network failure, known by where it happened rather than by sniffing the message.
|
|
271
|
+
const err = new Error(`Failed to wait for package: ${wait_result.message}`);
|
|
245
272
|
failed.set(pkg_name, err);
|
|
246
|
-
emit({event: 'package_failed', name: pkg_name, error: err.message, code: 'network'});
|
|
273
|
+
emit({ event: 'package_failed', name: pkg_name, error: err.message, code: 'network' });
|
|
247
274
|
log?.error(st('red', ` ❌ Failed to publish ${pkg_name}: ${err.message}`));
|
|
248
275
|
break; // fail fast
|
|
249
276
|
}
|
|
250
|
-
emit({event: 'npm_waited', name: pkg_name, version: version.new_version});
|
|
277
|
+
emit({ event: 'npm_waited', name: pkg_name, version: version.new_version });
|
|
251
278
|
|
|
252
279
|
// 3. Update every dependent the plan says has a prod/peer dep on this package.
|
|
253
280
|
// This rewrites their package.json ranges and creates their auto-changeset,
|
|
254
|
-
// which a later step of this same pass publishes
|
|
255
|
-
//
|
|
281
|
+
// which a later step of this same pass publishes; its `gro publish` installs the
|
|
282
|
+
// rewritten deps then (see the top of the loop).
|
|
256
283
|
const dependent_updates = group_dependency_updates(
|
|
257
284
|
plan.dependency_updates,
|
|
258
285
|
published,
|
|
259
286
|
(update) =>
|
|
260
287
|
update.updated_dependency === pkg_name &&
|
|
261
|
-
(update.type === 'dependencies' || update.type === 'peerDependencies')
|
|
288
|
+
(update.type === 'dependencies' || update.type === 'peerDependencies')
|
|
262
289
|
);
|
|
263
290
|
for (const [dependent_name, updates] of dependent_updates) {
|
|
264
291
|
const dependent_repo = repo_by_name.get(dependent_name);
|
|
@@ -277,27 +304,19 @@ export const execute_publishing_plan = async (
|
|
|
277
304
|
dependency: dep_name,
|
|
278
305
|
version: dep_version,
|
|
279
306
|
dep_type: dependency_update_type(plan, dependent_name, dep_name),
|
|
280
|
-
creates_changeset: republishes
|
|
307
|
+
creates_changeset: republishes
|
|
281
308
|
});
|
|
282
309
|
}
|
|
283
310
|
changed_repos.add(dependent_name); // Mark as changed for deployment
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
// update-only leaf: rewrite ranges + commit, no changeset (it won't republish)
|
|
294
|
-
await update_package_json(dependent_repo, updates, {
|
|
295
|
-
strategy: options.version_strategy || 'caret',
|
|
296
|
-
log,
|
|
297
|
-
git_ops: ops.git,
|
|
298
|
-
fs_ops: ops.fs,
|
|
299
|
-
});
|
|
300
|
-
}
|
|
311
|
+
await update_package_json(dependent_repo, updates, {
|
|
312
|
+
strategy: version_strategy,
|
|
313
|
+
// `published_versions` creates the auto-changeset; an update-only leaf gets
|
|
314
|
+
// its ranges rewritten and committed with no changeset (it won't republish)
|
|
315
|
+
published_versions: republishes ? published : undefined,
|
|
316
|
+
log,
|
|
317
|
+
git_ops: ops.git,
|
|
318
|
+
fs_ops: ops.fs
|
|
319
|
+
});
|
|
301
320
|
}
|
|
302
321
|
}
|
|
303
322
|
} catch (error) {
|
|
@@ -309,10 +328,11 @@ export const execute_publishing_plan = async (
|
|
|
309
328
|
error: err.message,
|
|
310
329
|
// TODO: emit a precise code once the npm/process ops return typed errors —
|
|
311
330
|
// today a publish-step cause lives in unstructured stderr, so use the honest
|
|
312
|
-
// coarse bucket rather than guessing
|
|
313
|
-
code: 'publish'
|
|
331
|
+
// coarse bucket rather than guessing a cause (auth, build, …) from the message.
|
|
332
|
+
code: 'publish'
|
|
314
333
|
});
|
|
315
|
-
|
|
334
|
+
// the first line alone: the stderr tail the rest repeats just streamed live
|
|
335
|
+
log?.error(st('red', ` ❌ Failed to publish ${pkg_name}: ${err.message.split('\n')[0]}`));
|
|
316
336
|
break; // Always fail fast on error
|
|
317
337
|
}
|
|
318
338
|
}
|
|
@@ -327,7 +347,7 @@ export const execute_publishing_plan = async (
|
|
|
327
347
|
const dev_updates_by_repo = group_dependency_updates(
|
|
328
348
|
plan.dependency_updates,
|
|
329
349
|
published,
|
|
330
|
-
(update) => update.type === 'devDependencies'
|
|
350
|
+
(update) => update.type === 'devDependencies'
|
|
331
351
|
);
|
|
332
352
|
|
|
333
353
|
if (dev_updates_by_repo.size > 0) {
|
|
@@ -346,7 +366,7 @@ export const execute_publishing_plan = async (
|
|
|
346
366
|
dependency: dep_name,
|
|
347
367
|
version: dep_version,
|
|
348
368
|
dep_type: 'dev',
|
|
349
|
-
creates_changeset: false
|
|
369
|
+
creates_changeset: false // dev-dep updates redeploy without republishing
|
|
350
370
|
});
|
|
351
371
|
}
|
|
352
372
|
changed_repos.add(repo_name); // Mark as changed for deployment
|
|
@@ -354,10 +374,10 @@ export const execute_publishing_plan = async (
|
|
|
354
374
|
// package.json but must NOT generate a changeset — dev-only changes redeploy
|
|
355
375
|
// (rebuild) without republishing, so they shouldn't bump the next release.
|
|
356
376
|
await update_package_json(repo, dev_updates, {
|
|
357
|
-
strategy:
|
|
377
|
+
strategy: version_strategy,
|
|
358
378
|
log,
|
|
359
379
|
git_ops: ops.git,
|
|
360
|
-
fs_ops: ops.fs
|
|
380
|
+
fs_ops: ops.fs
|
|
361
381
|
});
|
|
362
382
|
}
|
|
363
383
|
}
|
|
@@ -371,35 +391,40 @@ export const execute_publishing_plan = async (
|
|
|
371
391
|
.map((name) => repo_by_name.get(name))
|
|
372
392
|
.filter((r): r is LocalRepo => r !== undefined);
|
|
373
393
|
log?.info(
|
|
374
|
-
st(
|
|
375
|
-
'cyan',
|
|
376
|
-
`\n🚢 Deploying ${repos_to_deploy.length}/${repos.length} repos with changes...\n`,
|
|
377
|
-
),
|
|
394
|
+
st('cyan', `\n🚢 Deploying ${repos_to_deploy.length}/${repos.length} repos with changes...\n`)
|
|
378
395
|
);
|
|
379
396
|
|
|
380
397
|
for (const repo of repos_to_deploy) {
|
|
381
398
|
try {
|
|
382
|
-
emit({event: 'deploy_started', name: repo.library.name});
|
|
399
|
+
emit({ event: 'deploy_started', name: repo.library.name });
|
|
383
400
|
log?.info(` Deploying ${repo.library.name}...`);
|
|
384
401
|
// Build fresh (no --no-build): a deployed site bundles its dependencies, so it
|
|
385
402
|
// must be rebuilt against the versions this run just published — the preflight
|
|
386
403
|
// build ran against the old versions, before the cascade rewrote package.json.
|
|
387
|
-
const deploy_result = await ops.process.
|
|
404
|
+
const deploy_result = await ops.process.run_interactive({
|
|
388
405
|
cmd: 'gro',
|
|
389
406
|
args: ['deploy'],
|
|
390
407
|
cwd: repo.repo_dir,
|
|
408
|
+
stdout: options.child_stdout
|
|
391
409
|
});
|
|
392
410
|
|
|
393
411
|
if (deploy_result.ok) {
|
|
394
|
-
emit({event: 'deploy_completed', name: repo.library.name});
|
|
412
|
+
emit({ event: 'deploy_completed', name: repo.library.name });
|
|
395
413
|
log?.info(st('green', ` ✅ Deployed ${repo.library.name}`));
|
|
396
414
|
} else {
|
|
397
|
-
emit({
|
|
398
|
-
|
|
415
|
+
emit({
|
|
416
|
+
event: 'deploy_failed',
|
|
417
|
+
name: repo.library.name,
|
|
418
|
+
error: format_run_failure(deploy_result)
|
|
419
|
+
});
|
|
420
|
+
// the stderr tail just streamed live, so the log names the failure alone
|
|
421
|
+
log?.warn(
|
|
422
|
+
st('yellow', ` ⚠️ Failed to deploy ${repo.library.name}: ${deploy_result.message}`)
|
|
423
|
+
);
|
|
399
424
|
}
|
|
400
425
|
} catch (error) {
|
|
401
426
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
402
|
-
emit({event: 'deploy_failed', name: repo.library.name, error: err.message});
|
|
427
|
+
emit({ event: 'deploy_failed', name: repo.library.name, error: err.message });
|
|
403
428
|
log?.error(st('red', ` ❌ Error deploying ${repo.library.name}: ${err.message}`));
|
|
404
429
|
}
|
|
405
430
|
}
|
|
@@ -435,8 +460,8 @@ export const execute_publishing_plan = async (
|
|
|
435
460
|
log?.info(
|
|
436
461
|
st(
|
|
437
462
|
'green',
|
|
438
|
-
`\n✨ Dry run complete — ${published.size} package(s) would be published. Re-run with --wetrun to publish
|
|
439
|
-
)
|
|
463
|
+
`\n✨ Dry run complete — ${published.size} package(s) would be published. Re-run with --wetrun to publish.`
|
|
464
|
+
)
|
|
440
465
|
);
|
|
441
466
|
// The dry run is driven by the same plan as `gro gitops_plan`, so this
|
|
442
467
|
// count includes bump escalations and auto-generated changesets — it
|
|
@@ -449,22 +474,22 @@ export const execute_publishing_plan = async (
|
|
|
449
474
|
'red',
|
|
450
475
|
wetrun
|
|
451
476
|
? '\n❌ Some packages failed to publish\n'
|
|
452
|
-
: '\n❌ Some packages failed during dry run\n'
|
|
453
|
-
)
|
|
477
|
+
: '\n❌ Some packages failed during dry run\n'
|
|
478
|
+
)
|
|
454
479
|
);
|
|
455
480
|
}
|
|
456
481
|
|
|
457
|
-
emit({event: 'run_finished', summary});
|
|
482
|
+
emit({ event: 'run_finished', summary });
|
|
458
483
|
|
|
459
484
|
return {
|
|
460
485
|
ok,
|
|
461
486
|
published: Array.from(published.values()),
|
|
462
|
-
failed: Array.from(failed.entries()).map(([name, error]) => ({name, error})),
|
|
487
|
+
failed: Array.from(failed.entries()).map(([name, error]) => ({ name, error })),
|
|
463
488
|
duration,
|
|
464
489
|
events: capture.events,
|
|
465
490
|
summary,
|
|
466
491
|
plan_errors: plan.errors,
|
|
467
|
-
plan_warnings: plan.warnings
|
|
492
|
+
plan_warnings: plan.warnings
|
|
468
493
|
};
|
|
469
494
|
};
|
|
470
495
|
|
|
@@ -472,10 +497,13 @@ export const execute_publishing_plan = async (
|
|
|
472
497
|
* Publishes a single repo using `gro publish`.
|
|
473
498
|
*
|
|
474
499
|
* Dry run mode: reports the precomputed plan entry without side effects.
|
|
475
|
-
* Real mode: runs `gro publish --no-build
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
*
|
|
500
|
+
* Real mode: runs `gro publish --no-build --no-pull --branch <entry branch>` (builds
|
|
501
|
+
* already validated in preflight; the readiness re-check just found the branch in sync
|
|
502
|
+
* with origin or ahead of it, so gro's own `git pull` is skipped, and gro checks out the branch the
|
|
503
|
+
* registry entry follows rather than its `main` default) in the foreground — its output
|
|
504
|
+
* live and stdin the terminal's, for npm's one-time-password prompt — reads the new version from
|
|
505
|
+
* `package.json`, and returns it alongside the plan's predicted bump metadata. The
|
|
506
|
+
* caller compares the read-back version to the plan to detect drift.
|
|
479
507
|
*
|
|
480
508
|
* @throws {Error} if the publish, version read-back, or commit-hash lookup fails
|
|
481
509
|
*/
|
|
@@ -483,9 +511,9 @@ const publish_single_repo = async (
|
|
|
483
511
|
repo: LocalRepo,
|
|
484
512
|
options: PublishingOptions,
|
|
485
513
|
ops: GitopsOperations,
|
|
486
|
-
planned: VersionChange
|
|
514
|
+
planned: VersionChange
|
|
487
515
|
): Promise<PublishedVersion> => {
|
|
488
|
-
const {wetrun} = options;
|
|
516
|
+
const { wetrun } = options;
|
|
489
517
|
|
|
490
518
|
if (!wetrun) {
|
|
491
519
|
// Dry run reports the precomputed plan — the single source of truth for the
|
|
@@ -497,24 +525,25 @@ const publish_single_repo = async (
|
|
|
497
525
|
bump_type: planned.bump_type,
|
|
498
526
|
breaking: planned.breaking,
|
|
499
527
|
commit: 'simulated',
|
|
500
|
-
tag: `v${planned.to}
|
|
528
|
+
tag: `v${planned.to}`
|
|
501
529
|
};
|
|
502
530
|
}
|
|
503
531
|
|
|
504
|
-
|
|
505
|
-
const publish_result = await ops.process.spawn({
|
|
532
|
+
const publish_result = await ops.process.run_interactive({
|
|
506
533
|
cmd: 'gro',
|
|
507
|
-
args:
|
|
534
|
+
args: gro_publish_args(repo),
|
|
508
535
|
cwd: repo.repo_dir,
|
|
536
|
+
stdout: options.child_stdout
|
|
509
537
|
});
|
|
510
538
|
|
|
511
539
|
if (!publish_result.ok) {
|
|
512
|
-
|
|
540
|
+
// no "Failed to publish" prefix: the caller's log and report name the package
|
|
541
|
+
throw new Error(format_run_failure(publish_result));
|
|
513
542
|
}
|
|
514
543
|
|
|
515
544
|
// Read the new version from package.json after gro publish
|
|
516
545
|
const package_json_path = join(repo.repo_dir, 'package.json');
|
|
517
|
-
const content_result = await ops.fs.readFile({path: package_json_path, encoding: 'utf8'});
|
|
546
|
+
const content_result = await ops.fs.readFile({ path: package_json_path, encoding: 'utf8' });
|
|
518
547
|
|
|
519
548
|
if (!content_result.ok) {
|
|
520
549
|
throw new Error(`Failed to read package.json: ${content_result.message}`);
|
|
@@ -524,7 +553,7 @@ const publish_single_repo = async (
|
|
|
524
553
|
const new_version = package_json.version;
|
|
525
554
|
|
|
526
555
|
// Get actual commit hash
|
|
527
|
-
const commit_result = await ops.git.current_commit_hash({cwd: repo.repo_dir});
|
|
556
|
+
const commit_result = await ops.git.current_commit_hash({ cwd: repo.repo_dir });
|
|
528
557
|
|
|
529
558
|
if (!commit_result.ok) {
|
|
530
559
|
throw new Error(`Failed to get commit hash: ${commit_result.message}`);
|
|
@@ -541,10 +570,63 @@ const publish_single_repo = async (
|
|
|
541
570
|
bump_type: planned.bump_type,
|
|
542
571
|
breaking: planned.breaking,
|
|
543
572
|
commit,
|
|
544
|
-
tag: `v${new_version}
|
|
573
|
+
tag: `v${new_version}`
|
|
545
574
|
};
|
|
546
575
|
};
|
|
547
576
|
|
|
577
|
+
/**
|
|
578
|
+
* Re-checks one repo's readiness right before its `gro publish`: `repos status
|
|
579
|
+
* --fetch --json <key>` through `ops.repos`, then `check_publish_readiness` on
|
|
580
|
+
* that entry — the gate's predicate, so a branch ahead of origin (the
|
|
581
|
+
* executor's own dependency-rewrite commit leaves a dependent ahead) is ready.
|
|
582
|
+
*
|
|
583
|
+
* @returns ok, or the problems found (or why the check couldn't run), joined
|
|
584
|
+
*/
|
|
585
|
+
const recheck_publish_readiness = async (
|
|
586
|
+
repo: LocalRepo,
|
|
587
|
+
registry: string | undefined,
|
|
588
|
+
ops: GitopsOperations
|
|
589
|
+
): Promise<Result<object, { message: string }>> => {
|
|
590
|
+
const key = repo.entry.key;
|
|
591
|
+
const loaded = await load_repos_status({
|
|
592
|
+
keys: [key],
|
|
593
|
+
registry,
|
|
594
|
+
fetch: true,
|
|
595
|
+
repos_ops: ops.repos
|
|
596
|
+
});
|
|
597
|
+
if (!loaded.ok) return { ok: false, message: `the check didn't run: ${loaded.message}` };
|
|
598
|
+
const checked = check_publish_readiness({
|
|
599
|
+
report: loaded.report,
|
|
600
|
+
keys: [key],
|
|
601
|
+
repos_command: to_repos_command(registry)
|
|
602
|
+
});
|
|
603
|
+
return checked.ok ? { ok: true } : { ok: false, message: checked.lines.join('; ') };
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* The `gro publish` arguments the executor runs in a repo: `--no-build`, since
|
|
608
|
+
* preflight validated the build; `--no-pull`, since the readiness re-check right
|
|
609
|
+
* before found the branch in sync with origin or ahead of it, so a pull would only
|
|
610
|
+
* move what the check vouched for; and `--branch` naming the branch the registry entry follows,
|
|
611
|
+
* which gro checks out (its default is `main`).
|
|
612
|
+
*/
|
|
613
|
+
const gro_publish_args = (repo: Pick<LocalRepo, 'entry'>): Array<string> => {
|
|
614
|
+
const args = ['publish', '--no-build', '--no-pull'];
|
|
615
|
+
if (repo.entry.branch !== null) args.push('--branch', repo.entry.branch);
|
|
616
|
+
return args;
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* The failure message for a command the executor ran: why it failed on the first
|
|
621
|
+
* line, then the end of its stderr with known secret shapes redacted, since the
|
|
622
|
+
* message lands in the result, the event stream, and the JSON or markdown report.
|
|
623
|
+
* The live log shows the first line alone, the stderr having just streamed.
|
|
624
|
+
*/
|
|
625
|
+
const format_run_failure = (failure: { message: string; stderr_tail?: string }): string =>
|
|
626
|
+
failure.stderr_tail
|
|
627
|
+
? `${failure.message}\nthe end of its stderr:\n${redact_secrets(failure.stderr_tail)}`
|
|
628
|
+
: failure.message;
|
|
629
|
+
|
|
548
630
|
/**
|
|
549
631
|
* Groups dependency updates by dependent package — `dependent → (dependency → new
|
|
550
632
|
* version)`, the shape `update_package_json` consumes. Restricted by `predicate` (e.g.
|
|
@@ -554,7 +636,7 @@ const publish_single_repo = async (
|
|
|
554
636
|
export const group_dependency_updates = (
|
|
555
637
|
updates: Array<DependencyUpdate>,
|
|
556
638
|
published: Map<string, PublishedVersion>,
|
|
557
|
-
predicate: (update: DependencyUpdate) => boolean
|
|
639
|
+
predicate: (update: DependencyUpdate) => boolean
|
|
558
640
|
): Map<string, Map<string, string>> => {
|
|
559
641
|
const by_repo: Map<string, Map<string, string>> = new Map();
|
|
560
642
|
for (const update of updates) {
|
|
@@ -577,13 +659,13 @@ export const group_dependency_updates = (
|
|
|
577
659
|
const dependency_update_type = (
|
|
578
660
|
plan: PublishingPlan,
|
|
579
661
|
dependent: string,
|
|
580
|
-
dependency: string
|
|
662
|
+
dependency: string
|
|
581
663
|
): 'prod' | 'peer' =>
|
|
582
664
|
plan.dependency_updates.some(
|
|
583
665
|
(u) =>
|
|
584
666
|
u.dependent_package === dependent &&
|
|
585
667
|
u.updated_dependency === dependency &&
|
|
586
|
-
u.type === 'peerDependencies'
|
|
668
|
+
u.type === 'peerDependencies'
|
|
587
669
|
)
|
|
588
670
|
? 'peer'
|
|
589
671
|
: 'prod';
|