@fuzdev/fuz_gitops 0.65.1 → 0.65.2

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.
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Dependency graph data structure and algorithms for multi-repo publishing.
3
3
  *
4
- * Provides `DependencyGraph` class with topological sort and cycle detection.
4
+ * Provides `DependencyGraph` class with topological sort (via `@fuzdev/fuz_util/sort.js`)
5
+ * and cycle detection by dependency type.
5
6
  * For validation workflow and publishing order computation, see `graph_validation.ts`.
6
7
  *
7
8
  * @module
@@ -43,6 +44,7 @@ export interface DependencyNode {
43
44
  publishable: boolean;
44
45
  }
45
46
  export declare class DependencyGraph {
47
+ #private;
46
48
  nodes: Map<string, DependencyNode>;
47
49
  edges: Map<string, Set<string>>;
48
50
  constructor();
@@ -53,8 +55,8 @@ export declare class DependencyGraph {
53
55
  /**
54
56
  * Computes topological sort order for dependency graph.
55
57
  *
56
- * Uses Kahn's algorithm with alphabetical ordering within tiers for
57
- * deterministic results. Throws if cycles detected.
58
+ * Delegates to `@fuzdev/fuz_util/sort.js` for the sorting algorithm.
59
+ * Throws if cycles detected.
58
60
  *
59
61
  * @param exclude_dev if true, excludes dev dependencies to break cycles.
60
62
  * Publishing uses exclude_dev=true to handle circular dev deps.
@@ -62,7 +64,6 @@ export declare class DependencyGraph {
62
64
  * @throws {Error} if circular dependencies detected in included dependency types
63
65
  */
64
66
  topological_sort(exclude_dev?: boolean): Array<string>;
65
- detect_cycles(): Array<Array<string>>;
66
67
  /**
67
68
  * Detects circular dependencies, categorized by severity.
68
69
  *
@@ -1 +1 @@
1
- {"version":3,"file":"dependency_graph.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/dependency_graph.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dependency_graph.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/dependency_graph.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,14 @@
1
1
  /**
2
2
  * Dependency graph data structure and algorithms for multi-repo publishing.
3
3
  *
4
- * Provides `DependencyGraph` class with topological sort and cycle detection.
4
+ * Provides `DependencyGraph` class with topological sort (via `@fuzdev/fuz_util/sort.js`)
5
+ * and cycle detection by dependency type.
5
6
  * For validation workflow and publishing order computation, see `graph_validation.ts`.
6
7
  *
7
8
  * @module
8
9
  */
9
10
  import { EMPTY_OBJECT } from '@fuzdev/fuz_util/object.js';
11
+ import { topological_sort as topological_sort_generic } from '@fuzdev/fuz_util/sort.js';
10
12
  export const DEPENDENCY_TYPE = {
11
13
  PROD: 'prod',
12
14
  PEER: 'peer',
@@ -77,8 +79,8 @@ export class DependencyGraph {
77
79
  /**
78
80
  * Computes topological sort order for dependency graph.
79
81
  *
80
- * Uses Kahn's algorithm with alphabetical ordering within tiers for
81
- * deterministic results. Throws if cycles detected.
82
+ * Delegates to `@fuzdev/fuz_util/sort.js` for the sorting algorithm.
83
+ * Throws if cycles detected.
82
84
  *
83
85
  * @param exclude_dev if true, excludes dev dependencies to break cycles.
84
86
  * Publishing uses exclude_dev=true to handle circular dev deps.
@@ -86,97 +88,21 @@ export class DependencyGraph {
86
88
  * @throws {Error} if circular dependencies detected in included dependency types
87
89
  */
88
90
  topological_sort(exclude_dev = false) {
89
- const visited = new Set();
90
- const result = [];
91
- // Count incoming edges for each node
92
- const in_degree = new Map();
93
- for (const name of this.nodes.keys()) {
94
- in_degree.set(name, 0);
95
- }
96
- for (const node of this.nodes.values()) {
97
- for (const [dep_name, spec] of node.dependencies) {
98
- // Skip dev dependencies if requested
91
+ const items = Array.from(this.nodes.values()).map((node) => ({
92
+ id: node.name,
93
+ depends_on: Array.from(node.dependencies.entries())
94
+ .filter(([dep_name, spec]) => {
99
95
  if (exclude_dev && spec.type === DEPENDENCY_TYPE.DEV)
100
- continue;
101
- if (this.nodes.has(dep_name)) {
102
- in_degree.set(node.name, in_degree.get(node.name) + 1);
103
- }
104
- }
105
- }
106
- // Start with nodes that have no dependencies
107
- const queue = [];
108
- for (const [name, degree] of in_degree) {
109
- if (degree === 0) {
110
- queue.push(name);
111
- }
112
- }
113
- // Sort initial queue alphabetically for deterministic ordering within tier
114
- queue.sort();
115
- // Process nodes
116
- while (queue.length > 0) {
117
- const name = queue.shift();
118
- result.push(name);
119
- visited.add(name);
120
- // Reduce in-degree for dependents
121
- const node = this.nodes.get(name);
122
- if (node) {
123
- // Find packages that depend on this one
124
- // Sort nodes to ensure deterministic iteration order
125
- const sorted_nodes = Array.from(this.nodes.values()).sort((a, b) => a.name.localeCompare(b.name));
126
- for (const other_node of sorted_nodes) {
127
- for (const [dep_name, spec] of other_node.dependencies) {
128
- // Skip dev dependencies if requested
129
- if (exclude_dev && spec.type === DEPENDENCY_TYPE.DEV)
130
- continue;
131
- if (dep_name === name) {
132
- const new_degree = in_degree.get(other_node.name) - 1;
133
- in_degree.set(other_node.name, new_degree);
134
- if (new_degree === 0) {
135
- queue.push(other_node.name);
136
- }
137
- }
138
- }
139
- }
140
- }
141
- }
142
- // Check for cycles
143
- if (result.length !== this.nodes.size) {
144
- const unvisited = Array.from(this.nodes.keys()).filter((n) => !visited.has(n));
145
- throw new Error(`Circular dependency detected involving: ${unvisited.join(', ')}`);
146
- }
147
- return result;
148
- }
149
- detect_cycles() {
150
- const cycles = [];
151
- const visited = new Set();
152
- const rec_stack = new Set();
153
- const dfs = (name, path) => {
154
- visited.add(name);
155
- rec_stack.add(name);
156
- path.push(name);
157
- const node = this.nodes.get(name);
158
- if (node) {
159
- for (const [dep_name] of node.dependencies) {
160
- if (this.nodes.has(dep_name)) {
161
- if (!visited.has(dep_name)) {
162
- dfs(dep_name, [...path]);
163
- }
164
- else if (rec_stack.has(dep_name)) {
165
- // Found a cycle
166
- const cycle_start = path.indexOf(dep_name);
167
- cycles.push(path.slice(cycle_start).concat(dep_name));
168
- }
169
- }
170
- }
171
- }
172
- rec_stack.delete(name);
173
- };
174
- for (const name of this.nodes.keys()) {
175
- if (!visited.has(name)) {
176
- dfs(name, []);
177
- }
96
+ return false;
97
+ return this.nodes.has(dep_name);
98
+ })
99
+ .map(([dep_name]) => dep_name),
100
+ }));
101
+ const result = topological_sort_generic(items, 'package');
102
+ if (!result.ok) {
103
+ throw new Error(result.error);
178
104
  }
179
- return cycles;
105
+ return result.sorted.map((item) => item.id);
180
106
  }
181
107
  /**
182
108
  * Detects circular dependencies, categorized by severity.
@@ -190,87 +116,48 @@ export class DependencyGraph {
190
116
  * @returns object with production_cycles (errors) and dev_cycles (info)
191
117
  */
192
118
  detect_cycles_by_type() {
193
- const production_cycles = [];
194
- const dev_cycles = [];
195
- const visited_prod = new Set();
196
- const visited_dev = new Set();
197
- const rec_stack_prod = new Set();
198
- const rec_stack_dev = new Set();
199
- // DFS for production/peer dependencies only
200
- const dfs_prod = (name, path) => {
201
- visited_prod.add(name);
202
- rec_stack_prod.add(name);
203
- path.push(name);
204
- const node = this.nodes.get(name);
205
- if (node) {
206
- for (const [dep_name, spec] of node.dependencies) {
207
- // Skip dev dependencies
208
- if (spec.type === DEPENDENCY_TYPE.DEV)
209
- continue;
210
- if (this.nodes.has(dep_name)) {
211
- if (!visited_prod.has(dep_name)) {
212
- dfs_prod(dep_name, [...path]);
213
- }
214
- else if (rec_stack_prod.has(dep_name)) {
215
- // Found a production cycle
216
- const cycle_start = path.indexOf(dep_name);
217
- const cycle = path.slice(cycle_start).concat(dep_name);
218
- // Check if this cycle is unique
219
- const cycle_key = [...cycle].sort().join(',');
220
- const exists = production_cycles.some((c) => [...c].sort().join(',') === cycle_key);
221
- if (!exists) {
222
- production_cycles.push(cycle);
223
- }
224
- }
225
- }
226
- }
227
- }
228
- rec_stack_prod.delete(name);
229
- };
230
- // DFS for dev dependencies only
231
- const dfs_dev = (name, path) => {
232
- visited_dev.add(name);
233
- rec_stack_dev.add(name);
119
+ const production_cycles = this.#find_cycles((spec) => spec.type !== DEPENDENCY_TYPE.DEV);
120
+ const dev_cycles = this.#find_cycles((spec) => spec.type === DEPENDENCY_TYPE.DEV);
121
+ return { production_cycles, dev_cycles };
122
+ }
123
+ /** DFS cycle detection following only edges that match the filter. */
124
+ #find_cycles(include) {
125
+ const cycles = [];
126
+ const visited = new Set();
127
+ const rec_stack = new Set();
128
+ const dfs = (name, path) => {
129
+ visited.add(name);
130
+ rec_stack.add(name);
234
131
  path.push(name);
235
132
  const node = this.nodes.get(name);
236
133
  if (node) {
237
134
  for (const [dep_name, spec] of node.dependencies) {
238
- // Only check dev dependencies
239
- if (spec.type !== DEPENDENCY_TYPE.DEV)
135
+ if (!include(spec))
240
136
  continue;
241
137
  if (this.nodes.has(dep_name)) {
242
- if (!visited_dev.has(dep_name)) {
243
- dfs_dev(dep_name, [...path]);
138
+ if (!visited.has(dep_name)) {
139
+ dfs(dep_name, [...path]);
244
140
  }
245
- else if (rec_stack_dev.has(dep_name)) {
246
- // Found a dev cycle
141
+ else if (rec_stack.has(dep_name)) {
247
142
  const cycle_start = path.indexOf(dep_name);
248
143
  const cycle = path.slice(cycle_start).concat(dep_name);
249
- // Check if this cycle is unique
250
144
  const cycle_key = [...cycle].sort().join(',');
251
- const exists = dev_cycles.some((c) => [...c].sort().join(',') === cycle_key);
145
+ const exists = cycles.some((c) => [...c].sort().join(',') === cycle_key);
252
146
  if (!exists) {
253
- dev_cycles.push(cycle);
147
+ cycles.push(cycle);
254
148
  }
255
149
  }
256
150
  }
257
151
  }
258
152
  }
259
- rec_stack_dev.delete(name);
153
+ rec_stack.delete(name);
260
154
  };
261
- // Check for production/peer cycles
262
- for (const name of this.nodes.keys()) {
263
- if (!visited_prod.has(name)) {
264
- dfs_prod(name, []);
265
- }
266
- }
267
- // Check for dev cycles
268
155
  for (const name of this.nodes.keys()) {
269
- if (!visited_dev.has(name)) {
270
- dfs_dev(name, []);
156
+ if (!visited.has(name)) {
157
+ dfs(name, []);
271
158
  }
272
159
  }
273
- return { production_cycles, dev_cycles };
160
+ return cycles;
274
161
  }
275
162
  toJSON() {
276
163
  const nodes = Array.from(this.nodes.values()).map((node) => ({
@@ -1 +1 @@
1
- {"version":3,"file":"semver.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/semver.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAEnD,MAAM,WAAW,MAAM;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAiGD;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MAqB9D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAS,MAAM,EAAE,MAAM,QAAQ,KAAG,MAuBrE,CAAC"}
1
+ {"version":3,"file":"semver.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/semver.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAEnD,MAAM,WAAW,MAAM;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAiGD;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MAqB9D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAS,MAAM,EAAE,MAAM,QAAQ,KAAG,MAuBrE,CAAC"}
package/dist/semver.js CHANGED
@@ -1,9 +1,4 @@
1
- /**
2
- * Semantic Versioning 2.0.0 utilities
3
- * @see https://semver.org/
4
- *
5
- * @module
6
- */
1
+ // TODO: candidate for extraction to `@fuzdev/fuz_util`
7
2
  /**
8
3
  * SemVer 2.0.0 validation regex.
9
4
  * @see https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
@@ -1 +1 @@
1
- {"version":3,"file":"version_utils.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/version_utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,aAAa,CAAC;AAE1C,eAAO,MAAM,WAAW,GAAI,SAAS,MAAM,KAAG,OAE7C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAS,MAAM,KAAG,MAEtD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,MAAM,KAAG,MAGpD,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,gCAAgC,GAAI,SAAS,MAAM,KAAG,MAWlE,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,SAAS,MAAM,EAAE,aAAa,MAAM,KAAG,OASnE,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,GAC7B,iBAAiB,MAAM,EACvB,mBAAkB,GAAG,GAAG,GAAG,GAAG,EAAE,GAAG,IAAU,KAC3C,MAcF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAC9B,aAAa,MAAM,EACnB,WAAW,OAAO,GAAG,OAAO,GAAG,OAAO,KACpC,OAWF,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAC5B,aAAa,MAAM,EACnB,aAAa,MAAM,KACjB,OAAO,GAAG,OAAO,GAAG,OAOtB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAAI,GAAG,QAAQ,EAAE,GAAG,QAAQ,KAAG,MAO7D,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,iBAAiB,MAAM,EAAE,WAAW,QAAQ,KAAG,MAkBrF,CAAC"}
1
+ {"version":3,"file":"version_utils.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/version_utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,aAAa,CAAC;AAE1C,eAAO,MAAM,WAAW,GAAI,SAAS,MAAM,KAAG,OAE7C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAS,MAAM,KAAG,MAEtD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAAI,SAAS,MAAM,KAAG,MAGpD,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,gCAAgC,GAAI,SAAS,MAAM,KAAG,MAWlE,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,SAAS,MAAM,EAAE,aAAa,MAAM,KAAG,OASnE,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,GAC7B,iBAAiB,MAAM,EACvB,mBAAkB,GAAG,GAAG,GAAG,GAAG,EAAE,GAAG,IAAU,KAC3C,MAcF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAC9B,aAAa,MAAM,EACnB,WAAW,OAAO,GAAG,OAAO,GAAG,OAAO,KACpC,OAWF,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAC5B,aAAa,MAAM,EACnB,aAAa,MAAM,KACjB,OAAO,GAAG,OAAO,GAAG,OAOtB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAAI,GAAG,QAAQ,EAAE,GAAG,QAAQ,KAAG,MAO7D,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,iBAAiB,MAAM,EAAE,WAAW,QAAQ,KAAG,MAkBrF,CAAC"}
@@ -1,3 +1,4 @@
1
+ // TODO: candidate for extraction to `@fuzdev/fuz_util`
1
2
  export const is_wildcard = (version) => {
2
3
  return version === '*';
3
4
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_gitops",
3
- "version": "0.65.1",
3
+ "version": "0.65.2",
4
4
  "description": "a tool for managing many repos",
5
5
  "glyph": "🪄",
6
6
  "logo": "logo.svg",
@@ -32,18 +32,18 @@
32
32
  "@fuzdev/fuz_css": ">=0.44.1",
33
33
  "@fuzdev/fuz_ui": ">=0.179.0",
34
34
  "@fuzdev/fuz_util": ">=0.45.3",
35
- "@fuzdev/gro": "^0.192.1",
35
+ "@fuzdev/gro": "^0.194.0",
36
36
  "@sveltejs/kit": "^2",
37
37
  "svelte": "^5",
38
38
  "zod": "^4.1.13"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@changesets/changelog-git": "^0.2.1",
42
- "@fuzdev/fuz_code": "^0.45.0",
42
+ "@fuzdev/fuz_code": "^0.45.1",
43
43
  "@fuzdev/fuz_css": "^0.50.0",
44
- "@fuzdev/fuz_ui": "^0.183.1",
44
+ "@fuzdev/fuz_ui": "^0.183.2",
45
45
  "@fuzdev/fuz_util": "^0.50.1",
46
- "@fuzdev/gro": "^0.192.1",
46
+ "@fuzdev/gro": "^0.194.0",
47
47
  "@jridgewell/trace-mapping": "^0.3.31",
48
48
  "@ryanatkn/eslint-config": "^0.9.0",
49
49
  "@sveltejs/adapter-static": "^3.0.10",
@@ -1,14 +1,17 @@
1
1
  /**
2
2
  * Dependency graph data structure and algorithms for multi-repo publishing.
3
3
  *
4
- * Provides `DependencyGraph` class with topological sort and cycle detection.
4
+ * Provides `DependencyGraph` class with topological sort (via `@fuzdev/fuz_util/sort.js`)
5
+ * and cycle detection by dependency type.
5
6
  * For validation workflow and publishing order computation, see `graph_validation.ts`.
6
7
  *
7
8
  * @module
8
9
  */
9
10
 
10
- import type {LocalRepo} from './local_repo.js';
11
11
  import {EMPTY_OBJECT} from '@fuzdev/fuz_util/object.js';
12
+ import {topological_sort as topological_sort_generic} from '@fuzdev/fuz_util/sort.js';
13
+
14
+ import type {LocalRepo} from './local_repo.js';
12
15
 
13
16
  export const DEPENDENCY_TYPE = {
14
17
  PROD: 'prod',
@@ -121,8 +124,8 @@ export class DependencyGraph {
121
124
  /**
122
125
  * Computes topological sort order for dependency graph.
123
126
  *
124
- * Uses Kahn's algorithm with alphabetical ordering within tiers for
125
- * deterministic results. Throws if cycles detected.
127
+ * Delegates to `@fuzdev/fuz_util/sort.js` for the sorting algorithm.
128
+ * Throws if cycles detected.
126
129
  *
127
130
  * @param exclude_dev if true, excludes dev dependencies to break cycles.
128
131
  * Publishing uses exclude_dev=true to handle circular dev deps.
@@ -130,111 +133,20 @@ export class DependencyGraph {
130
133
  * @throws {Error} if circular dependencies detected in included dependency types
131
134
  */
132
135
  topological_sort(exclude_dev = false): Array<string> {
133
- const visited: Set<string> = new Set();
134
- const result: Array<string> = [];
135
-
136
- // Count incoming edges for each node
137
- const in_degree: Map<string, number> = new Map();
138
- for (const name of this.nodes.keys()) {
139
- in_degree.set(name, 0);
140
- }
141
- for (const node of this.nodes.values()) {
142
- for (const [dep_name, spec] of node.dependencies) {
143
- // Skip dev dependencies if requested
144
- if (exclude_dev && spec.type === DEPENDENCY_TYPE.DEV) continue;
145
-
146
- if (this.nodes.has(dep_name)) {
147
- in_degree.set(node.name, in_degree.get(node.name)! + 1);
148
- }
149
- }
150
- }
151
-
152
- // Start with nodes that have no dependencies
153
- const queue: Array<string> = [];
154
- for (const [name, degree] of in_degree) {
155
- if (degree === 0) {
156
- queue.push(name);
157
- }
158
- }
159
-
160
- // Sort initial queue alphabetically for deterministic ordering within tier
161
- queue.sort();
162
-
163
- // Process nodes
164
- while (queue.length > 0) {
165
- const name = queue.shift()!;
166
- result.push(name);
167
- visited.add(name);
168
-
169
- // Reduce in-degree for dependents
170
- const node = this.nodes.get(name);
171
- if (node) {
172
- // Find packages that depend on this one
173
- // Sort nodes to ensure deterministic iteration order
174
- const sorted_nodes = Array.from(this.nodes.values()).sort((a, b) =>
175
- a.name.localeCompare(b.name),
176
- );
177
- for (const other_node of sorted_nodes) {
178
- for (const [dep_name, spec] of other_node.dependencies) {
179
- // Skip dev dependencies if requested
180
- if (exclude_dev && spec.type === DEPENDENCY_TYPE.DEV) continue;
181
-
182
- if (dep_name === name) {
183
- const new_degree = in_degree.get(other_node.name)! - 1;
184
- in_degree.set(other_node.name, new_degree);
185
- if (new_degree === 0) {
186
- queue.push(other_node.name);
187
- }
188
- }
189
- }
190
- }
191
- }
192
- }
193
-
194
- // Check for cycles
195
- if (result.length !== this.nodes.size) {
196
- const unvisited = Array.from(this.nodes.keys()).filter((n) => !visited.has(n));
197
- throw new Error(`Circular dependency detected involving: ${unvisited.join(', ')}`);
198
- }
199
-
200
- return result;
201
- }
202
-
203
- detect_cycles(): Array<Array<string>> {
204
- const cycles: Array<Array<string>> = [];
205
- const visited: Set<string> = new Set();
206
- const rec_stack: Set<string> = new Set();
207
-
208
- const dfs = (name: string, path: Array<string>): void => {
209
- visited.add(name);
210
- rec_stack.add(name);
211
- path.push(name);
212
-
213
- const node = this.nodes.get(name);
214
- if (node) {
215
- for (const [dep_name] of node.dependencies) {
216
- if (this.nodes.has(dep_name)) {
217
- if (!visited.has(dep_name)) {
218
- dfs(dep_name, [...path]);
219
- } else if (rec_stack.has(dep_name)) {
220
- // Found a cycle
221
- const cycle_start = path.indexOf(dep_name);
222
- cycles.push(path.slice(cycle_start).concat(dep_name));
223
- }
224
- }
225
- }
226
- }
227
-
228
- rec_stack.delete(name);
229
- };
230
-
231
- for (const name of this.nodes.keys()) {
232
- if (!visited.has(name)) {
233
- dfs(name, []);
234
- }
136
+ const items = Array.from(this.nodes.values()).map((node) => ({
137
+ id: node.name,
138
+ depends_on: Array.from(node.dependencies.entries())
139
+ .filter(([dep_name, spec]) => {
140
+ if (exclude_dev && spec.type === DEPENDENCY_TYPE.DEV) return false;
141
+ return this.nodes.has(dep_name);
142
+ })
143
+ .map(([dep_name]) => dep_name),
144
+ }));
145
+ const result = topological_sort_generic(items, 'package');
146
+ if (!result.ok) {
147
+ throw new Error(result.error);
235
148
  }
236
-
237
- return cycles;
149
+ return result.sorted.map((item) => item.id);
238
150
  }
239
151
 
240
152
  /**
@@ -252,94 +164,53 @@ export class DependencyGraph {
252
164
  production_cycles: Array<Array<string>>;
253
165
  dev_cycles: Array<Array<string>>;
254
166
  } {
255
- const production_cycles: Array<Array<string>> = [];
256
- const dev_cycles: Array<Array<string>> = [];
257
- const visited_prod: Set<string> = new Set();
258
- const visited_dev: Set<string> = new Set();
259
- const rec_stack_prod: Set<string> = new Set();
260
- const rec_stack_dev: Set<string> = new Set();
261
-
262
- // DFS for production/peer dependencies only
263
- const dfs_prod = (name: string, path: Array<string>): void => {
264
- visited_prod.add(name);
265
- rec_stack_prod.add(name);
266
- path.push(name);
267
-
268
- const node = this.nodes.get(name);
269
- if (node) {
270
- for (const [dep_name, spec] of node.dependencies) {
271
- // Skip dev dependencies
272
- if (spec.type === DEPENDENCY_TYPE.DEV) continue;
273
-
274
- if (this.nodes.has(dep_name)) {
275
- if (!visited_prod.has(dep_name)) {
276
- dfs_prod(dep_name, [...path]);
277
- } else if (rec_stack_prod.has(dep_name)) {
278
- // Found a production cycle
279
- const cycle_start = path.indexOf(dep_name);
280
- const cycle = path.slice(cycle_start).concat(dep_name);
281
- // Check if this cycle is unique
282
- const cycle_key = [...cycle].sort().join(',');
283
- const exists = production_cycles.some((c) => [...c].sort().join(',') === cycle_key);
284
- if (!exists) {
285
- production_cycles.push(cycle);
286
- }
287
- }
288
- }
289
- }
290
- }
167
+ const production_cycles = this.#find_cycles((spec) => spec.type !== DEPENDENCY_TYPE.DEV);
168
+ const dev_cycles = this.#find_cycles((spec) => spec.type === DEPENDENCY_TYPE.DEV);
169
+ return {production_cycles, dev_cycles};
170
+ }
291
171
 
292
- rec_stack_prod.delete(name);
293
- };
172
+ /** DFS cycle detection following only edges that match the filter. */
173
+ #find_cycles(include: (spec: DependencySpec) => boolean): Array<Array<string>> {
174
+ const cycles: Array<Array<string>> = [];
175
+ const visited: Set<string> = new Set();
176
+ const rec_stack: Set<string> = new Set();
294
177
 
295
- // DFS for dev dependencies only
296
- const dfs_dev = (name: string, path: Array<string>): void => {
297
- visited_dev.add(name);
298
- rec_stack_dev.add(name);
178
+ const dfs = (name: string, path: Array<string>): void => {
179
+ visited.add(name);
180
+ rec_stack.add(name);
299
181
  path.push(name);
300
182
 
301
183
  const node = this.nodes.get(name);
302
184
  if (node) {
303
185
  for (const [dep_name, spec] of node.dependencies) {
304
- // Only check dev dependencies
305
- if (spec.type !== DEPENDENCY_TYPE.DEV) continue;
186
+ if (!include(spec)) continue;
306
187
 
307
188
  if (this.nodes.has(dep_name)) {
308
- if (!visited_dev.has(dep_name)) {
309
- dfs_dev(dep_name, [...path]);
310
- } else if (rec_stack_dev.has(dep_name)) {
311
- // Found a dev cycle
189
+ if (!visited.has(dep_name)) {
190
+ dfs(dep_name, [...path]);
191
+ } else if (rec_stack.has(dep_name)) {
312
192
  const cycle_start = path.indexOf(dep_name);
313
193
  const cycle = path.slice(cycle_start).concat(dep_name);
314
- // Check if this cycle is unique
315
194
  const cycle_key = [...cycle].sort().join(',');
316
- const exists = dev_cycles.some((c) => [...c].sort().join(',') === cycle_key);
195
+ const exists = cycles.some((c) => [...c].sort().join(',') === cycle_key);
317
196
  if (!exists) {
318
- dev_cycles.push(cycle);
197
+ cycles.push(cycle);
319
198
  }
320
199
  }
321
200
  }
322
201
  }
323
202
  }
324
203
 
325
- rec_stack_dev.delete(name);
204
+ rec_stack.delete(name);
326
205
  };
327
206
 
328
- // Check for production/peer cycles
329
- for (const name of this.nodes.keys()) {
330
- if (!visited_prod.has(name)) {
331
- dfs_prod(name, []);
332
- }
333
- }
334
-
335
- // Check for dev cycles
336
207
  for (const name of this.nodes.keys()) {
337
- if (!visited_dev.has(name)) {
338
- dfs_dev(name, []);
208
+ if (!visited.has(name)) {
209
+ dfs(name, []);
339
210
  }
340
211
  }
341
212
 
342
- return {production_cycles, dev_cycles};
213
+ return cycles;
343
214
  }
344
215
 
345
216
  toJSON(): DependencyGraphJson {
package/src/lib/semver.ts CHANGED
@@ -1,3 +1,5 @@
1
+ // TODO: candidate for extraction to `@fuzdev/fuz_util`
2
+
1
3
  /**
2
4
  * Semantic Versioning 2.0.0 utilities
3
5
  * @see https://semver.org/
@@ -1,3 +1,5 @@
1
+ // TODO: candidate for extraction to `@fuzdev/fuz_util`
2
+
1
3
  import type {BumpType} from './semver.js';
2
4
 
3
5
  export const is_wildcard = (version: string): boolean => {