@fungi.computer/oyster 0.0.0-stage → 0.0.1

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.
Files changed (242) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1082 -2
  3. package/dist/agents.d.ts +114 -0
  4. package/dist/agents.js +322 -0
  5. package/dist/app-builds.d.ts +151 -0
  6. package/dist/app-builds.js +355 -0
  7. package/dist/app-sandbox.d.ts +2 -0
  8. package/dist/app-sandbox.js +2 -0
  9. package/dist/app-sources.d.ts +46 -0
  10. package/dist/app-sources.js +335 -0
  11. package/dist/app-storage.d.ts +141 -0
  12. package/dist/app-storage.js +281 -0
  13. package/dist/apps/gate.d.ts +105 -0
  14. package/dist/apps/gate.js +273 -0
  15. package/dist/apps/index.d.ts +5 -0
  16. package/dist/apps/index.js +5 -0
  17. package/dist/apps/installation.d.ts +248 -0
  18. package/dist/apps/installation.js +307 -0
  19. package/dist/apps/operations.d.ts +8 -0
  20. package/dist/apps/operations.js +98 -0
  21. package/dist/apps/tools.d.ts +33 -0
  22. package/dist/apps/tools.js +17 -0
  23. package/dist/auth.d.ts +11 -0
  24. package/dist/auth.js +93 -0
  25. package/dist/backend-request.d.ts +3 -0
  26. package/dist/backend-request.js +53 -0
  27. package/dist/build/build-jobs.d.ts +128 -0
  28. package/dist/build/build-jobs.js +197 -0
  29. package/dist/build/build-step.d.ts +26 -0
  30. package/dist/build/build-step.js +20 -0
  31. package/dist/build/bundled-html.d.ts +7 -0
  32. package/dist/build/bundled-html.js +64 -0
  33. package/dist/build/definition-emission.d.ts +34 -0
  34. package/dist/build/definition-emission.js +201 -0
  35. package/dist/build/effect.d.ts +231 -0
  36. package/dist/build/effect.js +396 -0
  37. package/dist/build/git-environment.d.ts +6 -0
  38. package/dist/build/git-environment.js +48 -0
  39. package/dist/build/html-recipe.d.ts +20 -0
  40. package/dist/build/html-recipe.js +51 -0
  41. package/dist/build/local-build.d.ts +57 -0
  42. package/dist/build/local-build.js +1078 -0
  43. package/dist/build/r2-artifact-writer.d.ts +31 -0
  44. package/dist/build/r2-artifact-writer.js +179 -0
  45. package/dist/build/snapshot-package-store.d.ts +2 -0
  46. package/dist/build/snapshot-package-store.js +21 -0
  47. package/dist/build/snapshot-program-scripts.d.ts +6 -0
  48. package/dist/build/snapshot-program-scripts.js +2 -0
  49. package/dist/build/submission-contract.d.ts +384 -0
  50. package/dist/build/submission-contract.js +70 -0
  51. package/dist/build/workspace-build.d.ts +50 -0
  52. package/dist/build/workspace-build.js +156 -0
  53. package/dist/build-contract.d.ts +4 -0
  54. package/dist/build-contract.js +4 -0
  55. package/dist/build-jobs.d.ts +2 -0
  56. package/dist/build-jobs.js +2 -0
  57. package/dist/build.d.ts +11 -0
  58. package/dist/build.js +13 -0
  59. package/dist/capability-descriptor.d.ts +46 -0
  60. package/dist/capability-descriptor.js +51 -0
  61. package/dist/catalog-frame.d.ts +39 -0
  62. package/dist/catalog-frame.js +72 -0
  63. package/dist/cause-class.d.ts +19 -0
  64. package/dist/cause-class.js +54 -0
  65. package/dist/cloudflare.d.ts +123 -0
  66. package/dist/cloudflare.js +391 -0
  67. package/dist/computer-terminal.d.ts +38 -0
  68. package/dist/computer-terminal.js +410 -0
  69. package/dist/computer.d.ts +60 -0
  70. package/dist/computer.js +33 -0
  71. package/dist/event-recipients.d.ts +57 -0
  72. package/dist/event-recipients.js +144 -0
  73. package/dist/facet-invocation.d.ts +4 -0
  74. package/dist/facet-invocation.js +45 -0
  75. package/dist/files.d.ts +42 -0
  76. package/dist/files.js +123 -0
  77. package/dist/frame-binding-wire-script.d.ts +2 -0
  78. package/dist/frame-binding-wire-script.js +1 -0
  79. package/dist/frame-binding-wire.d.ts +28 -0
  80. package/dist/frame-binding-wire.js +54 -0
  81. package/dist/git-admission.d.ts +52 -0
  82. package/dist/git-admission.js +188 -0
  83. package/dist/git-auth.d.ts +11 -0
  84. package/dist/git-auth.js +27 -0
  85. package/dist/git-door.d.ts +35 -0
  86. package/dist/git-door.js +108 -0
  87. package/dist/git-journal.d.ts +31 -0
  88. package/dist/git-journal.js +188 -0
  89. package/dist/git.d.ts +31 -0
  90. package/dist/git.js +64 -0
  91. package/dist/guest-policy.d.ts +9 -0
  92. package/dist/guest-policy.js +13 -0
  93. package/dist/host.d.ts +244 -0
  94. package/dist/host.js +1043 -0
  95. package/dist/index.d.ts +166 -0
  96. package/dist/index.js +320 -0
  97. package/dist/installation-consent.d.ts +79 -0
  98. package/dist/installation-consent.js +82 -0
  99. package/dist/internal/background-origins.d.ts +3 -0
  100. package/dist/internal/background-origins.js +8 -0
  101. package/dist/internal/delivery.d.ts +12 -0
  102. package/dist/internal/delivery.js +167 -0
  103. package/dist/internal/host-gate.d.ts +14 -0
  104. package/dist/internal/host-gate.js +62 -0
  105. package/dist/internal/registry-installations.d.ts +6 -0
  106. package/dist/internal/registry-installations.js +12 -0
  107. package/dist/invocation.d.ts +251 -0
  108. package/dist/invocation.js +377 -0
  109. package/dist/keys.d.ts +48 -0
  110. package/dist/keys.js +52 -0
  111. package/dist/mcp-owner.d.ts +27 -0
  112. package/dist/mcp-owner.js +143 -0
  113. package/dist/mcp.d.ts +89 -0
  114. package/dist/mcp.js +429 -0
  115. package/dist/media.d.ts +23 -0
  116. package/dist/media.js +12 -0
  117. package/dist/pack.d.ts +76 -0
  118. package/dist/pack.js +134 -0
  119. package/dist/page/capabilities.d.ts +23 -0
  120. package/dist/page/capabilities.js +256 -0
  121. package/dist/page/computer-handles.d.ts +79 -0
  122. package/dist/page/computer-handles.js +44 -0
  123. package/dist/page/lifetime.d.ts +6 -0
  124. package/dist/page/lifetime.js +19 -0
  125. package/dist/page/mount.d.ts +163 -0
  126. package/dist/page/mount.js +390 -0
  127. package/dist/page/storage.d.ts +3 -0
  128. package/dist/page/storage.js +82 -0
  129. package/dist/page/terminal.d.ts +5 -0
  130. package/dist/page/terminal.js +7 -0
  131. package/dist/page-script.d.ts +14 -0
  132. package/dist/page-script.js +5 -0
  133. package/dist/r2-files.d.ts +3 -0
  134. package/dist/r2-files.js +79 -0
  135. package/dist/release.d.ts +72 -0
  136. package/dist/release.js +164 -0
  137. package/dist/root-data.d.ts +86 -0
  138. package/dist/root-data.js +69 -0
  139. package/dist/shell/app-directory.d.ts +537 -0
  140. package/dist/shell/app-directory.js +195 -0
  141. package/dist/shell/browser-actions.d.ts +7 -0
  142. package/dist/shell/browser-actions.js +56 -0
  143. package/dist/shell/command-host.d.ts +112 -0
  144. package/dist/shell/command-host.js +502 -0
  145. package/dist/shell/commands.d.ts +25 -0
  146. package/dist/shell/commands.js +78 -0
  147. package/dist/shell/index.d.ts +8 -0
  148. package/dist/shell/index.js +8 -0
  149. package/dist/shell/keys.d.ts +41 -0
  150. package/dist/shell/keys.js +134 -0
  151. package/dist/shell/shell-module.d.ts +31 -0
  152. package/dist/shell/shell-module.js +37 -0
  153. package/dist/shell/window-manager.d.ts +124 -0
  154. package/dist/shell/window-manager.js +711 -0
  155. package/dist/shell/workbench-contract.d.ts +68 -0
  156. package/dist/shell/workbench-contract.js +1 -0
  157. package/dist/shell/workbench-host-types.d.ts +96 -0
  158. package/dist/shell/workbench-host-types.js +1 -0
  159. package/dist/shell/workbench-host.d.ts +34 -0
  160. package/dist/shell/workbench-host.js +347 -0
  161. package/dist/shell/workbench-model.d.ts +192 -0
  162. package/dist/shell/workbench-model.js +53 -0
  163. package/dist/shell/workbench.d.ts +4 -0
  164. package/dist/shell/workbench.js +2 -0
  165. package/dist/source/fork.d.ts +27 -0
  166. package/dist/source/fork.js +46 -0
  167. package/dist/source/index.d.ts +11 -0
  168. package/dist/source/index.js +11 -0
  169. package/dist/source/seed-receipt.d.ts +53 -0
  170. package/dist/source/seed-receipt.js +18 -0
  171. package/dist/source/team-source.d.ts +32 -0
  172. package/dist/source/team-source.js +42 -0
  173. package/dist/source/template-repository.d.ts +2 -0
  174. package/dist/source/template-repository.js +8 -0
  175. package/dist/source/template-write.d.ts +69 -0
  176. package/dist/source/template-write.js +344 -0
  177. package/dist/storage-frame.d.ts +64 -0
  178. package/dist/storage-frame.js +28 -0
  179. package/dist/tenant.d.ts +81 -0
  180. package/dist/tenant.js +71 -0
  181. package/dist/testing/capabilities.d.ts +29 -0
  182. package/dist/testing/capabilities.js +130 -0
  183. package/dist/testing/catalog.d.ts +3 -0
  184. package/dist/testing/catalog.js +53 -0
  185. package/dist/testing/index.d.ts +10 -0
  186. package/dist/testing/index.js +10 -0
  187. package/dist/testing/installation.d.ts +11 -0
  188. package/dist/testing/installation.js +25 -0
  189. package/dist/testing/native-worker.d.ts +21 -0
  190. package/dist/testing/native-worker.js +106 -0
  191. package/dist/testing/worker.d.ts +18 -0
  192. package/dist/testing/worker.js +67 -0
  193. package/dist/testing/workers-runtime.d.ts +24 -0
  194. package/dist/testing/workers-runtime.js +33 -0
  195. package/dist/webhook-delivery.d.ts +14 -0
  196. package/dist/webhook-delivery.js +17 -0
  197. package/dist/workers-git.d.ts +285 -0
  198. package/dist/workers-git.js +1174 -0
  199. package/dist/workers.d.ts +352 -0
  200. package/dist/workers.js +866 -0
  201. package/docs/api/README.md +82 -0
  202. package/docs/api/agents.md +332 -0
  203. package/docs/api/app-storage.md +209 -0
  204. package/docs/api/apps.md +1375 -0
  205. package/docs/api/auth.md +63 -0
  206. package/docs/api/build-contract.md +507 -0
  207. package/docs/api/build.md +1476 -0
  208. package/docs/api/cloudflare.md +421 -0
  209. package/docs/api/computer.md +203 -0
  210. package/docs/api/files.md +117 -0
  211. package/docs/api/git.md +470 -0
  212. package/docs/api/host.md +1328 -0
  213. package/docs/api/index.md +430 -0
  214. package/docs/api/invocation.md +738 -0
  215. package/docs/api/keys.md +175 -0
  216. package/docs/api/mcp.md +301 -0
  217. package/docs/api/media.md +40 -0
  218. package/docs/api/modules.md +42 -0
  219. package/docs/api/pack.md +138 -0
  220. package/docs/api/page/mount.md +298 -0
  221. package/docs/api/root-data.md +111 -0
  222. package/docs/api/shell/app-directory.md +332 -0
  223. package/docs/api/shell/command-host.md +224 -0
  224. package/docs/api/shell/commands.md +197 -0
  225. package/docs/api/shell/keys.md +183 -0
  226. package/docs/api/shell/shell-module.md +143 -0
  227. package/docs/api/shell/window-manager.md +267 -0
  228. package/docs/api/shell/workbench.md +186 -0
  229. package/docs/api/shell.md +1788 -0
  230. package/docs/api/tenant.md +194 -0
  231. package/docs/api/workers-git.md +1527 -0
  232. package/docs/api/workers.md +1678 -0
  233. package/docs/media/architecture.mmd +32 -0
  234. package/docs/media/architecture.png +0 -0
  235. package/docs/media/bonsai-network.png +0 -0
  236. package/docs/media/bonsai-repository.png +0 -0
  237. package/docs/site/agent-quickstart.mdx +349 -0
  238. package/examples/git/worker.ts +39 -0
  239. package/examples/git/wrangler.jsonc +32 -0
  240. package/examples/standalone/worker.ts +14 -0
  241. package/examples/standalone/wrangler.jsonc +21 -0
  242. package/package.json +249 -3
@@ -0,0 +1,396 @@
1
+ import { assetBuildPolicySchema, assetBaseSchema, } from "@fungi.computer/puffball/assets";
2
+ import { boundedBundlePolicy, bundledHtmlRecipe } from "./html-recipe.js";
3
+ import * as crypto from "node:crypto";
4
+ import { backendJsonSchema, durableBackendSchema, } from "@fungi.computer/puffball/backend";
5
+ import { parseSha256Digest, } from "@fungi.computer/puffball";
6
+ import { appIdSchema } from "@fungi.computer/puffball/effect";
7
+ import * as Effect from "effect/Effect";
8
+ import * as Schema from "effect/Schema";
9
+ const relativeEntrySchema = Schema.String.pipe(Schema.check(Schema.isMinLength(1)), Schema.check(Schema.makeFilter((entry) => {
10
+ if (entry.startsWith("/") || entry.includes("\\"))
11
+ return false;
12
+ return entry
13
+ .split("/")
14
+ .every((part) => part !== "" && part !== "." && part !== "..");
15
+ })));
16
+ /** Canonical recipe policy shared by core and the local adapter. */
17
+ export const pinnedRecipeSchema = Schema.Struct({
18
+ kind: Schema.optional(Schema.Literals(["bundled-html", "source-html"])),
19
+ entry: relativeEntrySchema,
20
+ assets: Schema.optional(assetBuildPolicySchema),
21
+ assetBase: Schema.optionalKey(assetBaseSchema),
22
+ backend: Schema.Union([
23
+ Schema.Null,
24
+ Schema.Struct({
25
+ entry: Schema.optional(relativeEntrySchema),
26
+ runtime: durableBackendSchema.fields.runtime,
27
+ compatibilityDate: durableBackendSchema.fields.compatibilityDate,
28
+ exportName: durableBackendSchema.fields.exportName,
29
+ }),
30
+ ]),
31
+ maxOutputBytes: Schema.Number.pipe(Schema.check(Schema.isInt()), Schema.check(Schema.makeFilter((value) => Number.isFinite(value))), Schema.check(Schema.isGreaterThan(0))),
32
+ });
33
+ /** Core configuration contains only immutable App identity and recipe policy. */
34
+ export const AppPublisherCoreConfigSchema = Schema.Struct({
35
+ appId: appIdSchema,
36
+ recipe: pinnedRecipeSchema,
37
+ });
38
+ const esmToolchain = Object.freeze({ esbuild: "0.28.2" });
39
+ const sourceHtmlToolchain = Object.freeze({
40
+ ...esmToolchain,
41
+ node: "24.20.0",
42
+ npm: "11.19.0",
43
+ vite: "8.2.2",
44
+ pluginReact: "6.1.1",
45
+ recipe: "captured-npm-source-html-v1",
46
+ packageResolution: "captured-browser-module-main-v1",
47
+ });
48
+ /**
49
+ * The one pinned toolchain for an App page captured with its pnpm workspace.
50
+ * The App's own manifest declares its page policy and the package graph
51
+ * orders its dependency builds, so both are pinned by the source tree digest.
52
+ */
53
+ const workspaceHtmlToolchain = Object.freeze({
54
+ node: "24.20.0",
55
+ pnpm: "11.24.0",
56
+ vite: "8.2.2",
57
+ pluginReact: "6.1.1",
58
+ singlefile: "2.3.3",
59
+ react: "18.3.1",
60
+ typescript: "5.9.3",
61
+ tailwindcss: "4.2.1",
62
+ tailwindNode: "4.2.1",
63
+ tailwindOxide: "4.2.1",
64
+ tailwindResolver: "5.24.5",
65
+ typography: "0.5.19",
66
+ buildPolicy: "package-json-fungi-build-v1",
67
+ recipe: "captured-workspace-html-v1",
68
+ });
69
+ const workspaceHtmlBackendToolchain = Object.freeze({
70
+ ...workspaceHtmlToolchain,
71
+ ...esmToolchain,
72
+ packageResolution: "captured-browser-module-main-v1",
73
+ });
74
+ /** Package-export conditions used by the pinned Workers builder. */
75
+ export const appBackendExportConditions = Object.freeze([
76
+ "workerd",
77
+ "worker",
78
+ "browser",
79
+ ]);
80
+ /** Persisted, fully resolved recipe including its code-owned toolchain. */
81
+ export const capturedPinnedRecipeSchema = Schema.Struct({
82
+ kind: pinnedRecipeSchema.fields.kind,
83
+ bundle: Schema.Literal(true),
84
+ entry: relativeEntrySchema,
85
+ format: Schema.Literals(["html", "esm", "assets"]),
86
+ assets: Schema.optional(assetBuildPolicySchema),
87
+ assetBase: Schema.optionalKey(assetBaseSchema),
88
+ toolchain: Schema.Record(Schema.String, Schema.String),
89
+ maxOutputBytes: Schema.Number.pipe(Schema.check(Schema.isInt()), Schema.check(Schema.isGreaterThan(0))),
90
+ platform: Schema.Literals(["browser", "neutral"]),
91
+ backend: pinnedRecipeSchema.fields.backend,
92
+ target: Schema.Tuple([Schema.Literal("es2022")]),
93
+ });
94
+ /** Closed failure boundary for the four private composition stages. */
95
+ export class AppPublisherError extends Error {
96
+ stage;
97
+ _tag = "AppPublisherError";
98
+ constructor(stage, options) {
99
+ super(`App Publisher ${stage} stage failed`, options);
100
+ this.stage = stage;
101
+ this.name = this._tag;
102
+ }
103
+ }
104
+ function sha256Digest(value) {
105
+ return parseSha256Digest(`sha256:${crypto.createHash("sha256").update(value, "utf8").digest("hex")}`);
106
+ }
107
+ function captureConfig(input) {
108
+ let decoded;
109
+ try {
110
+ decoded = Schema.decodeUnknownSync(AppPublisherCoreConfigSchema)(input);
111
+ }
112
+ catch {
113
+ throw new AppPublisherError("configuration");
114
+ }
115
+ const bundled = decoded.recipe.kind === bundledHtmlRecipe.kind;
116
+ const sourceHtml = decoded.recipe.kind === "source-html";
117
+ // Any other HTML entry is a page built with its own pnpm workspace; the
118
+ // App's manifest, read from the captured source, declares how.
119
+ const workspaceHtml = decoded.recipe.kind === undefined && decoded.recipe.entry.endsWith(".html");
120
+ const html = workspaceHtml || bundled || sourceHtml;
121
+ if (sourceHtml &&
122
+ (!decoded.recipe.entry.endsWith(".html") ||
123
+ !decoded.recipe.assets ||
124
+ !boundedBundlePolicy(decoded.recipe.assets, decoded.recipe.maxOutputBytes)))
125
+ throw new AppPublisherError("configuration");
126
+ if (bundled) {
127
+ const policy = decoded.recipe.assets;
128
+ if (!policy ||
129
+ !decoded.recipe.entry.endsWith("/index.html") ||
130
+ !boundedBundlePolicy(policy, decoded.recipe.maxOutputBytes))
131
+ throw new AppPublisherError("configuration");
132
+ }
133
+ if ((html &&
134
+ decoded.recipe.backend !== null &&
135
+ decoded.recipe.backend.entry === undefined) ||
136
+ (!html && decoded.recipe.backend?.entry !== undefined))
137
+ throw new AppPublisherError("configuration");
138
+ if (decoded.recipe.assets && !html)
139
+ throw new AppPublisherError("configuration");
140
+ if (Boolean(decoded.recipe.assets) !==
141
+ (decoded.recipe.assetBase !== undefined))
142
+ throw new AppPublisherError("configuration");
143
+ const recipeBase = {
144
+ bundle: true,
145
+ entry: decoded.recipe.entry,
146
+ format: decoded.recipe.assets
147
+ ? "assets"
148
+ : workspaceHtml
149
+ ? "html"
150
+ : "esm",
151
+ toolchain: sourceHtml
152
+ ? sourceHtmlToolchain
153
+ : bundled
154
+ ? decoded.recipe.backend === null
155
+ ? bundledHtmlRecipe.toolchain
156
+ : { ...bundledHtmlRecipe.toolchain, ...esmToolchain, pnpm: "11.24.0" }
157
+ : workspaceHtml
158
+ ? decoded.recipe.backend === null
159
+ ? workspaceHtmlToolchain
160
+ : workspaceHtmlBackendToolchain
161
+ : esmToolchain,
162
+ maxOutputBytes: decoded.recipe.maxOutputBytes,
163
+ platform: decoded.recipe.backend === null
164
+ ? "browser"
165
+ : "neutral",
166
+ backend: decoded.recipe.backend,
167
+ target: ["es2022"],
168
+ };
169
+ const resolvedBase = decoded.recipe.kind !== undefined
170
+ ? { ...recipeBase, kind: decoded.recipe.kind }
171
+ : recipeBase;
172
+ const recipe = Object.freeze(decoded.recipe.assets && decoded.recipe.assetBase !== undefined
173
+ ? {
174
+ ...resolvedBase,
175
+ assets: Object.freeze({ ...decoded.recipe.assets }),
176
+ assetBase: decoded.recipe.assetBase,
177
+ }
178
+ : resolvedBase);
179
+ return Object.freeze({
180
+ appId: decoded.appId,
181
+ recipe,
182
+ buildRecipeDigest: digestPinnedRecipe(recipe),
183
+ });
184
+ }
185
+ /** Hash every captured build and output-policy input in the Publisher recipe. */
186
+ export function digestPinnedRecipe(recipe) {
187
+ const buildMaterial = {
188
+ bundle: recipe.bundle,
189
+ entry: recipe.entry,
190
+ format: recipe.format,
191
+ toolchain: recipe.toolchain,
192
+ };
193
+ const outputPolicy = {
194
+ maxOutputBytes: recipe.maxOutputBytes,
195
+ backendMinify: recipe.backend !== null,
196
+ platform: recipe.platform,
197
+ backend: recipe.backend,
198
+ target: recipe.target,
199
+ };
200
+ const outputMaterial = recipe.backend === null
201
+ ? outputPolicy
202
+ : { ...outputPolicy, conditions: appBackendExportConditions };
203
+ const vite = {
204
+ configFile: false,
205
+ envDir: false,
206
+ envPrefix: [],
207
+ cssCodeSplit: recipe.format === "assets",
208
+ inlineDynamicImports: recipe.format !== "assets",
209
+ assetsInlineLimit: recipe.format === "assets" ? 0 : recipe.maxOutputBytes,
210
+ output: recipe.format === "assets"
211
+ ? "immutable-asset-build"
212
+ : "one-complete-html",
213
+ css: "compiled-public-caps-stipe",
214
+ dependencyInputs: "contained-before-probe-typescript-host-tailwind-core-loaders",
215
+ subprocesses: "owned-process-group-term-kill-5-seconds",
216
+ };
217
+ const recipeVite = recipe.assets
218
+ ? {
219
+ ...vite,
220
+ assetPolicy: recipe.assets,
221
+ base: recipe.assetBase,
222
+ cssUrls: "tailwind-node-4.2.1-after-contained-root-check",
223
+ }
224
+ : vite;
225
+ const recipeMaterial = JSON.stringify(recipe.kind === bundledHtmlRecipe.kind
226
+ ? {
227
+ ...buildMaterial,
228
+ ...outputMaterial,
229
+ bundledHtmlRecipe,
230
+ assetPolicy: recipe.assets,
231
+ base: recipe.assetBase,
232
+ }
233
+ : recipe.format !== "esm"
234
+ ? {
235
+ ...buildMaterial,
236
+ hydration: {
237
+ frozenLockfile: true,
238
+ ignoreScripts: true,
239
+ ignorePnpmfile: true,
240
+ packageImportMethod: "auto",
241
+ store: "snapshot-local",
242
+ virtualStore: "snapshot-local",
243
+ environment: "tool-path-only-snapshot-home-no-user-or-global-npmrc",
244
+ },
245
+ vite: recipeVite,
246
+ ...outputMaterial,
247
+ }
248
+ : { ...buildMaterial, ...outputMaterial });
249
+ return sha256Digest(recipeMaterial);
250
+ }
251
+ /** Decode and normalize Publisher-owned recipe policy for durable seed proof. */
252
+ export function normalizeAppPublisherRecipe(input) {
253
+ const captured = captureConfig(input);
254
+ return Object.freeze({
255
+ recipe: captured.recipe,
256
+ buildRecipeDigest: captured.buildRecipeDigest,
257
+ });
258
+ }
259
+ function createReleaseBoundary(catalog, release) {
260
+ return Effect.tryPromise({
261
+ try: () => catalog.createRelease(release),
262
+ catch: () => new AppPublisherError("release"),
263
+ });
264
+ }
265
+ function stageFailure(stage) {
266
+ return new AppPublisherError(stage);
267
+ }
268
+ function prepareUiOutput(output, recipe) {
269
+ switch (output.kind) {
270
+ case "bytes":
271
+ return recipe.format === "html" &&
272
+ output.bytes.byteLength <= recipe.maxOutputBytes
273
+ ? {
274
+ content: { kind: "html", bytes: output.bytes },
275
+ mediaType: "text/html; charset=utf-8",
276
+ }
277
+ : undefined;
278
+ case "assets":
279
+ return recipe.format === "assets" && output.base === recipe.assetBase
280
+ ? {
281
+ content: output,
282
+ mediaType: "application/vnd.fungi.app-assets+json",
283
+ }
284
+ : undefined;
285
+ }
286
+ }
287
+ function validPreparedUiOutput(ui, recipe) {
288
+ switch (ui.content.kind) {
289
+ case "html":
290
+ return (ui.mediaType === "text/html; charset=utf-8" &&
291
+ ui.content.bytes.byteLength <= recipe.maxOutputBytes);
292
+ case "assets":
293
+ return (ui.mediaType === "application/vnd.fungi.app-assets+json" &&
294
+ recipe.assets !== undefined &&
295
+ ui.content.base === recipe.assetBase);
296
+ }
297
+ }
298
+ function encodeBackendOutput(bytes, recipe) {
299
+ if (recipe.backend === null)
300
+ return Effect.fail(stageFailure("build"));
301
+ return Schema.encodeEffect(backendJsonSchema)({
302
+ ...recipe.backend,
303
+ code: new TextDecoder().decode(bytes),
304
+ }).pipe(Effect.map((encoded) => new TextEncoder().encode(encoded)), Effect.filterOrFail((encoded) => encoded.byteLength <= recipe.maxOutputBytes, () => stageFailure("build")), Effect.mapError(() => stageFailure("build")));
305
+ }
306
+ function assignAppOutput(built, recipe) {
307
+ switch (built.kind) {
308
+ case "bytes":
309
+ case "assets":
310
+ return assignRawOutput(built, recipe);
311
+ case "app":
312
+ return assignJoinedOutput(built, recipe);
313
+ }
314
+ }
315
+ function assignRawOutput(built, recipe) {
316
+ const ui = prepareUiOutput(built, recipe);
317
+ if (recipe.backend === null)
318
+ return ui === undefined
319
+ ? Effect.fail(stageFailure("build"))
320
+ : Effect.succeed({ kind: "ui", ui });
321
+ if (built.kind !== "bytes")
322
+ return Effect.fail(stageFailure("build"));
323
+ return encodeBackendOutput(built.bytes, recipe).pipe(Effect.map((backend) => ({ kind: "backend", backend })));
324
+ }
325
+ function assignJoinedOutput(built, recipe) {
326
+ const ui = built.ui;
327
+ if (ui !== null && !validPreparedUiOutput(ui, recipe))
328
+ return Effect.fail(stageFailure("build"));
329
+ if (built.backend === null) {
330
+ if (ui === null || recipe.backend !== null)
331
+ return Effect.fail(stageFailure("build"));
332
+ return Effect.succeed({ kind: "ui", ui });
333
+ }
334
+ return encodeBackendOutput(built.backend, recipe).pipe(Effect.map((backend) => ui === null ? { kind: "backend", backend } : { kind: "app", ui, backend }));
335
+ }
336
+ /** Construct one private App Publisher with copied core configuration and roles. */
337
+ export function createAppCandidatePreparer(options) {
338
+ return createCapturedCandidatePreparer(captureConfig(options.config), options);
339
+ }
340
+ /** Prepare a candidate from a previously captured recipe. */
341
+ export function createCapturedCandidatePreparer(config, options) {
342
+ const { sourceCapture, buildExecutor } = options;
343
+ const prepareCandidate = (releaseId) => Effect.scoped(Effect.gen(function* () {
344
+ const source = yield* sourceCapture
345
+ .capture()
346
+ .pipe(Effect.mapError(() => stageFailure("source")));
347
+ const built = yield* buildExecutor
348
+ .build(source, config.recipe)
349
+ .pipe(Effect.mapError((error) => new AppPublisherError("build", { cause: error })));
350
+ const assigned = yield* assignAppOutput(built, config.recipe);
351
+ const declaration = buildExecutor.declaration === undefined
352
+ ? undefined
353
+ : yield* buildExecutor
354
+ .declaration(source, config.recipe)
355
+ .pipe(Effect.mapError((error) => new AppPublisherError("build", { cause: error })));
356
+ const output = declaration === undefined ? assigned : { ...assigned, declaration };
357
+ return {
358
+ appId: config.appId,
359
+ releaseId,
360
+ output,
361
+ provenance: {
362
+ sourceRevision: source.sourceRevision,
363
+ sourceTreeDigest: source.sourceTreeDigest,
364
+ buildRecipeDigest: config.buildRecipeDigest,
365
+ },
366
+ };
367
+ }));
368
+ return Object.freeze({ prepareCandidate });
369
+ }
370
+ /** Persist a prepared build through the native artifact and App Release owners. */
371
+ export function commitPreparedCandidate(prepared, options) {
372
+ return Effect.gen(function* () {
373
+ const stored = yield* options.artifactWriter
374
+ .write(prepared.output)
375
+ .pipe(Effect.mapError(() => stageFailure("artifact")));
376
+ const release = {
377
+ releaseId: prepared.releaseId,
378
+ appId: prepared.appId,
379
+ artifact: {
380
+ artifactRef: stored.artifactRef,
381
+ artifactDigest: stored.artifactDigest,
382
+ provenance: prepared.provenance,
383
+ },
384
+ };
385
+ return yield* createReleaseBoundary(options.releaseCatalog, release);
386
+ });
387
+ }
388
+ /** Compose capture, compilation, immutable writing and Release recording. */
389
+ export function createAppPublisher(options) {
390
+ const preparer = createAppCandidatePreparer(options);
391
+ const createCandidate = (releaseId) => preparer.prepareCandidate(releaseId).pipe(Effect.flatMap((prepared) => commitPreparedCandidate(prepared, {
392
+ artifactWriter: options.artifactWriter,
393
+ releaseCatalog: options.releaseCatalog,
394
+ })));
395
+ return Object.freeze({ createCandidate });
396
+ }
@@ -0,0 +1,6 @@
1
+ /** Run a nested repository without inheriting the caller's Git checkout. */
2
+ export declare function repositoryEnvironment(): NodeJS.ProcessEnv;
3
+ /** Git environment for one authenticated Artifacts fetch; caller validates token. */
4
+ export declare function artifactsGitEnvironment(token: string): NodeJS.ProcessEnv;
5
+ /** Credential-free private route used by the hosted Publisher Git client. */
6
+ export declare function hostedArtifactsGitRemote(endpoint: string, repository: string): string;
@@ -0,0 +1,48 @@
1
+ /** Run a nested repository without inheriting the caller's Git checkout. */
2
+ export function repositoryEnvironment() {
3
+ return Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith("GIT_")));
4
+ }
5
+ /** Git environment for one authenticated Artifacts fetch; caller validates token. */
6
+ export function artifactsGitEnvironment(token) {
7
+ const excludedEnvironmentNames = new Set([
8
+ "GIT_CURL_VERBOSE",
9
+ "CLOUDFLARE_API_TOKEN",
10
+ "ARTIFACTS_API_TOKEN",
11
+ "ARTIFACTS_REPO_TOKEN",
12
+ "ARTIFACTS_TOKEN",
13
+ ]);
14
+ const environment = Object.fromEntries(Object.entries(repositoryEnvironment()).filter(([name]) => !name.startsWith("GIT_TRACE") && !excludedEnvironmentNames.has(name)));
15
+ return {
16
+ ...environment,
17
+ GIT_CONFIG_KEY_0: "http.extraHeader",
18
+ GIT_CONFIG_VALUE_0: "",
19
+ GIT_CONFIG_KEY_1: "http.extraHeader",
20
+ GIT_CONFIG_VALUE_1: `Authorization: Bearer ${token}`,
21
+ GIT_CONFIG_KEY_2: "http.followRedirects",
22
+ GIT_CONFIG_VALUE_2: "false",
23
+ GIT_CONFIG_COUNT: "3",
24
+ GIT_TRACE: "0",
25
+ GIT_TRACE_CURL: "0",
26
+ };
27
+ }
28
+ /** Credential-free private route used by the hosted Publisher Git client. */
29
+ export function hostedArtifactsGitRemote(endpoint, repository) {
30
+ if (!/^[a-zA-Z0-9._-]{1,128}$/.test(repository))
31
+ throw new Error("Artifacts repository is invalid");
32
+ let base;
33
+ try {
34
+ base = new URL(endpoint);
35
+ }
36
+ catch (cause) {
37
+ throw new Error("Publisher endpoint is invalid", { cause });
38
+ }
39
+ const localHttp = base.protocol === "http:" &&
40
+ (base.hostname === "localhost" || base.hostname === "127.0.0.1");
41
+ if ((base.protocol !== "https:" && !localHttp) ||
42
+ base.username.length > 0 ||
43
+ base.password.length > 0 ||
44
+ base.search.length > 0 ||
45
+ base.hash.length > 0)
46
+ throw new Error("Publisher endpoint is invalid");
47
+ return new URL(`/v1/artifacts/git/${encodeURIComponent(repository)}.git`, base).toString();
48
+ }
@@ -0,0 +1,20 @@
1
+ import type { AssetBuildPolicy } from "@fungi.computer/puffball/assets";
2
+ /** Engine/toolchain-independent input: an already bundled, captured directory.
3
+ * The producer owns compilation; Publisher never discovers its build scripts.
4
+ * These ceilings cannot be raised by a publication request.
5
+ */
6
+ export declare const bundledHtmlRecipe: Readonly<{
7
+ kind: "bundled-html";
8
+ toolchain: Readonly<{
9
+ recipe: "captured-bundled-html-v1";
10
+ }>;
11
+ limits: Readonly<{
12
+ maxManifestBytes: 1048576;
13
+ maxObjects: 1024;
14
+ maxObjectBytes: 16777216;
15
+ maxTotalBytes: 67108864;
16
+ }>;
17
+ extensions: readonly string[];
18
+ }>;
19
+ /** Requests may tighten the catalog bounds, never raise them. */
20
+ export declare function boundedBundlePolicy(policy: AssetBuildPolicy, maxOutputBytes: number): boolean;
@@ -0,0 +1,51 @@
1
+ /** Engine/toolchain-independent input: an already bundled, captured directory.
2
+ * The producer owns compilation; Publisher never discovers its build scripts.
3
+ * These ceilings cannot be raised by a publication request.
4
+ */
5
+ export const bundledHtmlRecipe = Object.freeze({
6
+ kind: "bundled-html",
7
+ toolchain: Object.freeze({ recipe: "captured-bundled-html-v1" }),
8
+ limits: Object.freeze({
9
+ maxManifestBytes: 1_048_576,
10
+ maxObjects: 1024,
11
+ maxObjectBytes: 16_777_216,
12
+ maxTotalBytes: 67_108_864,
13
+ }),
14
+ extensions: Object.freeze([
15
+ ".html",
16
+ ".js",
17
+ ".mjs",
18
+ ".css",
19
+ ".json",
20
+ ".gz",
21
+ ".wasm",
22
+ ".png",
23
+ ".jpg",
24
+ ".jpeg",
25
+ ".webp",
26
+ ".avif",
27
+ ".gif",
28
+ ".svg",
29
+ ".ico",
30
+ ".woff",
31
+ ".woff2",
32
+ ".ttf",
33
+ ".otf",
34
+ ".mp3",
35
+ ".ogg",
36
+ ".wav",
37
+ ".mp4",
38
+ ".webm",
39
+ ".txt",
40
+ ]),
41
+ });
42
+ /** Requests may tighten the catalog bounds, never raise them. */
43
+ export function boundedBundlePolicy(policy, maxOutputBytes) {
44
+ const limits = bundledHtmlRecipe.limits;
45
+ return (maxOutputBytes <= limits.maxTotalBytes &&
46
+ policy.maxTotalBytes <= maxOutputBytes &&
47
+ policy.maxManifestBytes <= limits.maxManifestBytes &&
48
+ policy.maxObjects <= limits.maxObjects &&
49
+ policy.maxObjectBytes <= limits.maxObjectBytes &&
50
+ policy.maxTotalBytes <= limits.maxTotalBytes);
51
+ }
@@ -0,0 +1,57 @@
1
+ import { type DefinitionEmission } from "./definition-emission.js";
2
+ import { type AssetBuildPolicy } from "@fungi.computer/puffball/assets";
3
+ import { parseSha256Digest, type AppId, type SourceRevision } from "@fungi.computer/puffball";
4
+ import * as Effect from "effect/Effect";
5
+ import * as esbuild from "esbuild";
6
+ import { type AppPublisher, type ArtifactWriter, type BuildExecutor, type CapturedSource, type PinnedRecipe, type ReleaseCreator, type SourceCapture } from "./effect.js";
7
+ /** Local proving adapter; accepted source policy is intentionally fixed to main. */
8
+ export declare function createLocalSourceCapture(repositoryPath: string): SourceCapture;
9
+ /** Capture saved App files for local authoring without modifying the customer's
10
+ * repository. Provenance names the exact temporary Git snapshot, not an existing
11
+ * customer commit. Dependencies and local credentials are not source inputs.
12
+ */
13
+ export declare function createLocalDirectorySourceCapture(directory: string): SourceCapture;
14
+ /** Capture an exact Artifacts commit through a trusted host's credential-free Git URL. */
15
+ export declare function createArtifactsSourceCapture(options: {
16
+ readonly remote: string;
17
+ readonly token: string;
18
+ readonly sourceRevision: SourceRevision;
19
+ }): SourceCapture;
20
+ /** Capture one explicit committed revision without manufacturing a branch. */
21
+ export declare function createLocalCommitSourceCapture(repositoryPath: string, sourceRevision: SourceRevision): SourceCapture;
22
+ /** A first-party editable source tree ready to seed into an Artifacts template. */
23
+ export type FirstPartyAppSourceDistribution = Readonly<{
24
+ readonly appId: AppId;
25
+ readonly sourceRevision: SourceRevision;
26
+ readonly sourceTreeDigest: ReturnType<typeof parseSha256Digest>;
27
+ readonly appDirectory: string;
28
+ readonly files: readonly Readonly<{
29
+ readonly path: string;
30
+ readonly contents: Uint8Array;
31
+ }>[];
32
+ }>;
33
+ /**
34
+ * Project one pinned first-party App and its pnpm workspace dependency graph
35
+ * without changing upstream files or manufacturing Git history. The caller
36
+ * records the returned provenance on the Artifacts template seed commit.
37
+ */
38
+ export declare function createFirstPartyAppSourceDistribution(options: {
39
+ readonly appId: string;
40
+ readonly source: CapturedSource;
41
+ readonly entry: string;
42
+ }): Effect.Effect<FirstPartyAppSourceDistribution, unknown>;
43
+ /** Exact pinned esbuild options shared by local builds and artifact-budget proofs. */
44
+ export declare function localSnapshotBuildOptions(source: CapturedSource, recipe: PinnedRecipe, emission: DefinitionEmission): esbuild.BuildOptions;
45
+ /** Local proving adapter; it builds only the captured extracted snapshot. */
46
+ export declare function createLocalBuildExecutor(options?: Readonly<{
47
+ packageStoreDirectory?: string;
48
+ }>): BuildExecutor;
49
+ /** Local proving adapter; the configured root is private and write-once. */
50
+ export declare function createLocalArtifactWriter(artifactRoot: string, assetPolicy?: AssetBuildPolicy): ArtifactWriter;
51
+ /** Local Git, recipe and artifact directory configuration with a Release catalog. */
52
+ export type LocalAppPublisherOptions = Readonly<{
53
+ readonly config: unknown;
54
+ readonly releaseCatalog: ReleaseCreator;
55
+ }>;
56
+ /** Construct the complete local proving adapter around the provider-neutral core. */
57
+ export declare function createLocalAppPublisher(options: LocalAppPublisherOptions): AppPublisher;