@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.
Files changed (196) hide show
  1. package/README.md +91 -17
  2. package/dist/ModulesDetail.svelte +22 -27
  3. package/dist/ModulesDetail.svelte.d.ts.map +1 -1
  4. package/dist/ModulesNav.svelte +8 -5
  5. package/dist/ModulesNav.svelte.d.ts.map +1 -1
  6. package/dist/ModulesPage.svelte +3 -5
  7. package/dist/ModulesPage.svelte.d.ts.map +1 -1
  8. package/dist/PageFooter.svelte +2 -2
  9. package/dist/PageHeader.svelte +6 -6
  10. package/dist/PageHeader.svelte.d.ts +0 -3
  11. package/dist/PageHeader.svelte.d.ts.map +1 -1
  12. package/dist/PullRequestsDetail.svelte +29 -20
  13. package/dist/PullRequestsDetail.svelte.d.ts.map +1 -1
  14. package/dist/PullRequestsPage.svelte +4 -6
  15. package/dist/PullRequestsPage.svelte.d.ts.map +1 -1
  16. package/dist/ReposTable.svelte +43 -55
  17. package/dist/ReposTable.svelte.d.ts.map +1 -1
  18. package/dist/ReposTree.svelte +9 -21
  19. package/dist/ReposTree.svelte.d.ts.map +1 -1
  20. package/dist/ReposTreeNav.svelte +9 -12
  21. package/dist/ReposTreeNav.svelte.d.ts.map +1 -1
  22. package/dist/TablePage.svelte +3 -5
  23. package/dist/TablePage.svelte.d.ts.map +1 -1
  24. package/dist/TreeItemPage.svelte +3 -5
  25. package/dist/TreeItemPage.svelte.d.ts.map +1 -1
  26. package/dist/TreePage.svelte +3 -5
  27. package/dist/TreePage.svelte.d.ts.map +1 -1
  28. package/dist/changeset_generator.d.ts.map +1 -1
  29. package/dist/changeset_generator.js +2 -2
  30. package/dist/changeset_reader.d.ts.map +1 -1
  31. package/dist/changeset_reader.js +3 -3
  32. package/dist/ci_reconcile.d.ts +6 -10
  33. package/dist/ci_reconcile.d.ts.map +1 -1
  34. package/dist/ci_reconcile.js +6 -8
  35. package/dist/dependency_graph.d.ts +26 -49
  36. package/dist/dependency_graph.d.ts.map +1 -1
  37. package/dist/dependency_graph.js +40 -83
  38. package/dist/dependency_updater.d.ts +1 -1
  39. package/dist/dependency_updater.d.ts.map +1 -1
  40. package/dist/dependency_updater.js +14 -9
  41. package/dist/fetch_repo_data.d.ts +34 -8
  42. package/dist/fetch_repo_data.d.ts.map +1 -1
  43. package/dist/fetch_repo_data.js +44 -22
  44. package/dist/fs_fetch_value_cache.d.ts +1 -1
  45. package/dist/fs_fetch_value_cache.d.ts.map +1 -1
  46. package/dist/fs_fetch_value_cache.js +3 -3
  47. package/dist/git_operations.d.ts +6 -49
  48. package/dist/git_operations.d.ts.map +1 -1
  49. package/dist/git_operations.js +13 -126
  50. package/dist/github.d.ts +17 -2
  51. package/dist/github.d.ts.map +1 -1
  52. package/dist/github.js +17 -8
  53. package/dist/github_helpers.d.ts.map +1 -1
  54. package/dist/github_helpers.js +3 -11
  55. package/dist/gitops_analyze.task.d.ts +24 -2
  56. package/dist/gitops_analyze.task.d.ts.map +1 -1
  57. package/dist/gitops_analyze.task.js +47 -34
  58. package/dist/gitops_config.d.ts +34 -79
  59. package/dist/gitops_config.d.ts.map +1 -1
  60. package/dist/gitops_config.js +76 -53
  61. package/dist/gitops_constants.d.ts +6 -4
  62. package/dist/gitops_constants.d.ts.map +1 -1
  63. package/dist/gitops_constants.js +6 -4
  64. package/dist/gitops_plan.task.d.ts +29 -3
  65. package/dist/gitops_plan.task.d.ts.map +1 -1
  66. package/dist/gitops_plan.task.js +83 -61
  67. package/dist/gitops_publish.task.d.ts +62 -2
  68. package/dist/gitops_publish.task.d.ts.map +1 -1
  69. package/dist/gitops_publish.task.js +193 -105
  70. package/dist/gitops_run.task.d.ts +74 -0
  71. package/dist/gitops_run.task.d.ts.map +1 -1
  72. package/dist/gitops_run.task.js +176 -162
  73. package/dist/gitops_sync.task.d.ts +65 -3
  74. package/dist/gitops_sync.task.d.ts.map +1 -1
  75. package/dist/gitops_sync.task.js +163 -37
  76. package/dist/gitops_task_helpers.d.ts +86 -59
  77. package/dist/gitops_task_helpers.d.ts.map +1 -1
  78. package/dist/gitops_task_helpers.js +120 -79
  79. package/dist/gitops_validate.task.d.ts +1 -2
  80. package/dist/gitops_validate.task.d.ts.map +1 -1
  81. package/dist/gitops_validate.task.js +25 -32
  82. package/dist/graph_validation.d.ts +16 -27
  83. package/dist/graph_validation.d.ts.map +1 -1
  84. package/dist/graph_validation.js +24 -65
  85. package/dist/local_repo.d.ts +50 -62
  86. package/dist/local_repo.d.ts.map +1 -1
  87. package/dist/local_repo.js +83 -196
  88. package/dist/log_helpers.d.ts +8 -8
  89. package/dist/log_helpers.d.ts.map +1 -1
  90. package/dist/multi_repo_publisher.d.ts +10 -0
  91. package/dist/multi_repo_publisher.d.ts.map +1 -1
  92. package/dist/multi_repo_publisher.js +125 -61
  93. package/dist/npm_registry.d.ts +23 -19
  94. package/dist/npm_registry.d.ts.map +1 -1
  95. package/dist/npm_registry.js +21 -48
  96. package/dist/operations.d.ts +75 -180
  97. package/dist/operations.d.ts.map +1 -1
  98. package/dist/operations.js +15 -7
  99. package/dist/operations_defaults.d.ts +17 -2
  100. package/dist/operations_defaults.d.ts.map +1 -1
  101. package/dist/operations_defaults.js +111 -128
  102. package/dist/output_helpers.d.ts +36 -0
  103. package/dist/output_helpers.d.ts.map +1 -1
  104. package/dist/output_helpers.js +37 -3
  105. package/dist/preflight_checks.d.ts +20 -27
  106. package/dist/preflight_checks.d.ts.map +1 -1
  107. package/dist/preflight_checks.js +37 -126
  108. package/dist/publish_gate.d.ts.map +1 -1
  109. package/dist/publish_steps.d.ts +2 -4
  110. package/dist/publish_steps.d.ts.map +1 -1
  111. package/dist/publish_steps.js +4 -8
  112. package/dist/publishing_event.d.ts +2 -8
  113. package/dist/publishing_event.d.ts.map +1 -1
  114. package/dist/publishing_event.js +20 -17
  115. package/dist/publishing_event_handler.d.ts +10 -8
  116. package/dist/publishing_event_handler.d.ts.map +1 -1
  117. package/dist/publishing_event_handler.js +19 -17
  118. package/dist/publishing_plan.d.ts +16 -1
  119. package/dist/publishing_plan.d.ts.map +1 -1
  120. package/dist/publishing_plan.js +332 -254
  121. package/dist/publishing_plan_helpers.d.ts.map +1 -1
  122. package/dist/publishing_plan_helpers.js +3 -3
  123. package/dist/publishing_plan_logging.d.ts +1 -1
  124. package/dist/publishing_plan_logging.d.ts.map +1 -1
  125. package/dist/publishing_plan_logging.js +22 -15
  126. package/dist/repo.svelte.d.ts +7 -0
  127. package/dist/repo.svelte.d.ts.map +1 -1
  128. package/dist/repo.svelte.js +3 -0
  129. package/dist/repo_readiness.d.ts +210 -0
  130. package/dist/repo_readiness.d.ts.map +1 -0
  131. package/dist/repo_readiness.js +516 -0
  132. package/dist/repos_status.d.ts +3340 -0
  133. package/dist/repos_status.d.ts.map +1 -0
  134. package/dist/repos_status.js +555 -0
  135. package/dist/repos_status_load.d.ts +65 -0
  136. package/dist/repos_status_load.d.ts.map +1 -0
  137. package/dist/repos_status_load.js +131 -0
  138. package/dist/version_utils.js +1 -1
  139. package/package.json +15 -34
  140. package/src/lib/cargo_toml.ts +3 -3
  141. package/src/lib/changeset_generator.ts +19 -19
  142. package/src/lib/changeset_reader.ts +18 -18
  143. package/src/lib/ci_reconcile.ts +12 -16
  144. package/src/lib/dependency_graph.ts +61 -113
  145. package/src/lib/dependency_updater.ts +36 -30
  146. package/src/lib/fetch_repo_data.ts +71 -34
  147. package/src/lib/fs_fetch_value_cache.ts +15 -11
  148. package/src/lib/git_operations.ts +22 -194
  149. package/src/lib/github.ts +33 -21
  150. package/src/lib/github_helpers.ts +9 -16
  151. package/src/lib/gitops_analyze.task.ts +104 -66
  152. package/src/lib/gitops_config.ts +90 -139
  153. package/src/lib/gitops_constants.ts +6 -4
  154. package/src/lib/gitops_plan.task.ts +128 -76
  155. package/src/lib/gitops_publish.task.ts +280 -144
  156. package/src/lib/gitops_run.task.ts +265 -193
  157. package/src/lib/gitops_sync.task.ts +236 -52
  158. package/src/lib/gitops_task_helpers.ts +170 -114
  159. package/src/lib/gitops_validate.task.ts +44 -54
  160. package/src/lib/graph_validation.ts +28 -89
  161. package/src/lib/local_repo.ts +128 -335
  162. package/src/lib/log_helpers.ts +18 -28
  163. package/src/lib/multi_repo_publisher.ts +192 -110
  164. package/src/lib/npm_registry.ts +42 -67
  165. package/src/lib/operations.ts +99 -160
  166. package/src/lib/operations_defaults.ts +157 -201
  167. package/src/lib/output_helpers.ts +58 -6
  168. package/src/lib/preflight_checks.ts +52 -188
  169. package/src/lib/publish_gate.ts +7 -9
  170. package/src/lib/publish_steps.ts +27 -25
  171. package/src/lib/publishing_event.ts +26 -23
  172. package/src/lib/publishing_event_handler.ts +24 -22
  173. package/src/lib/publishing_plan.ts +474 -310
  174. package/src/lib/publishing_plan_helpers.ts +16 -12
  175. package/src/lib/publishing_plan_logging.ts +53 -44
  176. package/src/lib/repo.svelte.ts +16 -8
  177. package/src/lib/repo_readiness.ts +647 -0
  178. package/src/lib/repos_status.ts +671 -0
  179. package/src/lib/repos_status_load.ts +173 -0
  180. package/src/lib/version_utils.ts +3 -3
  181. package/dist/config_reconcile.d.ts +0 -46
  182. package/dist/config_reconcile.d.ts.map +0 -1
  183. package/dist/config_reconcile.js +0 -60
  184. package/dist/paths.d.ts +0 -7
  185. package/dist/paths.d.ts.map +0 -1
  186. package/dist/paths.js +0 -6
  187. package/dist/repo_ops.d.ts +0 -59
  188. package/dist/repo_ops.d.ts.map +0 -1
  189. package/dist/repo_ops.js +0 -170
  190. package/dist/resolved_gitops_config.d.ts +0 -9
  191. package/dist/resolved_gitops_config.d.ts.map +0 -1
  192. package/dist/resolved_gitops_config.js +0 -12
  193. package/src/lib/config_reconcile.ts +0 -94
  194. package/src/lib/paths.ts +0 -6
  195. package/src/lib/repo_ops.ts +0 -216
  196. 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 (via `@fuzdev/fuz_util/sort.ts`)
5
- * and cycle detection by dependency type.
6
- * For validation workflow and publishing order computation, see `graph_validation.ts`.
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
- export class DependencyGraph {
51
- nodes: Map<string, DependencyNode>;
52
- edges: Map<string, Set<string>>; // pkg -> dependents
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
- constructor() {
55
- this.nodes = new Map();
56
- this.edges = new Map();
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
- public init_from_repos(repos: Array<LocalRepo>): void {
60
- // First pass: create nodes
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
- // Only add dev deps if not already present as prod/peer
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
- // Second pass: build edges (dependents)
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
- if (this.nodes.has(dep_name)) {
100
- // Internal dependency
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 {production_cycles, dev_cycles, wildcard_deps, missing_peers};
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
- cwd: repo.repo_dir,
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 {fetch_github_check_runs, fetch_github_pull_requests} from './github.ts';
6
- import type {RepoJson} from './repo.svelte.ts';
7
- import type {LocalRepo} from './local_repo.ts';
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 between requests to respect GitHub API rate limits.
13
- * Uses `await_in_loop` intentionally to avoid parallel requests overwhelming the API.
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
- * Error handling: Logs fetch failures but continues processing remaining repos.
16
- * Repos with failed fetches will have `null` for `check_runs` or `pull_requests`.
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 delay - milliseconds between API requests (default: 33ms)
19
- * @param cache - optional cache from `fuz_util`'s `fetch.js` for response memoization
20
- * @returns array of `Repo` objects with GitHub metadata attached
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
- resolved_repos: Array<LocalRepo>,
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, repo_config} of resolved_repos) {
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
- await wait(delay);
36
- const check_runs = await fetch_github_check_runs(library, {
37
- cache,
38
- log,
39
- token,
40
- api_version: github_api_version,
41
- ref: repo_config.branch,
42
- });
43
- if (!check_runs) log?.error('failed to fetch CI status: ' + repo_url);
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 issues: ' + repo_url);
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 {deserialize_cache, serialize_cache, type FetchValueCache} from '@fuzdev/fuz_util/fetch.ts';
6
- import {existsSync} from 'node:fs';
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 Prettier before writing for version control friendliness.
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, await format_file(serialize_cache(data), {filepath: 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