@fungi.computer/oyster 0.0.1 → 0.2.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/README.md +228 -176
- package/dist/agent-avatars.d.ts +103 -0
- package/dist/agent-avatars.js +66 -0
- package/dist/agents.d.ts +12 -2
- package/dist/agents.js +39 -2
- package/dist/app-builds.d.ts +76 -17
- package/dist/app-builds.js +207 -63
- package/dist/app-sources.d.ts +7 -22
- package/dist/app-sources.js +49 -14
- package/dist/apps/gate.d.ts +7 -7
- package/dist/apps/gate.js +6 -6
- package/dist/apps/installation.d.ts +35 -35
- package/dist/apps/installation.js +20 -19
- package/dist/build/artifact-writer.d.ts +40 -0
- package/dist/build/artifact-writer.js +181 -0
- package/dist/build/build-jobs.js +7 -1
- package/dist/build/build-policy.d.ts +10 -0
- package/dist/build/build-policy.js +10 -0
- package/dist/build/build-step.d.ts +12 -1
- package/dist/build/build-step.js +9 -1
- package/dist/build/candidate-builder.d.ts +68 -0
- package/dist/build/candidate-builder.js +91 -0
- package/dist/build/candidate-cli.d.ts +1 -0
- package/dist/build/candidate-cli.js +119 -0
- package/dist/build/candidate-cli.mjs +28220 -0
- package/dist/build/content-objects.d.ts +37 -0
- package/dist/build/content-objects.js +53 -0
- package/dist/build/effect.d.ts +18 -0
- package/dist/build/effect.js +52 -12
- package/dist/build/git-environment.d.ts +3 -0
- package/dist/build/git-environment.js +21 -9
- package/dist/build/local-build.d.ts +13 -3
- package/dist/build/local-build.js +149 -99
- package/dist/build/snapshot-package-store.d.ts +10 -0
- package/dist/build/snapshot-package-store.js +15 -0
- package/dist/build/snapshot-program-scripts.js +1 -1
- package/dist/build/snapshot-programs/snapshot-path.d.ts +2 -0
- package/dist/build/snapshot-programs/snapshot-path.js +6 -0
- package/dist/build/submission-contract.d.ts +461 -17
- package/dist/build/submission-contract.js +136 -26
- package/dist/build/workspace-build.d.ts +7 -2
- package/dist/build/workspace-build.js +55 -11
- package/dist/build-contract.d.ts +3 -0
- package/dist/build-contract.js +3 -0
- package/dist/build-egress.d.ts +62 -0
- package/dist/build-egress.js +148 -0
- package/dist/build.d.ts +1 -0
- package/dist/build.js +1 -0
- package/dist/cloudflare.js +4 -4
- package/dist/frame-binding-wire-script.js +1 -1
- package/dist/frame-binding-wire.d.ts +1 -1
- package/dist/frame-binding-wire.js +1 -1
- package/dist/git-journal.js +4 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/installation-consent.d.ts +3 -1
- package/dist/installation-consent.js +2 -0
- package/dist/mcp-owner.js +20 -8
- package/dist/pack.d.ts +6 -3
- package/dist/pack.js +28 -11
- package/dist/page/gill-observation.d.ts +4 -0
- package/dist/page/gill-observation.js +31 -0
- package/dist/page/mount.d.ts +4 -8
- package/dist/page/window-launch.d.ts +18 -0
- package/dist/page/window-launch.js +11 -0
- package/dist/page-script.js +4 -4
- package/dist/release.d.ts +11 -0
- package/dist/release.js +4 -1
- package/dist/shell/app-directory.d.ts +50 -45
- package/dist/shell/app-directory.js +19 -18
- package/dist/shell/shell-module.d.ts +26 -2
- package/dist/shell/shell-module.js +51 -2
- package/dist/shell/window-manager.d.ts +5 -1
- package/dist/shell/window-manager.js +21 -2
- package/dist/shell/workbench-directory.d.ts +332 -0
- package/dist/shell/workbench-directory.js +35 -0
- package/dist/shell/workbench-host-types.d.ts +1 -1
- package/dist/shell/workbench-host.js +5 -0
- package/dist/shell/workbench-model.d.ts +3 -1
- package/dist/source/index.d.ts +6 -7
- package/dist/source/index.js +6 -7
- package/dist/source/template-push.d.ts +33 -0
- package/dist/source/template-push.js +223 -0
- package/dist/source/template-repository.d.ts +2 -0
- package/dist/source/template-repository.js +13 -0
- package/dist/source/{template-write.d.ts → template-seed.d.ts} +19 -20
- package/dist/source/template-seed.js +80 -0
- package/dist/standalone/agent-directory.d.ts +4 -0
- package/dist/standalone/agent-directory.js +4 -0
- package/dist/standalone/agents.d.ts +227 -0
- package/dist/standalone/agents.js +834 -0
- package/dist/standalone/app-builds-binding.d.ts +36 -0
- package/dist/standalone/app-builds-binding.js +119 -0
- package/dist/standalone/app-set.d.ts +91 -0
- package/dist/standalone/app-set.js +395 -0
- package/dist/standalone/app-source.d.ts +41 -0
- package/dist/standalone/app-source.js +59 -0
- package/dist/standalone/builds.d.ts +18 -0
- package/dist/standalone/builds.js +101 -0
- package/dist/standalone/computer-egress.d.ts +37 -0
- package/dist/standalone/computer-egress.js +111 -0
- package/dist/standalone/computer.d.ts +122 -0
- package/dist/standalone/computer.js +714 -0
- package/dist/standalone/install-apps-cli.d.ts +2 -0
- package/dist/standalone/install-apps-cli.js +126 -0
- package/dist/standalone/instance.d.ts +35 -0
- package/dist/standalone/instance.js +74 -0
- package/dist/standalone/media.d.ts +76 -0
- package/dist/standalone/media.js +227 -0
- package/dist/standalone/preview.d.ts +51 -0
- package/dist/standalone/preview.js +167 -0
- package/dist/standalone/registry.d.ts +102 -0
- package/dist/standalone/registry.js +193 -0
- package/dist/standalone/tenancy.d.ts +42 -0
- package/dist/standalone/tenancy.js +241 -0
- package/dist/standalone/worker.d.ts +55 -0
- package/dist/standalone/worker.js +147 -0
- package/dist/standalone/workflow-authority-readers.d.ts +16 -0
- package/dist/standalone/workflow-authority-readers.js +35 -0
- package/dist/standalone/workflow-binding.d.ts +216 -0
- package/dist/standalone/workflow-binding.js +217 -0
- package/dist/standalone/workflow-events.d.ts +46 -0
- package/dist/standalone/workflow-events.js +39 -0
- package/dist/standalone/workflow-git-capture.d.ts +7 -0
- package/dist/standalone/workflow-git-capture.js +62 -0
- package/dist/standalone/workflow-host.d.ts +955 -0
- package/dist/standalone/workflow-host.js +778 -0
- package/dist/standalone/workflow-node-capture.d.ts +13 -0
- package/dist/standalone/workflow-node-capture.js +72 -0
- package/dist/standalone/workflow-root-authority.d.ts +27 -0
- package/dist/standalone/workflow-root-authority.js +87 -0
- package/dist/standalone/workflow-schedules.d.ts +52 -0
- package/dist/standalone/workflow-schedules.js +40 -0
- package/dist/standalone/workflow-trigger-host.d.ts +1716 -0
- package/dist/standalone/workflow-trigger-host.js +52 -0
- package/dist/standalone/workflows.d.ts +713 -0
- package/dist/standalone/workflows.js +554 -0
- package/dist/tenancy/auth0.d.ts +64 -0
- package/dist/tenancy/auth0.js +198 -0
- package/dist/tenancy/cloudflare/rate-limit.d.ts +8 -0
- package/dist/tenancy/cloudflare/rate-limit.js +19 -0
- package/dist/tenancy/cloudflare/registry.d.ts +50 -0
- package/dist/tenancy/cloudflare/registry.js +301 -0
- package/dist/tenancy/cloudflare.d.ts +2 -0
- package/dist/tenancy/cloudflare.js +2 -0
- package/dist/tenancy/cold-directory.d.ts +13 -0
- package/dist/tenancy/cold-directory.js +43 -0
- package/dist/tenancy/contracts.d.ts +195 -0
- package/dist/tenancy/contracts.js +1 -0
- package/dist/tenancy/index.d.ts +19 -0
- package/dist/tenancy/index.js +156 -0
- package/dist/tenancy/owner-key.d.ts +26 -0
- package/dist/tenancy/owner-key.js +63 -0
- package/dist/tenancy/oyster.d.ts +4 -0
- package/dist/tenancy/oyster.js +25 -0
- package/dist/tenancy/registry-record.d.ts +7 -0
- package/dist/tenancy/registry-record.js +44 -0
- package/dist/tenancy/registry.d.ts +27 -0
- package/dist/tenancy/registry.js +138 -0
- package/dist/tenancy/resolve.d.ts +7 -0
- package/dist/tenancy/resolve.js +64 -0
- package/dist/workers-git.js +1 -4
- package/dist/workers.d.ts +2 -2
- package/dist/workers.js +3 -1
- package/docs/api/build-contract.md +829 -32
- package/docs/api/build.md +253 -17
- package/docs/api/modules.md +40 -32
- package/docs/api/pack.md +6 -3
- package/docs/api/shell/app-directory.md +267 -0
- package/docs/api/shell/shell-module.md +64 -3
- package/docs/api/shell/workbench-directory.md +119 -0
- package/docs/api/shell/workbench.md +19 -0
- package/docs/api/shell.md +127 -0
- package/docs/api/tenancy/auth0.md +110 -0
- package/docs/api/tenancy/cloudflare.md +221 -0
- package/docs/api/tenancy/cold-directory.md +46 -0
- package/docs/api/tenancy/owner-key.md +63 -0
- package/docs/api/tenancy/registry.md +77 -0
- package/docs/api/tenancy/resolve.md +69 -0
- package/docs/api/tenancy.md +430 -0
- package/docs/api/workers-git.md +140 -21
- package/docs/api/workers.md +15 -2
- package/docs/site/agent-quickstart.mdx +52 -55
- package/examples/standalone/worker.ts +7 -14
- package/examples/standalone/wrangler.jsonc +67 -6
- package/package.json +78 -25
- package/dist/build/r2-artifact-writer.d.ts +0 -31
- package/dist/build/r2-artifact-writer.js +0 -179
- package/dist/source/template-write.js +0 -344
- package/docs/media/architecture.mmd +0 -32
- package/docs/media/architecture.png +0 -0
- package/docs/media/bonsai-network.png +0 -0
- package/docs/media/bonsai-repository.png +0 -0
- package/examples/git/worker.ts +0 -39
- package/examples/git/wrangler.jsonc +0 -32
|
@@ -7,9 +7,10 @@ function logEvent(operation, cause) {
|
|
|
7
7
|
failureKind: cause instanceof Error ? "Error" : "NonErrorThrown",
|
|
8
8
|
}));
|
|
9
9
|
}
|
|
10
|
-
import { frozenSnapshotInstallArguments } from "./snapshot-package-store.js";
|
|
11
|
-
import
|
|
12
|
-
import {
|
|
10
|
+
import { frozenSnapshotInstallArguments, packageScriptArguments, } from "./snapshot-package-store.js";
|
|
11
|
+
import { isInsideSnapshot } from "./snapshot-programs/snapshot-path.js";
|
|
12
|
+
import { writeImmutableArtifact, } from "./artifact-writer.js";
|
|
13
|
+
import { assetPathSchema, } from "@fungi.computer/puffball/assets";
|
|
13
14
|
import { resolveWorkspaceBuild, workspaceProjectsArguments, } from "./workspace-build.js";
|
|
14
15
|
import { readBundledHtml } from "./bundled-html.js";
|
|
15
16
|
import { createHash, randomBytes } from "node:crypto";
|
|
@@ -22,13 +23,12 @@ import { pipeline } from "node:stream/promises";
|
|
|
22
23
|
import { setTimeout as delay } from "node:timers/promises";
|
|
23
24
|
import { parseArtifactRef, parseSha256Digest, parseSourceRevision, } from "@fungi.computer/puffball";
|
|
24
25
|
import { appIdSchema } from "@fungi.computer/puffball/effect";
|
|
25
|
-
import { appBuildJsonSchema, createAppBuild, } from "@fungi.computer/puffball/app";
|
|
26
26
|
import * as Effect from "effect/Effect";
|
|
27
27
|
import * as Schema from "effect/Schema";
|
|
28
28
|
import * as esbuild from "esbuild";
|
|
29
29
|
import { readWantedLockfile, writeWantedLockfile } from "@pnpm/lockfile.fs";
|
|
30
30
|
import { pruneSharedLockfile } from "@pnpm/lockfile.pruner";
|
|
31
|
-
import { AppPublisherError, appBackendExportConditions, createAppPublisher, pinnedRecipeSchema, } from "./effect.js";
|
|
31
|
+
import { AppPublisherError, appBackendExportConditions, createAppPublisher, pinnedRecipeSchema, pinsCurrentToolchain, } from "./effect.js";
|
|
32
32
|
import { artifactsGitEnvironment, repositoryEnvironment, } from "./git-environment.js";
|
|
33
33
|
const localConfigSchema = Schema.Struct({
|
|
34
34
|
appId: appIdSchema,
|
|
@@ -44,9 +44,6 @@ function localRepositoryPath(input) {
|
|
|
44
44
|
return input;
|
|
45
45
|
throw new AppPublisherError("configuration");
|
|
46
46
|
}
|
|
47
|
-
function sha256(bytes) {
|
|
48
|
-
return parseSha256Digest(`sha256:${createHash("sha256").update(bytes).digest("hex")}`);
|
|
49
|
-
}
|
|
50
47
|
async function sha256File(path, signal) {
|
|
51
48
|
const hash = createHash("sha256");
|
|
52
49
|
await pipeline(createReadStream(path), hash, { signal });
|
|
@@ -332,6 +329,63 @@ async function prunePnpmLockfile(snapshotPath, isIncludedPath) {
|
|
|
332
329
|
await rm(scratch, { recursive: true, force: true });
|
|
333
330
|
}
|
|
334
331
|
}
|
|
332
|
+
/** The provenance file every distribution records beside the App source. */
|
|
333
|
+
function provenanceFile(appId, source) {
|
|
334
|
+
return {
|
|
335
|
+
path: "botanical-app-source.json",
|
|
336
|
+
contents: new TextEncoder().encode(JSON.stringify({
|
|
337
|
+
appId,
|
|
338
|
+
sourceRevision: source.sourceRevision,
|
|
339
|
+
sourceTreeDigest: source.sourceTreeDigest,
|
|
340
|
+
}, null, 2) + "\n"),
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
/** Builder state a source listing writes into the snapshot, never source. */
|
|
344
|
+
const snapshotScratch = new Set([".publisher-source-home", "node_modules"]);
|
|
345
|
+
/**
|
|
346
|
+
* An App repository's distribution: every captured file, unchanged, plus its
|
|
347
|
+
* provenance. The repository's own pnpm lockfile pins its registry packages.
|
|
348
|
+
*/
|
|
349
|
+
function repositoryAppSourceDistribution(appId, source) {
|
|
350
|
+
return Effect.tryPromise({
|
|
351
|
+
try: async () => {
|
|
352
|
+
const snapshot = realpathSync(resolve(source.snapshotPath));
|
|
353
|
+
const collected = new Map();
|
|
354
|
+
const collect = async (relativePath) => {
|
|
355
|
+
const sourcePath = join(snapshot, relativePath);
|
|
356
|
+
const stats = await lstat(sourcePath);
|
|
357
|
+
if (stats.isSymbolicLink())
|
|
358
|
+
throw new Error("Source distribution cannot contain symbolic links");
|
|
359
|
+
if (stats.isFile()) {
|
|
360
|
+
collected.set(relativePath, await readFile(sourcePath));
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
if (!stats.isDirectory())
|
|
364
|
+
throw new Error("Source distribution contains a non-file entry");
|
|
365
|
+
const entries = await readdir(sourcePath, { withFileTypes: true });
|
|
366
|
+
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
367
|
+
for (const entry of entries)
|
|
368
|
+
if (relativePath !== "" || !snapshotScratch.has(entry.name))
|
|
369
|
+
await collect(join(relativePath, entry.name));
|
|
370
|
+
};
|
|
371
|
+
await collect("");
|
|
372
|
+
const provenance = provenanceFile(appId, source);
|
|
373
|
+
if (collected.has(provenance.path))
|
|
374
|
+
throw new Error("Source distribution reserves its provenance path");
|
|
375
|
+
collected.set(provenance.path, provenance.contents);
|
|
376
|
+
return Object.freeze({
|
|
377
|
+
appId,
|
|
378
|
+
sourceRevision: source.sourceRevision,
|
|
379
|
+
sourceTreeDigest: source.sourceTreeDigest,
|
|
380
|
+
appDirectory: "",
|
|
381
|
+
files: [...collected]
|
|
382
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
383
|
+
.map(([path, contents]) => Object.freeze({ path, contents })),
|
|
384
|
+
});
|
|
385
|
+
},
|
|
386
|
+
catch: (error) => error,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
335
389
|
/**
|
|
336
390
|
* Project one pinned first-party App and its pnpm workspace dependency graph
|
|
337
391
|
* without changing upstream files or manufacturing Git history. The caller
|
|
@@ -352,6 +406,10 @@ export function createFirstPartyAppSourceDistribution(options) {
|
|
|
352
406
|
return Effect.gen(function* () {
|
|
353
407
|
const recipe = yield* capturedWorkspaceBuild(snapshot, options.entry, (args) => runHtmlCommand(args, snapshot, env));
|
|
354
408
|
const appDirectory = recipe.directory;
|
|
409
|
+
// An App's own repository is the App at its root: its editable template
|
|
410
|
+
// is that repository's whole captured tree, as forking it would copy.
|
|
411
|
+
if (appDirectory === "")
|
|
412
|
+
return yield* repositoryAppSourceDistribution(appId, options.source);
|
|
355
413
|
const listed = yield* runHtmlCommand([
|
|
356
414
|
...productionWorkspaceSelectors(recipe),
|
|
357
415
|
"list",
|
|
@@ -432,13 +490,10 @@ export function createFirstPartyAppSourceDistribution(options) {
|
|
|
432
490
|
await collect("patches");
|
|
433
491
|
if (collected.has("pnpm-lock.yaml"))
|
|
434
492
|
collected.set("pnpm-lock.yaml", await prunePnpmLockfile(snapshot, (importerPath) => collected.has(join(importerPath, "package.json"))));
|
|
435
|
-
|
|
493
|
+
const provenance = provenanceFile(appId, options.source);
|
|
494
|
+
if (collected.has(provenance.path))
|
|
436
495
|
throw new Error("Source distribution reserves its provenance path");
|
|
437
|
-
collected.set(
|
|
438
|
-
appId,
|
|
439
|
-
sourceRevision: options.source.sourceRevision,
|
|
440
|
-
sourceTreeDigest: options.source.sourceTreeDigest,
|
|
441
|
-
}, null, 2) + "\n"));
|
|
496
|
+
collected.set(provenance.path, provenance.contents);
|
|
442
497
|
return [...collected]
|
|
443
498
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
444
499
|
.map(([path, contents]) => Object.freeze({ path, contents }));
|
|
@@ -533,13 +588,13 @@ function runHtmlCommand(args, cwd, env, command = "pnpm") {
|
|
|
533
588
|
function snapshotPrograms() {
|
|
534
589
|
return import("#snapshot-program-scripts");
|
|
535
590
|
}
|
|
536
|
-
/** Compiler heap ceiling: fits a
|
|
591
|
+
/** Compiler heap ceiling: fits a 12 GiB Computer with two builds at once. */
|
|
537
592
|
const snapshotBuildHeapMegabytes = 4096;
|
|
538
593
|
function snapshotBuildEnvironment(sourceSnapshotPath) {
|
|
539
594
|
// PATH locates pinned executables; the Computer's CA files authenticate
|
|
540
595
|
// its egress gateway. Credentials and package-manager settings stay out.
|
|
541
|
-
// V8 sizes its default heap from the machine, about
|
|
542
|
-
// standard-
|
|
596
|
+
// V8 sizes its default heap from the machine, about 3 GB in a 12 GiB
|
|
597
|
+
// standard-4 Computer, where compiling Bonsai runs out; the build owns one
|
|
543
598
|
// fixed ceiling instead.
|
|
544
599
|
const env = {
|
|
545
600
|
PATH: process.env.PATH ?? "/usr/bin:/bin",
|
|
@@ -553,7 +608,7 @@ function snapshotBuildEnvironment(sourceSnapshotPath) {
|
|
|
553
608
|
};
|
|
554
609
|
return env;
|
|
555
610
|
}
|
|
556
|
-
function rebuildViteSnapshot(sourceSnapshotPath, recipe,
|
|
611
|
+
function rebuildViteSnapshot(sourceSnapshotPath, recipe, options) {
|
|
557
612
|
if (recipe.kind === "source-html")
|
|
558
613
|
return rebuildNpmSnapshot(sourceSnapshotPath, recipe);
|
|
559
614
|
return Effect.gen(function* () {
|
|
@@ -567,10 +622,16 @@ function rebuildViteSnapshot(sourceSnapshotPath, recipe, packageStoreDirectory)
|
|
|
567
622
|
try: () => checkPinnedWorkspaceToolchain(sourceSnapshotPath, html, recipe),
|
|
568
623
|
catch: (error) => error,
|
|
569
624
|
});
|
|
625
|
+
if (options.packageScripts !== "run" &&
|
|
626
|
+
html.steps.some((step) => step.kind === "script"))
|
|
627
|
+
return yield* Effect.fail(new Error("This source builds a dependency with its package script, which only a Computer build runs"));
|
|
628
|
+
const installStarted = performance.now();
|
|
570
629
|
yield* run([
|
|
571
630
|
...productionWorkspaceSelectors(html),
|
|
572
|
-
...frozenSnapshotInstallArguments(packageStoreDirectory),
|
|
631
|
+
...frozenSnapshotInstallArguments(options.packageStoreDirectory),
|
|
573
632
|
]);
|
|
633
|
+
const buildStarted = performance.now();
|
|
634
|
+
options.onPhase?.("install", buildStarted - installStarted);
|
|
574
635
|
const appDirectory = join(sourceSnapshotPath, html.directory);
|
|
575
636
|
const dependencies = join(appDirectory, ".publisher-dependencies.mjs");
|
|
576
637
|
const program = join(appDirectory, ".publisher-build.mjs");
|
|
@@ -593,8 +654,23 @@ function rebuildViteSnapshot(sourceSnapshotPath, recipe, packageStoreDirectory)
|
|
|
593
654
|
else
|
|
594
655
|
groups.push([step]);
|
|
595
656
|
}
|
|
596
|
-
for (const group of groups)
|
|
657
|
+
for (const group of groups) {
|
|
658
|
+
const [step] = group;
|
|
659
|
+
if (step?.kind === "script") {
|
|
660
|
+
// Exactly the named script, in its own package: no pre/post hooks or
|
|
661
|
+
// implicit install, whatever the captured repository's pnpm settings
|
|
662
|
+
// ask for. The script is arbitrary package code: its isolation is the
|
|
663
|
+
// admitted Computer, not this builder's compiler path checks.
|
|
664
|
+
const directory = resolve(sourceSnapshotPath, step.directory);
|
|
665
|
+
if (!isInsideSnapshot(sourceSnapshotPath, directory))
|
|
666
|
+
return yield* Effect.fail(new Error("Package script directory escapes the captured source"));
|
|
667
|
+
yield* run([...packageScriptArguments, step.script], directory);
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
597
670
|
yield* run([
|
|
671
|
+
// The frozen install above is the only install, whatever the
|
|
672
|
+
// captured pnpm settings ask `exec` to verify first.
|
|
673
|
+
"--config.verify-deps-before-run=false",
|
|
598
674
|
"exec",
|
|
599
675
|
"node",
|
|
600
676
|
dependencies,
|
|
@@ -602,8 +678,11 @@ function rebuildViteSnapshot(sourceSnapshotPath, recipe, packageStoreDirectory)
|
|
|
602
678
|
html.directory,
|
|
603
679
|
recipe.format,
|
|
604
680
|
JSON.stringify(group),
|
|
681
|
+
tailwindToolchainDirectory(html),
|
|
605
682
|
], appDirectory);
|
|
683
|
+
}
|
|
606
684
|
yield* run([
|
|
685
|
+
"--config.verify-deps-before-run=false",
|
|
607
686
|
"exec",
|
|
608
687
|
"node",
|
|
609
688
|
program,
|
|
@@ -616,9 +695,20 @@ function rebuildViteSnapshot(sourceSnapshotPath, recipe, packageStoreDirectory)
|
|
|
616
695
|
String(html.react),
|
|
617
696
|
html.shellEntry ?? "",
|
|
618
697
|
], appDirectory);
|
|
698
|
+
options.onPhase?.("build", performance.now() - buildStarted);
|
|
619
699
|
return yield* readHtmlBuildOutput(sourceSnapshotPath, recipe);
|
|
620
700
|
});
|
|
621
701
|
}
|
|
702
|
+
/**
|
|
703
|
+
* Where the captured Tailwind compiler lives: Caps, when the App builds Caps
|
|
704
|
+
* from its workspace, or else the App itself, which then pins the compiler
|
|
705
|
+
* and installs Caps' built stylesheet from a registry.
|
|
706
|
+
*/
|
|
707
|
+
function tailwindToolchainDirectory(html) {
|
|
708
|
+
return html.dependencies.includes("packages/caps")
|
|
709
|
+
? "packages/caps"
|
|
710
|
+
: html.directory;
|
|
711
|
+
}
|
|
622
712
|
const pinnedVersionsSchema = Schema.Record(Schema.String, Schema.String);
|
|
623
713
|
const pinnedManifestSchema = Schema.fromJsonString(Schema.Struct({
|
|
624
714
|
dependencies: Schema.optional(pinnedVersionsSchema),
|
|
@@ -656,9 +746,13 @@ async function checkPinnedWorkspaceToolchain(sourceSnapshotPath, html, recipe) {
|
|
|
656
746
|
if (stylesheets.length === 0)
|
|
657
747
|
return;
|
|
658
748
|
// The captured Tailwind compiler and its typography plugin come from Caps.
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
749
|
+
// An App that installs Caps from a registry pins the compiler itself, and
|
|
750
|
+
// Caps' own dependencies bring the typography plugin its stylesheet uses.
|
|
751
|
+
const toolchain = tailwindToolchainDirectory(html);
|
|
752
|
+
const compiler = await manifest(toolchain);
|
|
753
|
+
pinned(toolchain, compiler.devDependencies, "@tailwindcss/cli", "4.2.1");
|
|
754
|
+
if (toolchain === "packages/caps")
|
|
755
|
+
pinned(toolchain, compiler.dependencies, "@tailwindcss/typography", "0.5.19");
|
|
662
756
|
for (const directory of html.dependencies) {
|
|
663
757
|
if (!stylesheets.some((step) => step.input.startsWith(`${directory}/`)))
|
|
664
758
|
continue;
|
|
@@ -876,7 +970,7 @@ export function createLocalBuildExecutor(options = {}) {
|
|
|
876
970
|
try: () => readBundledHtml(source.snapshotPath, recipe),
|
|
877
971
|
catch: () => new Error("Captured HTML bundle failed validation"),
|
|
878
972
|
})
|
|
879
|
-
: rebuildViteSnapshot(source.snapshotPath, recipe, options
|
|
973
|
+
: rebuildViteSnapshot(source.snapshotPath, recipe, options);
|
|
880
974
|
const code = yield* compiler.build(source, {
|
|
881
975
|
bundle: true,
|
|
882
976
|
entry,
|
|
@@ -905,7 +999,7 @@ export function createLocalBuildExecutor(options = {}) {
|
|
|
905
999
|
});
|
|
906
1000
|
}
|
|
907
1001
|
return recipe.format !== "esm"
|
|
908
|
-
? rebuildViteSnapshot(source.snapshotPath, recipe, options
|
|
1002
|
+
? rebuildViteSnapshot(source.snapshotPath, recipe, options)
|
|
909
1003
|
: Effect.acquireUseRelease(Effect.tryPromise({
|
|
910
1004
|
try: () => {
|
|
911
1005
|
const emission = emissions.get(source);
|
|
@@ -935,6 +1029,8 @@ export function createLocalBuildExecutor(options = {}) {
|
|
|
935
1029
|
const declarations = new WeakMap();
|
|
936
1030
|
return {
|
|
937
1031
|
build: (source, recipe) => Effect.gen(function* () {
|
|
1032
|
+
if (!pinsCurrentToolchain(recipe))
|
|
1033
|
+
return yield* Effect.fail(new Error("Recipe pins another build toolchain or policy"));
|
|
938
1034
|
const emission = yield* Effect.tryPromise({
|
|
939
1035
|
try: () => createDefinitionEmission(source, recipe),
|
|
940
1036
|
catch: (cause) => cause instanceof Error
|
|
@@ -960,83 +1056,37 @@ export function createLocalBuildExecutor(options = {}) {
|
|
|
960
1056
|
},
|
|
961
1057
|
};
|
|
962
1058
|
}
|
|
1059
|
+
/** The configured artifact directory as write-once immutable objects. */
|
|
1060
|
+
function directoryArtifactObjects(artifactRoot, signal) {
|
|
1061
|
+
const pathOf = (artifactRef) => join(artifactRoot, artifactRef.slice("artifact:".length));
|
|
1062
|
+
return {
|
|
1063
|
+
async create(artifactRef, bytes) {
|
|
1064
|
+
try {
|
|
1065
|
+
await writeFile(pathOf(artifactRef), bytes, { flag: "wx", signal });
|
|
1066
|
+
return "created";
|
|
1067
|
+
}
|
|
1068
|
+
catch (error) {
|
|
1069
|
+
if (error instanceof Error &&
|
|
1070
|
+
"code" in error &&
|
|
1071
|
+
error.code === "EEXIST")
|
|
1072
|
+
return "exists";
|
|
1073
|
+
throw error;
|
|
1074
|
+
}
|
|
1075
|
+
finally {
|
|
1076
|
+
signal.throwIfAborted();
|
|
1077
|
+
}
|
|
1078
|
+
},
|
|
1079
|
+
async read(artifactRef) {
|
|
1080
|
+
return new Uint8Array(await readFile(pathOf(artifactRef)));
|
|
1081
|
+
},
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
963
1084
|
async function writeArtifact(output, artifactRoot, signal, assetPolicy) {
|
|
964
1085
|
signal.throwIfAborted();
|
|
965
|
-
const ui = output.kind === "backend" ? null : output.ui;
|
|
966
1086
|
await mkdir(artifactRoot, { recursive: true });
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
const artifactPath = join(artifactRoot, artifactRef.slice("artifact:".length));
|
|
971
|
-
try {
|
|
972
|
-
await writeFile(artifactPath, bytes, { flag: "wx", signal });
|
|
973
|
-
}
|
|
974
|
-
catch (error) {
|
|
975
|
-
const code = error instanceof Error && "code" in error ? error.code : undefined;
|
|
976
|
-
if (code !== "EEXIST" || sha256(await readFile(artifactPath)) !== digest)
|
|
977
|
-
throw error;
|
|
978
|
-
}
|
|
979
|
-
signal.throwIfAborted();
|
|
980
|
-
return {
|
|
981
|
-
artifactRef,
|
|
982
|
-
digest,
|
|
983
|
-
version: digest,
|
|
984
|
-
byteLength: bytes.byteLength,
|
|
985
|
-
};
|
|
986
|
-
};
|
|
987
|
-
const uiBytes = await (async () => {
|
|
988
|
-
if (ui === null)
|
|
989
|
-
return null;
|
|
990
|
-
if (ui.content.kind === "html")
|
|
991
|
-
return ui.content.bytes.slice();
|
|
992
|
-
if (!assetPolicy)
|
|
993
|
-
throw new Error("Asset writer requires explicit policy");
|
|
994
|
-
const content = {};
|
|
995
|
-
for (const asset of ui.content.assets) {
|
|
996
|
-
const path = Schema.decodeUnknownSync(assetPathSchema)(asset.path);
|
|
997
|
-
if (Object.hasOwn(content, path))
|
|
998
|
-
throw new Error("Duplicate build path");
|
|
999
|
-
content[path] = new Uint8Array(asset.bytes).buffer;
|
|
1000
|
-
}
|
|
1001
|
-
const assets = [];
|
|
1002
|
-
for (const path of Object.keys(content).sort()) {
|
|
1003
|
-
const bytes = content[path];
|
|
1004
|
-
const mediaType = mime.getType(path);
|
|
1005
|
-
const contentType = mediaType?.startsWith("text/")
|
|
1006
|
-
? `${mediaType}; charset=utf-8`
|
|
1007
|
-
: mediaType;
|
|
1008
|
-
if (!bytes || !contentType)
|
|
1009
|
-
throw new Error("Build asset has no maintained content type");
|
|
1010
|
-
assets.push({
|
|
1011
|
-
path,
|
|
1012
|
-
...(await persist(new Uint8Array(bytes))),
|
|
1013
|
-
contentType,
|
|
1014
|
-
});
|
|
1015
|
-
}
|
|
1016
|
-
const manifest = new TextEncoder().encode(Schema.encodeUnknownSync(assetBuildJsonSchema)({
|
|
1017
|
-
format: "fungi-assets-v1",
|
|
1018
|
-
base: ui.content.base,
|
|
1019
|
-
assets,
|
|
1020
|
-
}));
|
|
1021
|
-
decodeAssetBuild(manifest, assetPolicy);
|
|
1022
|
-
return manifest;
|
|
1023
|
-
})();
|
|
1024
|
-
const storedUi = ui === null || uiBytes === null
|
|
1025
|
-
? null
|
|
1026
|
-
: { ...(await persist(uiBytes)), mediaType: ui.mediaType };
|
|
1027
|
-
const backendBytes = output.kind === "ui" ? null : output.backend;
|
|
1028
|
-
const storedBackend = backendBytes === null
|
|
1029
|
-
? null
|
|
1030
|
-
: await persist(backendBytes.slice()).then((stored) => ({
|
|
1031
|
-
...stored,
|
|
1032
|
-
mediaType: "application/vnd.fungi.app-backend+json",
|
|
1033
|
-
}));
|
|
1034
|
-
const root = new TextEncoder().encode(Schema.encodeUnknownSync(appBuildJsonSchema)(createAppBuild(storedUi, storedBackend, output.declaration)));
|
|
1035
|
-
const rootRef = `artifact:${Buffer.from(randomBytes(16)).toString("hex")}`;
|
|
1036
|
-
const storedRoot = await persist(root, rootRef);
|
|
1037
|
-
return Object.freeze({
|
|
1038
|
-
artifactRef: storedRoot.artifactRef,
|
|
1039
|
-
artifactDigest: storedRoot.digest,
|
|
1087
|
+
return writeImmutableArtifact(output, directoryArtifactObjects(artifactRoot, signal), {
|
|
1088
|
+
rootArtifactRef: parseArtifactRef(`artifact:${Buffer.from(randomBytes(16)).toString("hex")}`),
|
|
1089
|
+
assetPolicy,
|
|
1040
1090
|
});
|
|
1041
1091
|
}
|
|
1042
1092
|
/** Local proving adapter; the configured root is private and write-once. */
|
|
@@ -1,2 +1,12 @@
|
|
|
1
1
|
/** Frozen hydration policy. Cached bytes are verified and copied into each snapshot. */
|
|
2
2
|
export declare function frozenSnapshotInstallArguments(packageStoreDirectory?: string): string[];
|
|
3
|
+
/**
|
|
4
|
+
* `pnpm run` for one declared package build script, which follows `--` so
|
|
5
|
+
* pnpm selects that literal name (never an option or a `/pattern/`). pnpm 11
|
|
6
|
+
* runs `pre<name>`/`post<name>` by default, and installs first when it judges
|
|
7
|
+
* dependencies stale, running their lifecycle scripts; a captured `.npmrc` or
|
|
8
|
+
* `pnpm-workspace.yaml` may also ask for either. These command line settings
|
|
9
|
+
* win, so the frozen, script-free install stays the only install. What the
|
|
10
|
+
* named script itself runs is that package's own build.
|
|
11
|
+
*/
|
|
12
|
+
export declare const packageScriptArguments: readonly string[];
|
|
@@ -19,3 +19,18 @@ export function frozenSnapshotInstallArguments(packageStoreDirectory) {
|
|
|
19
19
|
"--reporter=append-only",
|
|
20
20
|
];
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* `pnpm run` for one declared package build script, which follows `--` so
|
|
24
|
+
* pnpm selects that literal name (never an option or a `/pattern/`). pnpm 11
|
|
25
|
+
* runs `pre<name>`/`post<name>` by default, and installs first when it judges
|
|
26
|
+
* dependencies stale, running their lifecycle scripts; a captured `.npmrc` or
|
|
27
|
+
* `pnpm-workspace.yaml` may also ask for either. These command line settings
|
|
28
|
+
* win, so the frozen, script-free install stays the only install. What the
|
|
29
|
+
* named script itself runs is that package's own build.
|
|
30
|
+
*/
|
|
31
|
+
export const packageScriptArguments = Object.freeze([
|
|
32
|
+
"run",
|
|
33
|
+
"--config.enable-pre-post-scripts=false",
|
|
34
|
+
"--config.verify-deps-before-run=false",
|
|
35
|
+
"--",
|
|
36
|
+
]);
|