@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
|
@@ -4,10 +4,12 @@ import { styleText as st } from 'node:util';
|
|
|
4
4
|
import { repo_is_npm } from './local_repo.js';
|
|
5
5
|
import { update_package_json } from './dependency_updater.js';
|
|
6
6
|
import { generate_publishing_plan } from './publishing_plan.js';
|
|
7
|
+
import { load_repos_status, to_repos_command } from './repos_status_load.js';
|
|
8
|
+
import { check_publish_readiness } from './repo_readiness.js';
|
|
7
9
|
import { default_gitops_operations } from './operations_defaults.js';
|
|
8
10
|
import { GITOPS_NPM_WAIT_TIMEOUT_DEFAULT } from './gitops_constants.js';
|
|
9
11
|
import { summarize_events } from './publishing_event.js';
|
|
10
|
-
import { capture_handler, multi_handler } from './publishing_event_handler.js';
|
|
12
|
+
import { capture_handler, multi_handler, redact_secrets } from './publishing_event_handler.js';
|
|
11
13
|
export const publish_repos = async (repos, options) => {
|
|
12
14
|
const { log, ops = default_gitops_operations } = options;
|
|
13
15
|
// Convenience entry: generate the plan, then execute it. Callers that already hold a
|
|
@@ -27,6 +29,7 @@ export const publish_repos = async (repos, options) => {
|
|
|
27
29
|
export const execute_publishing_plan = async (all_repos, plan, options) => {
|
|
28
30
|
const start_time = Date.now();
|
|
29
31
|
const { wetrun, log, ops = default_gitops_operations } = options;
|
|
32
|
+
const version_strategy = options.version_strategy ?? 'caret';
|
|
30
33
|
// Only npm repos publish; drop any non-npm repos (e.g. cargo) so preflight and the
|
|
31
34
|
// executor's name lookup never touch them. The plan already excludes them too.
|
|
32
35
|
const repos = all_repos.filter(repo_is_npm);
|
|
@@ -42,20 +45,16 @@ export const execute_publishing_plan = async (all_repos, plan, options) => {
|
|
|
42
45
|
const emit = (event) => {
|
|
43
46
|
events_handler.emit(event);
|
|
44
47
|
};
|
|
45
|
-
// Preflight checks (skip for dry runs since we're not actually publishing)
|
|
48
|
+
// Preflight checks (skip for dry runs since we're not actually publishing): it builds
|
|
49
|
+
// exactly the packages the plan publishes. Repo git state isn't preflight's:
|
|
50
|
+
// `gitops_publish --wetrun` gates on it before its confirmation prompt.
|
|
46
51
|
if (wetrun) {
|
|
47
|
-
const preflight_options = {
|
|
48
|
-
skip_changesets: false, // Always check for changesets
|
|
49
|
-
required_branch: 'main',
|
|
50
|
-
log
|
|
51
|
-
};
|
|
52
52
|
const preflight = await ops.preflight.run_preflight_checks({
|
|
53
53
|
repos,
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
version_changes: plan.version_changes,
|
|
55
|
+
log,
|
|
56
56
|
npm_ops: ops.npm,
|
|
57
|
-
build_ops: ops.build
|
|
58
|
-
changeset_ops: ops.changeset
|
|
57
|
+
build_ops: ops.build
|
|
59
58
|
});
|
|
60
59
|
if (!preflight.ok) {
|
|
61
60
|
throw new TaskError(`Preflight checks failed: ${preflight.errors.join(', ')}`);
|
|
@@ -96,6 +95,23 @@ export const execute_publishing_plan = async (all_repos, plan, options) => {
|
|
|
96
95
|
const repo = repo_by_name.get(pkg_name);
|
|
97
96
|
if (!repo)
|
|
98
97
|
continue;
|
|
98
|
+
// Re-check the repo right before its `gro publish`: the gate ran before the prompt, and a
|
|
99
|
+
// cascade's npm waits leave a long window. `--no-pull` means gro won't notice origin
|
|
100
|
+
// moving until its push is rejected — after `changeset publish` put the version on npm —
|
|
101
|
+
// and its `commit -a` would sweep a tracked edit made since into the release commit.
|
|
102
|
+
// Aborting here, before any npm side effect, leaves the dirty state resumption expects.
|
|
103
|
+
if (wetrun) {
|
|
104
|
+
const recheck = await recheck_publish_readiness(repo, options.registry, ops);
|
|
105
|
+
if (!recheck.ok) {
|
|
106
|
+
const err = new Error(`${pkg_name} isn't ready to publish, re-checked right before \`gro publish\`: ` +
|
|
107
|
+
`${recheck.message}. Aborting before it touches npm — fix it, then re-run ` +
|
|
108
|
+
`'gro gitops_publish --wetrun' to re-plan from the current state.`);
|
|
109
|
+
failed.set(pkg_name, err);
|
|
110
|
+
emit({ event: 'package_failed', name: pkg_name, error: err.message, code: 'not_ready' });
|
|
111
|
+
log?.error(st('red', ` ❌ ${err.message}`));
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
99
115
|
// An earlier publish may have rewritten this package's dependency ranges. No install is
|
|
100
116
|
// needed here: `gro publish` runs its own install (which self-heals npm's stale-cache
|
|
101
117
|
// ETARGET), so the package's freshly-rewritten deps are installed and healed as part of
|
|
@@ -146,10 +162,9 @@ export const execute_publishing_plan = async (all_repos, plan, options) => {
|
|
|
146
162
|
log
|
|
147
163
|
});
|
|
148
164
|
if (!wait_result.ok) {
|
|
149
|
-
// Handle inline (don't throw into the generic catch):
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
const err = new Error(`Failed to wait for package: ${wait_result.message}${wait_result.timeout ? ' (timeout)' : ''}`);
|
|
165
|
+
// Handle inline (don't throw into the generic catch): a failed npm wait is a
|
|
166
|
+
// network failure, known by where it happened rather than by sniffing the message.
|
|
167
|
+
const err = new Error(`Failed to wait for package: ${wait_result.message}`);
|
|
153
168
|
failed.set(pkg_name, err);
|
|
154
169
|
emit({ event: 'package_failed', name: pkg_name, error: err.message, code: 'network' });
|
|
155
170
|
log?.error(st('red', ` ❌ Failed to publish ${pkg_name}: ${err.message}`));
|
|
@@ -158,8 +173,8 @@ export const execute_publishing_plan = async (all_repos, plan, options) => {
|
|
|
158
173
|
emit({ event: 'npm_waited', name: pkg_name, version: version.new_version });
|
|
159
174
|
// 3. Update every dependent the plan says has a prod/peer dep on this package.
|
|
160
175
|
// This rewrites their package.json ranges and creates their auto-changeset,
|
|
161
|
-
// which a later step of this same pass publishes
|
|
162
|
-
//
|
|
176
|
+
// which a later step of this same pass publishes; its `gro publish` installs the
|
|
177
|
+
// rewritten deps then (see the top of the loop).
|
|
163
178
|
const dependent_updates = group_dependency_updates(plan.dependency_updates, published, (update) => update.updated_dependency === pkg_name &&
|
|
164
179
|
(update.type === 'dependencies' || update.type === 'peerDependencies'));
|
|
165
180
|
for (const [dependent_name, updates] of dependent_updates) {
|
|
@@ -184,24 +199,15 @@ export const execute_publishing_plan = async (all_repos, plan, options) => {
|
|
|
184
199
|
});
|
|
185
200
|
}
|
|
186
201
|
changed_repos.add(dependent_name); // Mark as changed for deployment
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
// update-only leaf: rewrite ranges + commit, no changeset (it won't republish)
|
|
198
|
-
await update_package_json(dependent_repo, updates, {
|
|
199
|
-
strategy: options.version_strategy || 'caret',
|
|
200
|
-
log,
|
|
201
|
-
git_ops: ops.git,
|
|
202
|
-
fs_ops: ops.fs
|
|
203
|
-
});
|
|
204
|
-
}
|
|
202
|
+
await update_package_json(dependent_repo, updates, {
|
|
203
|
+
strategy: version_strategy,
|
|
204
|
+
// `published_versions` creates the auto-changeset; an update-only leaf gets
|
|
205
|
+
// its ranges rewritten and committed with no changeset (it won't republish)
|
|
206
|
+
published_versions: republishes ? published : undefined,
|
|
207
|
+
log,
|
|
208
|
+
git_ops: ops.git,
|
|
209
|
+
fs_ops: ops.fs
|
|
210
|
+
});
|
|
205
211
|
}
|
|
206
212
|
}
|
|
207
213
|
}
|
|
@@ -214,10 +220,11 @@ export const execute_publishing_plan = async (all_repos, plan, options) => {
|
|
|
214
220
|
error: err.message,
|
|
215
221
|
// TODO: emit a precise code once the npm/process ops return typed errors —
|
|
216
222
|
// today a publish-step cause lives in unstructured stderr, so use the honest
|
|
217
|
-
// coarse bucket rather than guessing
|
|
223
|
+
// coarse bucket rather than guessing a cause (auth, build, …) from the message.
|
|
218
224
|
code: 'publish'
|
|
219
225
|
});
|
|
220
|
-
|
|
226
|
+
// the first line alone: the stderr tail the rest repeats just streamed live
|
|
227
|
+
log?.error(st('red', ` ❌ Failed to publish ${pkg_name}: ${err.message.split('\n')[0]}`));
|
|
221
228
|
break; // Always fail fast on error
|
|
222
229
|
}
|
|
223
230
|
}
|
|
@@ -252,7 +259,7 @@ export const execute_publishing_plan = async (all_repos, plan, options) => {
|
|
|
252
259
|
// package.json but must NOT generate a changeset — dev-only changes redeploy
|
|
253
260
|
// (rebuild) without republishing, so they shouldn't bump the next release.
|
|
254
261
|
await update_package_json(repo, dev_updates, {
|
|
255
|
-
strategy:
|
|
262
|
+
strategy: version_strategy,
|
|
256
263
|
log,
|
|
257
264
|
git_ops: ops.git,
|
|
258
265
|
fs_ops: ops.fs
|
|
@@ -275,18 +282,24 @@ export const execute_publishing_plan = async (all_repos, plan, options) => {
|
|
|
275
282
|
// Build fresh (no --no-build): a deployed site bundles its dependencies, so it
|
|
276
283
|
// must be rebuilt against the versions this run just published — the preflight
|
|
277
284
|
// build ran against the old versions, before the cascade rewrote package.json.
|
|
278
|
-
const deploy_result = await ops.process.
|
|
285
|
+
const deploy_result = await ops.process.run_interactive({
|
|
279
286
|
cmd: 'gro',
|
|
280
287
|
args: ['deploy'],
|
|
281
|
-
cwd: repo.repo_dir
|
|
288
|
+
cwd: repo.repo_dir,
|
|
289
|
+
stdout: options.child_stdout
|
|
282
290
|
});
|
|
283
291
|
if (deploy_result.ok) {
|
|
284
292
|
emit({ event: 'deploy_completed', name: repo.library.name });
|
|
285
293
|
log?.info(st('green', ` ✅ Deployed ${repo.library.name}`));
|
|
286
294
|
}
|
|
287
295
|
else {
|
|
288
|
-
emit({
|
|
289
|
-
|
|
296
|
+
emit({
|
|
297
|
+
event: 'deploy_failed',
|
|
298
|
+
name: repo.library.name,
|
|
299
|
+
error: format_run_failure(deploy_result)
|
|
300
|
+
});
|
|
301
|
+
// the stderr tail just streamed live, so the log names the failure alone
|
|
302
|
+
log?.warn(st('yellow', ` ⚠️ Failed to deploy ${repo.library.name}: ${deploy_result.message}`));
|
|
290
303
|
}
|
|
291
304
|
}
|
|
292
305
|
catch (error) {
|
|
@@ -351,10 +364,13 @@ export const execute_publishing_plan = async (all_repos, plan, options) => {
|
|
|
351
364
|
* Publishes a single repo using `gro publish`.
|
|
352
365
|
*
|
|
353
366
|
* Dry run mode: reports the precomputed plan entry without side effects.
|
|
354
|
-
* Real mode: runs `gro publish --no-build
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
*
|
|
367
|
+
* Real mode: runs `gro publish --no-build --no-pull --branch <entry branch>` (builds
|
|
368
|
+
* already validated in preflight; the readiness re-check just found the branch in sync
|
|
369
|
+
* with origin or ahead of it, so gro's own `git pull` is skipped, and gro checks out the branch the
|
|
370
|
+
* registry entry follows rather than its `main` default) in the foreground — its output
|
|
371
|
+
* live and stdin the terminal's, for npm's one-time-password prompt — reads the new version from
|
|
372
|
+
* `package.json`, and returns it alongside the plan's predicted bump metadata. The
|
|
373
|
+
* caller compares the read-back version to the plan to detect drift.
|
|
358
374
|
*
|
|
359
375
|
* @throws {Error} if the publish, version read-back, or commit-hash lookup fails
|
|
360
376
|
*/
|
|
@@ -373,14 +389,15 @@ const publish_single_repo = async (repo, options, ops, planned) => {
|
|
|
373
389
|
tag: `v${planned.to}`
|
|
374
390
|
};
|
|
375
391
|
}
|
|
376
|
-
|
|
377
|
-
const publish_result = await ops.process.spawn({
|
|
392
|
+
const publish_result = await ops.process.run_interactive({
|
|
378
393
|
cmd: 'gro',
|
|
379
|
-
args:
|
|
380
|
-
cwd: repo.repo_dir
|
|
394
|
+
args: gro_publish_args(repo),
|
|
395
|
+
cwd: repo.repo_dir,
|
|
396
|
+
stdout: options.child_stdout
|
|
381
397
|
});
|
|
382
398
|
if (!publish_result.ok) {
|
|
383
|
-
|
|
399
|
+
// no "Failed to publish" prefix: the caller's log and report name the package
|
|
400
|
+
throw new Error(format_run_failure(publish_result));
|
|
384
401
|
}
|
|
385
402
|
// Read the new version from package.json after gro publish
|
|
386
403
|
const package_json_path = join(repo.repo_dir, 'package.json');
|
|
@@ -408,6 +425,53 @@ const publish_single_repo = async (repo, options, ops, planned) => {
|
|
|
408
425
|
tag: `v${new_version}`
|
|
409
426
|
};
|
|
410
427
|
};
|
|
428
|
+
/**
|
|
429
|
+
* Re-checks one repo's readiness right before its `gro publish`: `repos status
|
|
430
|
+
* --fetch --json <key>` through `ops.repos`, then `check_publish_readiness` on
|
|
431
|
+
* that entry — the gate's predicate, so a branch ahead of origin (the
|
|
432
|
+
* executor's own dependency-rewrite commit leaves a dependent ahead) is ready.
|
|
433
|
+
*
|
|
434
|
+
* @returns ok, or the problems found (or why the check couldn't run), joined
|
|
435
|
+
*/
|
|
436
|
+
const recheck_publish_readiness = async (repo, registry, ops) => {
|
|
437
|
+
const key = repo.entry.key;
|
|
438
|
+
const loaded = await load_repos_status({
|
|
439
|
+
keys: [key],
|
|
440
|
+
registry,
|
|
441
|
+
fetch: true,
|
|
442
|
+
repos_ops: ops.repos
|
|
443
|
+
});
|
|
444
|
+
if (!loaded.ok)
|
|
445
|
+
return { ok: false, message: `the check didn't run: ${loaded.message}` };
|
|
446
|
+
const checked = check_publish_readiness({
|
|
447
|
+
report: loaded.report,
|
|
448
|
+
keys: [key],
|
|
449
|
+
repos_command: to_repos_command(registry)
|
|
450
|
+
});
|
|
451
|
+
return checked.ok ? { ok: true } : { ok: false, message: checked.lines.join('; ') };
|
|
452
|
+
};
|
|
453
|
+
/**
|
|
454
|
+
* The `gro publish` arguments the executor runs in a repo: `--no-build`, since
|
|
455
|
+
* preflight validated the build; `--no-pull`, since the readiness re-check right
|
|
456
|
+
* before found the branch in sync with origin or ahead of it, so a pull would only
|
|
457
|
+
* move what the check vouched for; and `--branch` naming the branch the registry entry follows,
|
|
458
|
+
* which gro checks out (its default is `main`).
|
|
459
|
+
*/
|
|
460
|
+
const gro_publish_args = (repo) => {
|
|
461
|
+
const args = ['publish', '--no-build', '--no-pull'];
|
|
462
|
+
if (repo.entry.branch !== null)
|
|
463
|
+
args.push('--branch', repo.entry.branch);
|
|
464
|
+
return args;
|
|
465
|
+
};
|
|
466
|
+
/**
|
|
467
|
+
* The failure message for a command the executor ran: why it failed on the first
|
|
468
|
+
* line, then the end of its stderr with known secret shapes redacted, since the
|
|
469
|
+
* message lands in the result, the event stream, and the JSON or markdown report.
|
|
470
|
+
* The live log shows the first line alone, the stderr having just streamed.
|
|
471
|
+
*/
|
|
472
|
+
const format_run_failure = (failure) => failure.stderr_tail
|
|
473
|
+
? `${failure.message}\nthe end of its stderr:\n${redact_secrets(failure.stderr_tail)}`
|
|
474
|
+
: failure.message;
|
|
411
475
|
/**
|
|
412
476
|
* Groups dependency updates by dependent package — `dependent → (dependency → new
|
|
413
477
|
* version)`, the shape `update_package_json` consumes. Restricted by `predicate` (e.g.
|
package/dist/npm_registry.d.ts
CHANGED
|
@@ -6,13 +6,31 @@ export interface WaitOptions {
|
|
|
6
6
|
max_delay?: number;
|
|
7
7
|
timeout?: number;
|
|
8
8
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
/**
|
|
10
|
+
* The side effects of the registry checks — running npm, sleeping, and reading
|
|
11
|
+
* the clock — injected so tests drive them with plain objects.
|
|
12
|
+
*/
|
|
13
|
+
export interface NpmRegistryDeps {
|
|
14
|
+
/**
|
|
15
|
+
* Runs `npm` with `args` and resolves to what it wrote to stdout — empty when
|
|
16
|
+
* nothing, or when npm couldn't run. A throw counts as unavailable too.
|
|
17
|
+
*/
|
|
18
|
+
run_npm: (args: Array<string>) => Promise<string>;
|
|
19
|
+
/** Sleeps for `ms` milliseconds. */
|
|
20
|
+
wait: (ms: number) => Promise<void>;
|
|
21
|
+
/** The current time in milliseconds, as `Date.now`. */
|
|
22
|
+
now: () => number;
|
|
12
23
|
}
|
|
24
|
+
export declare const default_npm_registry_deps: NpmRegistryDeps;
|
|
25
|
+
/**
|
|
26
|
+
* Checks whether `pkg@version` is on the npm registry, by `npm view`.
|
|
27
|
+
*
|
|
28
|
+
* @returns `true` when the registry reports that exact version, `false` when it
|
|
29
|
+
* doesn't or npm couldn't run
|
|
30
|
+
*/
|
|
13
31
|
export declare const check_package_available: (pkg: string, version: string, options?: {
|
|
14
32
|
log?: Logger;
|
|
15
|
-
}) => Promise<boolean>;
|
|
33
|
+
}, deps?: NpmRegistryDeps) => Promise<boolean>;
|
|
16
34
|
/**
|
|
17
35
|
* Waits for package version to propagate to NPM registry.
|
|
18
36
|
*
|
|
@@ -28,19 +46,5 @@ export declare const check_package_available: (pkg: string, version: string, opt
|
|
|
28
46
|
* @param options.timeout - total timeout in ms (default 300000 = 5min)
|
|
29
47
|
* @throws {Error} if timeout reached or max attempts exceeded
|
|
30
48
|
*/
|
|
31
|
-
export declare const wait_for_package: (pkg: string, version: string, options?: WaitOptions) => Promise<void>;
|
|
32
|
-
/**
|
|
33
|
-
* Fetches package metadata from NPM registry.
|
|
34
|
-
*
|
|
35
|
-
* Returns name and latest version. Returns null if package doesn't exist
|
|
36
|
-
* or registry is unreachable.
|
|
37
|
-
*
|
|
38
|
-
* @returns package info or null on error/not found
|
|
39
|
-
*/
|
|
40
|
-
export declare const get_package_info: (pkg: string, options?: {
|
|
41
|
-
log?: Logger;
|
|
42
|
-
}) => Promise<PackageInfo | null>;
|
|
43
|
-
export declare const package_exists: (pkg: string, options?: {
|
|
44
|
-
log?: Logger;
|
|
45
|
-
}) => Promise<boolean>;
|
|
49
|
+
export declare const wait_for_package: (pkg: string, version: string, options?: WaitOptions, deps?: NpmRegistryDeps) => Promise<void>;
|
|
46
50
|
//# sourceMappingURL=npm_registry.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"npm_registry.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/npm_registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAKtD,MAAM,WAAW,WAAW;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"npm_registry.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/npm_registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAKtD,MAAM,WAAW,WAAW;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC/B;;;OAGG;IACH,OAAO,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,oCAAoC;IACpC,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,uDAAuD;IACvD,GAAG,EAAE,MAAM,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,yBAAyB,EAAE,eAIvC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GACnC,KAAK,MAAM,EACX,SAAS,MAAM,EACf,UAAS;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,EAC9B,OAAM,eAA2C,KAC/C,OAAO,CAAC,OAAO,CASjB,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,gBAAgB,GAC5B,KAAK,MAAM,EACX,SAAS,MAAM,EACf,UAAS,WAAgB,EACzB,OAAM,eAA2C,KAC/C,OAAO,CAAC,IAAI,CAwCd,CAAC"}
|
package/dist/npm_registry.js
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import { spawn_out } from '@fuzdev/fuz_util/process.ts';
|
|
2
2
|
import { wait } from '@fuzdev/fuz_util/async.ts';
|
|
3
3
|
import { styleText as st } from 'node:util';
|
|
4
|
-
export const
|
|
4
|
+
export const default_npm_registry_deps = {
|
|
5
|
+
run_npm: async (args) => (await spawn_out('npm', args)).stdout ?? '',
|
|
6
|
+
wait,
|
|
7
|
+
now: () => Date.now()
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Checks whether `pkg@version` is on the npm registry, by `npm view`.
|
|
11
|
+
*
|
|
12
|
+
* @returns `true` when the registry reports that exact version, `false` when it
|
|
13
|
+
* doesn't or npm couldn't run
|
|
14
|
+
*/
|
|
15
|
+
export const check_package_available = async (pkg, version, options = {}, deps = default_npm_registry_deps) => {
|
|
5
16
|
const { log } = options;
|
|
6
17
|
try {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (result.stdout) {
|
|
10
|
-
const output = result.stdout.trim();
|
|
11
|
-
// If we get a version back, it exists
|
|
12
|
-
return output === version;
|
|
13
|
-
}
|
|
14
|
-
return false;
|
|
18
|
+
const stdout = await deps.run_npm(['view', `${pkg}@${version}`, 'version']);
|
|
19
|
+
return stdout.trim() === version;
|
|
15
20
|
}
|
|
16
21
|
catch (error) {
|
|
17
22
|
log?.debug(`Failed to check ${pkg}@${version}: ${error}`);
|
|
@@ -33,63 +38,31 @@ export const check_package_available = async (pkg, version, options = {}) => {
|
|
|
33
38
|
* @param options.timeout - total timeout in ms (default 300000 = 5min)
|
|
34
39
|
* @throws {Error} if timeout reached or max attempts exceeded
|
|
35
40
|
*/
|
|
36
|
-
export const wait_for_package = async (pkg, version, options = {}) => {
|
|
41
|
+
export const wait_for_package = async (pkg, version, options = {}, deps = default_npm_registry_deps) => {
|
|
37
42
|
const { log, max_attempts = 30, initial_delay = 1000, max_delay = 60000, timeout = 300000 // 5 minutes default
|
|
38
43
|
} = options;
|
|
39
|
-
const start_time =
|
|
44
|
+
const start_time = deps.now();
|
|
40
45
|
let attempt = 0;
|
|
41
46
|
let delay = initial_delay;
|
|
42
47
|
while (attempt < max_attempts) {
|
|
43
48
|
attempt++;
|
|
44
|
-
|
|
45
|
-
if (Date.now() - start_time > timeout) {
|
|
49
|
+
if (deps.now() - start_time > timeout) {
|
|
46
50
|
throw new Error(`Timeout waiting for ${pkg}@${version} after ${timeout}ms`);
|
|
47
51
|
}
|
|
48
|
-
|
|
49
|
-
if (await check_package_available(pkg, version, { log })) {
|
|
52
|
+
if (await check_package_available(pkg, version, { log }, deps)) {
|
|
50
53
|
log?.info(st('green', ` ✓ ${pkg}@${version} is now available on NPM`));
|
|
51
54
|
return;
|
|
52
55
|
}
|
|
53
56
|
// Log progress occasionally
|
|
54
|
-
if (attempt
|
|
57
|
+
if (attempt % 5 === 0) {
|
|
55
58
|
log?.info(st('dim', ` Still waiting... (attempt ${attempt}/${max_attempts})`));
|
|
56
59
|
}
|
|
57
60
|
// Wait with exponential backoff + jitter
|
|
58
61
|
const jitter = Math.random() * delay * 0.1; // 10% jitter
|
|
59
62
|
const actual_delay = Math.min(delay + jitter, max_delay);
|
|
60
|
-
await wait(actual_delay);
|
|
63
|
+
await deps.wait(actual_delay);
|
|
61
64
|
// Exponential backoff
|
|
62
65
|
delay = Math.min(delay * 1.5, max_delay);
|
|
63
66
|
}
|
|
64
67
|
throw new Error(`${pkg}@${version} not available after ${max_attempts} attempts`);
|
|
65
68
|
};
|
|
66
|
-
/**
|
|
67
|
-
* Fetches package metadata from NPM registry.
|
|
68
|
-
*
|
|
69
|
-
* Returns name and latest version. Returns null if package doesn't exist
|
|
70
|
-
* or registry is unreachable.
|
|
71
|
-
*
|
|
72
|
-
* @returns package info or null on error/not found
|
|
73
|
-
*/
|
|
74
|
-
export const get_package_info = async (pkg, options = {}) => {
|
|
75
|
-
const { log } = options;
|
|
76
|
-
try {
|
|
77
|
-
const result = await spawn_out('npm', ['view', pkg, '--json']);
|
|
78
|
-
if (result.stdout) {
|
|
79
|
-
const data = JSON.parse(result.stdout);
|
|
80
|
-
return {
|
|
81
|
-
name: data.name,
|
|
82
|
-
version: data.version
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
catch (error) {
|
|
88
|
-
log?.debug(`Failed to get package info for ${pkg}: ${error}`);
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
export const package_exists = async (pkg, options = {}) => {
|
|
93
|
-
const info = await get_package_info(pkg, options);
|
|
94
|
-
return info !== null;
|
|
95
|
-
};
|