@fungi.computer/bonsai 0.2.2 → 0.2.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @fungi.computer/bonsai
2
2
 
3
+ ## 0.2.4
4
+
5
+ ### Patch Changes
6
+
7
+ - Changes since 0.2.3:
8
+
9
+ - fix(oyster): ship starter dependencies and continue failed installs
10
+ - fix(oyster): bound builder failures and interrupt lost builds
11
+
12
+ - Updated dependencies
13
+ - Updated dependencies
14
+ - @fungi.computer/oyster@0.4.1
15
+ - @fungi.computer/trellis@0.2.3
16
+
17
+ ## 0.2.3
18
+
19
+ ### Patch Changes
20
+
21
+ - Updated dependencies
22
+ - Updated dependencies
23
+ - Updated dependencies
24
+ - @fungi.computer/caps@0.3.0
25
+ - @fungi.computer/issues@0.2.0
26
+ - @fungi.computer/oyster@0.4.0
27
+ - @fungi.computer/agar@0.3.1
28
+ - @fungi.computer/trellis@0.2.2
29
+ - @fungi.computer/braid@0.2.2
30
+ - @fungi.computer/nacre@0.0.2
31
+ - @fungi.computer/workflows-wire@0.2.2
32
+
3
33
  ## 0.2.2
4
34
 
5
35
  ### Patch Changes
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@fungi.computer/bonsai",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "author": "Levi <levi@technomancy.dev>",
5
5
  "type": "module",
6
6
  "description": "Bonsai: a git platform for many agents working at once, on Workers and Artifacts",
7
7
  "dependencies": {
8
- "@fungi.computer/agar": "0.3.0",
9
- "@fungi.computer/caps": "0.2.0",
10
- "@fungi.computer/issues": "0.1.0",
11
- "@fungi.computer/oyster": "0.3.2",
8
+ "@fungi.computer/agar": "0.3.1",
9
+ "@fungi.computer/caps": "0.3.0",
10
+ "@fungi.computer/issues": "0.2.0",
11
+ "@fungi.computer/oyster": "0.4.1",
12
12
  "@fungi.computer/puffball": "0.2.0",
13
13
  "@fungi.computer/sigils": "0.2.0",
14
14
  "@fungi.computer/stipe": "0.1.0",
@@ -29,10 +29,10 @@
29
29
  "@fungi.computer/maosaico": "0.1.0",
30
30
  "@visx/hierarchy": "3.12.0",
31
31
  "@fungi.computer/gills": "0.2.0",
32
- "@fungi.computer/nacre": "0.0.1",
33
- "@fungi.computer/trellis": "0.2.1",
34
- "@fungi.computer/braid": "0.2.1",
35
- "@fungi.computer/workflows-wire": "0.2.1"
32
+ "@fungi.computer/nacre": "0.0.2",
33
+ "@fungi.computer/trellis": "0.2.3",
34
+ "@fungi.computer/braid": "0.2.2",
35
+ "@fungi.computer/workflows-wire": "0.2.2"
36
36
  },
37
37
  "devDependencies": {
38
38
  "playwright": "1.62.1",
@@ -52,7 +52,7 @@
52
52
  "eslint": "10.9.1",
53
53
  "oxlint": "1.78.0",
54
54
  "jsdom": "29.0.1",
55
- "@fungi.computer/computer-contract": "0.0.2"
55
+ "@fungi.computer/computer-contract": "0.0.3"
56
56
  },
57
57
  "botanical": {
58
58
  "role": "app",
@@ -176,7 +176,7 @@
176
176
  "typecheck": "tsc -p tsconfig.json --noEmit",
177
177
  "lint": "eslint backend web shared test --max-warnings=0 && oxlint --config ../../oxlint.config.ts --deny-warnings backend web shared test",
178
178
  "test": "pnpm run test:unit && pnpm run test:merge",
179
- "test:unit": "vitest run && node --import tsx --test test/seed-resume.test.mjs test/insight-layout.test.mjs test/checks-workflow-template.test.mjs test/merge-template-build.test.mjs test/storyboard-save-template.test.mjs test/favicon.test.mjs",
179
+ "test:unit": "vitest run && node --import tsx --test test/seed-resume.test.mjs test/insight-layout.test.mjs test/checks-workflow-template.test.mjs test/merge-template-build.test.mjs test/storyboard-save-template.test.mjs test/favicon.test.mjs test/packed-trellis.test.mjs",
180
180
  "test:merge": "node --import tsx test/git-push.native.mjs",
181
181
  "format:check": "node ../../tooling/repository/scripts/format-package-check.mjs",
182
182
  "lint:anti-slop": "node ../../tooling/repository/scripts/lint-package-anti-slop.mjs",
@@ -420,7 +420,7 @@ export const appBuildView = z.strictObject({
420
420
  appId: z.string().min(1).max(256),
421
421
  releaseId: z.string().min(1).max(256),
422
422
  sourceRevision: z.string().min(1).max(256),
423
- status: z.enum(["running", "succeeded", "failed", "revoked"]),
423
+ status: z.enum(["running", "succeeded", "failed", "interrupted", "revoked"]),
424
424
  detail: z.string().max(65_536).nullable(),
425
425
  /** The recorded Release, parsed by its owner where it is installed. */
426
426
  release: z.unknown(),
@@ -0,0 +1,138 @@
1
+ import assert from "node:assert/strict";
2
+ import { execFileSync } from "node:child_process";
3
+ import { mkdtempSync, rmSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join, posix, resolve } from "node:path";
6
+ import test from "node:test";
7
+ import ts from "typescript";
8
+
9
+ const root = resolve(import.meta.dirname, "../../..");
10
+
11
+ test("packed Bonsai imports and Tailwind sources stay in its snapshot and packed Trellis", () => {
12
+ const out = mkdtempSync(join(tmpdir(), "bonsai-pack-"));
13
+ try {
14
+ const packed = (name) => {
15
+ const archive = execFileSync(
16
+ "pnpm",
17
+ ["--dir", `apps/${name}`, "pack", "--pack-destination", out],
18
+ { cwd: root, encoding: "utf8" },
19
+ )
20
+ .trim()
21
+ .split("\n")
22
+ .at(-1);
23
+ assert.ok(archive, `${name} packed`);
24
+ const paths = new Set(
25
+ execFileSync("tar", ["tzf", archive], { encoding: "utf8" })
26
+ .trim()
27
+ .split("\n")
28
+ .map((path) => path.replace(/^package\//u, "")),
29
+ );
30
+ const read = (path) =>
31
+ execFileSync("tar", ["xOzf", archive, `package/${path}`], {
32
+ encoding: "utf8",
33
+ });
34
+ return { paths, read };
35
+ };
36
+ const bonsai = packed("bonsai");
37
+ const trellis = packed("trellis");
38
+ const packages = { bonsai, trellis };
39
+ const source = (name, path) => {
40
+ const archive = packages[name];
41
+ assert.ok(archive, name);
42
+ assert.ok(archive.paths.has(path), `${name} ships ${path}`);
43
+ return archive.read(path);
44
+ };
45
+ const resolveSource = (name, from, specifier) => {
46
+ let path;
47
+ let owner = name;
48
+ if (specifier.startsWith("@fungi.computer/trellis/")) {
49
+ owner = "trellis";
50
+ const exportName = `./${specifier.slice("@fungi.computer/trellis/".length)}`;
51
+ const target = JSON.parse(source("trellis", "package.json")).exports[
52
+ exportName
53
+ ];
54
+ assert.ok(target, `Trellis exports ${exportName}`);
55
+ path = target.replace(/^\.\//u, "");
56
+ } else {
57
+ path = posix.normalize(posix.join(posix.dirname(from), specifier));
58
+ assert.ok(
59
+ !path.startsWith("../"),
60
+ `${name}/${from}: ${specifier} escapes`,
61
+ );
62
+ }
63
+ const found = [
64
+ path,
65
+ `${path}.ts`,
66
+ `${path}.tsx`,
67
+ `${path}.json`,
68
+ path.replace(/\.js$/u, ".ts"),
69
+ path.replace(/\.js$/u, ".tsx"),
70
+ posix.join(path, "index.ts"),
71
+ ].find((candidate) => packages[owner].paths.has(candidate));
72
+ assert.ok(
73
+ found,
74
+ `${name}/${from}: ${specifier} must be shipped by ${owner}`,
75
+ );
76
+ return [owner, found];
77
+ };
78
+ const visited = new Set();
79
+ const inspect = (name, path) => {
80
+ if (visited.has(`${name}/${path}`)) return;
81
+ visited.add(`${name}/${path}`);
82
+ const text = source(name, path);
83
+ if (path.endsWith(".css")) {
84
+ for (const [, specifier] of text.matchAll(/@source\s+"([^"]+)"/gu)) {
85
+ if (specifier === "./") continue;
86
+ if (specifier.startsWith("./") || specifier.startsWith("../")) {
87
+ const directory = posix.normalize(
88
+ posix.join(posix.dirname(path), specifier),
89
+ );
90
+ assert.ok(
91
+ !directory.startsWith("../"),
92
+ `${name}/${path}: @source escapes snapshot`,
93
+ );
94
+ const dependency = "node_modules/@fungi.computer/trellis/";
95
+ const owner = directory.startsWith(dependency) ? "trellis" : name;
96
+ const target =
97
+ owner === "trellis"
98
+ ? directory.slice(dependency.length)
99
+ : directory;
100
+ assert.ok(
101
+ [...packages[owner].paths].some(
102
+ (file) => file.startsWith(`${target}/`) || file === target,
103
+ ),
104
+ `${name}/${path}: @source ${specifier} has no shipped files`,
105
+ );
106
+ } else {
107
+ assert.fail(`${name}/${path}: unsupported @source ${specifier}`);
108
+ }
109
+ }
110
+ return;
111
+ }
112
+ const ast = ts.createSourceFile(path, text, ts.ScriptTarget.Latest, true);
113
+ const visit = (node) => {
114
+ const specifier =
115
+ ts.isImportDeclaration(node) || ts.isExportDeclaration(node)
116
+ ? node.moduleSpecifier
117
+ : undefined;
118
+ if (
119
+ specifier &&
120
+ ts.isStringLiteral(specifier) &&
121
+ (specifier.text.startsWith(".") ||
122
+ specifier.text.startsWith("@fungi.computer/trellis/"))
123
+ ) {
124
+ const [owner, target] = resolveSource(name, path, specifier.text);
125
+ if (/\.(?:ts|tsx|js|css)$/u.test(target)) inspect(owner, target);
126
+ }
127
+ ts.forEachChild(node, visit);
128
+ };
129
+ visit(ast);
130
+ };
131
+ for (const path of bonsai.paths) {
132
+ if (/^(web|backend|shared)\/.*\.(?:ts|tsx|css)$/u.test(path))
133
+ inspect("bonsai", path);
134
+ }
135
+ } finally {
136
+ rmSync(out, { recursive: true, force: true });
137
+ }
138
+ });
@@ -236,6 +236,11 @@ export function AppBuildPanel({ repo }: { repo: string }) {
236
236
  : ""}
237
237
  </p>
238
238
  )}
239
+ {settled?.status === "interrupted" ? (
240
+ <p role="status">
241
+ Build interrupted. Start a new build to retry.
242
+ </p>
243
+ ) : null}
239
244
  {status.isError ? <p role="alert">{status.error.message}</p> : null}
240
245
  {settled?.installation?.kind === "needs_consent" ? (
241
246
  <p role="status">
package/web/bonsai.css CHANGED
@@ -1,4 +1,4 @@
1
- @source "../../trellis/src/runs";
1
+ @source "../node_modules/@fungi.computer/trellis/src/runs";
2
2
  @import "@fungi.computer/caps/tailwind.css";
3
3
  @source "./";
4
4