@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
package/dist/publishing_plan.js
CHANGED
|
@@ -5,18 +5,22 @@ import { is_breaking_change, compare_bump_types, calculate_next_version } from '
|
|
|
5
5
|
import { default_changeset_operations } from './operations_defaults.js';
|
|
6
6
|
import { GITOPS_MAX_ITERATIONS_DEFAULT } from './gitops_constants.js';
|
|
7
7
|
import { calculate_dependency_updates, get_required_bump_for_dependencies } from './publishing_plan_helpers.js';
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Classifies a version change — the one classification the plan's logger,
|
|
10
|
+
* markdown, and side-effect preview share. A change without changesets of its
|
|
11
|
+
* own is `auto` even if its bump was raised.
|
|
12
|
+
*/
|
|
13
|
+
export const version_change_kind = (change) => !change.has_changesets ? 'auto' : change.needs_bump_escalation ? 'escalation' : 'explicit';
|
|
10
14
|
/**
|
|
11
15
|
* Generates a publishing plan showing what would happen during publishing.
|
|
12
16
|
* Shows version changes, dependency updates, and breaking change cascades.
|
|
13
17
|
* Uses fixed-point iteration to resolve transitive cascades.
|
|
14
18
|
*/
|
|
15
19
|
export const generate_publishing_plan = async (all_repos, options = {}) => {
|
|
16
|
-
const { log, ops = default_changeset_operations, verbose } = options;
|
|
20
|
+
const { log, ops = default_changeset_operations, verbose = false } = options;
|
|
17
21
|
log?.info(st('cyan', 'Generating publishing plan...'));
|
|
18
22
|
const warnings = [];
|
|
19
|
-
const info = [];
|
|
23
|
+
const info = [];
|
|
20
24
|
const errors = [];
|
|
21
25
|
// Publishing concerns only npm packages. Non-npm repos (e.g. Rust cargo repos) are
|
|
22
26
|
// loaded for the dashboard but have no npm identity in the changeset cascade — drop them
|
|
@@ -27,20 +31,13 @@ export const generate_publishing_plan = async (all_repos, options = {}) => {
|
|
|
27
31
|
non_npm_repos.map((r) => r.library.name).join(', '));
|
|
28
32
|
}
|
|
29
33
|
const repos = all_repos.filter(repo_is_npm);
|
|
30
|
-
// Verbose data collection
|
|
31
|
-
const verbose_changeset_details = [];
|
|
32
|
-
const verbose_iterations = [];
|
|
33
34
|
// Build dependency graph and validate
|
|
34
35
|
let publishing_order;
|
|
35
36
|
let production_cycles;
|
|
36
37
|
let dev_cycles;
|
|
37
|
-
let graph
|
|
38
|
+
let graph;
|
|
38
39
|
try {
|
|
39
|
-
const validation = validate_dependency_graph(repos
|
|
40
|
-
throw_on_prod_cycles: false, // Collect errors instead of throwing
|
|
41
|
-
log_cycles: false, // We'll handle our own error collection
|
|
42
|
-
log_order: false // Plan generation doesn't need to log order
|
|
43
|
-
});
|
|
40
|
+
const validation = validate_dependency_graph(repos);
|
|
44
41
|
publishing_order = validation.publishing_order;
|
|
45
42
|
production_cycles = validation.production_cycles;
|
|
46
43
|
dev_cycles = validation.dev_cycles;
|
|
@@ -59,94 +56,201 @@ export const generate_publishing_plan = async (all_repos, options = {}) => {
|
|
|
59
56
|
breaking_cascades: new Map(),
|
|
60
57
|
warnings,
|
|
61
58
|
info,
|
|
59
|
+
no_changes: [],
|
|
62
60
|
errors
|
|
63
61
|
};
|
|
64
62
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
for (const cycle of production_cycles) {
|
|
68
|
-
errors.push(`Production dependency cycle: ${cycle.join(' → ')}`);
|
|
69
|
-
}
|
|
63
|
+
for (const cycle of production_cycles) {
|
|
64
|
+
errors.push(`Production dependency cycle: ${cycle.join(' → ')}`);
|
|
70
65
|
}
|
|
71
66
|
// Dev cycles are shown in gitops_analyze, not repeated here
|
|
72
67
|
if (dev_cycles.length > 0) {
|
|
73
68
|
info.push(`${dev_cycles.length} dev dependency cycle(s) detected (normal, shown in gitops_analyze)`);
|
|
74
69
|
}
|
|
75
|
-
//
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
|
|
70
|
+
// Checked once per repo; the initial pass and the no-changes list both read it.
|
|
71
|
+
const changeset_presence = new Map(await Promise.all(repos.map(async (repo) => [repo.library.name, await ops.has_changesets({ repo })])));
|
|
72
|
+
const state = {
|
|
73
|
+
version_changes: [],
|
|
74
|
+
predicted_versions: new Map(),
|
|
75
|
+
breaking_packages: new Set()
|
|
76
|
+
};
|
|
77
|
+
const { changeset_details, repos_without_bump } = await plan_initial_pass({
|
|
78
|
+
repos,
|
|
79
|
+
publishing_order,
|
|
80
|
+
changeset_presence,
|
|
81
|
+
state,
|
|
82
|
+
ops,
|
|
83
|
+
log,
|
|
84
|
+
verbose,
|
|
85
|
+
warnings,
|
|
86
|
+
errors
|
|
87
|
+
});
|
|
88
|
+
const cascade = plan_resolve_cascades({ repos, state, changeset_details, verbose });
|
|
89
|
+
// Worded after the cascades, which may give such a repo an auto-changeset
|
|
90
|
+
for (const repo of repos_without_bump) {
|
|
91
|
+
const publishes = state.version_changes.some((vc) => vc.package_name === repo.library.name);
|
|
92
|
+
warnings.push(await describe_changesets_without_bump(repo, publishes, ops));
|
|
93
|
+
}
|
|
94
|
+
if (!cascade.converged) {
|
|
95
|
+
const convergence_warning = plan_convergence_warning(repos, state);
|
|
96
|
+
if (convergence_warning)
|
|
97
|
+
warnings.push(convergence_warning);
|
|
98
|
+
}
|
|
99
|
+
// Final dependency updates calculation after convergence
|
|
100
|
+
const { dependency_updates, breaking_cascades } = calculate_dependency_updates(repos, state.predicted_versions, state.breaking_packages);
|
|
101
|
+
// Packages with nothing to publish: no version change and no changesets
|
|
102
|
+
const no_changes = [];
|
|
103
|
+
for (const repo of repos) {
|
|
104
|
+
const name = repo.library.name;
|
|
105
|
+
if (state.version_changes.some((vc) => vc.package_name === name))
|
|
106
|
+
continue;
|
|
107
|
+
const presence = changeset_presence.get(name);
|
|
108
|
+
if (presence?.ok && !presence.value) {
|
|
109
|
+
no_changes.push(name);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const verbose_data = verbose
|
|
113
|
+
? plan_verbose_data({
|
|
114
|
+
graph,
|
|
115
|
+
version_changes: state.version_changes,
|
|
116
|
+
dependency_updates,
|
|
117
|
+
breaking_cascades,
|
|
118
|
+
changeset_details,
|
|
119
|
+
iterations: cascade.verbose_iterations,
|
|
120
|
+
total_iterations: cascade.iterations,
|
|
121
|
+
prod_cycle_count: production_cycles.length,
|
|
122
|
+
dev_cycle_count: dev_cycles.length
|
|
123
|
+
})
|
|
124
|
+
: undefined;
|
|
125
|
+
return {
|
|
126
|
+
publishing_order,
|
|
127
|
+
version_changes: state.version_changes,
|
|
128
|
+
dependency_updates,
|
|
129
|
+
breaking_cascades,
|
|
130
|
+
warnings,
|
|
131
|
+
info,
|
|
132
|
+
no_changes,
|
|
133
|
+
errors,
|
|
134
|
+
verbose_data
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* The initial pass: a version change for each package with explicit changesets,
|
|
139
|
+
* in publishing order.
|
|
140
|
+
*
|
|
141
|
+
* @mutates options.state - adds the explicit version changes, their predicted versions, and the breaking ones
|
|
142
|
+
* @mutates options.warnings - private packages carrying changesets
|
|
143
|
+
* @mutates options.errors - failed changeset checks and predictions
|
|
144
|
+
*/
|
|
145
|
+
const plan_initial_pass = async (options) => {
|
|
146
|
+
const { repos, publishing_order, changeset_presence, state, ops, log, verbose } = options;
|
|
147
|
+
const { warnings, errors } = options;
|
|
148
|
+
const changeset_details = [];
|
|
149
|
+
const repos_without_bump = [];
|
|
79
150
|
for (const pkg_name of publishing_order) {
|
|
80
151
|
const repo = repos.find((r) => r.library.name === pkg_name);
|
|
81
152
|
if (!repo)
|
|
82
153
|
continue;
|
|
154
|
+
const has_result = changeset_presence.get(pkg_name);
|
|
155
|
+
if (!has_result)
|
|
156
|
+
continue;
|
|
83
157
|
// Private packages never publish — exclude them from version changes entirely (no
|
|
84
158
|
// publish step, npm-wait, bump escalation, or auto-changeset). They keep their slot in
|
|
85
159
|
// the topological order. Flag a private package that carries a changeset, since that
|
|
86
160
|
// changeset can't be published.
|
|
87
161
|
if (repo.package_json.private) {
|
|
88
|
-
|
|
89
|
-
if (private_has_changesets.ok && private_has_changesets.value) {
|
|
162
|
+
if (has_result.ok && has_result.value) {
|
|
90
163
|
warnings.push(`${pkg_name} is private — its changeset(s) will not be published`);
|
|
91
164
|
}
|
|
92
165
|
continue;
|
|
93
166
|
}
|
|
94
|
-
// Check for changesets
|
|
95
|
-
const has_result = await ops.has_changesets({ repo });
|
|
96
167
|
if (!has_result.ok) {
|
|
97
168
|
errors.push(`Failed to check changesets for ${pkg_name}: ${has_result.message}`);
|
|
98
169
|
continue;
|
|
99
170
|
}
|
|
100
|
-
if (has_result.value)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
verbose_changeset_details.push({ package_name: pkg_name, files });
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
{
|
|
131
|
-
const old_version = repo.package_json.version || '0.0.0';
|
|
132
|
-
const is_breaking = is_breaking_change(old_version, prediction.bump_type);
|
|
133
|
-
predicted_versions.set(pkg_name, prediction.version);
|
|
134
|
-
if (is_breaking) {
|
|
135
|
-
breaking_packages.add(pkg_name);
|
|
136
|
-
}
|
|
137
|
-
version_changes.push({
|
|
138
|
-
package_name: pkg_name,
|
|
139
|
-
from: old_version,
|
|
140
|
-
to: prediction.version,
|
|
141
|
-
bump_type: prediction.bump_type,
|
|
142
|
-
breaking: is_breaking,
|
|
143
|
-
has_changesets: true
|
|
171
|
+
if (!has_result.value)
|
|
172
|
+
continue;
|
|
173
|
+
// Predict version from changesets
|
|
174
|
+
const prediction = await ops.predict_next_version({ repo, log });
|
|
175
|
+
if (!prediction) {
|
|
176
|
+
// Changeset files exist, but none yields a bump for this package
|
|
177
|
+
repos_without_bump.push(repo);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (!prediction.ok) {
|
|
181
|
+
errors.push(`Failed to predict version for ${pkg_name}: ${prediction.message}`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
// Capture changeset details for verbose output
|
|
185
|
+
if (verbose) {
|
|
186
|
+
const changesets_result = await ops.read_changesets({ repo, log });
|
|
187
|
+
if (changesets_result.ok) {
|
|
188
|
+
const files = changesets_result.value
|
|
189
|
+
.filter((cs) => cs.packages.some((p) => p.name === pkg_name))
|
|
190
|
+
.map((cs) => {
|
|
191
|
+
const pkg_entry = cs.packages.find((p) => p.name === pkg_name);
|
|
192
|
+
return {
|
|
193
|
+
filename: cs.filename,
|
|
194
|
+
bump_type: pkg_entry?.bump_type || prediction.bump_type,
|
|
195
|
+
summary: cs.summary
|
|
196
|
+
};
|
|
144
197
|
});
|
|
198
|
+
if (files.length > 0) {
|
|
199
|
+
changeset_details.push({ package_name: pkg_name, files });
|
|
200
|
+
}
|
|
145
201
|
}
|
|
146
202
|
}
|
|
203
|
+
const old_version = repo.package_json.version || '0.0.0';
|
|
204
|
+
const is_breaking = is_breaking_change(old_version, prediction.bump_type);
|
|
205
|
+
state.predicted_versions.set(pkg_name, prediction.version);
|
|
206
|
+
if (is_breaking) {
|
|
207
|
+
state.breaking_packages.add(pkg_name);
|
|
208
|
+
}
|
|
209
|
+
state.version_changes.push({
|
|
210
|
+
package_name: pkg_name,
|
|
211
|
+
from: old_version,
|
|
212
|
+
to: prediction.version,
|
|
213
|
+
bump_type: prediction.bump_type,
|
|
214
|
+
breaking: is_breaking,
|
|
215
|
+
has_changesets: true
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return { changeset_details, repos_without_bump };
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* Words the warning for a repo whose changeset files yield no bump for its
|
|
222
|
+
* package. It takes no bump from them: it publishes only as the auto-generated
|
|
223
|
+
* change a dependency update requires, and otherwise not at all.
|
|
224
|
+
*
|
|
225
|
+
* @param publishes - whether the settled plan has a version change for the repo
|
|
226
|
+
*/
|
|
227
|
+
const describe_changesets_without_bump = async (repo, publishes, ops) => {
|
|
228
|
+
const pkg_name = repo.library.name;
|
|
229
|
+
const consequence = publishes
|
|
230
|
+
? 'it takes no bump from them and publishes only as the auto-generated change a dependency update requires'
|
|
231
|
+
: 'it will not publish';
|
|
232
|
+
// no `log`: `predict_next_version` already logged any invalid files
|
|
233
|
+
const read = await ops.read_changesets({ repo });
|
|
234
|
+
if (!read.ok) {
|
|
235
|
+
return `${pkg_name} has changeset files that could not be read (${read.message}) — ${consequence}`;
|
|
147
236
|
}
|
|
148
|
-
|
|
149
|
-
|
|
237
|
+
if (read.value.length === 0) {
|
|
238
|
+
return `${pkg_name} has changeset files, but none parses — ${consequence}; check their frontmatter`;
|
|
239
|
+
}
|
|
240
|
+
return (`${pkg_name} has changeset files, but none that parses names ${pkg_name} — ${consequence}; ` +
|
|
241
|
+
`check the package names in their frontmatter`);
|
|
242
|
+
};
|
|
243
|
+
/**
|
|
244
|
+
* Fixed-point iteration over the dependency updates the current predictions
|
|
245
|
+
* imply: escalates explicit changes a breaking dependency outgrows, and adds or
|
|
246
|
+
* raises auto-changeset changes for dependents, until a pass finds nothing new.
|
|
247
|
+
*
|
|
248
|
+
* @mutates options.state - escalates and adds version changes, with their predicted versions and breaking set
|
|
249
|
+
*/
|
|
250
|
+
const plan_resolve_cascades = (options) => {
|
|
251
|
+
const { repos, state, changeset_details, verbose } = options;
|
|
252
|
+
const { version_changes, predicted_versions, breaking_packages } = state;
|
|
253
|
+
const verbose_iterations = [];
|
|
150
254
|
let iteration = 0;
|
|
151
255
|
let changed = true;
|
|
152
256
|
while (changed && iteration < GITOPS_MAX_ITERATIONS_DEFAULT) {
|
|
@@ -167,93 +271,89 @@ export const generate_publishing_plan = async (all_repos, options = {}) => {
|
|
|
167
271
|
continue;
|
|
168
272
|
// Get required bump from dependencies
|
|
169
273
|
const required_bump = get_required_bump_for_dependencies(repo, dependency_updates, breaking_packages);
|
|
274
|
+
if (!required_bump)
|
|
275
|
+
continue;
|
|
170
276
|
// Find triggering dependency for verbose output
|
|
171
277
|
let triggering_dep = null;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
triggering_dep = `${relevant_updates[0].updated_dependency} BREAKING`;
|
|
178
|
-
}
|
|
278
|
+
const relevant_updates = dependency_updates.filter((u) => u.dependent_package === pkg_name &&
|
|
279
|
+
(u.type === 'dependencies' || u.type === 'peerDependencies') &&
|
|
280
|
+
breaking_packages.has(u.updated_dependency));
|
|
281
|
+
if (relevant_updates.length > 0) {
|
|
282
|
+
triggering_dep = `${relevant_updates[0].updated_dependency} BREAKING`;
|
|
179
283
|
}
|
|
180
|
-
|
|
284
|
+
const old_version = repo.package_json.version || '0.0.0';
|
|
285
|
+
const new_version = calculate_next_version(old_version, required_bump);
|
|
286
|
+
const is_breaking = is_breaking_change(old_version, required_bump);
|
|
181
287
|
const existing_entry = version_changes.find((vc) => vc.package_name === pkg_name);
|
|
182
288
|
if (existing_entry) {
|
|
183
|
-
//
|
|
184
|
-
if
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
289
|
+
// Dependencies may require a larger bump than the entry has. Only mark as
|
|
290
|
+
// changed if the version actually changes.
|
|
291
|
+
if (compare_bump_types(required_bump, existing_entry.bump_type) <= 0)
|
|
292
|
+
continue;
|
|
293
|
+
if (existing_entry.to === new_version)
|
|
294
|
+
continue;
|
|
295
|
+
changed = true;
|
|
296
|
+
verbose_new_changes++;
|
|
297
|
+
// An auto-changeset entry raised by a dependency that turned breaking later in the
|
|
298
|
+
// iteration (a dependent listed before its dependency) is still an auto-changeset:
|
|
299
|
+
// the executor generates its changeset at the bump the plan settles on. Only an
|
|
300
|
+
// entry with changesets of its own escalates.
|
|
301
|
+
const kind = version_change_kind(existing_entry);
|
|
302
|
+
if (verbose) {
|
|
303
|
+
const changeset_detail = changeset_details.find((d) => d.package_name === pkg_name);
|
|
304
|
+
verbose_iteration_packages.push({
|
|
305
|
+
name: pkg_name,
|
|
306
|
+
changeset_count: kind === 'auto' ? 0 : changeset_detail?.files.length || 1,
|
|
307
|
+
bump_from_changesets: kind === 'auto' ? null : existing_entry.bump_type,
|
|
308
|
+
required_bump,
|
|
309
|
+
triggering_dep,
|
|
310
|
+
action: kind === 'auto' ? 'auto_changeset' : 'escalation',
|
|
311
|
+
version_to: new_version,
|
|
312
|
+
is_breaking
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
if (kind !== 'auto') {
|
|
316
|
+
existing_entry.needs_bump_escalation = true;
|
|
317
|
+
existing_entry.existing_bump ??= existing_entry.bump_type;
|
|
318
|
+
existing_entry.required_bump = required_bump;
|
|
319
|
+
}
|
|
320
|
+
existing_entry.bump_type = required_bump;
|
|
321
|
+
existing_entry.to = new_version;
|
|
322
|
+
existing_entry.breaking = is_breaking;
|
|
323
|
+
predicted_versions.set(pkg_name, new_version);
|
|
324
|
+
if (is_breaking) {
|
|
325
|
+
breaking_packages.add(pkg_name);
|
|
218
326
|
}
|
|
219
327
|
}
|
|
220
|
-
else if (
|
|
328
|
+
else if (!predicted_versions.has(pkg_name)) {
|
|
221
329
|
// No existing changesets but needs changeset for dependency updates
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
bump_from_changesets: null,
|
|
235
|
-
required_bump,
|
|
236
|
-
triggering_dep,
|
|
237
|
-
action: 'auto_changeset',
|
|
238
|
-
version_to: new_version,
|
|
239
|
-
is_breaking
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
if (is_breaking) {
|
|
243
|
-
breaking_packages.add(pkg_name);
|
|
244
|
-
}
|
|
245
|
-
version_changes.push({
|
|
246
|
-
package_name: pkg_name,
|
|
247
|
-
from: old_version,
|
|
248
|
-
to: new_version,
|
|
249
|
-
bump_type: required_bump,
|
|
250
|
-
breaking: is_breaking,
|
|
251
|
-
has_changesets: false,
|
|
252
|
-
will_generate_changeset: true
|
|
330
|
+
changed = true;
|
|
331
|
+
verbose_new_changes++;
|
|
332
|
+
if (verbose) {
|
|
333
|
+
verbose_iteration_packages.push({
|
|
334
|
+
name: pkg_name,
|
|
335
|
+
changeset_count: 0,
|
|
336
|
+
bump_from_changesets: null,
|
|
337
|
+
required_bump,
|
|
338
|
+
triggering_dep,
|
|
339
|
+
action: 'auto_changeset',
|
|
340
|
+
version_to: new_version,
|
|
341
|
+
is_breaking
|
|
253
342
|
});
|
|
254
|
-
// Update predicted versions
|
|
255
|
-
predicted_versions.set(pkg_name, new_version);
|
|
256
343
|
}
|
|
344
|
+
if (is_breaking) {
|
|
345
|
+
breaking_packages.add(pkg_name);
|
|
346
|
+
}
|
|
347
|
+
version_changes.push({
|
|
348
|
+
package_name: pkg_name,
|
|
349
|
+
from: old_version,
|
|
350
|
+
to: new_version,
|
|
351
|
+
bump_type: required_bump,
|
|
352
|
+
breaking: is_breaking,
|
|
353
|
+
has_changesets: false,
|
|
354
|
+
will_generate_changeset: true
|
|
355
|
+
});
|
|
356
|
+
predicted_versions.set(pkg_name, new_version);
|
|
257
357
|
}
|
|
258
358
|
}
|
|
259
359
|
// Store verbose iteration data
|
|
@@ -265,116 +365,94 @@ export const generate_publishing_plan = async (all_repos, options = {}) => {
|
|
|
265
365
|
});
|
|
266
366
|
}
|
|
267
367
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
368
|
+
return { iterations: iteration, converged: !changed, verbose_iterations };
|
|
369
|
+
};
|
|
370
|
+
/**
|
|
371
|
+
* Words the warning for a plan that hit `GITOPS_MAX_ITERATIONS_DEFAULT` still
|
|
372
|
+
* changing, naming the packages one more pass would change.
|
|
373
|
+
*
|
|
374
|
+
* @returns the warning, or `null` when one more pass would change nothing (the last pass converged it)
|
|
375
|
+
*/
|
|
376
|
+
const plan_convergence_warning = (repos, state) => {
|
|
377
|
+
const { version_changes, predicted_versions, breaking_packages } = state;
|
|
378
|
+
const pending_packages = [];
|
|
379
|
+
// Recalculate one more time to see what's pending
|
|
380
|
+
const { dependency_updates: pending_updates } = calculate_dependency_updates(repos, predicted_versions, breaking_packages);
|
|
381
|
+
for (const repo of repos) {
|
|
382
|
+
const pkg_name = repo.library.name;
|
|
383
|
+
if (repo.package_json.private)
|
|
384
|
+
continue; // private packages never publish
|
|
385
|
+
const required_bump = get_required_bump_for_dependencies(repo, pending_updates, breaking_packages);
|
|
386
|
+
if (!required_bump)
|
|
387
|
+
continue;
|
|
388
|
+
const existing_entry = version_changes.find((vc) => vc.package_name === pkg_name);
|
|
389
|
+
if (!existing_entry || compare_bump_types(required_bump, existing_entry.bump_type) > 0) {
|
|
390
|
+
pending_packages.push(pkg_name);
|
|
288
391
|
}
|
|
289
|
-
// Add warning with diagnostics
|
|
290
|
-
const pending_count = pending_packages.length;
|
|
291
|
-
const estimated_iterations = Math.ceil(pending_count / 2); // Rough estimate
|
|
292
|
-
warnings.push(`Reached maximum iterations (${GITOPS_MAX_ITERATIONS_DEFAULT}) without full convergence - ` +
|
|
293
|
-
`${pending_count} package(s) may still need processing: ${pending_packages.join(', ')}. ` +
|
|
294
|
-
`Estimated ${estimated_iterations} more iteration(s) needed.`);
|
|
295
392
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
393
|
+
if (pending_packages.length === 0)
|
|
394
|
+
return null;
|
|
395
|
+
return (`Reached maximum iterations (${GITOPS_MAX_ITERATIONS_DEFAULT}) without full convergence - ` +
|
|
396
|
+
`${pending_packages.length} package(s) may still need processing: ${pending_packages.join(', ')}.`);
|
|
397
|
+
};
|
|
398
|
+
/**
|
|
399
|
+
* Assembles the verbose diagnostics: propagation chains from the breaking
|
|
400
|
+
* cascades and a summary of the dependency graph, beside the changeset and
|
|
401
|
+
* iteration details the passes collected.
|
|
402
|
+
*/
|
|
403
|
+
const plan_verbose_data = (options) => {
|
|
404
|
+
const { graph, version_changes, dependency_updates, breaking_cascades } = options;
|
|
405
|
+
// Build propagation chains from breaking_cascades
|
|
406
|
+
const propagation_chains = [];
|
|
407
|
+
for (const [source, affected] of breaking_cascades) {
|
|
408
|
+
const chain = [];
|
|
409
|
+
for (const pkg of affected) {
|
|
410
|
+
// Determine dep type and action
|
|
411
|
+
const update = dependency_updates.find((u) => u.dependent_package === pkg && u.updated_dependency === source);
|
|
412
|
+
const dep_type = update?.type === 'peerDependencies' ? 'peer' : 'prod';
|
|
413
|
+
const version_change = version_changes.find((vc) => vc.package_name === pkg);
|
|
414
|
+
const kind = version_change && version_change_kind(version_change);
|
|
415
|
+
const action = kind === 'auto' ? 'auto-changeset' : kind === 'escalation' ? 'bump escalation' : 'update';
|
|
416
|
+
chain.push({ pkg, dep_type, action });
|
|
417
|
+
}
|
|
418
|
+
if (chain.length > 0) {
|
|
419
|
+
propagation_chains.push({ source, chain });
|
|
306
420
|
}
|
|
307
421
|
}
|
|
308
|
-
// Build
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
for (const [
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
const version_change = version_changes.find((vc) => vc.package_name === pkg);
|
|
320
|
-
let action = 'update';
|
|
321
|
-
if (version_change?.will_generate_changeset) {
|
|
322
|
-
action = 'auto-changeset';
|
|
422
|
+
// Build graph summary
|
|
423
|
+
const prod_peer_edges = [];
|
|
424
|
+
const dev_edges = [];
|
|
425
|
+
let internal_dep_count = 0;
|
|
426
|
+
for (const [pkg_name, node] of graph.nodes) {
|
|
427
|
+
for (const [dep_name, spec] of node.dependencies) {
|
|
428
|
+
// Only count internal dependencies (deps that are also in the graph)
|
|
429
|
+
if (graph.nodes.has(dep_name)) {
|
|
430
|
+
internal_dep_count++;
|
|
431
|
+
if (spec.type === 'dev') {
|
|
432
|
+
dev_edges.push({ from: pkg_name, to: dep_name });
|
|
323
433
|
}
|
|
324
|
-
else
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
propagation_chains.push({ source, chain });
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
// Build graph summary
|
|
334
|
-
const prod_peer_edges = [];
|
|
335
|
-
const dev_edges = [];
|
|
336
|
-
let internal_dep_count = 0;
|
|
337
|
-
for (const [pkg_name, node] of graph.nodes) {
|
|
338
|
-
for (const [dep_name, spec] of node.dependencies) {
|
|
339
|
-
// Only count internal dependencies (deps that are also in the graph)
|
|
340
|
-
if (graph.nodes.has(dep_name)) {
|
|
341
|
-
internal_dep_count++;
|
|
342
|
-
if (spec.type === 'dev') {
|
|
343
|
-
dev_edges.push({ from: pkg_name, to: dep_name });
|
|
344
|
-
}
|
|
345
|
-
else {
|
|
346
|
-
prod_peer_edges.push({
|
|
347
|
-
from: pkg_name,
|
|
348
|
-
to: dep_name,
|
|
349
|
-
type: spec.type === 'peer' ? 'peer' : 'prod'
|
|
350
|
-
});
|
|
351
|
-
}
|
|
434
|
+
else {
|
|
435
|
+
prod_peer_edges.push({
|
|
436
|
+
from: pkg_name,
|
|
437
|
+
to: dep_name,
|
|
438
|
+
type: spec.type === 'peer' ? 'peer' : 'prod'
|
|
439
|
+
});
|
|
352
440
|
}
|
|
353
441
|
}
|
|
354
442
|
}
|
|
355
|
-
verbose_data = {
|
|
356
|
-
changeset_details: verbose_changeset_details,
|
|
357
|
-
iterations: verbose_iterations,
|
|
358
|
-
propagation_chains,
|
|
359
|
-
graph_summary: {
|
|
360
|
-
package_count: graph.nodes.size,
|
|
361
|
-
internal_dep_count,
|
|
362
|
-
prod_peer_edges,
|
|
363
|
-
dev_edges,
|
|
364
|
-
prod_cycle_count: production_cycles.length,
|
|
365
|
-
dev_cycle_count: dev_cycles.length
|
|
366
|
-
},
|
|
367
|
-
total_iterations: iteration
|
|
368
|
-
};
|
|
369
443
|
}
|
|
370
444
|
return {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
445
|
+
changeset_details: options.changeset_details,
|
|
446
|
+
iterations: options.iterations,
|
|
447
|
+
propagation_chains,
|
|
448
|
+
graph_summary: {
|
|
449
|
+
package_count: graph.nodes.size,
|
|
450
|
+
internal_dep_count,
|
|
451
|
+
prod_peer_edges,
|
|
452
|
+
dev_edges,
|
|
453
|
+
prod_cycle_count: options.prod_cycle_count,
|
|
454
|
+
dev_cycle_count: options.dev_cycle_count
|
|
455
|
+
},
|
|
456
|
+
total_iterations: options.total_iterations
|
|
379
457
|
};
|
|
380
458
|
};
|