@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,22 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dependency graph data structure and algorithms for multi-repo publishing.
|
|
3
3
|
*
|
|
4
|
-
* Provides `DependencyGraph` class with topological sort
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Provides the `DependencyGraph` class, built from local repos, with topological sort
|
|
5
|
+
* (via `@fuzdev/fuz_util/sort.ts`), cycle detection by dependency type, and
|
|
6
|
+
* wildcard-range analysis.
|
|
7
|
+
* For the publishing order and the analysis workflow, see `graph_validation.ts`.
|
|
7
8
|
*
|
|
8
9
|
* @module
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
|
-
import {EMPTY_OBJECT} from '@fuzdev/fuz_util/object.ts';
|
|
12
|
-
import {topological_sort as topological_sort_generic} from '@fuzdev/fuz_util/sort.ts';
|
|
12
|
+
import { EMPTY_OBJECT } from '@fuzdev/fuz_util/object.ts';
|
|
13
|
+
import { topological_sort as topological_sort_generic } from '@fuzdev/fuz_util/sort.ts';
|
|
13
14
|
|
|
14
|
-
import type {LocalRepo} from './local_repo.ts';
|
|
15
|
+
import type { LocalRepo } from './local_repo.ts';
|
|
15
16
|
|
|
16
17
|
export const DEPENDENCY_TYPE = {
|
|
17
18
|
PROD: 'prod',
|
|
18
19
|
PEER: 'peer',
|
|
19
|
-
DEV: 'dev'
|
|
20
|
+
DEV: 'dev'
|
|
20
21
|
} as const;
|
|
21
22
|
|
|
22
23
|
export type DependencyType = (typeof DEPENDENCY_TYPE)[keyof typeof DEPENDENCY_TYPE];
|
|
@@ -24,68 +25,68 @@ export type DependencyType = (typeof DEPENDENCY_TYPE)[keyof typeof DEPENDENCY_TY
|
|
|
24
25
|
export interface DependencySpec {
|
|
25
26
|
type: DependencyType;
|
|
26
27
|
version: string;
|
|
27
|
-
resolved?: string;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export interface DependencyGraphJson {
|
|
31
31
|
nodes: Array<{
|
|
32
32
|
name: string;
|
|
33
33
|
version: string;
|
|
34
|
-
dependencies: Array<{name: string; spec: DependencySpec}>;
|
|
34
|
+
dependencies: Array<{ name: string; spec: DependencySpec }>;
|
|
35
35
|
dependents: Array<string>;
|
|
36
|
-
publishable: boolean;
|
|
37
36
|
}>;
|
|
38
|
-
edges: Array<{from: string; to: string}>;
|
|
37
|
+
edges: Array<{ from: string; to: string }>;
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
export interface DependencyNode {
|
|
42
41
|
name: string;
|
|
43
42
|
version: string;
|
|
44
|
-
repo?: LocalRepo;
|
|
45
43
|
dependencies: Map<string, DependencySpec>;
|
|
46
44
|
dependents: Set<string>;
|
|
47
|
-
publishable: boolean;
|
|
48
45
|
}
|
|
49
46
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
/** Cycles and wildcard dependencies found by `DependencyGraph.analyze`. */
|
|
48
|
+
export interface DependencyAnalysis {
|
|
49
|
+
production_cycles: Array<Array<string>>;
|
|
50
|
+
dev_cycles: Array<Array<string>>;
|
|
51
|
+
wildcard_deps: Array<{ pkg: string; dep: string; version: string }>;
|
|
52
|
+
}
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
54
|
+
export class DependencyGraph {
|
|
55
|
+
nodes: Map<string, DependencyNode> = new Map();
|
|
56
|
+
edges: Map<string, Set<string>> = new Map(); // pkg -> dependents
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Builds the graph from local repos.
|
|
60
|
+
*
|
|
61
|
+
* Two passes: first creates nodes, then builds edges (dependents).
|
|
62
|
+
* Prioritizes prod/peer deps over dev deps when the same package appears in
|
|
63
|
+
* multiple dependency types (the stronger constraint wins).
|
|
64
|
+
*/
|
|
65
|
+
constructor(repos: Array<LocalRepo>) {
|
|
66
|
+
// first pass: create nodes
|
|
61
67
|
for (const repo of repos) {
|
|
62
|
-
const {library, package_json} = repo;
|
|
68
|
+
const { library, package_json } = repo;
|
|
63
69
|
const node: DependencyNode = {
|
|
64
70
|
name: library.name,
|
|
65
71
|
version: package_json.version || '0.0.0',
|
|
66
|
-
repo,
|
|
67
72
|
dependencies: new Map(),
|
|
68
|
-
dependents: new Set()
|
|
69
|
-
publishable: !package_json.private,
|
|
73
|
+
dependents: new Set()
|
|
70
74
|
};
|
|
71
75
|
|
|
72
|
-
// Extract dependencies
|
|
73
76
|
const deps = package_json.dependencies || (EMPTY_OBJECT as Record<string, string>);
|
|
74
77
|
const dev_deps = package_json.devDependencies || (EMPTY_OBJECT as Record<string, string>);
|
|
75
78
|
const peer_deps = package_json.peerDependencies || (EMPTY_OBJECT as Record<string, string>);
|
|
76
79
|
|
|
77
|
-
// Add dependencies, prioritizing prod/peer over dev
|
|
78
|
-
// (if a package appears in multiple dep types, use the stronger constraint)
|
|
79
80
|
for (const [name, version] of Object.entries(deps)) {
|
|
80
|
-
node.dependencies.set(name, {type: DEPENDENCY_TYPE.PROD, version});
|
|
81
|
+
node.dependencies.set(name, { type: DEPENDENCY_TYPE.PROD, version });
|
|
81
82
|
}
|
|
82
83
|
for (const [name, version] of Object.entries(peer_deps)) {
|
|
83
|
-
node.dependencies.set(name, {type: DEPENDENCY_TYPE.PEER, version});
|
|
84
|
+
node.dependencies.set(name, { type: DEPENDENCY_TYPE.PEER, version });
|
|
84
85
|
}
|
|
85
86
|
for (const [name, version] of Object.entries(dev_deps)) {
|
|
86
|
-
//
|
|
87
|
+
// only add dev deps if not already present as prod/peer
|
|
87
88
|
if (!node.dependencies.has(name)) {
|
|
88
|
-
node.dependencies.set(name, {type: DEPENDENCY_TYPE.DEV, version});
|
|
89
|
+
node.dependencies.set(name, { type: DEPENDENCY_TYPE.DEV, version });
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
|
|
@@ -93,12 +94,11 @@ export class DependencyGraph {
|
|
|
93
94
|
this.edges.set(library.name, new Set());
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
//
|
|
97
|
+
// second pass: build edges (dependents) for internal dependencies
|
|
97
98
|
for (const node of this.nodes.values()) {
|
|
98
99
|
for (const [dep_name] of node.dependencies) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const dep_node = this.nodes.get(dep_name)!;
|
|
100
|
+
const dep_node = this.nodes.get(dep_name);
|
|
101
|
+
if (dep_node) {
|
|
102
102
|
dep_node.dependents.add(node.name);
|
|
103
103
|
this.edges.get(dep_name)!.add(node.name);
|
|
104
104
|
}
|
|
@@ -110,15 +110,6 @@ export class DependencyGraph {
|
|
|
110
110
|
return this.nodes.get(name);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
get_dependents(name: string): Set<string> {
|
|
114
|
-
return this.edges.get(name) || new Set();
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
get_dependencies(name: string): Map<string, DependencySpec> {
|
|
118
|
-
const node = this.nodes.get(name);
|
|
119
|
-
return node ? node.dependencies : new Map();
|
|
120
|
-
}
|
|
121
|
-
|
|
122
113
|
/**
|
|
123
114
|
* Computes topological sort order for dependency graph.
|
|
124
115
|
*
|
|
@@ -138,7 +129,7 @@ export class DependencyGraph {
|
|
|
138
129
|
if (exclude_dev && spec.type === DEPENDENCY_TYPE.DEV) return false;
|
|
139
130
|
return this.nodes.has(dep_name);
|
|
140
131
|
})
|
|
141
|
-
.map(([dep_name]) => dep_name)
|
|
132
|
+
.map(([dep_name]) => dep_name)
|
|
142
133
|
}));
|
|
143
134
|
const result = topological_sort_generic(items, 'package');
|
|
144
135
|
if (!result.ok) {
|
|
@@ -164,7 +155,24 @@ export class DependencyGraph {
|
|
|
164
155
|
} {
|
|
165
156
|
const production_cycles = this.#find_cycles((spec) => spec.type !== DEPENDENCY_TYPE.DEV);
|
|
166
157
|
const dev_cycles = this.#find_cycles((spec) => spec.type === DEPENDENCY_TYPE.DEV);
|
|
167
|
-
return {production_cycles, dev_cycles};
|
|
158
|
+
return { production_cycles, dev_cycles };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Reports cycles by type and wildcard (`*`) dependency ranges.
|
|
163
|
+
* Tolerates cycles: it reports them rather than throwing.
|
|
164
|
+
*/
|
|
165
|
+
analyze(): DependencyAnalysis {
|
|
166
|
+
const { production_cycles, dev_cycles } = this.detect_cycles_by_type();
|
|
167
|
+
const wildcard_deps: DependencyAnalysis['wildcard_deps'] = [];
|
|
168
|
+
for (const node of this.nodes.values()) {
|
|
169
|
+
for (const [dep_name, spec] of node.dependencies) {
|
|
170
|
+
if (spec.version === '*') {
|
|
171
|
+
wildcard_deps.push({ pkg: node.name, dep: dep_name, version: spec.version });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { production_cycles, dev_cycles, wildcard_deps };
|
|
168
176
|
}
|
|
169
177
|
|
|
170
178
|
/** DFS cycle detection following only edges that match the filter. */
|
|
@@ -217,78 +225,18 @@ export class DependencyGraph {
|
|
|
217
225
|
version: node.version,
|
|
218
226
|
dependencies: Array.from(node.dependencies.entries()).map(([name, spec]) => ({
|
|
219
227
|
name,
|
|
220
|
-
spec
|
|
228
|
+
spec
|
|
221
229
|
})),
|
|
222
|
-
dependents: Array.from(node.dependents)
|
|
223
|
-
publishable: node.publishable,
|
|
230
|
+
dependents: Array.from(node.dependents)
|
|
224
231
|
}));
|
|
225
232
|
|
|
226
|
-
const edges: Array<{from: string; to: string}> = [];
|
|
233
|
+
const edges: Array<{ from: string; to: string }> = [];
|
|
227
234
|
for (const [from, tos] of this.edges) {
|
|
228
235
|
for (const to of tos) {
|
|
229
|
-
edges.push({from, to});
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return {nodes, edges};
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Builder for creating and analyzing dependency graphs.
|
|
239
|
-
*/
|
|
240
|
-
export class DependencyGraphBuilder {
|
|
241
|
-
/**
|
|
242
|
-
* Constructs dependency graph from local repos.
|
|
243
|
-
*
|
|
244
|
-
* Two-pass algorithm: first creates nodes, then builds edges (dependents).
|
|
245
|
-
* Prioritizes prod/peer deps over dev deps when same package appears in
|
|
246
|
-
* multiple dependency types (stronger constraint wins).
|
|
247
|
-
*
|
|
248
|
-
* @returns fully initialized dependency graph with all nodes and edges
|
|
249
|
-
*/
|
|
250
|
-
build_from_repos(repos: Array<LocalRepo>): DependencyGraph {
|
|
251
|
-
const graph = new DependencyGraph();
|
|
252
|
-
graph.init_from_repos(repos);
|
|
253
|
-
return graph;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Computes publishing order using topological sort with dev deps excluded.
|
|
258
|
-
*
|
|
259
|
-
* Excludes dev dependencies to break circular dev dependency cycles while
|
|
260
|
-
* preserving production/peer dependency ordering. This allows patterns like
|
|
261
|
-
* shared test utilities that depend on each other for development.
|
|
262
|
-
*
|
|
263
|
-
* @returns package names in safe publishing order (dependencies before dependents)
|
|
264
|
-
* @throws {Error} if production/peer cycles detected (cannot be resolved by exclusion)
|
|
265
|
-
*/
|
|
266
|
-
compute_publishing_order(graph: DependencyGraph): Array<string> {
|
|
267
|
-
return graph.topological_sort(true); // Exclude dev dependencies
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
analyze(graph: DependencyGraph): {
|
|
271
|
-
production_cycles: Array<Array<string>>;
|
|
272
|
-
dev_cycles: Array<Array<string>>;
|
|
273
|
-
wildcard_deps: Array<{pkg: string; dep: string; version: string}>;
|
|
274
|
-
missing_peers: Array<{pkg: string; dep: string}>;
|
|
275
|
-
} {
|
|
276
|
-
const {production_cycles, dev_cycles} = graph.detect_cycles_by_type();
|
|
277
|
-
const wildcard_deps: Array<{pkg: string; dep: string; version: string}> = [];
|
|
278
|
-
const missing_peers: Array<{pkg: string; dep: string}> = [];
|
|
279
|
-
|
|
280
|
-
for (const node of graph.nodes.values()) {
|
|
281
|
-
for (const [dep_name, spec] of node.dependencies) {
|
|
282
|
-
if (spec.version === '*') {
|
|
283
|
-
wildcard_deps.push({pkg: node.name, dep: dep_name, version: spec.version});
|
|
284
|
-
}
|
|
285
|
-
if (spec.type === DEPENDENCY_TYPE.PEER && !graph.nodes.has(dep_name)) {
|
|
286
|
-
// External peer dependency
|
|
287
|
-
missing_peers.push({pkg: node.name, dep: dep_name});
|
|
288
|
-
}
|
|
236
|
+
edges.push({ from, to });
|
|
289
237
|
}
|
|
290
238
|
}
|
|
291
239
|
|
|
292
|
-
return {
|
|
240
|
+
return { nodes, edges };
|
|
293
241
|
}
|
|
294
242
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import type {Logger} from '@fuzdev/fuz_util/log.ts';
|
|
2
|
-
import {join} from 'node:path';
|
|
1
|
+
import type { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
3
|
|
|
4
|
-
import type {LocalRepo} from './local_repo.ts';
|
|
5
|
-
import type {PublishedVersion} from './multi_repo_publisher.ts';
|
|
4
|
+
import type { LocalRepo } from './local_repo.ts';
|
|
5
|
+
import type { PublishedVersion } from './multi_repo_publisher.ts';
|
|
6
6
|
import {
|
|
7
7
|
create_changeset_for_dependency_updates,
|
|
8
|
-
create_dependency_updates
|
|
8
|
+
create_dependency_updates
|
|
9
9
|
} from './changeset_generator.ts';
|
|
10
|
-
import {needs_update, get_update_prefix} from './version_utils.ts';
|
|
11
|
-
import type {GitOperations, FsOperations} from './operations.ts';
|
|
12
|
-
import {default_git_operations, default_fs_operations} from './operations_defaults.ts';
|
|
10
|
+
import { needs_update, get_update_prefix } from './version_utils.ts';
|
|
11
|
+
import type { GitOperations, FsOperations } from './operations.ts';
|
|
12
|
+
import { default_git_operations, default_fs_operations } from './operations_defaults.ts';
|
|
13
13
|
|
|
14
14
|
export type VersionStrategy = 'exact' | 'caret' | 'tilde' | 'gte';
|
|
15
15
|
|
|
@@ -30,7 +30,7 @@ export interface UpdatePackageJsonOptions {
|
|
|
30
30
|
* 3. Creates auto-changeset if `published_versions` provided (for transitive updates)
|
|
31
31
|
* 4. Commits both `package.json` and changeset with standard message
|
|
32
32
|
*
|
|
33
|
-
* Uses version strategy to determine prefix (exact, caret, tilde) while preserving
|
|
33
|
+
* Uses version strategy to determine prefix (exact, caret, tilde, gte) while preserving
|
|
34
34
|
* existing prefixes when possible.
|
|
35
35
|
*
|
|
36
36
|
* @throws {Error} if file operations or git operations fail
|
|
@@ -38,21 +38,21 @@ export interface UpdatePackageJsonOptions {
|
|
|
38
38
|
export const update_package_json = async (
|
|
39
39
|
repo: LocalRepo,
|
|
40
40
|
updates: Map<string, string>,
|
|
41
|
-
options: UpdatePackageJsonOptions = {}
|
|
41
|
+
options: UpdatePackageJsonOptions = {}
|
|
42
42
|
): Promise<void> => {
|
|
43
43
|
const {
|
|
44
44
|
strategy = 'caret',
|
|
45
45
|
published_versions,
|
|
46
46
|
log,
|
|
47
47
|
git_ops = default_git_operations,
|
|
48
|
-
fs_ops = default_fs_operations
|
|
48
|
+
fs_ops = default_fs_operations
|
|
49
49
|
} = options;
|
|
50
50
|
if (updates.size === 0) return;
|
|
51
51
|
|
|
52
52
|
const package_json_path = join(repo.repo_dir, 'package.json');
|
|
53
53
|
|
|
54
54
|
// Read current package.json
|
|
55
|
-
const content_result = await fs_ops.readFile({path: package_json_path, encoding: 'utf8'});
|
|
55
|
+
const content_result = await fs_ops.readFile({ path: package_json_path, encoding: 'utf8' });
|
|
56
56
|
if (!content_result.ok) {
|
|
57
57
|
throw new Error(`Failed to read package.json: ${content_result.message}`);
|
|
58
58
|
}
|
|
@@ -105,12 +105,14 @@ export const update_package_json = async (
|
|
|
105
105
|
// Write updated package.json
|
|
106
106
|
const write_result = await fs_ops.writeFile({
|
|
107
107
|
path: package_json_path,
|
|
108
|
-
content: JSON.stringify(package_json, null, '\t') + '\n'
|
|
108
|
+
content: JSON.stringify(package_json, null, '\t') + '\n'
|
|
109
109
|
});
|
|
110
110
|
if (!write_result.ok) {
|
|
111
111
|
throw new Error(`Failed to write package.json: ${write_result.message}`);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
const staged: Array<string> = [];
|
|
115
|
+
|
|
114
116
|
// Create changeset if we have published version info
|
|
115
117
|
if (published_versions && published_versions.size > 0) {
|
|
116
118
|
// Build dependency updates info for changeset
|
|
@@ -145,26 +147,30 @@ export const update_package_json = async (
|
|
|
145
147
|
const changeset_path = await create_changeset_for_dependency_updates(
|
|
146
148
|
repo,
|
|
147
149
|
dependency_updates,
|
|
148
|
-
{log, fs_ops}
|
|
150
|
+
{ log, fs_ops }
|
|
149
151
|
);
|
|
150
152
|
|
|
151
153
|
// Add changeset to git
|
|
152
|
-
const add_result = await git_ops.add({files: changeset_path, cwd: repo.repo_dir});
|
|
154
|
+
const add_result = await git_ops.add({ files: changeset_path, cwd: repo.repo_dir });
|
|
153
155
|
if (!add_result.ok) {
|
|
154
156
|
throw new Error(`Failed to stage changeset: ${add_result.message}`);
|
|
155
157
|
}
|
|
158
|
+
staged.push(changeset_path);
|
|
156
159
|
}
|
|
157
160
|
}
|
|
158
161
|
|
|
159
162
|
// Commit the changes (including both package.json and changeset)
|
|
160
|
-
const add_pkg_result = await git_ops.add({files: 'package.json', cwd: repo.repo_dir});
|
|
163
|
+
const add_pkg_result = await git_ops.add({ files: 'package.json', cwd: repo.repo_dir });
|
|
161
164
|
if (!add_pkg_result.ok) {
|
|
162
165
|
throw new Error(`Failed to stage package.json: ${add_pkg_result.message}`);
|
|
163
166
|
}
|
|
167
|
+
staged.push('package.json');
|
|
164
168
|
|
|
169
|
+
// commit only what this staged, so nothing else in the index rides along
|
|
165
170
|
const commit_result = await git_ops.commit({
|
|
166
171
|
message: `update dependencies after publishing`,
|
|
167
|
-
|
|
172
|
+
files: staged,
|
|
173
|
+
cwd: repo.repo_dir
|
|
168
174
|
});
|
|
169
175
|
if (!commit_result.ok) {
|
|
170
176
|
throw new Error(`Failed to commit: ${commit_result.message}`);
|
|
@@ -181,16 +187,16 @@ export interface UpdateAllReposOptions {
|
|
|
181
187
|
export const update_all_repos = async (
|
|
182
188
|
repos: Array<LocalRepo>,
|
|
183
189
|
published: Map<string, string>,
|
|
184
|
-
options: UpdateAllReposOptions = {}
|
|
185
|
-
): Promise<{updated: number; failed: Array<{repo: string; error: Error}>}> => {
|
|
190
|
+
options: UpdateAllReposOptions = {}
|
|
191
|
+
): Promise<{ updated: number; failed: Array<{ repo: string; error: Error }> }> => {
|
|
186
192
|
const {
|
|
187
193
|
strategy = 'caret',
|
|
188
194
|
log,
|
|
189
195
|
git_ops = default_git_operations,
|
|
190
|
-
fs_ops = default_fs_operations
|
|
196
|
+
fs_ops = default_fs_operations
|
|
191
197
|
} = options;
|
|
192
198
|
let updated_count = 0;
|
|
193
|
-
const failed: Array<{repo: string; error: Error}> = [];
|
|
199
|
+
const failed: Array<{ repo: string; error: Error }> = [];
|
|
194
200
|
|
|
195
201
|
for (const repo of repos) {
|
|
196
202
|
const updates: Map<string, string> = new Map();
|
|
@@ -226,29 +232,29 @@ export const update_all_repos = async (
|
|
|
226
232
|
if (updates.size === 0) continue;
|
|
227
233
|
|
|
228
234
|
try {
|
|
229
|
-
await update_package_json(repo, updates, {strategy, log, git_ops, fs_ops});
|
|
235
|
+
await update_package_json(repo, updates, { strategy, log, git_ops, fs_ops });
|
|
230
236
|
updated_count++;
|
|
231
237
|
log?.info(` Updated ${updates.size} dependencies in ${repo.library.name}`);
|
|
232
238
|
} catch (error) {
|
|
233
239
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
234
|
-
failed.push({repo: repo.library.name, error: err});
|
|
240
|
+
failed.push({ repo: repo.library.name, error: err });
|
|
235
241
|
log?.error(` Failed to update ${repo.library.name}: ${err.message}`);
|
|
236
242
|
}
|
|
237
243
|
}
|
|
238
244
|
|
|
239
|
-
return {updated: updated_count, failed};
|
|
245
|
+
return { updated: updated_count, failed };
|
|
240
246
|
};
|
|
241
247
|
|
|
242
248
|
export const find_updates_needed = (
|
|
243
249
|
repo: LocalRepo,
|
|
244
|
-
published: Map<string, string
|
|
250
|
+
published: Map<string, string>
|
|
245
251
|
): Map<
|
|
246
252
|
string,
|
|
247
|
-
{current: string; new: string; type: 'dependencies' | 'devDependencies' | 'peerDependencies'}
|
|
253
|
+
{ current: string; new: string; type: 'dependencies' | 'devDependencies' | 'peerDependencies' }
|
|
248
254
|
> => {
|
|
249
255
|
const updates: Map<
|
|
250
256
|
string,
|
|
251
|
-
{current: string; new: string; type: 'dependencies' | 'devDependencies' | 'peerDependencies'}
|
|
257
|
+
{ current: string; new: string; type: 'dependencies' | 'devDependencies' | 'peerDependencies' }
|
|
252
258
|
> = new Map();
|
|
253
259
|
|
|
254
260
|
// Check dependencies
|
|
@@ -259,7 +265,7 @@ export const find_updates_needed = (
|
|
|
259
265
|
updates.set(dep_name, {
|
|
260
266
|
current: current_version,
|
|
261
267
|
new: new_version,
|
|
262
|
-
type: 'dependencies'
|
|
268
|
+
type: 'dependencies'
|
|
263
269
|
});
|
|
264
270
|
}
|
|
265
271
|
}
|
|
@@ -273,7 +279,7 @@ export const find_updates_needed = (
|
|
|
273
279
|
updates.set(dep_name, {
|
|
274
280
|
current: current_version,
|
|
275
281
|
new: new_version,
|
|
276
|
-
type: 'devDependencies'
|
|
282
|
+
type: 'devDependencies'
|
|
277
283
|
});
|
|
278
284
|
}
|
|
279
285
|
}
|
|
@@ -287,7 +293,7 @@ export const find_updates_needed = (
|
|
|
287
293
|
updates.set(dep_name, {
|
|
288
294
|
current: current_version,
|
|
289
295
|
new: new_version,
|
|
290
|
-
type: 'peerDependencies'
|
|
296
|
+
type: 'peerDependencies'
|
|
291
297
|
});
|
|
292
298
|
}
|
|
293
299
|
}
|
|
@@ -1,46 +1,81 @@
|
|
|
1
|
-
import type {Logger} from '@fuzdev/fuz_util/log.ts';
|
|
2
|
-
import {wait} from '@fuzdev/fuz_util/async.ts';
|
|
3
|
-
import type {FetchValueCache} from '@fuzdev/fuz_util/fetch.ts';
|
|
1
|
+
import type { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
2
|
+
import { wait } from '@fuzdev/fuz_util/async.ts';
|
|
3
|
+
import type { FetchValueCache } from '@fuzdev/fuz_util/fetch.ts';
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import {
|
|
6
|
+
fetch_github_check_runs,
|
|
7
|
+
fetch_github_pull_requests,
|
|
8
|
+
type GithubCheckRunsItem
|
|
9
|
+
} from './github.ts';
|
|
10
|
+
import type { RepoJson } from './repo.svelte.ts';
|
|
11
|
+
import type { LocalRepo } from './local_repo.ts';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Options for `fetch_repo_data`.
|
|
15
|
+
*/
|
|
16
|
+
export interface FetchRepoDataOptions {
|
|
17
|
+
/** The repos to fetch GitHub metadata for, in order. */
|
|
18
|
+
local_repos: Array<LocalRepo>;
|
|
19
|
+
/** GitHub API token, sent with each request. */
|
|
20
|
+
token?: string;
|
|
21
|
+
/** Response memoization, from `fuz_util`'s `fetch.ts`. */
|
|
22
|
+
cache?: FetchValueCache;
|
|
23
|
+
log?: Logger;
|
|
24
|
+
/** Milliseconds to wait before each API request. Defaults to `33`. */
|
|
25
|
+
delay?: number;
|
|
26
|
+
/** Sent as the `x-github-api-version` header when set. */
|
|
27
|
+
github_api_version?: string;
|
|
28
|
+
/** The `fetch` the API requests go through. Defaults to `globalThis.fetch`. */
|
|
29
|
+
fetch?: typeof globalThis.fetch;
|
|
30
|
+
}
|
|
8
31
|
|
|
9
32
|
/**
|
|
10
33
|
* Fetches GitHub metadata (CI status, PRs) for all repos.
|
|
11
34
|
*
|
|
12
|
-
* Fetches sequentially with delay
|
|
13
|
-
* Uses `await_in_loop` intentionally to avoid parallel requests
|
|
35
|
+
* Fetches sequentially with a delay before each request to respect GitHub API
|
|
36
|
+
* rate limits. Uses `await_in_loop` intentionally to avoid parallel requests
|
|
37
|
+
* overwhelming the API. CI status is read for the branch each repo's registry
|
|
38
|
+
* entry follows (`main` when it names none), and that branch is written to
|
|
39
|
+
* `RepoJson.branch`.
|
|
14
40
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
41
|
+
* A repo whose registry entry declares no CI (`ci: false`) isn't asked for
|
|
42
|
+
* check runs, and one with none on its branch isn't a failure: both leave
|
|
43
|
+
* `check_runs` `null` without logging. A failed fetch is logged as an error
|
|
44
|
+
* and leaves `null` for that repo's `check_runs` or `pull_requests`, and the
|
|
45
|
+
* remaining repos still fetch — except a 401 response or a repo URL without a
|
|
46
|
+
* GitHub owner, which throw.
|
|
17
47
|
*
|
|
18
|
-
* @param
|
|
19
|
-
* @
|
|
20
|
-
* @
|
|
48
|
+
* @param options - the repos, credentials, cache, and pacing
|
|
49
|
+
* @returns a `RepoJson` for each repo, in the order given
|
|
50
|
+
* @throws {Error} on a 401 response (check `SECRET_GITHUB_API_TOKEN`) or a
|
|
51
|
+
* repo whose URL has no GitHub owner
|
|
21
52
|
*/
|
|
22
|
-
export const fetch_repo_data = async (
|
|
23
|
-
|
|
24
|
-
token?: string,
|
|
25
|
-
cache?: FetchValueCache,
|
|
26
|
-
log?: Logger,
|
|
27
|
-
delay = 33,
|
|
28
|
-
github_api_version?: string,
|
|
29
|
-
): Promise<Array<RepoJson>> => {
|
|
53
|
+
export const fetch_repo_data = async (options: FetchRepoDataOptions): Promise<Array<RepoJson>> => {
|
|
54
|
+
const { local_repos, token, cache, log, delay = 33, github_api_version, fetch } = options;
|
|
30
55
|
const repos: Array<RepoJson> = [];
|
|
31
|
-
for (const {library, package_json,
|
|
56
|
+
for (const { library, package_json, entry } of local_repos) {
|
|
32
57
|
const repo_url = library.repo_url;
|
|
58
|
+
const branch = entry.branch ?? 'main';
|
|
33
59
|
|
|
34
|
-
// CI status
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
60
|
+
// CI status, for repos the registry says run CI
|
|
61
|
+
let check_runs: GithubCheckRunsItem | null = null;
|
|
62
|
+
if (entry.ci) {
|
|
63
|
+
await wait(delay);
|
|
64
|
+
const fetched = await fetch_github_check_runs(library, {
|
|
65
|
+
cache,
|
|
66
|
+
log,
|
|
67
|
+
token,
|
|
68
|
+
api_version: github_api_version,
|
|
69
|
+
ref: branch,
|
|
70
|
+
fetch
|
|
71
|
+
});
|
|
72
|
+
if (fetched.ok) {
|
|
73
|
+
check_runs = fetched.value;
|
|
74
|
+
if (!check_runs) log?.debug(`no check runs on \`${branch}\`: ${repo_url}`);
|
|
75
|
+
} else {
|
|
76
|
+
log?.error(`failed to fetch CI status (${fetched.status}): ${repo_url}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
44
79
|
|
|
45
80
|
// pull requests
|
|
46
81
|
await wait(delay);
|
|
@@ -49,14 +84,16 @@ export const fetch_repo_data = async (
|
|
|
49
84
|
log,
|
|
50
85
|
token,
|
|
51
86
|
api_version: github_api_version,
|
|
87
|
+
fetch
|
|
52
88
|
});
|
|
53
|
-
if (!pull_requests) log?.error('failed to fetch
|
|
89
|
+
if (!pull_requests) log?.error('failed to fetch pull requests: ' + repo_url);
|
|
54
90
|
|
|
55
91
|
repos.push({
|
|
56
92
|
library_json: library.library_json,
|
|
57
93
|
package_json,
|
|
94
|
+
branch,
|
|
58
95
|
check_runs,
|
|
59
|
-
pull_requests
|
|
96
|
+
pull_requests
|
|
60
97
|
});
|
|
61
98
|
}
|
|
62
99
|
return repos;
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import {mkdir, readFile, rm, writeFile} from 'node:fs/promises';
|
|
2
|
-
import {dirname, join} from 'node:path';
|
|
3
|
-
import {paths} from '@fuzdev/gro/paths.ts';
|
|
4
|
-
import {format_file} from '@fuzdev/gro/format_file.ts';
|
|
5
|
-
import {
|
|
6
|
-
|
|
1
|
+
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { paths } from '@fuzdev/gro/paths.ts';
|
|
4
|
+
import { format_file } from '@fuzdev/gro/format_file.ts';
|
|
5
|
+
import {
|
|
6
|
+
deserialize_cache,
|
|
7
|
+
serialize_cache,
|
|
8
|
+
type FetchValueCache
|
|
9
|
+
} from '@fuzdev/fuz_util/fetch.ts';
|
|
10
|
+
import { existsSync } from 'node:fs';
|
|
7
11
|
|
|
8
12
|
// TODO upstream to Gro probably, and rename/redesign?
|
|
9
13
|
|
|
@@ -23,7 +27,7 @@ export interface FetchCache {
|
|
|
23
27
|
* cache is cleared (delete file) and starts fresh. This handles format changes.
|
|
24
28
|
*
|
|
25
29
|
* Uses `structuredClone` to track changes - only writes to disk if data modified.
|
|
26
|
-
* Formatted with
|
|
30
|
+
* Formatted with gro's `format_file` before writing for version control friendliness.
|
|
27
31
|
*
|
|
28
32
|
* @param name - cache filename (without .json extension)
|
|
29
33
|
* @param dir - cache directory (defaults to `.gro/build/fetch/`)
|
|
@@ -31,7 +35,7 @@ export interface FetchCache {
|
|
|
31
35
|
*/
|
|
32
36
|
export const create_fs_fetch_value_cache = async (
|
|
33
37
|
name: string,
|
|
34
|
-
dir = join(paths.build, 'fetch')
|
|
38
|
+
dir = join(paths.build, 'fetch')
|
|
35
39
|
): Promise<FetchCache> => {
|
|
36
40
|
const data_path = join(dir, name + '.json');
|
|
37
41
|
let data: FetchValueCache;
|
|
@@ -54,10 +58,10 @@ export const create_fs_fetch_value_cache = async (
|
|
|
54
58
|
if (deep_equal_maps(initial, data)) {
|
|
55
59
|
return false; // no changes to save
|
|
56
60
|
}
|
|
57
|
-
await mkdir(dirname(data_path), {recursive: true});
|
|
58
|
-
await writeFile(data_path,
|
|
61
|
+
await mkdir(dirname(data_path), { recursive: true });
|
|
62
|
+
await writeFile(data_path, format_file(serialize_cache(data), { lang: 'json' }));
|
|
59
63
|
return true;
|
|
60
|
-
}
|
|
64
|
+
}
|
|
61
65
|
};
|
|
62
66
|
};
|
|
63
67
|
|