ignotum 0.0.13 → 0.0.15

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 (131) hide show
  1. package/README.md +6 -6
  2. package/dist/cli/bin.mjs +6111 -4448
  3. package/dist/cli/bin.mjs.map +1 -1
  4. package/dist/cli/emscripten-module-D5GzfUNv.mjs +533 -0
  5. package/dist/cli/emscripten-module-D5GzfUNv.mjs.map +1 -0
  6. package/dist/cli/ffi-7IElU4Co.mjs +148 -0
  7. package/dist/cli/ffi-7IElU4Co.mjs.map +1 -0
  8. package/dist/cli/managed-release-WHYLZwaS.mjs +10902 -0
  9. package/dist/cli/managed-release-WHYLZwaS.mjs.map +1 -0
  10. package/dist/cli/managed-release.d.mts +1 -0
  11. package/dist/cli/managed-release.mjs +14 -0
  12. package/dist/cli/managed-release.mjs.map +1 -0
  13. package/dist/cli/module-ES6BEMUI-D36I4NVn.mjs +1289 -0
  14. package/dist/cli/module-ES6BEMUI-D36I4NVn.mjs.map +1 -0
  15. package/dist/runtime/{api-Cv3hMbzo.d.ts → api-B4fwp5a2.d.ts} +4 -4
  16. package/dist/runtime/bootstrap-BISQSn_O.js +916 -0
  17. package/dist/runtime/bootstrap-BISQSn_O.js.map +1 -0
  18. package/dist/runtime/bootstrap-BTFScKAc.d.ts +545 -0
  19. package/dist/runtime/client.d.ts +50 -15
  20. package/dist/runtime/client.js +7 -1138
  21. package/dist/runtime/client.js.map +1 -1
  22. package/dist/runtime/descriptor-CTY4mtwS-cS43EbwS.js +269 -0
  23. package/dist/runtime/descriptor-CTY4mtwS-cS43EbwS.js.map +1 -0
  24. package/dist/runtime/{id-Bt9XWRGL.js → id--1m0NuUL.js} +4 -4
  25. package/dist/runtime/id--1m0NuUL.js.map +1 -0
  26. package/dist/runtime/{id-BzFHf3Wo-DGPCjgrf.d.ts → id-7an3nxTu-DZJSM5xh.d.ts} +2 -2
  27. package/dist/runtime/id-CBOt2kDo.d.ts +1 -0
  28. package/dist/runtime/identity-QjtJRxBD.d.ts +2 -0
  29. package/dist/runtime/image-Djtjy4Z8.js +19 -0
  30. package/dist/runtime/image-Djtjy4Z8.js.map +1 -0
  31. package/dist/runtime/index-D4seNHlX.d.ts +184 -0
  32. package/dist/runtime/internal/api.d.ts +1 -1
  33. package/dist/runtime/internal/api.js +30 -1
  34. package/dist/runtime/internal/api.js.map +1 -0
  35. package/dist/runtime/internal/bootstrap.d.ts +2 -0
  36. package/dist/runtime/internal/bootstrap.js +2 -0
  37. package/dist/runtime/internal/client.d.ts +2 -0
  38. package/dist/runtime/internal/client.js +75 -0
  39. package/dist/runtime/internal/client.js.map +1 -0
  40. package/dist/runtime/internal/host.d.ts +25 -8
  41. package/dist/runtime/internal/host.js +27 -6
  42. package/dist/runtime/internal/host.js.map +1 -1
  43. package/dist/runtime/internal/routes.d.ts +2 -0
  44. package/dist/runtime/internal/routes.js +2 -0
  45. package/dist/runtime/internal/server.d.ts +1 -1
  46. package/dist/runtime/internal/server.js +1 -1
  47. package/dist/runtime/internal/types.d.ts +2 -1
  48. package/dist/runtime/internal/types.js +1 -1
  49. package/dist/runtime/pagination-CBOt2kDo.d.ts +1 -0
  50. package/dist/runtime/pagination-D3qd6s9N.js +33 -0
  51. package/dist/runtime/pagination-D3qd6s9N.js.map +1 -0
  52. package/dist/runtime/result-BkziOG1L.d.ts +1 -0
  53. package/dist/runtime/router-BSBI1oN1.js +2377 -0
  54. package/dist/runtime/router-BSBI1oN1.js.map +1 -0
  55. package/dist/runtime/routes-Ce8TVP-T.js +690 -0
  56. package/dist/runtime/routes-Ce8TVP-T.js.map +1 -0
  57. package/dist/runtime/{schema-1Zs03-iS.js → schema-D1wOqiNH.js} +40 -12
  58. package/dist/runtime/schema-D1wOqiNH.js.map +1 -0
  59. package/dist/runtime/server.d.ts +5 -5
  60. package/dist/runtime/server.js +2 -2
  61. package/dist/runtime/{sync-Bs8J3fIr.d.ts → sync-D-GK2sv9.d.ts} +3 -2
  62. package/dist/runtime/{api-CAgKDij7.js → sync-DzUwA8W9.js} +22 -41
  63. package/dist/runtime/sync-DzUwA8W9.js.map +1 -0
  64. package/dist/runtime/types-DeCCyqLU-BIm7rU-0.d.ts +341 -0
  65. package/package.json +29 -4
  66. package/src/cli/agent-files.ts +14 -8
  67. package/src/cli/build/managed-client.ts +411 -0
  68. package/src/cli/build/managed-server.ts +294 -0
  69. package/src/cli/build/managed.ts +67 -0
  70. package/src/cli/build/output.ts +47 -0
  71. package/src/cli/build/server.ts +51 -220
  72. package/src/cli/client-config.ts +1 -140
  73. package/src/cli/client-styles.ts +1 -0
  74. package/src/cli/codegen.ts +9 -4
  75. package/src/cli/command.ts +28 -5
  76. package/src/cli/control-client.ts +84 -101
  77. package/src/cli/deploy.ts +133 -177
  78. package/src/cli/dev.ts +506 -107
  79. package/src/cli/image-assets.ts +499 -0
  80. package/src/cli/managed-client-boundaries.ts +103 -0
  81. package/src/cli/managed-dev-platform.ts +118 -0
  82. package/src/cli/managed-exports.ts +219 -0
  83. package/src/cli/managed-release-bin.ts +13 -0
  84. package/src/cli/managed-release.ts +260 -0
  85. package/src/cli/module-boundaries.ts +11 -1
  86. package/src/cli/new-app.ts +111 -87
  87. package/src/cli/route-codegen.ts +311 -0
  88. package/src/cli/route-static.ts +710 -0
  89. package/src/client/bootstrap.ts +110 -0
  90. package/src/client/errors.ts +13 -7
  91. package/src/client/files.ts +64 -0
  92. package/src/client/id.ts +9 -5
  93. package/src/client/image.ts +28 -0
  94. package/src/client/index.ts +25 -2
  95. package/src/client/managed-client.ts +729 -0
  96. package/src/client/managed-upgrade.ts +283 -0
  97. package/src/client/recovery-journal.ts +195 -0
  98. package/src/client/route-boundaries.ts +184 -0
  99. package/src/client/router-history.ts +152 -0
  100. package/src/client/router-store.ts +644 -0
  101. package/src/client/router.ts +324 -0
  102. package/src/client/routes.ts +28 -0
  103. package/src/client/sync.ts +66 -592
  104. package/src/dev-runtime/database.ts +77 -2
  105. package/src/dev-runtime/functions.ts +3 -3
  106. package/src/dev-runtime/id.ts +19 -4
  107. package/src/dev-runtime/managed-functions.ts +255 -0
  108. package/src/dev-runtime/managed-sync.ts +263 -0
  109. package/src/dev-runtime/managed-websocket.ts +71 -0
  110. package/src/dev-runtime/migrations.ts +20 -0
  111. package/src/dev-runtime/sync.ts +236 -236
  112. package/src/image-assets.d.ts +36 -0
  113. package/src/internal/client.ts +186 -0
  114. package/src/internal/host.ts +1 -1
  115. package/src/internal/http-paths.ts +0 -3
  116. package/src/internal/routes.ts +45 -0
  117. package/dist/runtime/api-CAgKDij7.js.map +0 -1
  118. package/dist/runtime/descriptor-Cyo9FP9n-C0SRVXNW.js +0 -154
  119. package/dist/runtime/descriptor-Cyo9FP9n-C0SRVXNW.js.map +0 -1
  120. package/dist/runtime/id-Bt9XWRGL.js.map +0 -1
  121. package/dist/runtime/id-Dz0apuB3.d.ts +0 -1
  122. package/dist/runtime/index-D54flWtH.d.ts +0 -402
  123. package/dist/runtime/pagination-DnKg3dkI-r5ZUxBBx.d.ts +0 -112
  124. package/dist/runtime/pagination-Dz0apuB3.d.ts +0 -1
  125. package/dist/runtime/result-DKAA4gpS.d.ts +0 -1
  126. package/dist/runtime/schema-1Zs03-iS.js.map +0 -1
  127. package/src/cli/build/client.ts +0 -119
  128. package/src/cli/build/public.ts +0 -186
  129. package/src/cli/client-entry.ts +0 -152
  130. package/src/cli/client-plugin.ts +0 -136
  131. package/src/client/app.ts +0 -15
@@ -0,0 +1,294 @@
1
+ import { Crypto, Effect, Encoding, FileSystem, Path, Schema } from "effect";
2
+ import { normalizePath, type Plugin } from "vite";
3
+ import {
4
+ AppModuleId,
5
+ AppArtifactPath,
6
+ type AppArtifact,
7
+ type AppDefinition,
8
+ type AppModule,
9
+ type ModuleImport,
10
+ type UploadDeclaration,
11
+ } from "@ignotum/contracts/app-definition";
12
+ import { EnvironmentArtifact, serverEnvironmentPath } from "@ignotum/contracts/version";
13
+ import {
14
+ artifactReference,
15
+ encodeCanonical,
16
+ schemaIdentity,
17
+ sha256,
18
+ utf8Bytes,
19
+ validateAppModuleBytes,
20
+ validateManagedServerDefinition,
21
+ } from "@ignotum/versions";
22
+ import { managedGuestExports } from "../managed-exports.js";
23
+ import { moduleBoundariesPlugin } from "../module-boundaries.js";
24
+ import { InvalidBuildOutput, runViteBuild, sourceMapPathTransform } from "./artifact.js";
25
+ import type { ServerDiscoveryManifest } from "./server.js";
26
+
27
+ export interface ManagedServerBuildResult {
28
+ readonly server: AppDefinition["server"];
29
+ readonly artifacts: ReadonlyArray<AppArtifact>;
30
+ readonly bytes: ReadonlyMap<string, Uint8Array>;
31
+ readonly environment: {
32
+ readonly bytes: Uint8Array;
33
+ readonly path: typeof serverEnvironmentPath;
34
+ readonly declaration: NonNullable<UploadDeclaration["environment"]>;
35
+ };
36
+ }
37
+
38
+ const literal = Schema.encodeSync(Schema.fromJsonString(Schema.String));
39
+ const externals = managedGuestExports
40
+ .filter((entry) => entry.specifier !== "ignotum/internal/host")
41
+ .map((entry) => entry.specifier);
42
+
43
+ export const compileManagedServer = Effect.fn("Deploy.compileManagedServer")(function* (
44
+ appDirectory: string,
45
+ outputDirectory: string,
46
+ conditions: ReadonlyArray<string>,
47
+ discovered: ServerDiscoveryManifest,
48
+ ) {
49
+ const fs = yield* FileSystem.FileSystem;
50
+ const path = yield* Path.Path;
51
+ const crypto = yield* Crypto.Crypto;
52
+ const sources = new Map<string, string>();
53
+ const input: Record<string, string> = {};
54
+ const addEntry = (name: string, modulePath: string, exportName: string) => {
55
+ const id = `virtual:ignotum/managed-server/${name}`;
56
+ input[name] = id;
57
+ sources.set(
58
+ `\0${id}`,
59
+ `export { ${exportName} as default } from ${literal(normalizePath(modulePath))};\n`,
60
+ );
61
+ };
62
+ addEntry("schema", path.join(appDirectory, "server/schema.ts"), "default");
63
+ for (const [index, definition] of discovered.functions.entries()) {
64
+ addEntry(`function-${index}`, definition.modulePath, definition.exportName);
65
+ }
66
+ const emittedMaps = new Map<string, Uint8Array>();
67
+ const entryPlugin: Plugin = {
68
+ name: "ignotum:managed-server-entries",
69
+ resolveId: (id) => (sources.has(`\0${id}`) ? `\0${id}` : undefined),
70
+ load: (id) => sources.get(id),
71
+ generateBundle(_options, bundle) {
72
+ for (const item of Object.values(bundle)) {
73
+ if (item.type !== "chunk") continue;
74
+ if (item.map === null) {
75
+ if (Object.values(item.modules).some((module) => module.renderedLength > 0))
76
+ this.error(`Missing source map for app implementation '${item.fileName}'.`);
77
+ continue;
78
+ }
79
+ const encodedDirectory = JSON.stringify(normalizePath(appDirectory)).slice(1, -1);
80
+ emittedMaps.set(
81
+ `${item.fileName}.map`,
82
+ utf8Bytes(item.map.toString().replaceAll(encodedDirectory, "ignotum://app")),
83
+ );
84
+ }
85
+ },
86
+ };
87
+ const runPromise = Effect.runPromiseWith(yield* Effect.context<never>());
88
+ const packageNames = new Map<string, string | undefined>();
89
+ const packageName = Effect.fn("Deploy.managedServerPackageName")(function* (id: string) {
90
+ if (!path.isAbsolute(id)) return undefined;
91
+ let directory = path.dirname(id.split("?", 1)[0]!);
92
+ const start = directory;
93
+ if (packageNames.has(start)) return packageNames.get(start);
94
+ while (path.dirname(directory) !== directory) {
95
+ const manifest = path.join(directory, "package.json");
96
+ if (yield* fs.exists(manifest)) {
97
+ const metadata = yield* Schema.decodeUnknownEffect(
98
+ Schema.fromJsonString(Schema.Struct({ name: Schema.optional(Schema.String) })),
99
+ )(yield* fs.readFileString(manifest));
100
+ packageNames.set(start, metadata.name);
101
+ return metadata.name;
102
+ }
103
+ directory = path.dirname(directory);
104
+ }
105
+ packageNames.set(start, undefined);
106
+ return undefined;
107
+ });
108
+ const boundaries = yield* moduleBoundariesPlugin("Server", appDirectory);
109
+ const staging = yield* fs.makeTempDirectoryScoped({ prefix: "ignotum-managed-server-" });
110
+ const output = yield* runViteBuild("managed server graph", {
111
+ root: appDirectory,
112
+ configFile: false,
113
+ publicDir: false,
114
+ logLevel: "warn",
115
+ mode: "production",
116
+ resolve: { conditions: [...conditions], noExternal: true, tsconfigPaths: true },
117
+ plugins: [
118
+ boundaries,
119
+ entryPlugin,
120
+ {
121
+ name: "ignotum:managed-server-platform-boundary",
122
+ async transform(_code, id) {
123
+ const relative = normalizePath(path.relative(appDirectory, id));
124
+ if (
125
+ !relative.startsWith("../") &&
126
+ !path.isAbsolute(relative) &&
127
+ !relative.split("/").includes("node_modules")
128
+ )
129
+ return;
130
+ const owner = await runPromise(packageName(id));
131
+ if (
132
+ owner === "ignotum" ||
133
+ owner === "effect" ||
134
+ owner?.startsWith("@ignotum/") ||
135
+ owner?.startsWith("@effect/")
136
+ ) {
137
+ throw InvalidBuildOutput.make({
138
+ message: `App server graph embeds private platform implementation '${id}'.`,
139
+ });
140
+ }
141
+ },
142
+ },
143
+ ],
144
+ build: {
145
+ emptyOutDir: false,
146
+ minify: "oxc",
147
+ modulePreload: false,
148
+ outDir: staging,
149
+ sourcemap: true,
150
+ target: "es2020",
151
+ rolldownOptions: {
152
+ input,
153
+ external: externals,
154
+ preserveEntrySignatures: "strict",
155
+ output: {
156
+ format: "es",
157
+ entryFileNames: "[name].mjs",
158
+ chunkFileNames: "shared-[hash].mjs",
159
+ sourcemapPathTransform: yield* sourceMapPathTransform(appDirectory),
160
+ },
161
+ },
162
+ },
163
+ });
164
+ const chunks = output.filter((item) => item.type === "chunk");
165
+ if (chunks.some((chunk) => chunk.dynamicImports.length > 0))
166
+ return yield* InvalidBuildOutput.make({
167
+ message: "Managed server modules cannot use dynamic imports.",
168
+ });
169
+ if (output.some((item) => item.type === "asset" && !item.fileName.endsWith(".map")))
170
+ return yield* InvalidBuildOutput.make({
171
+ message: "Managed server modules cannot emit browser assets.",
172
+ });
173
+ const files = new Map<string, Uint8Array>();
174
+ for (const item of output)
175
+ files.set(item.fileName, yield* fs.readFile(path.join(staging, item.fileName)));
176
+ for (const chunk of chunks) {
177
+ const mapName = `${chunk.fileName}.map`;
178
+ if (yield* fs.exists(path.join(staging, mapName)))
179
+ files.set(mapName, yield* fs.readFile(path.join(staging, mapName)));
180
+ }
181
+ for (const [name, map] of emittedMaps) files.set(name, map);
182
+ // Hash the complete emitted inventory before assigning its namespace. Relative imports and
183
+ // cyclic chunks do not contain this digest, so naming never requires a hash fixed point.
184
+ const inventory = yield* Effect.forEach(
185
+ [...files].sort(([a], [b]) => a.localeCompare(b)),
186
+ ([name, bytes]) => sha256(bytes).pipe(Effect.map((digest) => ({ name, digest }))),
187
+ );
188
+ const graphDigest = yield* sha256(utf8Bytes(JSON.stringify(inventory)));
189
+ const moduleId = (file: string) => AppModuleId.make(`app/server/${graphDigest}/${file}`);
190
+ const artifacts: AppArtifact[] = [];
191
+ const bytes = new Map<string, Uint8Array>();
192
+ const modules: AppModule[] = [];
193
+ const chunkNames = new Set(chunks.map((chunk) => chunk.fileName));
194
+ for (const chunk of chunks) {
195
+ const id = moduleId(chunk.fileName);
196
+ const artifactPath = AppArtifactPath.make(`server/${graphDigest}/${chunk.fileName}`);
197
+ const sourceMap = AppArtifactPath.make(`${artifactPath}.map`);
198
+ const source = files.get(chunk.fileName)!;
199
+ const map = files.get(`${chunk.fileName}.map`);
200
+ const imports: ModuleImport[] = [];
201
+ for (const imported of chunk.imports) {
202
+ if (chunkNames.has(imported)) {
203
+ const relative = normalizePath(path.relative(path.dirname(chunk.fileName), imported));
204
+ imports.push({
205
+ kind: "App",
206
+ specifier: relative.startsWith(".") ? relative : `./${relative}`,
207
+ module: moduleId(imported),
208
+ });
209
+ } else if (imported === "ignotum/server")
210
+ imports.push({ kind: "Runtime", specifier: imported });
211
+ else if (imported === "ignotum/internal/server" || imported === "ignotum/internal/api")
212
+ imports.push({ kind: "Compiler", specifier: imported });
213
+ else
214
+ return yield* InvalidBuildOutput.make({
215
+ message: `Unsupported managed server import '${imported}'.`,
216
+ });
217
+ }
218
+ const baseModule = { id, artifact: artifactPath, imports };
219
+ const module: AppModule = map === undefined ? baseModule : { ...baseModule, sourceMap };
220
+ const artifact: AppArtifact = {
221
+ ...(yield* artifactReference(artifactPath, source)),
222
+ kind: "ServerModule",
223
+ visibility: "private",
224
+ contentType: "text/javascript",
225
+ };
226
+ yield* validateAppModuleBytes(module, artifact, source, chunk.isEntry ? ["default"] : []);
227
+ modules.push(module);
228
+ artifacts.push(artifact);
229
+ bytes.set(artifactPath, source);
230
+ if (map !== undefined) {
231
+ artifacts.push({
232
+ ...(yield* artifactReference(sourceMap, map)),
233
+ kind: "SourceMap",
234
+ visibility: "private",
235
+ contentType: "application/json",
236
+ owner: id,
237
+ });
238
+ bytes.set(sourceMap, map);
239
+ }
240
+ }
241
+ const reference = (name: string) => ({ module: moduleId(`${name}.mjs`), exportName: "default" });
242
+ const schemaEntry = reference("schema");
243
+ const server: AppDefinition["server"] = {
244
+ graph: {
245
+ entries: chunks.filter((chunk) => chunk.isEntry).map((chunk) => moduleId(chunk.fileName)),
246
+ modules,
247
+ },
248
+ functions: discovered.functions.map((definition, index) => {
249
+ const fn: AppDefinition["server"]["functions"][number] = {
250
+ address: definition.address,
251
+ kind: definition.kind,
252
+ entry: reference(`function-${index}`),
253
+ args: definition.args ?? { type: "object", fields: [] },
254
+ errors: definition.errors ?? { type: "never" },
255
+ };
256
+ return definition.returns === undefined ? fn : { ...fn, returns: definition.returns };
257
+ }),
258
+ schema: {
259
+ entry: schemaEntry,
260
+ descriptor: discovered.schema,
261
+ identity: yield* schemaIdentity(discovered.schema),
262
+ conversions: discovered.conversions.map((conversion) => ({
263
+ ...conversion,
264
+ entry: schemaEntry,
265
+ })),
266
+ },
267
+ };
268
+ yield* validateManagedServerDefinition(server, artifacts);
269
+ const environmentBytes = utf8Bytes(
270
+ `${encodeCanonical(EnvironmentArtifact, {
271
+ formatVersion: 1,
272
+ nonce: Encoding.encodeBase64Url(yield* crypto.randomBytes(32)),
273
+ values: discovered.environment.values,
274
+ })}\n`,
275
+ );
276
+ for (const [name, content] of bytes) {
277
+ const target = path.join(outputDirectory, name);
278
+ yield* fs.makeDirectory(path.dirname(target), { recursive: true });
279
+ yield* fs.writeFile(target, content);
280
+ }
281
+ return {
282
+ server,
283
+ artifacts,
284
+ bytes,
285
+ environment: {
286
+ bytes: environmentBytes,
287
+ path: serverEnvironmentPath,
288
+ declaration: {
289
+ artifact: yield* artifactReference(serverEnvironmentPath, environmentBytes),
290
+ fields: discovered.environment.fields,
291
+ },
292
+ },
293
+ } satisfies ManagedServerBuildResult;
294
+ });
@@ -0,0 +1,67 @@
1
+ import { Effect, FileSystem, Path, Schema } from "effect";
2
+ import { UploadDeclaration } from "@ignotum/contracts/app-definition";
3
+ import { RuntimeRequirements } from "@ignotum/contracts/platform";
4
+ import { encodeCanonical, validateUploadDeclaration, utf8Bytes } from "@ignotum/versions";
5
+ import { generate } from "../codegen.js";
6
+ import { replaceBuildDirectory } from "./output.js";
7
+ import { managedCompatibility } from "../managed-exports.js";
8
+ import { buildManagedClient } from "./managed-client.js";
9
+ import { buildManagedServer } from "./server.js";
10
+
11
+ export interface ManagedVersionBuildResult {
12
+ readonly declaration: UploadDeclaration;
13
+ readonly declarationBytes: Uint8Array;
14
+ readonly outputDirectory: string;
15
+ readonly bytes: ReadonlyMap<string, Uint8Array>;
16
+ readonly environment: { readonly path: string; readonly bytes: Uint8Array };
17
+ }
18
+
19
+ /** Platform selection never enters application bytes. */
20
+ export const buildManagedVersion = Effect.fn("Deploy.buildManagedVersion")(function* (
21
+ appDirectory: string,
22
+ options: { readonly applicationName: string },
23
+ ) {
24
+ const fs = yield* FileSystem.FileSystem;
25
+ const path = yield* Path.Path;
26
+ const stateDirectory = path.join(appDirectory, ".ignotum");
27
+ const outputDirectory = path.join(stateDirectory, "build");
28
+ yield* fs.makeDirectory(stateDirectory, { recursive: true });
29
+ const generated = yield* generate(appDirectory);
30
+ return yield* Effect.scoped(
31
+ Effect.gen(function* () {
32
+ const staging = yield* Effect.acquireRelease(
33
+ fs.makeTempDirectory({ directory: stateDirectory, prefix: ".build-staging-" }),
34
+ (directory) => fs.remove(directory, { force: true, recursive: true }).pipe(Effect.orDie),
35
+ );
36
+ const client = yield* buildManagedClient(appDirectory, staging, options);
37
+ const server = yield* buildManagedServer(appDirectory, staging, generated.functionModules);
38
+ const requirements = yield* Schema.decodeUnknownEffect(RuntimeRequirements)({
39
+ clientApiVersion: managedCompatibility.clientApiVersion,
40
+ serverApiVersion: managedCompatibility.serverApiVersion,
41
+ capabilities: managedCompatibility.capabilities,
42
+ });
43
+ const declaration = yield* validateUploadDeclaration({
44
+ formatVersion: 1,
45
+ definition: {
46
+ formatVersion: 1,
47
+ appName: client.appName,
48
+ requirements,
49
+ client: client.client,
50
+ server: server.server,
51
+ artifacts: [...client.artifacts, ...server.artifacts],
52
+ },
53
+ environment: server.environment.declaration,
54
+ });
55
+ const declarationBytes = utf8Bytes(`${encodeCanonical(UploadDeclaration, declaration)}\n`);
56
+ yield* fs.writeFile(path.join(staging, "declaration.json"), declarationBytes);
57
+ yield* replaceBuildDirectory(stateDirectory, staging, outputDirectory);
58
+ return {
59
+ declaration,
60
+ declarationBytes,
61
+ outputDirectory,
62
+ bytes: new Map([...client.bytes, ...server.bytes]),
63
+ environment: { path: server.environment.path, bytes: server.environment.bytes },
64
+ } satisfies ManagedVersionBuildResult;
65
+ }),
66
+ );
67
+ });
@@ -0,0 +1,47 @@
1
+ import { Effect, FileSystem, Result, Schema } from "effect";
2
+
3
+ export class BuildReplacementFailed extends Schema.TaggedError<BuildReplacementFailed>()(
4
+ "BuildReplacementFailed",
5
+ {
6
+ backupPath: Schema.String,
7
+ cause: Schema.Defect(),
8
+ message: Schema.String,
9
+ },
10
+ ) {}
11
+
12
+ export const replaceBuildDirectory = Effect.fn("Deploy.replaceBuildDirectory")(function* (
13
+ stateDirectory: string,
14
+ stagingDirectory: string,
15
+ buildDirectory: string,
16
+ ) {
17
+ const fileSystem = yield* FileSystem.FileSystem;
18
+ const currentExists = yield* fileSystem.exists(buildDirectory);
19
+ if (!currentExists) {
20
+ yield* fileSystem.rename(stagingDirectory, buildDirectory);
21
+ return;
22
+ }
23
+
24
+ const backupPath = yield* fileSystem.makeTempDirectory({
25
+ directory: stateDirectory,
26
+ prefix: ".build-backup-",
27
+ });
28
+ yield* fileSystem.remove(backupPath, { recursive: true });
29
+ yield* fileSystem.rename(buildDirectory, backupPath);
30
+
31
+ const replacement = yield* Effect.result(fileSystem.rename(stagingDirectory, buildDirectory));
32
+ if (Result.isSuccess(replacement)) {
33
+ yield* fileSystem.remove(backupPath, { recursive: true });
34
+ return;
35
+ }
36
+
37
+ const restoration = yield* Effect.result(fileSystem.rename(backupPath, buildDirectory));
38
+ if (Result.isFailure(restoration)) {
39
+ return yield* BuildReplacementFailed.make({
40
+ backupPath,
41
+ cause: restoration.failure,
42
+ message: `Could not install the new build or restore the previous build. The previous build remains at ${backupPath}.`,
43
+ });
44
+ }
45
+
46
+ return yield* replacement.failure;
47
+ });