@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
|
@@ -18,9 +18,6 @@ import {
|
|
|
18
18
|
get_required_bump_for_dependencies
|
|
19
19
|
} from './publishing_plan_helpers.ts';
|
|
20
20
|
|
|
21
|
-
// Re-export logging functions
|
|
22
|
-
export { log_publishing_plan, type LogPlanOptions } from './publishing_plan_logging.ts';
|
|
23
|
-
|
|
24
21
|
export interface VersionChange {
|
|
25
22
|
package_name: string;
|
|
26
23
|
from: string;
|
|
@@ -34,6 +31,22 @@ export interface VersionChange {
|
|
|
34
31
|
required_bump?: BumpType; // The required bump type from dependencies
|
|
35
32
|
}
|
|
36
33
|
|
|
34
|
+
/**
|
|
35
|
+
* How a version change arises in the plan: from the package's own changesets
|
|
36
|
+
* (`explicit`), from its changesets with the bump raised to what a breaking
|
|
37
|
+
* dependency requires (`escalation`), or from a changeset the executor
|
|
38
|
+
* generates for a dependency update (`auto`).
|
|
39
|
+
*/
|
|
40
|
+
export type VersionChangeKind = 'explicit' | 'escalation' | 'auto';
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Classifies a version change — the one classification the plan's logger,
|
|
44
|
+
* markdown, and side-effect preview share. A change without changesets of its
|
|
45
|
+
* own is `auto` even if its bump was raised.
|
|
46
|
+
*/
|
|
47
|
+
export const version_change_kind = (change: VersionChange): VersionChangeKind =>
|
|
48
|
+
!change.has_changesets ? 'auto' : change.needs_bump_escalation ? 'escalation' : 'explicit';
|
|
49
|
+
|
|
37
50
|
export interface DependencyUpdate {
|
|
38
51
|
dependent_package: string;
|
|
39
52
|
updated_dependency: string;
|
|
@@ -93,7 +106,10 @@ export interface PublishingPlan {
|
|
|
93
106
|
dependency_updates: Array<DependencyUpdate>;
|
|
94
107
|
breaking_cascades: Map<string, Array<string>>;
|
|
95
108
|
warnings: Array<string>;
|
|
96
|
-
|
|
109
|
+
/** Informational sentences, not warnings: excluded non-npm repos, dev dependency cycles. */
|
|
110
|
+
info: Array<string>;
|
|
111
|
+
/** Package names with no changesets and no version change — nothing to publish. */
|
|
112
|
+
no_changes: Array<string>;
|
|
97
113
|
errors: Array<string>;
|
|
98
114
|
verbose_data?: VerboseData;
|
|
99
115
|
}
|
|
@@ -104,6 +120,18 @@ export interface GeneratePlanOptions {
|
|
|
104
120
|
verbose?: boolean;
|
|
105
121
|
}
|
|
106
122
|
|
|
123
|
+
type ChangesetPresence = Awaited<ReturnType<ChangesetOperations['has_changesets']>>;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The plan's working state, shared by the initial pass and the fixed-point
|
|
127
|
+
* iteration that extends it.
|
|
128
|
+
*/
|
|
129
|
+
interface CascadeState {
|
|
130
|
+
version_changes: Array<VersionChange>;
|
|
131
|
+
predicted_versions: Map<string, string>;
|
|
132
|
+
breaking_packages: Set<string>;
|
|
133
|
+
}
|
|
134
|
+
|
|
107
135
|
/**
|
|
108
136
|
* Generates a publishing plan showing what would happen during publishing.
|
|
109
137
|
* Shows version changes, dependency updates, and breaking change cascades.
|
|
@@ -113,11 +141,11 @@ export const generate_publishing_plan = async (
|
|
|
113
141
|
all_repos: Array<LocalRepo>,
|
|
114
142
|
options: GeneratePlanOptions = {}
|
|
115
143
|
): Promise<PublishingPlan> => {
|
|
116
|
-
const { log, ops = default_changeset_operations, verbose } = options;
|
|
144
|
+
const { log, ops = default_changeset_operations, verbose = false } = options;
|
|
117
145
|
log?.info(st('cyan', 'Generating publishing plan...'));
|
|
118
146
|
|
|
119
147
|
const warnings: Array<string> = [];
|
|
120
|
-
const info: Array<string> = [];
|
|
148
|
+
const info: Array<string> = [];
|
|
121
149
|
const errors: Array<string> = [];
|
|
122
150
|
|
|
123
151
|
// Publishing concerns only npm packages. Non-npm repos (e.g. Rust cargo repos) are
|
|
@@ -132,22 +160,14 @@ export const generate_publishing_plan = async (
|
|
|
132
160
|
}
|
|
133
161
|
const repos = all_repos.filter(repo_is_npm);
|
|
134
162
|
|
|
135
|
-
// Verbose data collection
|
|
136
|
-
const verbose_changeset_details: Array<VerboseChangesetDetail> = [];
|
|
137
|
-
const verbose_iterations: Array<VerboseIteration> = [];
|
|
138
|
-
|
|
139
163
|
// Build dependency graph and validate
|
|
140
164
|
let publishing_order: Array<string>;
|
|
141
165
|
let production_cycles: Array<Array<string>>;
|
|
142
166
|
let dev_cycles: Array<Array<string>>;
|
|
143
|
-
let graph: DependencyGraph
|
|
167
|
+
let graph: DependencyGraph;
|
|
144
168
|
|
|
145
169
|
try {
|
|
146
|
-
const validation = validate_dependency_graph(repos
|
|
147
|
-
throw_on_prod_cycles: false, // Collect errors instead of throwing
|
|
148
|
-
log_cycles: false, // We'll handle our own error collection
|
|
149
|
-
log_order: false // Plan generation doesn't need to log order
|
|
150
|
-
});
|
|
170
|
+
const validation = validate_dependency_graph(repos);
|
|
151
171
|
publishing_order = validation.publishing_order;
|
|
152
172
|
production_cycles = validation.production_cycles;
|
|
153
173
|
dev_cycles = validation.dev_cycles;
|
|
@@ -166,15 +186,13 @@ export const generate_publishing_plan = async (
|
|
|
166
186
|
breaking_cascades: new Map(),
|
|
167
187
|
warnings,
|
|
168
188
|
info,
|
|
189
|
+
no_changes: [],
|
|
169
190
|
errors
|
|
170
191
|
};
|
|
171
192
|
}
|
|
172
193
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
for (const cycle of production_cycles) {
|
|
176
|
-
errors.push(`Production dependency cycle: ${cycle.join(' → ')}`);
|
|
177
|
-
}
|
|
194
|
+
for (const cycle of production_cycles) {
|
|
195
|
+
errors.push(`Production dependency cycle: ${cycle.join(' → ')}`);
|
|
178
196
|
}
|
|
179
197
|
|
|
180
198
|
// Dev cycles are shown in gitops_analyze, not repeated here
|
|
@@ -184,93 +202,265 @@ export const generate_publishing_plan = async (
|
|
|
184
202
|
);
|
|
185
203
|
}
|
|
186
204
|
|
|
187
|
-
//
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
205
|
+
// Checked once per repo; the initial pass and the no-changes list both read it.
|
|
206
|
+
const changeset_presence: Map<string, ChangesetPresence> = new Map(
|
|
207
|
+
await Promise.all(
|
|
208
|
+
repos.map(async (repo) => [repo.library.name, await ops.has_changesets({ repo })] as const)
|
|
209
|
+
)
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
const state: CascadeState = {
|
|
213
|
+
version_changes: [],
|
|
214
|
+
predicted_versions: new Map(),
|
|
215
|
+
breaking_packages: new Set()
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const { changeset_details, repos_without_bump } = await plan_initial_pass({
|
|
219
|
+
repos,
|
|
220
|
+
publishing_order,
|
|
221
|
+
changeset_presence,
|
|
222
|
+
state,
|
|
223
|
+
ops,
|
|
224
|
+
log,
|
|
225
|
+
verbose,
|
|
226
|
+
warnings,
|
|
227
|
+
errors
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const cascade = plan_resolve_cascades({ repos, state, changeset_details, verbose });
|
|
231
|
+
|
|
232
|
+
// Worded after the cascades, which may give such a repo an auto-changeset
|
|
233
|
+
for (const repo of repos_without_bump) {
|
|
234
|
+
const publishes = state.version_changes.some((vc) => vc.package_name === repo.library.name);
|
|
235
|
+
warnings.push(await describe_changesets_without_bump(repo, publishes, ops));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (!cascade.converged) {
|
|
239
|
+
const convergence_warning = plan_convergence_warning(repos, state);
|
|
240
|
+
if (convergence_warning) warnings.push(convergence_warning);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Final dependency updates calculation after convergence
|
|
244
|
+
const { dependency_updates, breaking_cascades } = calculate_dependency_updates(
|
|
245
|
+
repos,
|
|
246
|
+
state.predicted_versions,
|
|
247
|
+
state.breaking_packages
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
// Packages with nothing to publish: no version change and no changesets
|
|
251
|
+
const no_changes: Array<string> = [];
|
|
252
|
+
for (const repo of repos) {
|
|
253
|
+
const name = repo.library.name;
|
|
254
|
+
if (state.version_changes.some((vc) => vc.package_name === name)) continue;
|
|
255
|
+
const presence = changeset_presence.get(name);
|
|
256
|
+
if (presence?.ok && !presence.value) {
|
|
257
|
+
no_changes.push(name);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const verbose_data = verbose
|
|
262
|
+
? plan_verbose_data({
|
|
263
|
+
graph,
|
|
264
|
+
version_changes: state.version_changes,
|
|
265
|
+
dependency_updates,
|
|
266
|
+
breaking_cascades,
|
|
267
|
+
changeset_details,
|
|
268
|
+
iterations: cascade.verbose_iterations,
|
|
269
|
+
total_iterations: cascade.iterations,
|
|
270
|
+
prod_cycle_count: production_cycles.length,
|
|
271
|
+
dev_cycle_count: dev_cycles.length
|
|
272
|
+
})
|
|
273
|
+
: undefined;
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
publishing_order,
|
|
277
|
+
version_changes: state.version_changes,
|
|
278
|
+
dependency_updates,
|
|
279
|
+
breaking_cascades,
|
|
280
|
+
warnings,
|
|
281
|
+
info,
|
|
282
|
+
no_changes,
|
|
283
|
+
errors,
|
|
284
|
+
verbose_data
|
|
285
|
+
};
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
interface PlanInitialPassOptions {
|
|
289
|
+
repos: Array<LocalRepo>;
|
|
290
|
+
publishing_order: Array<string>;
|
|
291
|
+
changeset_presence: Map<string, ChangesetPresence>;
|
|
292
|
+
state: CascadeState;
|
|
293
|
+
ops: ChangesetOperations;
|
|
294
|
+
log: Logger | undefined;
|
|
295
|
+
verbose: boolean;
|
|
296
|
+
warnings: Array<string>;
|
|
297
|
+
errors: Array<string>;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
interface PlanInitialPassResult {
|
|
301
|
+
/** The changeset details for verbose output, empty unless `verbose`. */
|
|
302
|
+
changeset_details: Array<VerboseChangesetDetail>;
|
|
303
|
+
/**
|
|
304
|
+
* Repos whose changeset files yield no bump for their package, warned on
|
|
305
|
+
* once the cascades settle whether each publishes.
|
|
306
|
+
*/
|
|
307
|
+
repos_without_bump: Array<LocalRepo>;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* The initial pass: a version change for each package with explicit changesets,
|
|
312
|
+
* in publishing order.
|
|
313
|
+
*
|
|
314
|
+
* @mutates options.state - adds the explicit version changes, their predicted versions, and the breaking ones
|
|
315
|
+
* @mutates options.warnings - private packages carrying changesets
|
|
316
|
+
* @mutates options.errors - failed changeset checks and predictions
|
|
317
|
+
*/
|
|
318
|
+
const plan_initial_pass = async (
|
|
319
|
+
options: PlanInitialPassOptions
|
|
320
|
+
): Promise<PlanInitialPassResult> => {
|
|
321
|
+
const { repos, publishing_order, changeset_presence, state, ops, log, verbose } = options;
|
|
322
|
+
const { warnings, errors } = options;
|
|
323
|
+
const changeset_details: Array<VerboseChangesetDetail> = [];
|
|
324
|
+
const repos_without_bump: Array<LocalRepo> = [];
|
|
191
325
|
|
|
192
326
|
for (const pkg_name of publishing_order) {
|
|
193
327
|
const repo = repos.find((r) => r.library.name === pkg_name);
|
|
194
328
|
if (!repo) continue;
|
|
329
|
+
const has_result = changeset_presence.get(pkg_name);
|
|
330
|
+
if (!has_result) continue;
|
|
195
331
|
|
|
196
332
|
// Private packages never publish — exclude them from version changes entirely (no
|
|
197
333
|
// publish step, npm-wait, bump escalation, or auto-changeset). They keep their slot in
|
|
198
334
|
// the topological order. Flag a private package that carries a changeset, since that
|
|
199
335
|
// changeset can't be published.
|
|
200
336
|
if (repo.package_json.private) {
|
|
201
|
-
|
|
202
|
-
if (private_has_changesets.ok && private_has_changesets.value) {
|
|
337
|
+
if (has_result.ok && has_result.value) {
|
|
203
338
|
warnings.push(`${pkg_name} is private — its changeset(s) will not be published`);
|
|
204
339
|
}
|
|
205
340
|
continue;
|
|
206
341
|
}
|
|
207
342
|
|
|
208
|
-
// Check for changesets
|
|
209
|
-
const has_result = await ops.has_changesets({ repo });
|
|
210
|
-
|
|
211
343
|
if (!has_result.ok) {
|
|
212
344
|
errors.push(`Failed to check changesets for ${pkg_name}: ${has_result.message}`);
|
|
213
345
|
continue;
|
|
214
346
|
}
|
|
215
347
|
|
|
216
|
-
if (has_result.value)
|
|
217
|
-
// Predict version from changesets
|
|
218
|
-
const prediction = await ops.predict_next_version({ repo, log });
|
|
348
|
+
if (!has_result.value) continue;
|
|
219
349
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
continue;
|
|
223
|
-
}
|
|
350
|
+
// Predict version from changesets
|
|
351
|
+
const prediction = await ops.predict_next_version({ repo, log });
|
|
224
352
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
353
|
+
if (!prediction) {
|
|
354
|
+
// Changeset files exist, but none yields a bump for this package
|
|
355
|
+
repos_without_bump.push(repo);
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (!prediction.ok) {
|
|
360
|
+
errors.push(`Failed to predict version for ${pkg_name}: ${prediction.message}`);
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
229
363
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
364
|
+
// Capture changeset details for verbose output
|
|
365
|
+
if (verbose) {
|
|
366
|
+
const changesets_result = await ops.read_changesets({ repo, log });
|
|
367
|
+
if (changesets_result.ok) {
|
|
368
|
+
const files = changesets_result.value
|
|
369
|
+
.filter((cs) => cs.packages.some((p) => p.name === pkg_name))
|
|
370
|
+
.map((cs) => {
|
|
371
|
+
const pkg_entry = cs.packages.find((p) => p.name === pkg_name);
|
|
372
|
+
return {
|
|
373
|
+
filename: cs.filename,
|
|
374
|
+
bump_type: pkg_entry?.bump_type || prediction.bump_type,
|
|
375
|
+
summary: cs.summary
|
|
376
|
+
};
|
|
377
|
+
});
|
|
378
|
+
if (files.length > 0) {
|
|
379
|
+
changeset_details.push({ package_name: pkg_name, files });
|
|
247
380
|
}
|
|
248
381
|
}
|
|
382
|
+
}
|
|
249
383
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const is_breaking = is_breaking_change(old_version, prediction.bump_type);
|
|
253
|
-
|
|
254
|
-
predicted_versions.set(pkg_name, prediction.version);
|
|
384
|
+
const old_version = repo.package_json.version || '0.0.0';
|
|
385
|
+
const is_breaking = is_breaking_change(old_version, prediction.bump_type);
|
|
255
386
|
|
|
256
|
-
|
|
257
|
-
breaking_packages.add(pkg_name);
|
|
258
|
-
}
|
|
387
|
+
state.predicted_versions.set(pkg_name, prediction.version);
|
|
259
388
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
from: old_version,
|
|
263
|
-
to: prediction.version,
|
|
264
|
-
bump_type: prediction.bump_type,
|
|
265
|
-
breaking: is_breaking,
|
|
266
|
-
has_changesets: true
|
|
267
|
-
});
|
|
268
|
-
}
|
|
389
|
+
if (is_breaking) {
|
|
390
|
+
state.breaking_packages.add(pkg_name);
|
|
269
391
|
}
|
|
392
|
+
|
|
393
|
+
state.version_changes.push({
|
|
394
|
+
package_name: pkg_name,
|
|
395
|
+
from: old_version,
|
|
396
|
+
to: prediction.version,
|
|
397
|
+
bump_type: prediction.bump_type,
|
|
398
|
+
breaking: is_breaking,
|
|
399
|
+
has_changesets: true
|
|
400
|
+
});
|
|
270
401
|
}
|
|
271
402
|
|
|
272
|
-
|
|
273
|
-
|
|
403
|
+
return { changeset_details, repos_without_bump };
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Words the warning for a repo whose changeset files yield no bump for its
|
|
408
|
+
* package. It takes no bump from them: it publishes only as the auto-generated
|
|
409
|
+
* change a dependency update requires, and otherwise not at all.
|
|
410
|
+
*
|
|
411
|
+
* @param publishes - whether the settled plan has a version change for the repo
|
|
412
|
+
*/
|
|
413
|
+
const describe_changesets_without_bump = async (
|
|
414
|
+
repo: LocalRepo,
|
|
415
|
+
publishes: boolean,
|
|
416
|
+
ops: ChangesetOperations
|
|
417
|
+
): Promise<string> => {
|
|
418
|
+
const pkg_name = repo.library.name;
|
|
419
|
+
const consequence = publishes
|
|
420
|
+
? 'it takes no bump from them and publishes only as the auto-generated change a dependency update requires'
|
|
421
|
+
: 'it will not publish';
|
|
422
|
+
// no `log`: `predict_next_version` already logged any invalid files
|
|
423
|
+
const read = await ops.read_changesets({ repo });
|
|
424
|
+
if (!read.ok) {
|
|
425
|
+
return `${pkg_name} has changeset files that could not be read (${read.message}) — ${consequence}`;
|
|
426
|
+
}
|
|
427
|
+
if (read.value.length === 0) {
|
|
428
|
+
return `${pkg_name} has changeset files, but none parses — ${consequence}; check their frontmatter`;
|
|
429
|
+
}
|
|
430
|
+
return (
|
|
431
|
+
`${pkg_name} has changeset files, but none that parses names ${pkg_name} — ${consequence}; ` +
|
|
432
|
+
`check the package names in their frontmatter`
|
|
433
|
+
);
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
interface PlanResolveCascadesOptions {
|
|
437
|
+
repos: Array<LocalRepo>;
|
|
438
|
+
state: CascadeState;
|
|
439
|
+
changeset_details: Array<VerboseChangesetDetail>;
|
|
440
|
+
verbose: boolean;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
interface PlanCascadeResult {
|
|
444
|
+
/** Iterations run, at most `GITOPS_MAX_ITERATIONS_DEFAULT`. */
|
|
445
|
+
iterations: number;
|
|
446
|
+
/** False when the last iteration still found changes. */
|
|
447
|
+
converged: boolean;
|
|
448
|
+
/** Per-iteration details, empty unless `verbose`. */
|
|
449
|
+
verbose_iterations: Array<VerboseIteration>;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Fixed-point iteration over the dependency updates the current predictions
|
|
454
|
+
* imply: escalates explicit changes a breaking dependency outgrows, and adds or
|
|
455
|
+
* raises auto-changeset changes for dependents, until a pass finds nothing new.
|
|
456
|
+
*
|
|
457
|
+
* @mutates options.state - escalates and adds version changes, with their predicted versions and breaking set
|
|
458
|
+
*/
|
|
459
|
+
const plan_resolve_cascades = (options: PlanResolveCascadesOptions): PlanCascadeResult => {
|
|
460
|
+
const { repos, state, changeset_details, verbose } = options;
|
|
461
|
+
const { version_changes, predicted_versions, breaking_packages } = state;
|
|
462
|
+
const verbose_iterations: Array<VerboseIteration> = [];
|
|
463
|
+
|
|
274
464
|
let iteration = 0;
|
|
275
465
|
let changed = true;
|
|
276
466
|
|
|
@@ -304,111 +494,101 @@ export const generate_publishing_plan = async (
|
|
|
304
494
|
dependency_updates,
|
|
305
495
|
breaking_packages
|
|
306
496
|
);
|
|
497
|
+
if (!required_bump) continue;
|
|
307
498
|
|
|
308
499
|
// Find triggering dependency for verbose output
|
|
309
500
|
let triggering_dep: string | null = null;
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
triggering_dep = `${relevant_updates[0]!.updated_dependency} BREAKING`;
|
|
319
|
-
}
|
|
501
|
+
const relevant_updates = dependency_updates.filter(
|
|
502
|
+
(u) =>
|
|
503
|
+
u.dependent_package === pkg_name &&
|
|
504
|
+
(u.type === 'dependencies' || u.type === 'peerDependencies') &&
|
|
505
|
+
breaking_packages.has(u.updated_dependency)
|
|
506
|
+
);
|
|
507
|
+
if (relevant_updates.length > 0) {
|
|
508
|
+
triggering_dep = `${relevant_updates[0]!.updated_dependency} BREAKING`;
|
|
320
509
|
}
|
|
321
510
|
|
|
322
|
-
|
|
511
|
+
const old_version = repo.package_json.version || '0.0.0';
|
|
512
|
+
const new_version = calculate_next_version(old_version, required_bump);
|
|
513
|
+
const is_breaking = is_breaking_change(old_version, required_bump);
|
|
514
|
+
|
|
323
515
|
const existing_entry = version_changes.find((vc) => vc.package_name === pkg_name);
|
|
324
516
|
|
|
325
517
|
if (existing_entry) {
|
|
326
|
-
//
|
|
327
|
-
if
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
existing_entry.needs_bump_escalation = true;
|
|
355
|
-
existing_entry.existing_bump = existing_entry.bump_type;
|
|
356
|
-
existing_entry.required_bump = required_bump;
|
|
357
|
-
existing_entry.bump_type = required_bump;
|
|
358
|
-
existing_entry.to = new_version;
|
|
359
|
-
existing_entry.breaking = is_breaking_change(old_version, required_bump);
|
|
360
|
-
|
|
361
|
-
// Update predicted versions
|
|
362
|
-
predicted_versions.set(pkg_name, new_version);
|
|
363
|
-
|
|
364
|
-
if (existing_entry.breaking) {
|
|
365
|
-
breaking_packages.add(pkg_name);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
518
|
+
// Dependencies may require a larger bump than the entry has. Only mark as
|
|
519
|
+
// changed if the version actually changes.
|
|
520
|
+
if (compare_bump_types(required_bump, existing_entry.bump_type) <= 0) continue;
|
|
521
|
+
if (existing_entry.to === new_version) continue;
|
|
522
|
+
|
|
523
|
+
changed = true;
|
|
524
|
+
verbose_new_changes++;
|
|
525
|
+
|
|
526
|
+
// An auto-changeset entry raised by a dependency that turned breaking later in the
|
|
527
|
+
// iteration (a dependent listed before its dependency) is still an auto-changeset:
|
|
528
|
+
// the executor generates its changeset at the bump the plan settles on. Only an
|
|
529
|
+
// entry with changesets of its own escalates.
|
|
530
|
+
const kind = version_change_kind(existing_entry);
|
|
531
|
+
|
|
532
|
+
if (verbose) {
|
|
533
|
+
const changeset_detail = changeset_details.find((d) => d.package_name === pkg_name);
|
|
534
|
+
verbose_iteration_packages.push({
|
|
535
|
+
name: pkg_name,
|
|
536
|
+
changeset_count: kind === 'auto' ? 0 : changeset_detail?.files.length || 1,
|
|
537
|
+
bump_from_changesets: kind === 'auto' ? null : existing_entry.bump_type,
|
|
538
|
+
required_bump,
|
|
539
|
+
triggering_dep,
|
|
540
|
+
action: kind === 'auto' ? 'auto_changeset' : 'escalation',
|
|
541
|
+
version_to: new_version,
|
|
542
|
+
is_breaking
|
|
543
|
+
});
|
|
368
544
|
}
|
|
369
|
-
|
|
545
|
+
|
|
546
|
+
if (kind !== 'auto') {
|
|
547
|
+
existing_entry.needs_bump_escalation = true;
|
|
548
|
+
existing_entry.existing_bump ??= existing_entry.bump_type;
|
|
549
|
+
existing_entry.required_bump = required_bump;
|
|
550
|
+
}
|
|
551
|
+
existing_entry.bump_type = required_bump;
|
|
552
|
+
existing_entry.to = new_version;
|
|
553
|
+
existing_entry.breaking = is_breaking;
|
|
554
|
+
|
|
555
|
+
predicted_versions.set(pkg_name, new_version);
|
|
556
|
+
if (is_breaking) {
|
|
557
|
+
breaking_packages.add(pkg_name);
|
|
558
|
+
}
|
|
559
|
+
} else if (!predicted_versions.has(pkg_name)) {
|
|
370
560
|
// No existing changesets but needs changeset for dependency updates
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
name: pkg_name,
|
|
385
|
-
changeset_count: 0,
|
|
386
|
-
bump_from_changesets: null,
|
|
387
|
-
required_bump,
|
|
388
|
-
triggering_dep,
|
|
389
|
-
action: 'auto_changeset',
|
|
390
|
-
version_to: new_version,
|
|
391
|
-
is_breaking
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
if (is_breaking) {
|
|
396
|
-
breaking_packages.add(pkg_name);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
version_changes.push({
|
|
400
|
-
package_name: pkg_name,
|
|
401
|
-
from: old_version,
|
|
402
|
-
to: new_version,
|
|
403
|
-
bump_type: required_bump,
|
|
404
|
-
breaking: is_breaking,
|
|
405
|
-
has_changesets: false,
|
|
406
|
-
will_generate_changeset: true
|
|
561
|
+
changed = true;
|
|
562
|
+
verbose_new_changes++;
|
|
563
|
+
|
|
564
|
+
if (verbose) {
|
|
565
|
+
verbose_iteration_packages.push({
|
|
566
|
+
name: pkg_name,
|
|
567
|
+
changeset_count: 0,
|
|
568
|
+
bump_from_changesets: null,
|
|
569
|
+
required_bump,
|
|
570
|
+
triggering_dep,
|
|
571
|
+
action: 'auto_changeset',
|
|
572
|
+
version_to: new_version,
|
|
573
|
+
is_breaking
|
|
407
574
|
});
|
|
575
|
+
}
|
|
408
576
|
|
|
409
|
-
|
|
410
|
-
|
|
577
|
+
if (is_breaking) {
|
|
578
|
+
breaking_packages.add(pkg_name);
|
|
411
579
|
}
|
|
580
|
+
|
|
581
|
+
version_changes.push({
|
|
582
|
+
package_name: pkg_name,
|
|
583
|
+
from: old_version,
|
|
584
|
+
to: new_version,
|
|
585
|
+
bump_type: required_bump,
|
|
586
|
+
breaking: is_breaking,
|
|
587
|
+
has_changesets: false,
|
|
588
|
+
will_generate_changeset: true
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
predicted_versions.set(pkg_name, new_version);
|
|
412
592
|
}
|
|
413
593
|
}
|
|
414
594
|
|
|
@@ -422,142 +602,126 @@ export const generate_publishing_plan = async (
|
|
|
422
602
|
}
|
|
423
603
|
}
|
|
424
604
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
// Calculate how many packages still need processing
|
|
428
|
-
const pending_packages: Array<string> = [];
|
|
429
|
-
|
|
430
|
-
// Recalculate one more time to see what's pending
|
|
431
|
-
const { dependency_updates: pending_updates } = calculate_dependency_updates(
|
|
432
|
-
repos,
|
|
433
|
-
predicted_versions,
|
|
434
|
-
breaking_packages
|
|
435
|
-
);
|
|
436
|
-
|
|
437
|
-
for (const repo of repos) {
|
|
438
|
-
const pkg_name = repo.library.name;
|
|
439
|
-
if (repo.package_json.private) continue; // private packages never publish
|
|
440
|
-
const required_bump = get_required_bump_for_dependencies(
|
|
441
|
-
repo,
|
|
442
|
-
pending_updates,
|
|
443
|
-
breaking_packages
|
|
444
|
-
);
|
|
445
|
-
|
|
446
|
-
// Check if this package would need processing
|
|
447
|
-
const existing_entry = version_changes.find((vc) => vc.package_name === pkg_name);
|
|
448
|
-
const needs_escalation =
|
|
449
|
-
existing_entry &&
|
|
450
|
-
required_bump &&
|
|
451
|
-
compare_bump_types(required_bump, existing_entry.bump_type) > 0;
|
|
452
|
-
const needs_auto_changeset = !existing_entry && required_bump;
|
|
453
|
-
|
|
454
|
-
if (needs_escalation || needs_auto_changeset) {
|
|
455
|
-
pending_packages.push(pkg_name);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
605
|
+
return { iterations: iteration, converged: !changed, verbose_iterations };
|
|
606
|
+
};
|
|
458
607
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
608
|
+
/**
|
|
609
|
+
* Words the warning for a plan that hit `GITOPS_MAX_ITERATIONS_DEFAULT` still
|
|
610
|
+
* changing, naming the packages one more pass would change.
|
|
611
|
+
*
|
|
612
|
+
* @returns the warning, or `null` when one more pass would change nothing (the last pass converged it)
|
|
613
|
+
*/
|
|
614
|
+
const plan_convergence_warning = (repos: Array<LocalRepo>, state: CascadeState): string | null => {
|
|
615
|
+
const { version_changes, predicted_versions, breaking_packages } = state;
|
|
616
|
+
const pending_packages: Array<string> = [];
|
|
468
617
|
|
|
469
|
-
//
|
|
470
|
-
const { dependency_updates
|
|
618
|
+
// Recalculate one more time to see what's pending
|
|
619
|
+
const { dependency_updates: pending_updates } = calculate_dependency_updates(
|
|
471
620
|
repos,
|
|
472
621
|
predicted_versions,
|
|
473
622
|
breaking_packages
|
|
474
623
|
);
|
|
475
624
|
|
|
476
|
-
// Identify packages with no changes
|
|
477
625
|
for (const repo of repos) {
|
|
478
|
-
const
|
|
479
|
-
if (
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
626
|
+
const pkg_name = repo.library.name;
|
|
627
|
+
if (repo.package_json.private) continue; // private packages never publish
|
|
628
|
+
const required_bump = get_required_bump_for_dependencies(
|
|
629
|
+
repo,
|
|
630
|
+
pending_updates,
|
|
631
|
+
breaking_packages
|
|
632
|
+
);
|
|
633
|
+
if (!required_bump) continue;
|
|
634
|
+
|
|
635
|
+
const existing_entry = version_changes.find((vc) => vc.package_name === pkg_name);
|
|
636
|
+
if (!existing_entry || compare_bump_types(required_bump, existing_entry.bump_type) > 0) {
|
|
637
|
+
pending_packages.push(pkg_name);
|
|
484
638
|
}
|
|
485
639
|
}
|
|
486
640
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
641
|
+
if (pending_packages.length === 0) return null;
|
|
642
|
+
|
|
643
|
+
return (
|
|
644
|
+
`Reached maximum iterations (${GITOPS_MAX_ITERATIONS_DEFAULT}) without full convergence - ` +
|
|
645
|
+
`${pending_packages.length} package(s) may still need processing: ${pending_packages.join(', ')}.`
|
|
646
|
+
);
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
interface PlanVerboseDataOptions {
|
|
650
|
+
graph: DependencyGraph;
|
|
651
|
+
version_changes: Array<VersionChange>;
|
|
652
|
+
dependency_updates: Array<DependencyUpdate>;
|
|
653
|
+
breaking_cascades: Map<string, Array<string>>;
|
|
654
|
+
changeset_details: Array<VerboseChangesetDetail>;
|
|
655
|
+
iterations: Array<VerboseIteration>;
|
|
656
|
+
total_iterations: number;
|
|
657
|
+
prod_cycle_count: number;
|
|
658
|
+
dev_cycle_count: number;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Assembles the verbose diagnostics: propagation chains from the breaking
|
|
663
|
+
* cascades and a summary of the dependency graph, beside the changeset and
|
|
664
|
+
* iteration details the passes collected.
|
|
665
|
+
*/
|
|
666
|
+
const plan_verbose_data = (options: PlanVerboseDataOptions): VerboseData => {
|
|
667
|
+
const { graph, version_changes, dependency_updates, breaking_cascades } = options;
|
|
668
|
+
|
|
669
|
+
// Build propagation chains from breaking_cascades
|
|
670
|
+
const propagation_chains: Array<VerbosePropagationChain> = [];
|
|
671
|
+
for (const [source, affected] of breaking_cascades) {
|
|
672
|
+
const chain: Array<{ pkg: string; dep_type: 'prod' | 'peer'; action: string }> = [];
|
|
673
|
+
for (const pkg of affected) {
|
|
674
|
+
// Determine dep type and action
|
|
675
|
+
const update = dependency_updates.find(
|
|
676
|
+
(u) => u.dependent_package === pkg && u.updated_dependency === source
|
|
677
|
+
);
|
|
678
|
+
const dep_type: 'prod' | 'peer' = update?.type === 'peerDependencies' ? 'peer' : 'prod';
|
|
679
|
+
const version_change = version_changes.find((vc) => vc.package_name === pkg);
|
|
680
|
+
const kind = version_change && version_change_kind(version_change);
|
|
681
|
+
const action =
|
|
682
|
+
kind === 'auto' ? 'auto-changeset' : kind === 'escalation' ? 'bump escalation' : 'update';
|
|
683
|
+
chain.push({ pkg, dep_type, action });
|
|
512
684
|
}
|
|
685
|
+
if (chain.length > 0) {
|
|
686
|
+
propagation_chains.push({ source, chain });
|
|
687
|
+
}
|
|
688
|
+
}
|
|
513
689
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
}
|
|
690
|
+
// Build graph summary
|
|
691
|
+
const prod_peer_edges: Array<{ from: string; to: string; type: 'prod' | 'peer' }> = [];
|
|
692
|
+
const dev_edges: Array<{ from: string; to: string }> = [];
|
|
693
|
+
let internal_dep_count = 0;
|
|
694
|
+
|
|
695
|
+
for (const [pkg_name, node] of graph.nodes) {
|
|
696
|
+
for (const [dep_name, spec] of node.dependencies) {
|
|
697
|
+
// Only count internal dependencies (deps that are also in the graph)
|
|
698
|
+
if (graph.nodes.has(dep_name)) {
|
|
699
|
+
internal_dep_count++;
|
|
700
|
+
if (spec.type === 'dev') {
|
|
701
|
+
dev_edges.push({ from: pkg_name, to: dep_name });
|
|
702
|
+
} else {
|
|
703
|
+
prod_peer_edges.push({
|
|
704
|
+
from: pkg_name,
|
|
705
|
+
to: dep_name,
|
|
706
|
+
type: spec.type === 'peer' ? 'peer' : 'prod'
|
|
707
|
+
});
|
|
533
708
|
}
|
|
534
709
|
}
|
|
535
710
|
}
|
|
536
|
-
|
|
537
|
-
verbose_data = {
|
|
538
|
-
changeset_details: verbose_changeset_details,
|
|
539
|
-
iterations: verbose_iterations,
|
|
540
|
-
propagation_chains,
|
|
541
|
-
graph_summary: {
|
|
542
|
-
package_count: graph.nodes.size,
|
|
543
|
-
internal_dep_count,
|
|
544
|
-
prod_peer_edges,
|
|
545
|
-
dev_edges,
|
|
546
|
-
prod_cycle_count: production_cycles.length,
|
|
547
|
-
dev_cycle_count: dev_cycles.length
|
|
548
|
-
},
|
|
549
|
-
total_iterations: iteration
|
|
550
|
-
};
|
|
551
711
|
}
|
|
552
712
|
|
|
553
713
|
return {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
714
|
+
changeset_details: options.changeset_details,
|
|
715
|
+
iterations: options.iterations,
|
|
716
|
+
propagation_chains,
|
|
717
|
+
graph_summary: {
|
|
718
|
+
package_count: graph.nodes.size,
|
|
719
|
+
internal_dep_count,
|
|
720
|
+
prod_peer_edges,
|
|
721
|
+
dev_edges,
|
|
722
|
+
prod_cycle_count: options.prod_cycle_count,
|
|
723
|
+
dev_cycle_count: options.dev_cycle_count
|
|
724
|
+
},
|
|
725
|
+
total_iterations: options.total_iterations
|
|
562
726
|
};
|
|
563
727
|
};
|