@fuzdev/fuz_gitops 0.78.0 → 0.79.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -17
- package/dist/ModulesDetail.svelte +22 -27
- package/dist/ModulesDetail.svelte.d.ts.map +1 -1
- package/dist/ModulesNav.svelte +8 -5
- package/dist/ModulesNav.svelte.d.ts.map +1 -1
- package/dist/ModulesPage.svelte +3 -5
- package/dist/ModulesPage.svelte.d.ts.map +1 -1
- package/dist/PageFooter.svelte +2 -2
- package/dist/PageHeader.svelte +6 -6
- package/dist/PageHeader.svelte.d.ts +0 -3
- package/dist/PageHeader.svelte.d.ts.map +1 -1
- package/dist/PullRequestsDetail.svelte +29 -20
- package/dist/PullRequestsDetail.svelte.d.ts.map +1 -1
- package/dist/PullRequestsPage.svelte +4 -6
- package/dist/PullRequestsPage.svelte.d.ts.map +1 -1
- package/dist/ReposTable.svelte +43 -55
- package/dist/ReposTable.svelte.d.ts.map +1 -1
- package/dist/ReposTree.svelte +9 -21
- package/dist/ReposTree.svelte.d.ts.map +1 -1
- package/dist/ReposTreeNav.svelte +9 -12
- package/dist/ReposTreeNav.svelte.d.ts.map +1 -1
- package/dist/TablePage.svelte +3 -5
- package/dist/TablePage.svelte.d.ts.map +1 -1
- package/dist/TreeItemPage.svelte +3 -5
- package/dist/TreeItemPage.svelte.d.ts.map +1 -1
- package/dist/TreePage.svelte +3 -5
- package/dist/TreePage.svelte.d.ts.map +1 -1
- package/dist/changeset_generator.d.ts.map +1 -1
- package/dist/changeset_generator.js +2 -2
- package/dist/changeset_reader.d.ts.map +1 -1
- package/dist/changeset_reader.js +3 -3
- package/dist/ci_reconcile.d.ts +6 -10
- package/dist/ci_reconcile.d.ts.map +1 -1
- package/dist/ci_reconcile.js +6 -8
- package/dist/dependency_graph.d.ts +26 -49
- package/dist/dependency_graph.d.ts.map +1 -1
- package/dist/dependency_graph.js +40 -83
- package/dist/dependency_updater.d.ts +1 -1
- package/dist/dependency_updater.d.ts.map +1 -1
- package/dist/dependency_updater.js +14 -9
- package/dist/fetch_repo_data.d.ts +34 -8
- package/dist/fetch_repo_data.d.ts.map +1 -1
- package/dist/fetch_repo_data.js +44 -22
- package/dist/fs_fetch_value_cache.d.ts +1 -1
- package/dist/fs_fetch_value_cache.d.ts.map +1 -1
- package/dist/fs_fetch_value_cache.js +3 -3
- package/dist/git_operations.d.ts +6 -49
- package/dist/git_operations.d.ts.map +1 -1
- package/dist/git_operations.js +13 -126
- package/dist/github.d.ts +17 -2
- package/dist/github.d.ts.map +1 -1
- package/dist/github.js +17 -8
- package/dist/github_helpers.d.ts.map +1 -1
- package/dist/github_helpers.js +3 -11
- package/dist/gitops_analyze.task.d.ts +24 -2
- package/dist/gitops_analyze.task.d.ts.map +1 -1
- package/dist/gitops_analyze.task.js +47 -34
- package/dist/gitops_config.d.ts +34 -79
- package/dist/gitops_config.d.ts.map +1 -1
- package/dist/gitops_config.js +76 -53
- package/dist/gitops_constants.d.ts +6 -4
- package/dist/gitops_constants.d.ts.map +1 -1
- package/dist/gitops_constants.js +6 -4
- package/dist/gitops_plan.task.d.ts +29 -3
- package/dist/gitops_plan.task.d.ts.map +1 -1
- package/dist/gitops_plan.task.js +83 -61
- package/dist/gitops_publish.task.d.ts +62 -2
- package/dist/gitops_publish.task.d.ts.map +1 -1
- package/dist/gitops_publish.task.js +193 -105
- package/dist/gitops_run.task.d.ts +74 -0
- package/dist/gitops_run.task.d.ts.map +1 -1
- package/dist/gitops_run.task.js +176 -162
- package/dist/gitops_sync.task.d.ts +65 -3
- package/dist/gitops_sync.task.d.ts.map +1 -1
- package/dist/gitops_sync.task.js +163 -37
- package/dist/gitops_task_helpers.d.ts +86 -59
- package/dist/gitops_task_helpers.d.ts.map +1 -1
- package/dist/gitops_task_helpers.js +120 -79
- package/dist/gitops_validate.task.d.ts +1 -2
- package/dist/gitops_validate.task.d.ts.map +1 -1
- package/dist/gitops_validate.task.js +25 -32
- package/dist/graph_validation.d.ts +16 -27
- package/dist/graph_validation.d.ts.map +1 -1
- package/dist/graph_validation.js +24 -65
- package/dist/local_repo.d.ts +50 -62
- package/dist/local_repo.d.ts.map +1 -1
- package/dist/local_repo.js +83 -196
- package/dist/log_helpers.d.ts +8 -8
- package/dist/log_helpers.d.ts.map +1 -1
- package/dist/multi_repo_publisher.d.ts +10 -0
- package/dist/multi_repo_publisher.d.ts.map +1 -1
- package/dist/multi_repo_publisher.js +125 -61
- package/dist/npm_registry.d.ts +23 -19
- package/dist/npm_registry.d.ts.map +1 -1
- package/dist/npm_registry.js +21 -48
- package/dist/operations.d.ts +75 -180
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +15 -7
- package/dist/operations_defaults.d.ts +17 -2
- package/dist/operations_defaults.d.ts.map +1 -1
- package/dist/operations_defaults.js +111 -128
- package/dist/output_helpers.d.ts +36 -0
- package/dist/output_helpers.d.ts.map +1 -1
- package/dist/output_helpers.js +37 -3
- package/dist/preflight_checks.d.ts +20 -27
- package/dist/preflight_checks.d.ts.map +1 -1
- package/dist/preflight_checks.js +37 -126
- package/dist/publish_gate.d.ts.map +1 -1
- package/dist/publish_steps.d.ts +2 -4
- package/dist/publish_steps.d.ts.map +1 -1
- package/dist/publish_steps.js +4 -8
- package/dist/publishing_event.d.ts +2 -8
- package/dist/publishing_event.d.ts.map +1 -1
- package/dist/publishing_event.js +20 -17
- package/dist/publishing_event_handler.d.ts +10 -8
- package/dist/publishing_event_handler.d.ts.map +1 -1
- package/dist/publishing_event_handler.js +19 -17
- package/dist/publishing_plan.d.ts +16 -1
- package/dist/publishing_plan.d.ts.map +1 -1
- package/dist/publishing_plan.js +332 -254
- package/dist/publishing_plan_helpers.d.ts.map +1 -1
- package/dist/publishing_plan_helpers.js +3 -3
- package/dist/publishing_plan_logging.d.ts +1 -1
- package/dist/publishing_plan_logging.d.ts.map +1 -1
- package/dist/publishing_plan_logging.js +22 -15
- package/dist/repo.svelte.d.ts +7 -0
- package/dist/repo.svelte.d.ts.map +1 -1
- package/dist/repo.svelte.js +3 -0
- package/dist/repo_readiness.d.ts +210 -0
- package/dist/repo_readiness.d.ts.map +1 -0
- package/dist/repo_readiness.js +516 -0
- package/dist/repos_status.d.ts +3340 -0
- package/dist/repos_status.d.ts.map +1 -0
- package/dist/repos_status.js +555 -0
- package/dist/repos_status_load.d.ts +65 -0
- package/dist/repos_status_load.d.ts.map +1 -0
- package/dist/repos_status_load.js +131 -0
- package/dist/version_utils.js +1 -1
- package/package.json +15 -34
- package/src/lib/cargo_toml.ts +3 -3
- package/src/lib/changeset_generator.ts +19 -19
- package/src/lib/changeset_reader.ts +18 -18
- package/src/lib/ci_reconcile.ts +12 -16
- package/src/lib/dependency_graph.ts +61 -113
- package/src/lib/dependency_updater.ts +36 -30
- package/src/lib/fetch_repo_data.ts +71 -34
- package/src/lib/fs_fetch_value_cache.ts +15 -11
- package/src/lib/git_operations.ts +22 -194
- package/src/lib/github.ts +33 -21
- package/src/lib/github_helpers.ts +9 -16
- package/src/lib/gitops_analyze.task.ts +104 -66
- package/src/lib/gitops_config.ts +90 -139
- package/src/lib/gitops_constants.ts +6 -4
- package/src/lib/gitops_plan.task.ts +128 -76
- package/src/lib/gitops_publish.task.ts +280 -144
- package/src/lib/gitops_run.task.ts +265 -193
- package/src/lib/gitops_sync.task.ts +236 -52
- package/src/lib/gitops_task_helpers.ts +170 -114
- package/src/lib/gitops_validate.task.ts +44 -54
- package/src/lib/graph_validation.ts +28 -89
- package/src/lib/local_repo.ts +128 -335
- package/src/lib/log_helpers.ts +18 -28
- package/src/lib/multi_repo_publisher.ts +192 -110
- package/src/lib/npm_registry.ts +42 -67
- package/src/lib/operations.ts +99 -160
- package/src/lib/operations_defaults.ts +157 -201
- package/src/lib/output_helpers.ts +58 -6
- package/src/lib/preflight_checks.ts +52 -188
- package/src/lib/publish_gate.ts +7 -9
- package/src/lib/publish_steps.ts +27 -25
- package/src/lib/publishing_event.ts +26 -23
- package/src/lib/publishing_event_handler.ts +24 -22
- package/src/lib/publishing_plan.ts +474 -310
- package/src/lib/publishing_plan_helpers.ts +16 -12
- package/src/lib/publishing_plan_logging.ts +53 -44
- package/src/lib/repo.svelte.ts +16 -8
- package/src/lib/repo_readiness.ts +647 -0
- package/src/lib/repos_status.ts +671 -0
- package/src/lib/repos_status_load.ts +173 -0
- package/src/lib/version_utils.ts +3 -3
- package/dist/config_reconcile.d.ts +0 -46
- package/dist/config_reconcile.d.ts.map +0 -1
- package/dist/config_reconcile.js +0 -60
- package/dist/paths.d.ts +0 -7
- package/dist/paths.d.ts.map +0 -1
- package/dist/paths.js +0 -6
- package/dist/repo_ops.d.ts +0 -59
- package/dist/repo_ops.d.ts.map +0 -1
- package/dist/repo_ops.js +0 -170
- package/dist/resolved_gitops_config.d.ts +0 -9
- package/dist/resolved_gitops_config.d.ts.map +0 -1
- package/dist/resolved_gitops_config.js +0 -12
- package/src/lib/config_reconcile.ts +0 -94
- package/src/lib/paths.ts +0 -6
- package/src/lib/repo_ops.ts +0 -216
- package/src/lib/resolved_gitops_config.ts +0 -27
|
@@ -1,26 +1,23 @@
|
|
|
1
|
-
import type {Logger} from '@fuzdev/fuz_util/log.ts';
|
|
2
|
-
import {styleText as st} from 'node:util';
|
|
1
|
+
import type { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
2
|
+
import { styleText as st } from 'node:util';
|
|
3
3
|
|
|
4
|
-
import {repo_is_npm, type LocalRepo} from './local_repo.ts';
|
|
5
|
-
import {validate_dependency_graph} from './graph_validation.ts';
|
|
4
|
+
import { repo_is_npm, type LocalRepo } from './local_repo.ts';
|
|
5
|
+
import { validate_dependency_graph } from './graph_validation.ts';
|
|
6
6
|
import {
|
|
7
7
|
type BumpType,
|
|
8
8
|
is_breaking_change,
|
|
9
9
|
compare_bump_types,
|
|
10
|
-
calculate_next_version
|
|
10
|
+
calculate_next_version
|
|
11
11
|
} from './version_utils.ts';
|
|
12
|
-
import type {ChangesetOperations} from './operations.ts';
|
|
13
|
-
import {default_changeset_operations} from './operations_defaults.ts';
|
|
14
|
-
import {GITOPS_MAX_ITERATIONS_DEFAULT} from './gitops_constants.ts';
|
|
15
|
-
import type {DependencyGraph} from './dependency_graph.ts';
|
|
12
|
+
import type { ChangesetOperations } from './operations.ts';
|
|
13
|
+
import { default_changeset_operations } from './operations_defaults.ts';
|
|
14
|
+
import { GITOPS_MAX_ITERATIONS_DEFAULT } from './gitops_constants.ts';
|
|
15
|
+
import type { DependencyGraph } from './dependency_graph.ts';
|
|
16
16
|
import {
|
|
17
17
|
calculate_dependency_updates,
|
|
18
|
-
get_required_bump_for_dependencies
|
|
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;
|
|
@@ -45,7 +58,7 @@ export interface DependencyUpdate {
|
|
|
45
58
|
// Verbose data types for diagnostic output
|
|
46
59
|
export interface VerboseChangesetDetail {
|
|
47
60
|
package_name: string;
|
|
48
|
-
files: Array<{filename: string; bump_type: BumpType; summary: string}>;
|
|
61
|
+
files: Array<{ filename: string; bump_type: BumpType; summary: string }>;
|
|
49
62
|
}
|
|
50
63
|
|
|
51
64
|
export interface VerboseIterationPackage {
|
|
@@ -67,14 +80,14 @@ export interface VerboseIteration {
|
|
|
67
80
|
|
|
68
81
|
export interface VerbosePropagationChain {
|
|
69
82
|
source: string;
|
|
70
|
-
chain: Array<{pkg: string; dep_type: 'prod' | 'peer'; action: string}>;
|
|
83
|
+
chain: Array<{ pkg: string; dep_type: 'prod' | 'peer'; action: string }>;
|
|
71
84
|
}
|
|
72
85
|
|
|
73
86
|
export interface VerboseGraphSummary {
|
|
74
87
|
package_count: number;
|
|
75
88
|
internal_dep_count: number;
|
|
76
|
-
prod_peer_edges: Array<{from: string; to: string; type: 'prod' | 'peer'}>;
|
|
77
|
-
dev_edges: Array<{from: string; to: string}>;
|
|
89
|
+
prod_peer_edges: Array<{ from: string; to: string; type: 'prod' | 'peer' }>;
|
|
90
|
+
dev_edges: Array<{ from: string; to: string }>;
|
|
78
91
|
prod_cycle_count: number;
|
|
79
92
|
dev_cycle_count: number;
|
|
80
93
|
}
|
|
@@ -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.
|
|
@@ -111,13 +139,13 @@ export interface GeneratePlanOptions {
|
|
|
111
139
|
*/
|
|
112
140
|
export const generate_publishing_plan = async (
|
|
113
141
|
all_repos: Array<LocalRepo>,
|
|
114
|
-
options: GeneratePlanOptions = {}
|
|
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
|
|
@@ -127,27 +155,19 @@ export const generate_publishing_plan = async (
|
|
|
127
155
|
if (non_npm_repos.length > 0) {
|
|
128
156
|
info.push(
|
|
129
157
|
`Excluded ${non_npm_repos.length} non-npm repo(s) from publishing: ` +
|
|
130
|
-
non_npm_repos.map((r) => r.library.name).join(', ')
|
|
158
|
+
non_npm_repos.map((r) => r.library.name).join(', ')
|
|
131
159
|
);
|
|
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,111 +186,281 @@ export const generate_publishing_plan = async (
|
|
|
166
186
|
breaking_cascades: new Map(),
|
|
167
187
|
warnings,
|
|
168
188
|
info,
|
|
169
|
-
|
|
189
|
+
no_changes: [],
|
|
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
|
|
181
199
|
if (dev_cycles.length > 0) {
|
|
182
200
|
info.push(
|
|
183
|
-
`${dev_cycles.length} dev dependency cycle(s) detected (normal, shown in gitops_analyze)
|
|
201
|
+
`${dev_cycles.length} dev dependency cycle(s) detected (normal, shown in gitops_analyze)`
|
|
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
|
+
}
|
|
229
358
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
359
|
+
if (!prediction.ok) {
|
|
360
|
+
errors.push(`Failed to predict version for ${pkg_name}: ${prediction.message}`);
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
|
|
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);
|
|
384
|
+
const old_version = repo.package_json.version || '0.0.0';
|
|
385
|
+
const is_breaking = is_breaking_change(old_version, prediction.bump_type);
|
|
253
386
|
|
|
254
|
-
|
|
387
|
+
state.predicted_versions.set(pkg_name, prediction.version);
|
|
255
388
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
version_changes.push({
|
|
261
|
-
package_name: pkg_name,
|
|
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
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
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`;
|
|
270
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> = [];
|
|
271
463
|
|
|
272
|
-
// Fixed-point iteration to resolve transitive cascades
|
|
273
|
-
// Loop until no new version changes are discovered
|
|
274
464
|
let iteration = 0;
|
|
275
465
|
let changed = true;
|
|
276
466
|
|
|
@@ -284,10 +474,10 @@ export const generate_publishing_plan = async (
|
|
|
284
474
|
|
|
285
475
|
// Recalculate dependency updates based on current predicted versions
|
|
286
476
|
// (breaking_cascades not needed during iteration, only calculated at the end)
|
|
287
|
-
const {dependency_updates} = calculate_dependency_updates(
|
|
477
|
+
const { dependency_updates } = calculate_dependency_updates(
|
|
288
478
|
repos,
|
|
289
479
|
predicted_versions,
|
|
290
|
-
breaking_packages
|
|
480
|
+
breaking_packages
|
|
291
481
|
);
|
|
292
482
|
|
|
293
483
|
// Process packages to check for bump escalation and auto-generated changesets
|
|
@@ -302,113 +492,103 @@ export const generate_publishing_plan = async (
|
|
|
302
492
|
const required_bump = get_required_bump_for_dependencies(
|
|
303
493
|
repo,
|
|
304
494
|
dependency_updates,
|
|
305
|
-
breaking_packages
|
|
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
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
+
});
|
|
544
|
+
}
|
|
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);
|
|
368
558
|
}
|
|
369
|
-
} else if (
|
|
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
|
|
|
@@ -417,147 +597,131 @@ export const generate_publishing_plan = async (
|
|
|
417
597
|
verbose_iterations.push({
|
|
418
598
|
iteration,
|
|
419
599
|
packages: verbose_iteration_packages,
|
|
420
|
-
new_changes: verbose_new_changes
|
|
600
|
+
new_changes: verbose_new_changes
|
|
421
601
|
});
|
|
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
|
-
breaking_packages
|
|
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 });
|
|
684
|
+
}
|
|
685
|
+
if (chain.length > 0) {
|
|
686
|
+
propagation_chains.push({ source, chain });
|
|
512
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
|
};
|