@fuzdev/fuz_gitops 0.71.0 → 0.72.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/gitops_publish.task.d.ts +1 -0
- package/dist/gitops_publish.task.d.ts.map +1 -1
- package/dist/gitops_publish.task.js +10 -1
- package/dist/multi_repo_publisher.d.ts +8 -0
- package/dist/multi_repo_publisher.d.ts.map +1 -1
- package/dist/multi_repo_publisher.js +123 -41
- package/dist/publishing_event.d.ts +130 -0
- package/dist/publishing_event.d.ts.map +1 -0
- package/dist/publishing_event.js +149 -0
- package/dist/publishing_event_handler.d.ts +42 -0
- package/dist/publishing_event_handler.d.ts.map +1 -0
- package/dist/publishing_event_handler.js +75 -0
- package/dist/repo_ops.d.ts +1 -1
- package/dist/repo_ops.js +1 -1
- package/package.json +3 -3
- package/src/lib/gitops_publish.task.ts +10 -0
- package/src/lib/multi_repo_publisher.ts +176 -41
- package/src/lib/publishing_event.ts +160 -0
- package/src/lib/publishing_event_handler.ts +98 -0
- package/src/lib/repo_ops.ts +1 -1
|
@@ -20,6 +20,7 @@ export declare const Args: z.ZodObject<{
|
|
|
20
20
|
'no-plan': z.ZodDefault<z.ZodBoolean>;
|
|
21
21
|
max_wait: z.ZodDefault<z.ZodNumber>;
|
|
22
22
|
skip_install: z.ZodDefault<z.ZodBoolean>;
|
|
23
|
+
emit_json: z.ZodDefault<z.ZodBoolean>;
|
|
23
24
|
outfile: z.ZodOptional<z.ZodString>;
|
|
24
25
|
verbose: z.ZodDefault<z.ZodBoolean>;
|
|
25
26
|
}, z.core.$strict>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gitops_publish.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_publish.task.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,aAAa,CAAC;AACtC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"gitops_publish.task.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/gitops_publish.task.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,aAAa,CAAC;AACtC,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAetB,cAAc;AACd,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;kBAsCf,CAAC;AACH,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;AAExC,cAAc;AACd,eAAO,MAAM,IAAI,EAAE,IAAI,CAAC,IAAI,CA+F3B,CAAC"}
|
|
@@ -3,6 +3,7 @@ import { createInterface } from 'node:readline/promises';
|
|
|
3
3
|
import { styleText as st } from 'node:util';
|
|
4
4
|
import { get_gitops_ready } from './gitops_task_helpers.js';
|
|
5
5
|
import { publish_repos, } from './multi_repo_publisher.js';
|
|
6
|
+
import { stdout_handler } from './publishing_event_handler.js';
|
|
6
7
|
import { generate_publishing_plan, log_publishing_plan } from './publishing_plan.js';
|
|
7
8
|
import { format_and_output } from './output_helpers.js';
|
|
8
9
|
import { GITOPS_CONFIG_PATH_DEFAULT, GITOPS_NPM_WAIT_TIMEOUT_DEFAULT } from './gitops_constants.js';
|
|
@@ -39,6 +40,10 @@ export const Args = z.strictObject({
|
|
|
39
40
|
.boolean()
|
|
40
41
|
.meta({ description: 'skip npm install after dependency updates' })
|
|
41
42
|
.default(false),
|
|
43
|
+
emit_json: z
|
|
44
|
+
.boolean()
|
|
45
|
+
.meta({ description: 'stream structured publishing events as JSON-lines to stdout' })
|
|
46
|
+
.default(false),
|
|
42
47
|
outfile: z.string().meta({ description: 'write output to file instead of logging' }).optional(),
|
|
43
48
|
verbose: z.boolean().meta({ description: 'show additional details in plan output' }).default(false),
|
|
44
49
|
});
|
|
@@ -47,7 +52,7 @@ export const task = {
|
|
|
47
52
|
summary: 'publish all repos in dependency order',
|
|
48
53
|
Args,
|
|
49
54
|
run: async ({ args, log }) => {
|
|
50
|
-
const { config, dir, peer_strategy, wetrun, format, deploy, plan, max_wait, skip_install, outfile, verbose, } = args;
|
|
55
|
+
const { config, dir, peer_strategy, wetrun, format, deploy, plan, max_wait, skip_install, emit_json, outfile, verbose, } = args;
|
|
51
56
|
// Load repos
|
|
52
57
|
const { local_repos: repos } = await get_gitops_ready({
|
|
53
58
|
config,
|
|
@@ -81,6 +86,8 @@ export const task = {
|
|
|
81
86
|
max_wait,
|
|
82
87
|
skip_install,
|
|
83
88
|
log,
|
|
89
|
+
// Live JSON-lines stream when requested; events also surface on the result.
|
|
90
|
+
events: emit_json ? stdout_handler() : undefined,
|
|
84
91
|
};
|
|
85
92
|
// Execute publishing (may throw on fatal errors like circular dependencies)
|
|
86
93
|
let result;
|
|
@@ -97,6 +104,8 @@ export const task = {
|
|
|
97
104
|
// Note: FATAL_ERROR is a placeholder - only fatal_error.message is displayed in output
|
|
98
105
|
failed: [{ name: 'FATAL_ERROR', error: fatal_error }],
|
|
99
106
|
duration: 0,
|
|
107
|
+
events: [],
|
|
108
|
+
summary: { total: 0, published: 0, failed: 1, skipped: 0, duration: 0 },
|
|
100
109
|
};
|
|
101
110
|
}
|
|
102
111
|
// Format and output result (always runs, even on fatal errors)
|
|
@@ -2,6 +2,8 @@ import type { Logger } from '@fuzdev/fuz_util/log.js';
|
|
|
2
2
|
import type { LocalRepo } from './local_repo.js';
|
|
3
3
|
import { type VersionStrategy } from './dependency_updater.js';
|
|
4
4
|
import type { GitopsOperations } from './operations.js';
|
|
5
|
+
import { type PublishingEvent, type PublishingRunSummary } from './publishing_event.js';
|
|
6
|
+
import { type PublishingEventHandler } from './publishing_event_handler.js';
|
|
5
7
|
export interface PublishingOptions {
|
|
6
8
|
wetrun: boolean;
|
|
7
9
|
update_deps: boolean;
|
|
@@ -11,6 +13,8 @@ export interface PublishingOptions {
|
|
|
11
13
|
skip_install?: boolean;
|
|
12
14
|
log?: Logger;
|
|
13
15
|
ops?: GitopsOperations;
|
|
16
|
+
/** Structured event sink; defaults to capture-only (events surface on the result). */
|
|
17
|
+
events?: PublishingEventHandler;
|
|
14
18
|
}
|
|
15
19
|
export interface PublishedVersion {
|
|
16
20
|
name: string;
|
|
@@ -29,6 +33,10 @@ export interface PublishingResult {
|
|
|
29
33
|
error: Error;
|
|
30
34
|
}>;
|
|
31
35
|
duration: number;
|
|
36
|
+
/** The structured event stream for this run, in emission order. */
|
|
37
|
+
events: Array<PublishingEvent>;
|
|
38
|
+
/** Tallied outcome, derived from `events`. */
|
|
39
|
+
summary: PublishingRunSummary;
|
|
32
40
|
}
|
|
33
41
|
export declare const publish_repos: (repos: Array<LocalRepo>, options: PublishingOptions) => Promise<PublishingResult>;
|
|
34
42
|
//# sourceMappingURL=multi_repo_publisher.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multi_repo_publisher.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/multi_repo_publisher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAKpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAsB,KAAK,eAAe,EAAC,MAAM,yBAAyB,CAAC;AAIlF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"multi_repo_publisher.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/multi_repo_publisher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAKpD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAsB,KAAK,eAAe,EAAC,MAAM,yBAAyB,CAAC;AAIlF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,iBAAiB,CAAC;AAOtD,OAAO,EACN,KAAK,eAAe,EACpB,KAAK,oBAAoB,EAEzB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACN,KAAK,sBAAsB,EAG3B,MAAM,+BAA+B,CAAC;AAEvC,MAAM,WAAW,iBAAiB;IACjC,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;IACrB,gBAAgB,CAAC,EAAE,eAAe,CAAC;IACnC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,gBAAgB,CAAC;IACvB,sFAAsF;IACtF,MAAM,CAAC,EAAE,sBAAsB,CAAC;CAChC;AAED,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IACvC,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,gBAAgB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,SAAS,EAAE,KAAK,CAAC,gBAAgB,CAAC,CAAC;IACnC,MAAM,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAC,CAAC,CAAC;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,MAAM,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;IAC/B,8CAA8C;IAC9C,OAAO,EAAE,oBAAoB,CAAC;CAC9B;AAED,eAAO,MAAM,aAAa,GACzB,OAAO,KAAK,CAAC,SAAS,CAAC,EACvB,SAAS,iBAAiB,KACxB,OAAO,CAAC,gBAAgB,CA2Z1B,CAAC"}
|
|
@@ -7,9 +7,17 @@ import { needs_update, is_breaking_change, detect_bump_type } from './version_ut
|
|
|
7
7
|
import { default_gitops_operations } from './operations_defaults.js';
|
|
8
8
|
import { GITOPS_MAX_ITERATIONS_DEFAULT, GITOPS_NPM_WAIT_TIMEOUT_DEFAULT, } from './gitops_constants.js';
|
|
9
9
|
import { install_with_cache_healing } from './npm_install_helpers.js';
|
|
10
|
+
import { summarize_events, } from './publishing_event.js';
|
|
11
|
+
import { capture_handler, multi_handler, } from './publishing_event_handler.js';
|
|
10
12
|
export const publish_repos = async (repos, options) => {
|
|
11
13
|
const start_time = Date.now();
|
|
12
14
|
const { wetrun, update_deps, log, ops = default_gitops_operations } = options;
|
|
15
|
+
// Capture every event for the result; also forward to the caller's sink if provided.
|
|
16
|
+
const capture = capture_handler();
|
|
17
|
+
const events_handler = options.events ? multi_handler([options.events, capture]) : capture;
|
|
18
|
+
const emit = (event) => {
|
|
19
|
+
events_handler.emit(event);
|
|
20
|
+
};
|
|
13
21
|
// Preflight checks (skip for dry runs since we're not actually publishing)
|
|
14
22
|
if (wetrun) {
|
|
15
23
|
const preflight_options = {
|
|
@@ -39,16 +47,19 @@ export const publish_repos = async (repos, options) => {
|
|
|
39
47
|
log_cycles: true,
|
|
40
48
|
log_order: true,
|
|
41
49
|
});
|
|
50
|
+
emit({ event: 'run_started', wetrun, total: order.length });
|
|
42
51
|
const published = new Map();
|
|
43
52
|
const failed = new Map();
|
|
44
53
|
const changed_repos = new Set(); // Track repos with any changes for selective deployment
|
|
54
|
+
const skipped_packages = new Set(); // dedupe the package_skipped event across iterations
|
|
45
55
|
// Fixed-point iteration: keep publishing until no new changesets are created
|
|
46
56
|
// This handles transitive dependency updates (auto-generated changesets)
|
|
47
57
|
let iteration = 0;
|
|
48
58
|
let converged = false;
|
|
49
59
|
while (!converged && iteration < GITOPS_MAX_ITERATIONS_DEFAULT) {
|
|
50
60
|
iteration++;
|
|
51
|
-
|
|
61
|
+
emit({ event: 'iteration_started', iteration, max: GITOPS_MAX_ITERATIONS_DEFAULT });
|
|
62
|
+
log?.info(st('cyan', `\n🚀 ${wetrun ? 'Publishing' : 'Dry run'} iteration ${iteration}/${GITOPS_MAX_ITERATIONS_DEFAULT}...\n`));
|
|
52
63
|
// Track if any packages were published in this iteration
|
|
53
64
|
let published_in_iteration = false;
|
|
54
65
|
let published_count = 0;
|
|
@@ -70,6 +81,7 @@ export const publish_repos = async (repos, options) => {
|
|
|
70
81
|
// Failed to check changesets
|
|
71
82
|
const err = new Error(`Failed to check changesets: ${has_result.message}`);
|
|
72
83
|
failed.set(pkg_name, err);
|
|
84
|
+
emit({ event: 'package_failed', name: pkg_name, error: err.message, code: 'dependency' });
|
|
73
85
|
log?.error(st('red', ` ❌ ${err.message}`));
|
|
74
86
|
break;
|
|
75
87
|
}
|
|
@@ -77,6 +89,11 @@ export const publish_repos = async (repos, options) => {
|
|
|
77
89
|
// Skip packages without changesets
|
|
78
90
|
// In real publish: They might get auto-changesets during dependency updates
|
|
79
91
|
// In dry run: We can't simulate auto-changesets, so just skip
|
|
92
|
+
// Emit once per package — the loop revisits no-changeset packages each iteration
|
|
93
|
+
if (!skipped_packages.has(pkg_name)) {
|
|
94
|
+
skipped_packages.add(pkg_name);
|
|
95
|
+
emit({ event: 'package_skipped', name: pkg_name, reason: 'no changesets' });
|
|
96
|
+
}
|
|
80
97
|
if (!wetrun) {
|
|
81
98
|
// Silent skip in dry run - plan shows which packages get auto-changesets
|
|
82
99
|
continue;
|
|
@@ -88,7 +105,7 @@ export const publish_repos = async (repos, options) => {
|
|
|
88
105
|
}
|
|
89
106
|
try {
|
|
90
107
|
// 1. Publish this package
|
|
91
|
-
log?.info(st('dim', ` [${i + 1}/${order.length}] Publishing ${pkg_name}...`));
|
|
108
|
+
log?.info(st('dim', ` [${i + 1}/${order.length}] ${wetrun ? 'Publishing' : 'Would publish'} ${pkg_name}...`));
|
|
92
109
|
const version = await publish_single_repo(repo, options, ops);
|
|
93
110
|
published.set(pkg_name, version);
|
|
94
111
|
changed_repos.add(pkg_name); // Mark as changed for deployment
|
|
@@ -96,7 +113,19 @@ export const publish_repos = async (repos, options) => {
|
|
|
96
113
|
// (their dependencies didn't change, only their version)
|
|
97
114
|
published_in_iteration = true;
|
|
98
115
|
published_count++;
|
|
99
|
-
|
|
116
|
+
emit({
|
|
117
|
+
event: 'package_completed',
|
|
118
|
+
name: pkg_name,
|
|
119
|
+
old_version: version.old_version,
|
|
120
|
+
new_version: version.new_version,
|
|
121
|
+
bump_type: version.bump_type,
|
|
122
|
+
breaking: version.breaking,
|
|
123
|
+
commit: version.commit,
|
|
124
|
+
tag: version.tag,
|
|
125
|
+
});
|
|
126
|
+
log?.info(wetrun
|
|
127
|
+
? st('green', ` ✅ Published ${pkg_name}@${version.new_version}`)
|
|
128
|
+
: st('cyan', ` ◇ Would publish ${pkg_name}@${version.new_version}`));
|
|
100
129
|
if (wetrun) {
|
|
101
130
|
// 2. Wait for this package to be available on NPM
|
|
102
131
|
log?.info(` ⏳ Waiting for ${pkg_name}@${version.new_version} on NPM...`);
|
|
@@ -112,7 +141,14 @@ export const publish_repos = async (repos, options) => {
|
|
|
112
141
|
log,
|
|
113
142
|
});
|
|
114
143
|
if (!wait_result.ok) {
|
|
115
|
-
|
|
144
|
+
// Handle inline (don't throw into the generic catch): the npm-wait failure
|
|
145
|
+
// carries a typed `timeout` signal, so we know this is a network failure
|
|
146
|
+
// without sniffing the message.
|
|
147
|
+
const err = new Error(`Failed to wait for package: ${wait_result.message}${wait_result.timeout ? ' (timeout)' : ''}`);
|
|
148
|
+
failed.set(pkg_name, err);
|
|
149
|
+
emit({ event: 'package_failed', name: pkg_name, error: err.message, code: 'network' });
|
|
150
|
+
log?.error(st('red', ` ❌ Failed to publish ${pkg_name}: ${err.message}`));
|
|
151
|
+
break; // fail fast
|
|
116
152
|
}
|
|
117
153
|
// 3. Update all repos that have prod/peer deps on this package
|
|
118
154
|
if (update_deps) {
|
|
@@ -135,6 +171,12 @@ export const publish_repos = async (repos, options) => {
|
|
|
135
171
|
// Apply updates if any
|
|
136
172
|
if (updates.size > 0) {
|
|
137
173
|
log?.info(` Updating ${dependent_repo.library.name}'s dependency on ${pkg_name}`);
|
|
174
|
+
emit({
|
|
175
|
+
event: 'dependency_updated',
|
|
176
|
+
dependent: dependent_repo.library.name,
|
|
177
|
+
dependency: pkg_name,
|
|
178
|
+
version: version.new_version,
|
|
179
|
+
});
|
|
138
180
|
changed_repos.add(dependent_repo.library.name); // Mark as changed for deployment
|
|
139
181
|
changed_in_iteration.add(dependent_repo.library.name); // Track for batch install
|
|
140
182
|
await update_package_json(dependent_repo, updates, {
|
|
@@ -151,6 +193,15 @@ export const publish_repos = async (repos, options) => {
|
|
|
151
193
|
catch (error) {
|
|
152
194
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
153
195
|
failed.set(pkg_name, err);
|
|
196
|
+
emit({
|
|
197
|
+
event: 'package_failed',
|
|
198
|
+
name: pkg_name,
|
|
199
|
+
error: err.message,
|
|
200
|
+
// TODO: emit a precise code once the npm/process ops return typed errors —
|
|
201
|
+
// today a publish-step cause lives in unstructured stderr, so use the honest
|
|
202
|
+
// coarse bucket rather than guessing 'auth'/'network'/'build' from the message.
|
|
203
|
+
code: 'publish',
|
|
204
|
+
});
|
|
154
205
|
log?.error(st('red', ` ❌ Failed to publish ${pkg_name}: ${err.message}`));
|
|
155
206
|
break; // Always fail fast on error
|
|
156
207
|
}
|
|
@@ -159,31 +210,26 @@ export const publish_repos = async (repos, options) => {
|
|
|
159
210
|
// This ensures workspace stays consistent before next iteration
|
|
160
211
|
if (wetrun && !options.skip_install && changed_in_iteration.size > 0) {
|
|
161
212
|
log?.info(st('cyan', '\n📦 Installing dependencies for updated repos...\n'));
|
|
162
|
-
for (const
|
|
163
|
-
|
|
164
|
-
if (!repo)
|
|
165
|
-
continue;
|
|
166
|
-
try {
|
|
167
|
-
log?.info(` Installing ${pkg_name}...`);
|
|
168
|
-
await install_with_cache_healing(repo, ops, log);
|
|
169
|
-
log?.info(st('green', ` ✅ Installed ${pkg_name}`));
|
|
170
|
-
}
|
|
171
|
-
catch (error) {
|
|
172
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
173
|
-
failed.set(pkg_name, err);
|
|
174
|
-
log?.error(st('red', ` ❌ Failed to install ${pkg_name}: ${err.message}`));
|
|
175
|
-
// Continue with other installs instead of breaking
|
|
176
|
-
}
|
|
213
|
+
for (const [name, err] of await install_repos(changed_in_iteration, repos, ops, emit, log)) {
|
|
214
|
+
failed.set(name, err);
|
|
177
215
|
}
|
|
178
216
|
}
|
|
179
217
|
// Log iteration summary
|
|
180
218
|
if (published_count > 0) {
|
|
181
|
-
log?.info(st('dim', `\nIteration ${iteration}: ${published_count} package(s) published\n`));
|
|
219
|
+
log?.info(st('dim', `\nIteration ${iteration}: ${published_count} package(s) ${wetrun ? 'published' : 'would be published'}\n`));
|
|
182
220
|
}
|
|
221
|
+
emit({
|
|
222
|
+
event: 'iteration_finished',
|
|
223
|
+
iteration,
|
|
224
|
+
published_count,
|
|
225
|
+
converged: !published_in_iteration,
|
|
226
|
+
});
|
|
183
227
|
// Check for convergence: no packages published in this iteration
|
|
184
228
|
if (!published_in_iteration) {
|
|
185
229
|
converged = true;
|
|
186
|
-
log?.info(st('green',
|
|
230
|
+
log?.info(st('green', wetrun
|
|
231
|
+
? `\n✓ Converged after ${iteration} iteration(s) - no new changesets\n`
|
|
232
|
+
: `\n✓ Dry run complete after ${iteration} iteration(s)\n`));
|
|
187
233
|
}
|
|
188
234
|
else if (iteration === GITOPS_MAX_ITERATIONS_DEFAULT) {
|
|
189
235
|
// Count packages that still have changesets (not yet published)
|
|
@@ -212,6 +258,14 @@ export const publish_repos = async (repos, options) => {
|
|
|
212
258
|
}
|
|
213
259
|
if (dev_updates.size > 0) {
|
|
214
260
|
log?.info(` Updating ${dev_updates.size} dev dependencies in ${repo.library.name}`);
|
|
261
|
+
for (const [dep_name, dep_version] of dev_updates) {
|
|
262
|
+
emit({
|
|
263
|
+
event: 'dependency_updated',
|
|
264
|
+
dependent: repo.library.name,
|
|
265
|
+
dependency: dep_name,
|
|
266
|
+
version: dep_version,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
215
269
|
changed_repos.add(repo.library.name); // Mark as changed for deployment
|
|
216
270
|
dev_updated_repos.add(repo.library.name); // Track for batch install
|
|
217
271
|
await update_package_json(repo, dev_updates, {
|
|
@@ -226,21 +280,8 @@ export const publish_repos = async (repos, options) => {
|
|
|
226
280
|
// Phase 2b: Install dev dependencies for repos with dev dep updates
|
|
227
281
|
if (wetrun && !options.skip_install && dev_updated_repos.size > 0) {
|
|
228
282
|
log?.info(st('cyan', '\n📦 Installing dev dependencies for updated repos...\n'));
|
|
229
|
-
for (const
|
|
230
|
-
|
|
231
|
-
if (!repo)
|
|
232
|
-
continue;
|
|
233
|
-
try {
|
|
234
|
-
log?.info(` Installing ${pkg_name}...`);
|
|
235
|
-
await install_with_cache_healing(repo, ops, log);
|
|
236
|
-
log?.info(st('green', ` ✅ Installed ${pkg_name}`));
|
|
237
|
-
}
|
|
238
|
-
catch (error) {
|
|
239
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
240
|
-
failed.set(pkg_name, err);
|
|
241
|
-
log?.error(st('red', ` ❌ Failed to install ${pkg_name}: ${err.message}`));
|
|
242
|
-
// Continue with other installs instead of breaking
|
|
243
|
-
}
|
|
283
|
+
for (const [name, err] of await install_repos(dev_updated_repos, repos, ops, emit, log)) {
|
|
284
|
+
failed.set(name, err);
|
|
244
285
|
}
|
|
245
286
|
}
|
|
246
287
|
// Phase 3: Deploy repos with changes (optional)
|
|
@@ -250,6 +291,7 @@ export const publish_repos = async (repos, options) => {
|
|
|
250
291
|
log?.info(st('cyan', `\n🚢 Deploying ${repos_to_deploy.length}/${repos.length} repos with changes...\n`));
|
|
251
292
|
for (const repo of repos_to_deploy) {
|
|
252
293
|
try {
|
|
294
|
+
emit({ event: 'deploy_started', name: repo.library.name });
|
|
253
295
|
log?.info(` Deploying ${repo.library.name}...`);
|
|
254
296
|
const deploy_result = await ops.process.spawn({
|
|
255
297
|
cmd: 'gro',
|
|
@@ -257,37 +299,49 @@ export const publish_repos = async (repos, options) => {
|
|
|
257
299
|
cwd: repo.repo_dir,
|
|
258
300
|
});
|
|
259
301
|
if (deploy_result.ok) {
|
|
302
|
+
emit({ event: 'deploy_completed', name: repo.library.name });
|
|
260
303
|
log?.info(st('green', ` ✅ Deployed ${repo.library.name}`));
|
|
261
304
|
}
|
|
262
305
|
else {
|
|
306
|
+
emit({ event: 'deploy_failed', name: repo.library.name, error: deploy_result.message });
|
|
263
307
|
log?.warn(st('yellow', ` ⚠️ Failed to deploy ${repo.library.name}`));
|
|
264
308
|
}
|
|
265
309
|
}
|
|
266
310
|
catch (error) {
|
|
267
|
-
|
|
311
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
312
|
+
emit({ event: 'deploy_failed', name: repo.library.name, error: err.message });
|
|
313
|
+
log?.error(st('red', ` ❌ Error deploying ${repo.library.name}: ${err.message}`));
|
|
268
314
|
}
|
|
269
315
|
}
|
|
270
316
|
}
|
|
271
317
|
// Summary
|
|
272
318
|
const duration = Date.now() - start_time;
|
|
273
319
|
const ok = failed.size === 0;
|
|
274
|
-
|
|
320
|
+
const summary = summarize_events(capture.events, duration);
|
|
321
|
+
log?.info(st('cyan', `\n📋 ${wetrun ? 'Publishing' : 'Dry Run'} Summary\n`));
|
|
275
322
|
log?.info(` Duration: ${(duration / 1000).toFixed(1)}s`);
|
|
276
|
-
log?.info(` Published: ${published.size} packages`);
|
|
323
|
+
log?.info(` ${wetrun ? 'Published' : 'Would publish'}: ${published.size} packages`);
|
|
277
324
|
if (failed.size > 0) {
|
|
278
325
|
log?.info(` Failed: ${failed.size} packages`);
|
|
279
326
|
}
|
|
280
327
|
if (ok) {
|
|
281
|
-
log?.info(st('green',
|
|
328
|
+
log?.info(st('green', wetrun
|
|
329
|
+
? '\n✨ All packages published successfully!\n'
|
|
330
|
+
: `\n✨ Dry run complete — ${published.size} package(s) would be published. Re-run with --wetrun to publish.\n`));
|
|
282
331
|
}
|
|
283
332
|
else {
|
|
284
|
-
log?.error(st('red',
|
|
333
|
+
log?.error(st('red', wetrun
|
|
334
|
+
? '\n❌ Some packages failed to publish\n'
|
|
335
|
+
: '\n❌ Some packages failed during dry run\n'));
|
|
285
336
|
}
|
|
337
|
+
emit({ event: 'run_finished', summary });
|
|
286
338
|
return {
|
|
287
339
|
ok,
|
|
288
340
|
published: Array.from(published.values()),
|
|
289
341
|
failed: Array.from(failed.entries()).map(([name, error]) => ({ name, error })),
|
|
290
342
|
duration,
|
|
343
|
+
events: capture.events,
|
|
344
|
+
summary,
|
|
291
345
|
};
|
|
292
346
|
};
|
|
293
347
|
/**
|
|
@@ -361,3 +415,31 @@ const publish_single_repo = async (repo, options, ops = default_gitops_operation
|
|
|
361
415
|
tag: `v${new_version}`,
|
|
362
416
|
};
|
|
363
417
|
};
|
|
418
|
+
/**
|
|
419
|
+
* Installs dependencies for each named repo (with cache healing), emitting install
|
|
420
|
+
* events and logging progress. A failed install doesn't stop the batch — failures are
|
|
421
|
+
* collected and returned so the caller can fold them into the run's failures.
|
|
422
|
+
*/
|
|
423
|
+
const install_repos = async (names, repos, ops, emit, log) => {
|
|
424
|
+
const failures = new Map();
|
|
425
|
+
for (const name of names) {
|
|
426
|
+
const repo = repos.find((r) => r.library.name === name);
|
|
427
|
+
if (!repo)
|
|
428
|
+
continue;
|
|
429
|
+
try {
|
|
430
|
+
emit({ event: 'install_started', name });
|
|
431
|
+
log?.info(` Installing ${name}...`);
|
|
432
|
+
await install_with_cache_healing(repo, ops, log);
|
|
433
|
+
emit({ event: 'install_completed', name });
|
|
434
|
+
log?.info(st('green', ` ✅ Installed ${name}`));
|
|
435
|
+
}
|
|
436
|
+
catch (error) {
|
|
437
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
438
|
+
failures.set(name, err);
|
|
439
|
+
emit({ event: 'install_failed', name, error: err.message });
|
|
440
|
+
log?.error(st('red', ` ❌ Failed to install ${name}: ${err.message}`));
|
|
441
|
+
// continue with other installs instead of breaking
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return failures;
|
|
445
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured events for multi-repo publishing.
|
|
3
|
+
*
|
|
4
|
+
* Publishing emits a stream of tagged events alongside its human-readable logging,
|
|
5
|
+
* so machine consumers (CI, dashboards) can follow a run step by step. Every run
|
|
6
|
+
* opens with a `run_started` event carrying `wetrun`: when `false`, the run is a dry
|
|
7
|
+
* run and every `package_completed` is a prediction (its `commit` is `'simulated'`)
|
|
8
|
+
* rather than an applied change. A run's `run_finished` summary is derived from the
|
|
9
|
+
* same event list via `summarize_events`, so the stream and the summary never drift.
|
|
10
|
+
*
|
|
11
|
+
* Events are consumed through a `PublishingEventHandler` sink (see
|
|
12
|
+
* `publishing_event_handler.ts`).
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
/**
|
|
18
|
+
* Coarse triage classification for a failed package. Lets consumers branch on
|
|
19
|
+
* failure kind without parsing the message.
|
|
20
|
+
*/
|
|
21
|
+
export declare const PublishingErrorCode: z.ZodEnum<{
|
|
22
|
+
other: "other";
|
|
23
|
+
build: "build";
|
|
24
|
+
publish: "publish";
|
|
25
|
+
network: "network";
|
|
26
|
+
auth: "auth";
|
|
27
|
+
dependency: "dependency";
|
|
28
|
+
}>;
|
|
29
|
+
export type PublishingErrorCode = z.infer<typeof PublishingErrorCode>;
|
|
30
|
+
/** Tallied outcome of a publishing run, derived from its events via `summarize_events`. */
|
|
31
|
+
export declare const PublishingRunSummary: z.ZodObject<{
|
|
32
|
+
total: z.ZodNumber;
|
|
33
|
+
published: z.ZodNumber;
|
|
34
|
+
failed: z.ZodNumber;
|
|
35
|
+
skipped: z.ZodNumber;
|
|
36
|
+
duration: z.ZodNumber;
|
|
37
|
+
}, z.core.$strict>;
|
|
38
|
+
export type PublishingRunSummary = z.infer<typeof PublishingRunSummary>;
|
|
39
|
+
/**
|
|
40
|
+
* A single structured event emitted during a publishing run. Tagged on `event` so the
|
|
41
|
+
* union serializes as one self-describing JSON object per event (JSON-lines on the wire).
|
|
42
|
+
*/
|
|
43
|
+
export declare const PublishingEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
44
|
+
event: z.ZodLiteral<"run_started">;
|
|
45
|
+
wetrun: z.ZodBoolean;
|
|
46
|
+
total: z.ZodNumber;
|
|
47
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
48
|
+
event: z.ZodLiteral<"iteration_started">;
|
|
49
|
+
iteration: z.ZodNumber;
|
|
50
|
+
max: z.ZodNumber;
|
|
51
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
52
|
+
event: z.ZodLiteral<"iteration_finished">;
|
|
53
|
+
iteration: z.ZodNumber;
|
|
54
|
+
published_count: z.ZodNumber;
|
|
55
|
+
converged: z.ZodBoolean;
|
|
56
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
57
|
+
event: z.ZodLiteral<"package_skipped">;
|
|
58
|
+
name: z.ZodString;
|
|
59
|
+
reason: z.ZodString;
|
|
60
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
61
|
+
event: z.ZodLiteral<"package_completed">;
|
|
62
|
+
name: z.ZodString;
|
|
63
|
+
old_version: z.ZodString;
|
|
64
|
+
new_version: z.ZodString;
|
|
65
|
+
bump_type: z.ZodEnum<{
|
|
66
|
+
major: "major";
|
|
67
|
+
minor: "minor";
|
|
68
|
+
patch: "patch";
|
|
69
|
+
}>;
|
|
70
|
+
breaking: z.ZodBoolean;
|
|
71
|
+
commit: z.ZodString;
|
|
72
|
+
tag: z.ZodString;
|
|
73
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
74
|
+
event: z.ZodLiteral<"package_failed">;
|
|
75
|
+
name: z.ZodString;
|
|
76
|
+
error: z.ZodString;
|
|
77
|
+
code: z.ZodEnum<{
|
|
78
|
+
other: "other";
|
|
79
|
+
build: "build";
|
|
80
|
+
publish: "publish";
|
|
81
|
+
network: "network";
|
|
82
|
+
auth: "auth";
|
|
83
|
+
dependency: "dependency";
|
|
84
|
+
}>;
|
|
85
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
86
|
+
event: z.ZodLiteral<"dependency_updated">;
|
|
87
|
+
dependent: z.ZodString;
|
|
88
|
+
dependency: z.ZodString;
|
|
89
|
+
version: z.ZodString;
|
|
90
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
91
|
+
event: z.ZodLiteral<"install_started">;
|
|
92
|
+
name: z.ZodString;
|
|
93
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
94
|
+
event: z.ZodLiteral<"install_completed">;
|
|
95
|
+
name: z.ZodString;
|
|
96
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
97
|
+
event: z.ZodLiteral<"install_failed">;
|
|
98
|
+
name: z.ZodString;
|
|
99
|
+
error: z.ZodString;
|
|
100
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
101
|
+
event: z.ZodLiteral<"deploy_started">;
|
|
102
|
+
name: z.ZodString;
|
|
103
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
104
|
+
event: z.ZodLiteral<"deploy_completed">;
|
|
105
|
+
name: z.ZodString;
|
|
106
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
107
|
+
event: z.ZodLiteral<"deploy_failed">;
|
|
108
|
+
name: z.ZodString;
|
|
109
|
+
error: z.ZodString;
|
|
110
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
111
|
+
event: z.ZodLiteral<"run_finished">;
|
|
112
|
+
summary: z.ZodObject<{
|
|
113
|
+
total: z.ZodNumber;
|
|
114
|
+
published: z.ZodNumber;
|
|
115
|
+
failed: z.ZodNumber;
|
|
116
|
+
skipped: z.ZodNumber;
|
|
117
|
+
duration: z.ZodNumber;
|
|
118
|
+
}, z.core.$strict>;
|
|
119
|
+
}, z.core.$strict>], "event">;
|
|
120
|
+
export type PublishingEvent = z.infer<typeof PublishingEvent>;
|
|
121
|
+
/**
|
|
122
|
+
* Derives a run summary from the captured event list — the single canonical path from
|
|
123
|
+
* events to summary, so the `run_finished` summary always agrees with the stream.
|
|
124
|
+
* Call before emitting `run_finished` (which is not itself counted).
|
|
125
|
+
*
|
|
126
|
+
* @param events - the events captured so far this run
|
|
127
|
+
* @param duration - wall-clock duration in milliseconds
|
|
128
|
+
*/
|
|
129
|
+
export declare const summarize_events: (events: Array<PublishingEvent>, duration: number) => PublishingRunSummary;
|
|
130
|
+
//# sourceMappingURL=publishing_event.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publishing_event.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/publishing_event.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB;;;GAGG;AACH,eAAO,MAAM,mBAAmB;;;;;;;EAO9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,2FAA2F;AAC3F,eAAO,MAAM,oBAAoB;;;;;;kBAM/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE;;;GAGG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6BA2E1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,GAC5B,QAAQ,KAAK,CAAC,eAAe,CAAC,EAC9B,UAAU,MAAM,KACd,oBAwBF,CAAC"}
|