@fuzdev/fuz_util 0.48.3 → 0.49.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/dist/async.d.ts +11 -0
- package/dist/async.d.ts.map +1 -1
- package/dist/async.js +30 -0
- package/dist/dag.d.ts +80 -0
- package/dist/dag.d.ts.map +1 -0
- package/dist/dag.js +156 -0
- package/dist/diff.d.ts +61 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +185 -0
- package/dist/path.d.ts +11 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +15 -0
- package/dist/sort.d.ts +38 -0
- package/dist/sort.d.ts.map +1 -0
- package/dist/sort.js +123 -0
- package/dist/source_json.d.ts +4 -4
- package/dist/string.d.ts +16 -0
- package/dist/string.d.ts.map +1 -1
- package/dist/string.js +33 -0
- package/dist/svelte_preprocess_helpers.d.ts +134 -0
- package/dist/svelte_preprocess_helpers.d.ts.map +1 -0
- package/dist/svelte_preprocess_helpers.js +243 -0
- package/package.json +15 -6
- package/src/lib/async.ts +33 -0
- package/src/lib/dag.ts +240 -0
- package/src/lib/diff.ts +234 -0
- package/src/lib/path.ts +20 -0
- package/src/lib/sort.ts +160 -0
- package/src/lib/string.ts +36 -0
- package/src/lib/svelte_preprocess_helpers.ts +270 -0
package/dist/async.d.ts
CHANGED
|
@@ -77,4 +77,15 @@ export declare const map_concurrent: <T, R>(items: Array<T>, fn: (item: T, index
|
|
|
77
77
|
* ```
|
|
78
78
|
*/
|
|
79
79
|
export declare const map_concurrent_settled: <T, R>(items: Array<T>, fn: (item: T, index: number) => Promise<R>, concurrency: number) => Promise<Array<PromiseSettledResult<R>>>;
|
|
80
|
+
/**
|
|
81
|
+
* Async semaphore for concurrency limiting.
|
|
82
|
+
*
|
|
83
|
+
* With `Infinity` permits, `acquire()` always resolves immediately.
|
|
84
|
+
*/
|
|
85
|
+
export declare class AsyncSemaphore {
|
|
86
|
+
#private;
|
|
87
|
+
constructor(permits: number);
|
|
88
|
+
acquire(): Promise<void>;
|
|
89
|
+
release(): void;
|
|
90
|
+
}
|
|
80
91
|
//# sourceMappingURL=async.d.ts.map
|
package/dist/async.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"async.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/async.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAExE;;GAEG;AACH,eAAO,MAAM,IAAI,GAAI,iBAAY,KAAG,OAAO,CAAC,IAAI,CACQ,CAAC;AAEzD;;GAEG;AACH,eAAO,MAAM,UAAU,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,OAAO,CAAC,OAAO,CACI,CAAC;AAEzE;;GAEG;AACH,MAAM,WAAW,QAAQ,CAAC,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACpB,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC5B,MAAM,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;CAC9B;AAED;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,OAAK,QAAQ,CAAC,CAAC,CAQ/C,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,eAAe,GAAU,CAAC,EACtC,OAAO,KAAK,CAAC,CAAC,CAAC,EACf,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,EAC7C,aAAa,MAAM,KACjB,OAAO,CAAC,IAAI,CAgDd,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,cAAc,GAAU,CAAC,EAAE,CAAC,EACxC,OAAO,KAAK,CAAC,CAAC,CAAC,EACf,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,EAC1C,aAAa,MAAM,KACjB,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAkDlB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,sBAAsB,GAAU,CAAC,EAAE,CAAC,EAChD,OAAO,KAAK,CAAC,CAAC,CAAC,EACf,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,EAC1C,aAAa,MAAM,KACjB,OAAO,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,CA6CxC,CAAC"}
|
|
1
|
+
{"version":3,"file":"async.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/async.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAExE;;GAEG;AACH,eAAO,MAAM,IAAI,GAAI,iBAAY,KAAG,OAAO,CAAC,IAAI,CACQ,CAAC;AAEzD;;GAEG;AACH,eAAO,MAAM,UAAU,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,OAAO,CAAC,OAAO,CACI,CAAC;AAEzE;;GAEG;AACH,MAAM,WAAW,QAAQ,CAAC,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACpB,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC5B,MAAM,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;CAC9B;AAED;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,OAAK,QAAQ,CAAC,CAAC,CAQ/C,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,eAAe,GAAU,CAAC,EACtC,OAAO,KAAK,CAAC,CAAC,CAAC,EACf,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,EAC7C,aAAa,MAAM,KACjB,OAAO,CAAC,IAAI,CAgDd,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,cAAc,GAAU,CAAC,EAAE,CAAC,EACxC,OAAO,KAAK,CAAC,CAAC,CAAC,EACf,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,EAC1C,aAAa,MAAM,KACjB,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAkDlB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,sBAAsB,GAAU,CAAC,EAAE,CAAC,EAChD,OAAO,KAAK,CAAC,CAAC,CAAC,EACf,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,EAC1C,aAAa,MAAM,KACjB,OAAO,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,CA6CxC,CAAC;AAEF;;;;GAIG;AACH,qBAAa,cAAc;;gBAId,OAAO,EAAE,MAAM;IAIrB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAU9B,OAAO,IAAI,IAAI;CAQf"}
|
package/dist/async.js
CHANGED
|
@@ -205,3 +205,33 @@ export const map_concurrent_settled = async (items, fn, concurrency) => {
|
|
|
205
205
|
run_next();
|
|
206
206
|
});
|
|
207
207
|
};
|
|
208
|
+
/**
|
|
209
|
+
* Async semaphore for concurrency limiting.
|
|
210
|
+
*
|
|
211
|
+
* With `Infinity` permits, `acquire()` always resolves immediately.
|
|
212
|
+
*/
|
|
213
|
+
export class AsyncSemaphore {
|
|
214
|
+
#permits;
|
|
215
|
+
#waiters = [];
|
|
216
|
+
constructor(permits) {
|
|
217
|
+
this.#permits = permits;
|
|
218
|
+
}
|
|
219
|
+
async acquire() {
|
|
220
|
+
if (this.#permits > 0) {
|
|
221
|
+
this.#permits--;
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
return new Promise((resolve) => {
|
|
225
|
+
this.#waiters.push(resolve);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
release() {
|
|
229
|
+
const next = this.#waiters.shift();
|
|
230
|
+
if (next) {
|
|
231
|
+
next();
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
this.#permits++;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
package/dist/dag.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic concurrent DAG executor.
|
|
3
|
+
*
|
|
4
|
+
* Executes nodes in a dependency graph with configurable concurrency,
|
|
5
|
+
* failure handling, and skip semantics. Nodes without dependency edges
|
|
6
|
+
* run in parallel (up to max_concurrency). Dependencies are respected
|
|
7
|
+
* via per-node deferreds.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
import { type Sortable } from './sort.js';
|
|
12
|
+
/**
|
|
13
|
+
* Minimum shape for a DAG node.
|
|
14
|
+
*/
|
|
15
|
+
export interface DagNode extends Sortable {
|
|
16
|
+
id: string;
|
|
17
|
+
depends_on?: Array<string>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Options for running a DAG.
|
|
21
|
+
*/
|
|
22
|
+
export interface DagOptions<T extends DagNode> {
|
|
23
|
+
/** Nodes to execute. */
|
|
24
|
+
nodes: Array<T>;
|
|
25
|
+
/** Execute a node. Throw on failure. */
|
|
26
|
+
execute: (node: T) => Promise<void>;
|
|
27
|
+
/** Called after a node fails. For observability — the error is already recorded. */
|
|
28
|
+
on_error?: (node: T, error: Error) => Promise<void>;
|
|
29
|
+
/** Called when a node is skipped (pre-skip or dependency failure). */
|
|
30
|
+
on_skip?: (node: T, reason: string) => Promise<void>;
|
|
31
|
+
/** Return true to skip a node without executing. Dependents still proceed. */
|
|
32
|
+
should_skip?: (node: T) => boolean;
|
|
33
|
+
/** Maximum concurrent executions. Default: Infinity. */
|
|
34
|
+
max_concurrency?: number;
|
|
35
|
+
/** Stop starting new nodes on first failure. Default: true. */
|
|
36
|
+
stop_on_failure?: boolean;
|
|
37
|
+
/** Skip internal graph validation (caller already validated). */
|
|
38
|
+
skip_validation?: boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Result for a single node.
|
|
42
|
+
*/
|
|
43
|
+
export interface DagNodeResult {
|
|
44
|
+
id: string;
|
|
45
|
+
status: 'completed' | 'failed' | 'skipped';
|
|
46
|
+
error?: string;
|
|
47
|
+
duration_ms: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Result of a DAG execution.
|
|
51
|
+
*/
|
|
52
|
+
export interface DagResult {
|
|
53
|
+
/** Whether all executed nodes succeeded. */
|
|
54
|
+
success: boolean;
|
|
55
|
+
/** Per-node results. */
|
|
56
|
+
results: Map<string, DagNodeResult>;
|
|
57
|
+
/** Number of nodes that completed successfully. */
|
|
58
|
+
completed: number;
|
|
59
|
+
/** Number of nodes that failed. */
|
|
60
|
+
failed: number;
|
|
61
|
+
/** Number of nodes that were skipped. */
|
|
62
|
+
skipped: number;
|
|
63
|
+
/** Total execution time in milliseconds. */
|
|
64
|
+
duration_ms: number;
|
|
65
|
+
/** Error message if any nodes failed. */
|
|
66
|
+
error?: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Execute nodes in a dependency graph concurrently.
|
|
70
|
+
*
|
|
71
|
+
* Independent nodes (no unmet dependencies) run in parallel up to
|
|
72
|
+
* `max_concurrency`. When a node completes, its dependents become
|
|
73
|
+
* eligible to start. Failure cascading and stop-on-failure are handled
|
|
74
|
+
* per the options.
|
|
75
|
+
*
|
|
76
|
+
* @param options - DAG execution options.
|
|
77
|
+
* @returns Aggregated result with per-node details.
|
|
78
|
+
*/
|
|
79
|
+
export declare const run_dag: <T extends DagNode>(options: DagOptions<T>) => Promise<DagResult>;
|
|
80
|
+
//# sourceMappingURL=dag.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dag.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/dag.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAmB,KAAK,QAAQ,EAAC,MAAM,WAAW,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,OAAQ,SAAQ,QAAQ;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,OAAO;IAC5C,wBAAwB;IACxB,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAChB,wCAAwC;IACxC,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,oFAAoF;IACpF,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,sEAAsE;IACtE,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,8EAA8E;IAC9E,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC;IACnC,wDAAwD;IACxD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,+DAA+D;IAC/D,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,iEAAiE;IACjE,eAAe,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACzB,4CAA4C;IAC5C,OAAO,EAAE,OAAO,CAAC;IACjB,wBAAwB;IACxB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACpC,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,OAAO,GAAU,CAAC,SAAS,OAAO,EAAE,SAAS,UAAU,CAAC,CAAC,CAAC,KAAG,OAAO,CAAC,SAAS,CA0J1F,CAAC"}
|
package/dist/dag.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic concurrent DAG executor.
|
|
3
|
+
*
|
|
4
|
+
* Executes nodes in a dependency graph with configurable concurrency,
|
|
5
|
+
* failure handling, and skip semantics. Nodes without dependency edges
|
|
6
|
+
* run in parallel (up to max_concurrency). Dependencies are respected
|
|
7
|
+
* via per-node deferreds.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
import { AsyncSemaphore, create_deferred } from './async.js';
|
|
12
|
+
import { topological_sort } from './sort.js';
|
|
13
|
+
/**
|
|
14
|
+
* Execute nodes in a dependency graph concurrently.
|
|
15
|
+
*
|
|
16
|
+
* Independent nodes (no unmet dependencies) run in parallel up to
|
|
17
|
+
* `max_concurrency`. When a node completes, its dependents become
|
|
18
|
+
* eligible to start. Failure cascading and stop-on-failure are handled
|
|
19
|
+
* per the options.
|
|
20
|
+
*
|
|
21
|
+
* @param options - DAG execution options.
|
|
22
|
+
* @returns Aggregated result with per-node details.
|
|
23
|
+
*/
|
|
24
|
+
export const run_dag = async (options) => {
|
|
25
|
+
const { nodes, execute, on_error, on_skip, should_skip, max_concurrency = Infinity, stop_on_failure = true, skip_validation = false, } = options;
|
|
26
|
+
const start_time = Date.now();
|
|
27
|
+
// Empty graph
|
|
28
|
+
if (nodes.length === 0) {
|
|
29
|
+
return {
|
|
30
|
+
success: true,
|
|
31
|
+
results: new Map(),
|
|
32
|
+
completed: 0,
|
|
33
|
+
failed: 0,
|
|
34
|
+
skipped: 0,
|
|
35
|
+
duration_ms: 0,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// Validate graph (cycle detection, duplicate IDs, missing deps)
|
|
39
|
+
if (!skip_validation) {
|
|
40
|
+
const sort_result = topological_sort(nodes, 'node');
|
|
41
|
+
if (!sort_result.ok) {
|
|
42
|
+
return {
|
|
43
|
+
success: false,
|
|
44
|
+
results: new Map(),
|
|
45
|
+
completed: 0,
|
|
46
|
+
failed: 0,
|
|
47
|
+
skipped: 0,
|
|
48
|
+
duration_ms: Date.now() - start_time,
|
|
49
|
+
error: sort_result.error,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Build deferreds and tracking maps
|
|
54
|
+
const deferreds = new Map();
|
|
55
|
+
const outcomes = new Map();
|
|
56
|
+
const results = new Map();
|
|
57
|
+
for (const node of nodes) {
|
|
58
|
+
deferreds.set(node.id, create_deferred());
|
|
59
|
+
}
|
|
60
|
+
let stopping = false;
|
|
61
|
+
const semaphore = new AsyncSemaphore(max_concurrency);
|
|
62
|
+
// Skip a node, record outcome, notify dependents
|
|
63
|
+
const skip_node = async (node, outcome, reason) => {
|
|
64
|
+
outcomes.set(node.id, outcome);
|
|
65
|
+
results.set(node.id, { id: node.id, status: 'skipped', duration_ms: 0 });
|
|
66
|
+
if (on_skip)
|
|
67
|
+
await on_skip(node, reason);
|
|
68
|
+
deferreds.get(node.id).resolve();
|
|
69
|
+
};
|
|
70
|
+
// Per-node async task
|
|
71
|
+
const run_node = async (node) => {
|
|
72
|
+
const deps = node.depends_on ?? [];
|
|
73
|
+
// Wait for all dependencies to resolve
|
|
74
|
+
if (deps.length > 0) {
|
|
75
|
+
await Promise.all(deps.map((d) => deferreds.get(d).promise));
|
|
76
|
+
}
|
|
77
|
+
// Pre-skip check (e.g., pipeline step.skip or change.action === 'skip')
|
|
78
|
+
if (should_skip?.(node)) {
|
|
79
|
+
return skip_node(node, 'ok', 'pre-skipped');
|
|
80
|
+
}
|
|
81
|
+
// Check if any dependency failed — skip this node too
|
|
82
|
+
if (deps.some((d) => outcomes.get(d) === 'fail')) {
|
|
83
|
+
return skip_node(node, 'fail', 'dependency failed');
|
|
84
|
+
}
|
|
85
|
+
// Check if we're stopping (some other node failed with stop_on_failure)
|
|
86
|
+
if (stopping) {
|
|
87
|
+
return skip_node(node, 'fail', 'stopped');
|
|
88
|
+
}
|
|
89
|
+
// Acquire concurrency slot
|
|
90
|
+
await semaphore.acquire();
|
|
91
|
+
// Double-check stopping after acquiring slot
|
|
92
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
93
|
+
if (stopping) {
|
|
94
|
+
semaphore.release();
|
|
95
|
+
return skip_node(node, 'fail', 'stopped');
|
|
96
|
+
}
|
|
97
|
+
// Execute
|
|
98
|
+
const exec_start = Date.now();
|
|
99
|
+
try {
|
|
100
|
+
await execute(node);
|
|
101
|
+
outcomes.set(node.id, 'ok');
|
|
102
|
+
results.set(node.id, {
|
|
103
|
+
id: node.id,
|
|
104
|
+
status: 'completed',
|
|
105
|
+
duration_ms: Date.now() - exec_start,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
110
|
+
outcomes.set(node.id, 'fail');
|
|
111
|
+
results.set(node.id, {
|
|
112
|
+
id: node.id,
|
|
113
|
+
status: 'failed',
|
|
114
|
+
error: error.message,
|
|
115
|
+
duration_ms: Date.now() - exec_start,
|
|
116
|
+
});
|
|
117
|
+
if (stop_on_failure)
|
|
118
|
+
stopping = true;
|
|
119
|
+
if (on_error)
|
|
120
|
+
await on_error(node, error);
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
semaphore.release();
|
|
124
|
+
deferreds.get(node.id).resolve();
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
// Launch all nodes — they naturally wait for their deps via deferreds
|
|
128
|
+
await Promise.all(nodes.map(run_node));
|
|
129
|
+
// Aggregate results
|
|
130
|
+
let completed = 0;
|
|
131
|
+
let failed = 0;
|
|
132
|
+
let skipped = 0;
|
|
133
|
+
for (const result of results.values()) {
|
|
134
|
+
switch (result.status) {
|
|
135
|
+
case 'completed':
|
|
136
|
+
completed++;
|
|
137
|
+
break;
|
|
138
|
+
case 'failed':
|
|
139
|
+
failed++;
|
|
140
|
+
break;
|
|
141
|
+
case 'skipped':
|
|
142
|
+
skipped++;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const success = failed === 0;
|
|
147
|
+
return {
|
|
148
|
+
success,
|
|
149
|
+
results,
|
|
150
|
+
completed,
|
|
151
|
+
failed,
|
|
152
|
+
skipped,
|
|
153
|
+
duration_ms: Date.now() - start_time,
|
|
154
|
+
error: success ? undefined : `${failed} node(s) failed`,
|
|
155
|
+
};
|
|
156
|
+
};
|
package/dist/diff.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Line-based diff utilities using LCS (Longest Common Subsequence).
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
/** Line diff result */
|
|
7
|
+
export interface DiffLine {
|
|
8
|
+
type: 'same' | 'add' | 'remove';
|
|
9
|
+
line: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Generate a line-based diff between two strings using LCS algorithm.
|
|
13
|
+
*
|
|
14
|
+
* @param a - The original/current content.
|
|
15
|
+
* @param b - The new/desired content.
|
|
16
|
+
* @returns Array of diff lines with type annotations.
|
|
17
|
+
*/
|
|
18
|
+
export declare const diff_lines: (a: string, b: string) => Array<DiffLine>;
|
|
19
|
+
/**
|
|
20
|
+
* Filter diff to only include lines within N lines of context around changes.
|
|
21
|
+
*
|
|
22
|
+
* @param diff - The full diff lines.
|
|
23
|
+
* @param context_lines - Number of context lines to show around changes (default: 3).
|
|
24
|
+
* @returns Filtered diff with ellipsis markers for skipped regions.
|
|
25
|
+
*/
|
|
26
|
+
export declare const filter_diff_context: (diff: Array<DiffLine>, context_lines?: number) => Array<DiffLine>;
|
|
27
|
+
/**
|
|
28
|
+
* Format options for diff output.
|
|
29
|
+
*/
|
|
30
|
+
export interface FormatDiffOptions {
|
|
31
|
+
/** Prefix for each line (for indentation in plan output). */
|
|
32
|
+
prefix?: string;
|
|
33
|
+
/** Whether to use ANSI colors. */
|
|
34
|
+
use_color?: boolean;
|
|
35
|
+
/** Maximum number of diff lines to show (0 = unlimited). */
|
|
36
|
+
max_lines?: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Format a diff for display.
|
|
40
|
+
*
|
|
41
|
+
* @param diff - The diff lines to format.
|
|
42
|
+
* @param current_path - Path label for "current" content.
|
|
43
|
+
* @param desired_path - Path label for "desired" content.
|
|
44
|
+
* @param options - Formatting options.
|
|
45
|
+
* @returns Formatted diff string.
|
|
46
|
+
*/
|
|
47
|
+
export declare const format_diff: (diff: Array<DiffLine>, current_path: string, desired_path: string, options?: FormatDiffOptions) => string;
|
|
48
|
+
/**
|
|
49
|
+
* Generate a formatted diff between two strings.
|
|
50
|
+
*
|
|
51
|
+
* Combines diff_lines, filter_diff_context, and format_diff for convenience.
|
|
52
|
+
* Returns null if content is binary.
|
|
53
|
+
*
|
|
54
|
+
* @param current - Current content.
|
|
55
|
+
* @param desired - Desired content.
|
|
56
|
+
* @param path - File path for labels.
|
|
57
|
+
* @param options - Formatting options.
|
|
58
|
+
* @returns Formatted diff string, or null if binary.
|
|
59
|
+
*/
|
|
60
|
+
export declare const generate_diff: (current: string, desired: string, path: string, options?: FormatDiffOptions) => string | null;
|
|
61
|
+
//# sourceMappingURL=diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/diff.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,uBAAuB;AACvB,MAAM,WAAW,QAAQ;IACxB,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;CACb;AAED;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,KAAK,CAAC,QAAQ,CA+B/D,CAAC;AAyCF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAAI,MAAM,KAAK,CAAC,QAAQ,CAAC,EAAE,sBAAiB,KAAG,KAAK,CAAC,QAAQ,CAyC5F,CAAC;AASF;;GAEG;AACH,MAAM,WAAW,iBAAiB;IACjC,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,GACvB,MAAM,KAAK,CAAC,QAAQ,CAAC,EACrB,cAAc,MAAM,EACpB,cAAc,MAAM,EACpB,UAAS,iBAAsB,KAC7B,MA6BF,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,aAAa,GACzB,SAAS,MAAM,EACf,SAAS,MAAM,EACf,MAAM,MAAM,EACZ,UAAS,iBAAsB,KAC7B,MAAM,GAAG,IASX,CAAC"}
|
package/dist/diff.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Line-based diff utilities using LCS (Longest Common Subsequence).
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { string_is_binary } from './string.js';
|
|
7
|
+
/**
|
|
8
|
+
* Generate a line-based diff between two strings using LCS algorithm.
|
|
9
|
+
*
|
|
10
|
+
* @param a - The original/current content.
|
|
11
|
+
* @param b - The new/desired content.
|
|
12
|
+
* @returns Array of diff lines with type annotations.
|
|
13
|
+
*/
|
|
14
|
+
export const diff_lines = (a, b) => {
|
|
15
|
+
const a_lines = a.split('\n');
|
|
16
|
+
const b_lines = b.split('\n');
|
|
17
|
+
const result = [];
|
|
18
|
+
const lcs = compute_lcs(a_lines, b_lines);
|
|
19
|
+
let ai = 0;
|
|
20
|
+
let bi = 0;
|
|
21
|
+
let li = 0;
|
|
22
|
+
while (ai < a_lines.length || bi < b_lines.length) {
|
|
23
|
+
if (li < lcs.length && ai < a_lines.length && a_lines[ai] === lcs[li]) {
|
|
24
|
+
if (bi < b_lines.length && b_lines[bi] === lcs[li]) {
|
|
25
|
+
result.push({ type: 'same', line: a_lines[ai] });
|
|
26
|
+
ai++;
|
|
27
|
+
bi++;
|
|
28
|
+
li++;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
result.push({ type: 'add', line: b_lines[bi] });
|
|
32
|
+
bi++;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (ai < a_lines.length && (li >= lcs.length || a_lines[ai] !== lcs[li])) {
|
|
36
|
+
result.push({ type: 'remove', line: a_lines[ai] });
|
|
37
|
+
ai++;
|
|
38
|
+
}
|
|
39
|
+
else if (bi < b_lines.length) {
|
|
40
|
+
result.push({ type: 'add', line: b_lines[bi] });
|
|
41
|
+
bi++;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Compute longest common subsequence of two string arrays.
|
|
48
|
+
*
|
|
49
|
+
* Uses dynamic programming with O(m*n) time and space complexity.
|
|
50
|
+
*/
|
|
51
|
+
const compute_lcs = (a, b) => {
|
|
52
|
+
const m = a.length;
|
|
53
|
+
const n = b.length;
|
|
54
|
+
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
55
|
+
for (let i = 1; i <= m; i++) {
|
|
56
|
+
for (let j = 1; j <= n; j++) {
|
|
57
|
+
if (a[i - 1] === b[j - 1]) {
|
|
58
|
+
dp[i][j] = dp[i - 1][j - 1] + 1;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Backtrack to find LCS
|
|
66
|
+
const lcs = [];
|
|
67
|
+
let i = m;
|
|
68
|
+
let j = n;
|
|
69
|
+
while (i > 0 && j > 0) {
|
|
70
|
+
if (a[i - 1] === b[j - 1]) {
|
|
71
|
+
lcs.unshift(a[i - 1]);
|
|
72
|
+
i--;
|
|
73
|
+
j--;
|
|
74
|
+
}
|
|
75
|
+
else if (dp[i - 1][j] > dp[i][j - 1]) {
|
|
76
|
+
i--;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
j--;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return lcs;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Filter diff to only include lines within N lines of context around changes.
|
|
86
|
+
*
|
|
87
|
+
* @param diff - The full diff lines.
|
|
88
|
+
* @param context_lines - Number of context lines to show around changes (default: 3).
|
|
89
|
+
* @returns Filtered diff with ellipsis markers for skipped regions.
|
|
90
|
+
*/
|
|
91
|
+
export const filter_diff_context = (diff, context_lines = 3) => {
|
|
92
|
+
if (diff.length === 0)
|
|
93
|
+
return [];
|
|
94
|
+
// Find indices of all changed lines
|
|
95
|
+
const changed_indices = [];
|
|
96
|
+
for (let i = 0; i < diff.length; i++) {
|
|
97
|
+
if (diff[i].type !== 'same') {
|
|
98
|
+
changed_indices.push(i);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (changed_indices.length === 0)
|
|
102
|
+
return [];
|
|
103
|
+
// Build set of indices to include (changed lines + context)
|
|
104
|
+
const include_indices = new Set();
|
|
105
|
+
for (const idx of changed_indices) {
|
|
106
|
+
for (let i = Math.max(0, idx - context_lines); i <= Math.min(diff.length - 1, idx + context_lines); i++) {
|
|
107
|
+
include_indices.add(i);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Build result with ellipsis markers for gaps
|
|
111
|
+
const result = [];
|
|
112
|
+
let last_included = -1;
|
|
113
|
+
for (let i = 0; i < diff.length; i++) {
|
|
114
|
+
if (include_indices.has(i)) {
|
|
115
|
+
// Add ellipsis if there's a gap
|
|
116
|
+
if (last_included >= 0 && i > last_included + 1) {
|
|
117
|
+
result.push({ type: 'same', line: '...' });
|
|
118
|
+
}
|
|
119
|
+
result.push(diff[i]);
|
|
120
|
+
last_included = i;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
};
|
|
125
|
+
/** ANSI color codes */
|
|
126
|
+
const colors = {
|
|
127
|
+
red: '\x1b[31m',
|
|
128
|
+
green: '\x1b[32m',
|
|
129
|
+
reset: '\x1b[0m',
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Format a diff for display.
|
|
133
|
+
*
|
|
134
|
+
* @param diff - The diff lines to format.
|
|
135
|
+
* @param current_path - Path label for "current" content.
|
|
136
|
+
* @param desired_path - Path label for "desired" content.
|
|
137
|
+
* @param options - Formatting options.
|
|
138
|
+
* @returns Formatted diff string.
|
|
139
|
+
*/
|
|
140
|
+
export const format_diff = (diff, current_path, desired_path, options = {}) => {
|
|
141
|
+
const { prefix = '', use_color = true, max_lines = 50 } = options;
|
|
142
|
+
const lines = [
|
|
143
|
+
`${prefix}--- ${current_path} (current)`,
|
|
144
|
+
`${prefix}+++ ${desired_path} (desired)`,
|
|
145
|
+
];
|
|
146
|
+
let count = 0;
|
|
147
|
+
for (const d of diff) {
|
|
148
|
+
if (max_lines > 0 && count >= max_lines) {
|
|
149
|
+
const remaining = diff.length - count;
|
|
150
|
+
lines.push(`${prefix}... (${remaining} more lines)`);
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
const line_prefix = d.type === 'add' ? '+' : d.type === 'remove' ? '-' : ' ';
|
|
154
|
+
if (use_color && d.type !== 'same') {
|
|
155
|
+
const color = d.type === 'add' ? colors.green : colors.red;
|
|
156
|
+
lines.push(`${prefix}${color}${line_prefix}${d.line}${colors.reset}`);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
lines.push(`${prefix}${line_prefix}${d.line}`);
|
|
160
|
+
}
|
|
161
|
+
count++;
|
|
162
|
+
}
|
|
163
|
+
return lines.join('\n');
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* Generate a formatted diff between two strings.
|
|
167
|
+
*
|
|
168
|
+
* Combines diff_lines, filter_diff_context, and format_diff for convenience.
|
|
169
|
+
* Returns null if content is binary.
|
|
170
|
+
*
|
|
171
|
+
* @param current - Current content.
|
|
172
|
+
* @param desired - Desired content.
|
|
173
|
+
* @param path - File path for labels.
|
|
174
|
+
* @param options - Formatting options.
|
|
175
|
+
* @returns Formatted diff string, or null if binary.
|
|
176
|
+
*/
|
|
177
|
+
export const generate_diff = (current, desired, path, options = {}) => {
|
|
178
|
+
// Skip binary files
|
|
179
|
+
if (string_is_binary(current) || string_is_binary(desired)) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
const diff = diff_lines(current, desired);
|
|
183
|
+
const filtered = filter_diff_context(diff);
|
|
184
|
+
return format_diff(filtered, path, path, options);
|
|
185
|
+
};
|
package/dist/path.d.ts
CHANGED
|
@@ -53,6 +53,17 @@ export type PathPiece = {
|
|
|
53
53
|
* @todo maybe rethink this API, it's a bit weird, but fits the usage in `ui/Breadcrumbs.svelte`
|
|
54
54
|
*/
|
|
55
55
|
export declare const parse_path_pieces: (raw_path: string) => Array<PathPiece>;
|
|
56
|
+
/**
|
|
57
|
+
* Checks if a filename matches any exclusion pattern.
|
|
58
|
+
*
|
|
59
|
+
* Returns `false` when `filename` is `undefined`, empty string, or `exclude` is empty.
|
|
60
|
+
* String patterns use substring matching. RegExp patterns use `.test()`.
|
|
61
|
+
*
|
|
62
|
+
* @param filename The file path to check, or `undefined` for virtual files.
|
|
63
|
+
* @param exclude Array of string or RegExp exclusion patterns.
|
|
64
|
+
* @returns `true` if the file should be excluded from processing.
|
|
65
|
+
*/
|
|
66
|
+
export declare const should_exclude_path: (filename: string | undefined, exclude: Array<string | RegExp>) => boolean;
|
|
56
67
|
/**
|
|
57
68
|
* Converts a string into a URL-compatible slug.
|
|
58
69
|
* @param str the string to convert
|
package/dist/path.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/path.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,YAAY,CAAC;AAEzC;;GAEG;AACH,MAAM,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AAEhD;;GAEG;AACH,MAAM,WAAW,QAAQ;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,OAAO,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,YAAa,SAAQ,QAAQ;IAC7C,IAAI,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,KAAK,OAAO,CAAC;AAE1E;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;AAEnD;;GAEG;AACH,eAAO,MAAM,YAAY,GAAI,aAAa,MAAM,GAAG,GAAG,KAAG,MACgC,CAAC;AAE1F;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,KAAG,KAAK,CAAC,MAAM,CAU3D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAI,MAAM,MAAM,KAAG,KAAK,CAAC,MAAM,CACH,CAAC;AAE7D;;GAEG;AACH,MAAM,MAAM,SAAS,GAClB;IACA,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACZ,GACD;IACA,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACZ,CAAC;AAEL;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAI,UAAU,MAAM,KAAG,KAAK,CAAC,SAAS,CAgBnE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,OAAO,GAAI,KAAK,MAAM,EAAE,gCAA6B,KAAG,MAYpE,CAAC"}
|
|
1
|
+
{"version":3,"file":"path.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/path.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,YAAY,CAAC;AAEzC;;GAEG;AACH,MAAM,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AAEhD;;GAEG;AACH,MAAM,WAAW,QAAQ;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,OAAO,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,YAAa,SAAQ,QAAQ;IAC7C,IAAI,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,KAAK,OAAO,CAAC;AAE1E;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;AAEnD;;GAEG;AACH,eAAO,MAAM,YAAY,GAAI,aAAa,MAAM,GAAG,GAAG,KAAG,MACgC,CAAC;AAE1F;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,KAAG,KAAK,CAAC,MAAM,CAU3D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAI,MAAM,MAAM,KAAG,KAAK,CAAC,MAAM,CACH,CAAC;AAE7D;;GAEG;AACH,MAAM,MAAM,SAAS,GAClB;IACA,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACZ,GACD;IACA,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACZ,CAAC;AAEL;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAI,UAAU,MAAM,KAAG,KAAK,CAAC,SAAS,CAgBnE,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,GAC/B,UAAU,MAAM,GAAG,SAAS,EAC5B,SAAS,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,KAC7B,OAKF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,OAAO,GAAI,KAAK,MAAM,EAAE,gCAA6B,KAAG,MAYpE,CAAC"}
|
package/dist/path.js
CHANGED
|
@@ -42,6 +42,21 @@ export const parse_path_pieces = (raw_path) => {
|
|
|
42
42
|
}
|
|
43
43
|
return pieces;
|
|
44
44
|
};
|
|
45
|
+
/**
|
|
46
|
+
* Checks if a filename matches any exclusion pattern.
|
|
47
|
+
*
|
|
48
|
+
* Returns `false` when `filename` is `undefined`, empty string, or `exclude` is empty.
|
|
49
|
+
* String patterns use substring matching. RegExp patterns use `.test()`.
|
|
50
|
+
*
|
|
51
|
+
* @param filename The file path to check, or `undefined` for virtual files.
|
|
52
|
+
* @param exclude Array of string or RegExp exclusion patterns.
|
|
53
|
+
* @returns `true` if the file should be excluded from processing.
|
|
54
|
+
*/
|
|
55
|
+
export const should_exclude_path = (filename, exclude) => {
|
|
56
|
+
if (!filename || exclude.length === 0)
|
|
57
|
+
return false;
|
|
58
|
+
return exclude.some((pattern) => typeof pattern === 'string' ? filename.includes(pattern) : pattern.test(filename));
|
|
59
|
+
};
|
|
45
60
|
/**
|
|
46
61
|
* Converts a string into a URL-compatible slug.
|
|
47
62
|
* @param str the string to convert
|
package/dist/sort.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic topological sort using Kahn's algorithm.
|
|
3
|
+
*
|
|
4
|
+
* Orders items so that dependencies come before dependents.
|
|
5
|
+
* Works with any item type that has `id` and optional `depends_on`.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Minimum shape required for topological sorting.
|
|
11
|
+
*/
|
|
12
|
+
export interface Sortable {
|
|
13
|
+
id: string;
|
|
14
|
+
depends_on?: Array<string>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Result of topological sort.
|
|
18
|
+
*/
|
|
19
|
+
export type TopologicalSortResult<T extends Sortable> = {
|
|
20
|
+
ok: true;
|
|
21
|
+
sorted: Array<T>;
|
|
22
|
+
} | {
|
|
23
|
+
ok: false;
|
|
24
|
+
error: string;
|
|
25
|
+
cycle?: Array<string>;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Sort items by their dependencies using Kahn's algorithm.
|
|
29
|
+
*
|
|
30
|
+
* Returns items ordered so that dependencies come before dependents.
|
|
31
|
+
* If a cycle is detected, returns an error with the cycle path.
|
|
32
|
+
*
|
|
33
|
+
* @param items - Array of items to sort.
|
|
34
|
+
* @param label - Label for error messages (e.g. "resource", "step").
|
|
35
|
+
* @returns Sorted items or error if cycle detected.
|
|
36
|
+
*/
|
|
37
|
+
export declare const topological_sort: <T extends Sortable>(items: Array<T>, label?: string) => TopologicalSortResult<T>;
|
|
38
|
+
//# sourceMappingURL=sort.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sort.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/sort.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;GAEG;AACH,MAAM,WAAW,QAAQ;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,MAAM,qBAAqB,CAAC,CAAC,SAAS,QAAQ,IACjD;IAAC,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAA;CAAC,GAC5B;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;CAAC,CAAC;AAErD;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,GAAI,CAAC,SAAS,QAAQ,EAClD,OAAO,KAAK,CAAC,CAAC,CAAC,EACf,QAAO,MAAe,KACpB,qBAAqB,CAAC,CAAC,CAuEzB,CAAC"}
|