@heychunky/core 0.29.1 → 0.30.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.
@@ -0,0 +1,57 @@
1
+ /**
2
+ * What an app is made of, and what a change to one part would reach.
3
+ *
4
+ * An app is chunks and chunks depend on each other, which is the shape that
5
+ * decides whether a change is small. Nothing showed it, and the chunk READMEs
6
+ * that describe it in prose go stale the first time somebody deletes an import
7
+ * and not a sentence.
8
+ *
9
+ * So the edges are read rather than restated. `@chunks/<name>` is the boundary
10
+ * the app itself declares in its manifest aliases, so a scan of the chunk
11
+ * sources is the whole graph — the same discipline as everything else here:
12
+ * derive the fact, never keep a second copy of it.
13
+ *
14
+ * Three callers want this. The console draws it, the CLI prints it, and a run
15
+ * reads it to know when it has left its chunk, so it lives where none of them
16
+ * owns it.
17
+ */
18
+ /** One chunk, and what it reaches. */
19
+ export interface ChunkNode {
20
+ name: string;
21
+ /** Chunks this one imports from, sorted. */
22
+ dependsOn: string[];
23
+ /** Chunks that import this one, sorted. */
24
+ dependedOnBy: string[];
25
+ /**
26
+ * Every chunk reachable by following dependents — what a change here could
27
+ * break. The number that answers "is this a small change".
28
+ */
29
+ blastRadius: string[];
30
+ }
31
+ export interface ChunkGraph {
32
+ chunks: ChunkNode[];
33
+ /**
34
+ * Pairs that import each other.
35
+ *
36
+ * Reported because they are the reason a blast radius is twelve rather than
37
+ * two: a cycle collapses everything it touches into one cluster, and an app
38
+ * whose graph has cycles has no clean layering to read.
39
+ */
40
+ cycles: [string, string][];
41
+ }
42
+ /** A chunk's sources, as the scan needs them: its name and its file contents. */
43
+ export interface ChunkSource {
44
+ name: string;
45
+ /** Every text file in the chunk. Contents only; paths are not needed. */
46
+ files: string[];
47
+ }
48
+ /**
49
+ * Build the graph from a set of chunks and their sources.
50
+ *
51
+ * Pure, so it can be tested against a real app's shape without a network, and
52
+ * so the expensive half — fetching the sources — is somebody else's problem.
53
+ * An edge to a chunk that is not in the set is dropped: an app importing
54
+ * something it does not have is a broken app, not an extra node.
55
+ */
56
+ export declare function chunkGraph(sources: readonly ChunkSource[]): ChunkGraph;
57
+ //# sourceMappingURL=graph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../src/admin/graph.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,sCAAsC;AACtC,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,2CAA2C;IAC3C,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB;;;OAGG;IACH,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB;;;;;;OAMG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;CAC5B;AAED,iFAAiF;AACjF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAWD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,UAAU,CAoCtE"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * What an app is made of, and what a change to one part would reach.
3
+ *
4
+ * An app is chunks and chunks depend on each other, which is the shape that
5
+ * decides whether a change is small. Nothing showed it, and the chunk READMEs
6
+ * that describe it in prose go stale the first time somebody deletes an import
7
+ * and not a sentence.
8
+ *
9
+ * So the edges are read rather than restated. `@chunks/<name>` is the boundary
10
+ * the app itself declares in its manifest aliases, so a scan of the chunk
11
+ * sources is the whole graph — the same discipline as everything else here:
12
+ * derive the fact, never keep a second copy of it.
13
+ *
14
+ * Three callers want this. The console draws it, the CLI prints it, and a run
15
+ * reads it to know when it has left its chunk, so it lives where none of them
16
+ * owns it.
17
+ */
18
+ /**
19
+ * An import of another chunk, by the alias an app declares for its chunks root.
20
+ *
21
+ * Deliberately not a TypeScript parse. The alias is the boundary, so the name
22
+ * after it is the edge — and a scan that cannot be wrong about syntax it does
23
+ * not read is worth more here than one that understands every import form.
24
+ */
25
+ const EDGE = /@chunks\/([a-z0-9][a-z0-9-]*)/gi;
26
+ /**
27
+ * Build the graph from a set of chunks and their sources.
28
+ *
29
+ * Pure, so it can be tested against a real app's shape without a network, and
30
+ * so the expensive half — fetching the sources — is somebody else's problem.
31
+ * An edge to a chunk that is not in the set is dropped: an app importing
32
+ * something it does not have is a broken app, not an extra node.
33
+ */
34
+ export function chunkGraph(sources) {
35
+ const names = new Set(sources.map((s) => s.name));
36
+ const out = new Map();
37
+ for (const { name, files } of sources) {
38
+ const found = new Set();
39
+ for (const text of files) {
40
+ for (const [, to] of text.matchAll(EDGE)) {
41
+ const chunk = to.toLowerCase();
42
+ // A chunk importing itself is a file next door, not an edge.
43
+ if (chunk !== name && names.has(chunk))
44
+ found.add(chunk);
45
+ }
46
+ }
47
+ out.set(name, found);
48
+ }
49
+ const into = new Map([...names].map((n) => [n, new Set()]));
50
+ for (const [from, tos] of out) {
51
+ for (const to of tos)
52
+ into.get(to).add(from);
53
+ }
54
+ const chunks = [...names].sort().map((name) => ({
55
+ name,
56
+ dependsOn: [...out.get(name)].sort(),
57
+ dependedOnBy: [...into.get(name)].sort(),
58
+ blastRadius: [...reach(name, into)].sort(),
59
+ }));
60
+ const cycles = [];
61
+ for (const [a, tos] of out) {
62
+ for (const b of tos) {
63
+ if (a < b && out.get(b)?.has(a))
64
+ cycles.push([a, b]);
65
+ }
66
+ }
67
+ cycles.sort(([a], [b]) => a.localeCompare(b));
68
+ return { chunks, cycles };
69
+ }
70
+ /**
71
+ * Everything reachable by following dependents, excluding the start.
72
+ *
73
+ * Iterative rather than recursive because the graphs that most need this are
74
+ * the ones with cycles, and a cycle is where a naive recursion does not come
75
+ * back.
76
+ */
77
+ function reach(start, into) {
78
+ const seen = new Set();
79
+ const stack = [start];
80
+ while (stack.length) {
81
+ for (const up of into.get(stack.pop()) ?? []) {
82
+ if (up !== start && !seen.has(up)) {
83
+ seen.add(up);
84
+ stack.push(up);
85
+ }
86
+ }
87
+ }
88
+ return seen;
89
+ }
90
+ //# sourceMappingURL=graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.js","sourceRoot":"","sources":["../../src/admin/graph.ts"],"names":[],"mappings":"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"}
@@ -28,6 +28,7 @@ export * from "./appkeys.ts";
28
28
  export * from "./runs.ts";
29
29
  export * from "./entrypoints.ts";
30
30
  export * from "./chunks.ts";
31
+ export * from "./graph.ts";
31
32
  export * from "./services/github.ts";
32
33
  export * from "./services/vercel.ts";
33
34
  export * from "./services/neon.ts";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAG5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAG3B,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC"}
@@ -28,6 +28,7 @@ export * from "./appkeys.js";
28
28
  export * from "./runs.js";
29
29
  export * from "./entrypoints.js";
30
30
  export * from "./chunks.js";
31
+ export * from "./graph.js";
31
32
  // One file per service, matching the registry.
32
33
  export * from "./services/github.js";
33
34
  export * from "./services/vercel.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAE5B,+CAA+C;AAC/C,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAE3B,+CAA+C;AAC/C,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heychunky/core",
3
- "version": "0.29.1",
3
+ "version": "0.30.0",
4
4
  "description": "The engine the Chunky CLI, worker and apps run on.",
5
5
  "license": "MIT",
6
6
  "type": "module",