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.
- package/README.md +6 -6
- package/dist/cli/bin.mjs +6111 -4448
- package/dist/cli/bin.mjs.map +1 -1
- package/dist/cli/emscripten-module-D5GzfUNv.mjs +533 -0
- package/dist/cli/emscripten-module-D5GzfUNv.mjs.map +1 -0
- package/dist/cli/ffi-7IElU4Co.mjs +148 -0
- package/dist/cli/ffi-7IElU4Co.mjs.map +1 -0
- package/dist/cli/managed-release-WHYLZwaS.mjs +10902 -0
- package/dist/cli/managed-release-WHYLZwaS.mjs.map +1 -0
- package/dist/cli/managed-release.d.mts +1 -0
- package/dist/cli/managed-release.mjs +14 -0
- package/dist/cli/managed-release.mjs.map +1 -0
- package/dist/cli/module-ES6BEMUI-D36I4NVn.mjs +1289 -0
- package/dist/cli/module-ES6BEMUI-D36I4NVn.mjs.map +1 -0
- package/dist/runtime/{api-Cv3hMbzo.d.ts → api-B4fwp5a2.d.ts} +4 -4
- package/dist/runtime/bootstrap-BISQSn_O.js +916 -0
- package/dist/runtime/bootstrap-BISQSn_O.js.map +1 -0
- package/dist/runtime/bootstrap-BTFScKAc.d.ts +545 -0
- package/dist/runtime/client.d.ts +50 -15
- package/dist/runtime/client.js +7 -1138
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/descriptor-CTY4mtwS-cS43EbwS.js +269 -0
- package/dist/runtime/descriptor-CTY4mtwS-cS43EbwS.js.map +1 -0
- package/dist/runtime/{id-Bt9XWRGL.js → id--1m0NuUL.js} +4 -4
- package/dist/runtime/id--1m0NuUL.js.map +1 -0
- package/dist/runtime/{id-BzFHf3Wo-DGPCjgrf.d.ts → id-7an3nxTu-DZJSM5xh.d.ts} +2 -2
- package/dist/runtime/id-CBOt2kDo.d.ts +1 -0
- package/dist/runtime/identity-QjtJRxBD.d.ts +2 -0
- package/dist/runtime/image-Djtjy4Z8.js +19 -0
- package/dist/runtime/image-Djtjy4Z8.js.map +1 -0
- package/dist/runtime/index-D4seNHlX.d.ts +184 -0
- package/dist/runtime/internal/api.d.ts +1 -1
- package/dist/runtime/internal/api.js +30 -1
- package/dist/runtime/internal/api.js.map +1 -0
- package/dist/runtime/internal/bootstrap.d.ts +2 -0
- package/dist/runtime/internal/bootstrap.js +2 -0
- package/dist/runtime/internal/client.d.ts +2 -0
- package/dist/runtime/internal/client.js +75 -0
- package/dist/runtime/internal/client.js.map +1 -0
- package/dist/runtime/internal/host.d.ts +25 -8
- package/dist/runtime/internal/host.js +27 -6
- package/dist/runtime/internal/host.js.map +1 -1
- package/dist/runtime/internal/routes.d.ts +2 -0
- package/dist/runtime/internal/routes.js +2 -0
- package/dist/runtime/internal/server.d.ts +1 -1
- package/dist/runtime/internal/server.js +1 -1
- package/dist/runtime/internal/types.d.ts +2 -1
- package/dist/runtime/internal/types.js +1 -1
- package/dist/runtime/pagination-CBOt2kDo.d.ts +1 -0
- package/dist/runtime/pagination-D3qd6s9N.js +33 -0
- package/dist/runtime/pagination-D3qd6s9N.js.map +1 -0
- package/dist/runtime/result-BkziOG1L.d.ts +1 -0
- package/dist/runtime/router-BSBI1oN1.js +2377 -0
- package/dist/runtime/router-BSBI1oN1.js.map +1 -0
- package/dist/runtime/routes-Ce8TVP-T.js +690 -0
- package/dist/runtime/routes-Ce8TVP-T.js.map +1 -0
- package/dist/runtime/{schema-1Zs03-iS.js → schema-D1wOqiNH.js} +40 -12
- package/dist/runtime/schema-D1wOqiNH.js.map +1 -0
- package/dist/runtime/server.d.ts +5 -5
- package/dist/runtime/server.js +2 -2
- package/dist/runtime/{sync-Bs8J3fIr.d.ts → sync-D-GK2sv9.d.ts} +3 -2
- package/dist/runtime/{api-CAgKDij7.js → sync-DzUwA8W9.js} +22 -41
- package/dist/runtime/sync-DzUwA8W9.js.map +1 -0
- package/dist/runtime/types-DeCCyqLU-BIm7rU-0.d.ts +341 -0
- package/package.json +29 -4
- package/src/cli/agent-files.ts +14 -8
- package/src/cli/build/managed-client.ts +411 -0
- package/src/cli/build/managed-server.ts +294 -0
- package/src/cli/build/managed.ts +67 -0
- package/src/cli/build/output.ts +47 -0
- package/src/cli/build/server.ts +51 -220
- package/src/cli/client-config.ts +1 -140
- package/src/cli/client-styles.ts +1 -0
- package/src/cli/codegen.ts +9 -4
- package/src/cli/command.ts +28 -5
- package/src/cli/control-client.ts +84 -101
- package/src/cli/deploy.ts +133 -177
- package/src/cli/dev.ts +506 -107
- package/src/cli/image-assets.ts +499 -0
- package/src/cli/managed-client-boundaries.ts +103 -0
- package/src/cli/managed-dev-platform.ts +118 -0
- package/src/cli/managed-exports.ts +219 -0
- package/src/cli/managed-release-bin.ts +13 -0
- package/src/cli/managed-release.ts +260 -0
- package/src/cli/module-boundaries.ts +11 -1
- package/src/cli/new-app.ts +111 -87
- package/src/cli/route-codegen.ts +311 -0
- package/src/cli/route-static.ts +710 -0
- package/src/client/bootstrap.ts +110 -0
- package/src/client/errors.ts +13 -7
- package/src/client/files.ts +64 -0
- package/src/client/id.ts +9 -5
- package/src/client/image.ts +28 -0
- package/src/client/index.ts +25 -2
- package/src/client/managed-client.ts +729 -0
- package/src/client/managed-upgrade.ts +283 -0
- package/src/client/recovery-journal.ts +195 -0
- package/src/client/route-boundaries.ts +184 -0
- package/src/client/router-history.ts +152 -0
- package/src/client/router-store.ts +644 -0
- package/src/client/router.ts +324 -0
- package/src/client/routes.ts +28 -0
- package/src/client/sync.ts +66 -592
- package/src/dev-runtime/database.ts +77 -2
- package/src/dev-runtime/functions.ts +3 -3
- package/src/dev-runtime/id.ts +19 -4
- package/src/dev-runtime/managed-functions.ts +255 -0
- package/src/dev-runtime/managed-sync.ts +263 -0
- package/src/dev-runtime/managed-websocket.ts +71 -0
- package/src/dev-runtime/migrations.ts +20 -0
- package/src/dev-runtime/sync.ts +236 -236
- package/src/image-assets.d.ts +36 -0
- package/src/internal/client.ts +186 -0
- package/src/internal/host.ts +1 -1
- package/src/internal/http-paths.ts +0 -3
- package/src/internal/routes.ts +45 -0
- package/dist/runtime/api-CAgKDij7.js.map +0 -1
- package/dist/runtime/descriptor-Cyo9FP9n-C0SRVXNW.js +0 -154
- package/dist/runtime/descriptor-Cyo9FP9n-C0SRVXNW.js.map +0 -1
- package/dist/runtime/id-Bt9XWRGL.js.map +0 -1
- package/dist/runtime/id-Dz0apuB3.d.ts +0 -1
- package/dist/runtime/index-D54flWtH.d.ts +0 -402
- package/dist/runtime/pagination-DnKg3dkI-r5ZUxBBx.d.ts +0 -112
- package/dist/runtime/pagination-Dz0apuB3.d.ts +0 -1
- package/dist/runtime/result-DKAA4gpS.d.ts +0 -1
- package/dist/runtime/schema-1Zs03-iS.js.map +0 -1
- package/src/cli/build/client.ts +0 -119
- package/src/cli/build/public.ts +0 -186
- package/src/cli/client-entry.ts +0 -152
- package/src/cli/client-plugin.ts +0 -136
- package/src/client/app.ts +0 -15
|
@@ -4,29 +4,27 @@ import { isIP } from "node:net";
|
|
|
4
4
|
import type {
|
|
5
5
|
AppSlug,
|
|
6
6
|
ControlApp,
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
ControlVersion,
|
|
8
|
+
BeginVersionResponse,
|
|
9
9
|
} from "@ignotum/contracts/control";
|
|
10
10
|
import {
|
|
11
|
-
|
|
11
|
+
BeginVersionResponse as BeginVersionResponseSchema,
|
|
12
12
|
ControlApp as ControlAppSchema,
|
|
13
|
-
|
|
13
|
+
ControlVersion as ControlVersionSchema,
|
|
14
14
|
ControlErrorCode as ControlErrorCodeSchema,
|
|
15
15
|
ControlErrorResponse,
|
|
16
16
|
CreateAppRequest,
|
|
17
|
-
DeploymentFileUploadResponse,
|
|
18
17
|
controlAppBySlugPath,
|
|
19
18
|
controlAppPath,
|
|
20
19
|
controlAppsPath,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
controlUploadHeaderNames,
|
|
20
|
+
controlVersionActivatePath,
|
|
21
|
+
controlVersionFilePath,
|
|
22
|
+
controlVersionFinalizePath,
|
|
23
|
+
controlVersionPath,
|
|
26
24
|
} from "@ignotum/contracts/control";
|
|
27
|
-
import
|
|
28
|
-
import {
|
|
29
|
-
import type { AppId,
|
|
25
|
+
import { UploadDeclaration } from "@ignotum/contracts/app-definition";
|
|
26
|
+
import { ExecutionSelection } from "@ignotum/contracts/platform";
|
|
27
|
+
import type { AppId, VersionId } from "@ignotum/contracts/runtime/identity";
|
|
30
28
|
import {
|
|
31
29
|
Config,
|
|
32
30
|
Context,
|
|
@@ -34,7 +32,6 @@ import {
|
|
|
34
32
|
FileSystem,
|
|
35
33
|
Layer,
|
|
36
34
|
Option,
|
|
37
|
-
Path,
|
|
38
35
|
Redacted,
|
|
39
36
|
Schedule,
|
|
40
37
|
Schema,
|
|
@@ -101,9 +98,10 @@ export class ControlClientUnavailable extends Schema.TaggedError<ControlClientUn
|
|
|
101
98
|
},
|
|
102
99
|
) {}
|
|
103
100
|
|
|
104
|
-
export interface
|
|
101
|
+
export interface VersionUploadFile {
|
|
105
102
|
readonly bytes?: Uint8Array;
|
|
106
103
|
readonly contentType: string;
|
|
104
|
+
readonly contentEncoding?: string;
|
|
107
105
|
readonly path: string;
|
|
108
106
|
readonly sha256: string;
|
|
109
107
|
readonly size: number;
|
|
@@ -111,32 +109,37 @@ export interface DeploymentUploadFile {
|
|
|
111
109
|
}
|
|
112
110
|
|
|
113
111
|
interface ControlClientService {
|
|
114
|
-
readonly
|
|
112
|
+
readonly activateVersion: (
|
|
115
113
|
appId: AppId,
|
|
116
|
-
|
|
117
|
-
) => Effect.Effect<
|
|
118
|
-
readonly
|
|
114
|
+
versionId: VersionId,
|
|
115
|
+
) => Effect.Effect<ExecutionSelection, ControlApiError | ControlClientUnavailable>;
|
|
116
|
+
readonly beginVersion: (
|
|
119
117
|
appId: AppId,
|
|
120
|
-
|
|
121
|
-
|
|
118
|
+
versionId: VersionId,
|
|
119
|
+
declaration: UploadDeclaration,
|
|
120
|
+
) => Effect.Effect<BeginVersionResponse, ControlApiError | ControlClientUnavailable>;
|
|
122
121
|
readonly createApp: (
|
|
123
122
|
slug: AppSlug,
|
|
124
123
|
) => Effect.Effect<ControlApp, ControlApiError | ControlClientUnavailable>;
|
|
125
|
-
readonly
|
|
124
|
+
readonly finalizeVersion: (
|
|
125
|
+
appId: AppId,
|
|
126
|
+
versionId: VersionId,
|
|
127
|
+
) => Effect.Effect<ControlVersion, ControlApiError | ControlClientUnavailable>;
|
|
128
|
+
readonly getVersion: (
|
|
126
129
|
appId: AppId,
|
|
127
|
-
|
|
128
|
-
) => Effect.Effect<
|
|
130
|
+
versionId: VersionId,
|
|
131
|
+
) => Effect.Effect<ControlVersion, ControlApiError | ControlClientUnavailable>;
|
|
129
132
|
readonly getApp: (
|
|
130
133
|
appId: AppId,
|
|
131
134
|
) => Effect.Effect<ControlApp, ControlApiError | ControlClientUnavailable>;
|
|
132
135
|
readonly getAppBySlug: (
|
|
133
136
|
slug: AppSlug,
|
|
134
137
|
) => Effect.Effect<ControlApp, ControlApiError | ControlClientUnavailable>;
|
|
135
|
-
readonly
|
|
138
|
+
readonly uploadVersionFile: (
|
|
136
139
|
appId: AppId,
|
|
137
|
-
|
|
138
|
-
file:
|
|
139
|
-
) => Effect.Effect<
|
|
140
|
+
versionId: VersionId,
|
|
141
|
+
file: VersionUploadFile,
|
|
142
|
+
) => Effect.Effect<void, ControlApiError | ControlClientUnavailable>;
|
|
140
143
|
}
|
|
141
144
|
|
|
142
145
|
export class ControlClient extends Context.Service<ControlClient, ControlClientService>()(
|
|
@@ -156,45 +159,6 @@ const unavailable = (message: string, cause: unknown) =>
|
|
|
156
159
|
|
|
157
160
|
const endpoint = (apiUrl: URL, path: string): URL => new URL(path, apiUrl);
|
|
158
161
|
|
|
159
|
-
const uploadFile = (file: ArtifactFile, sourcePath: string) => ({
|
|
160
|
-
contentType: file.contentType,
|
|
161
|
-
path: file.path,
|
|
162
|
-
sha256: file.sha256,
|
|
163
|
-
size: file.size,
|
|
164
|
-
sourcePath,
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
export const deploymentUploadFiles = Effect.fn("ControlClient.deploymentUploadFiles")(function* (
|
|
168
|
-
inventory: DeploymentInventory,
|
|
169
|
-
inventorySha256: string,
|
|
170
|
-
inventorySize: number,
|
|
171
|
-
buildDirectory: string,
|
|
172
|
-
memoryFiles: ReadonlyMap<string, Uint8Array> = new Map(),
|
|
173
|
-
) {
|
|
174
|
-
const path = yield* Path.Path;
|
|
175
|
-
return [
|
|
176
|
-
{
|
|
177
|
-
contentType: "application/json; charset=utf-8",
|
|
178
|
-
path: deploymentInventoryPath,
|
|
179
|
-
sha256: inventorySha256,
|
|
180
|
-
size: inventorySize,
|
|
181
|
-
sourcePath: path.join(buildDirectory, deploymentInventoryPath),
|
|
182
|
-
},
|
|
183
|
-
...inventory.files.map((file) => {
|
|
184
|
-
const bytes = memoryFiles.get(file.path);
|
|
185
|
-
return bytes === undefined
|
|
186
|
-
? uploadFile(file, path.join(buildDirectory, file.path))
|
|
187
|
-
: {
|
|
188
|
-
bytes,
|
|
189
|
-
contentType: file.contentType,
|
|
190
|
-
path: file.path,
|
|
191
|
-
sha256: file.sha256,
|
|
192
|
-
size: file.size,
|
|
193
|
-
};
|
|
194
|
-
}),
|
|
195
|
-
];
|
|
196
|
-
});
|
|
197
|
-
|
|
198
162
|
export const controlClientLayer = Layer.effect(
|
|
199
163
|
ControlClient,
|
|
200
164
|
Effect.gen(function* () {
|
|
@@ -297,82 +261,101 @@ export const controlClientLayer = Layer.effect(
|
|
|
297
261
|
);
|
|
298
262
|
});
|
|
299
263
|
|
|
300
|
-
const
|
|
264
|
+
const beginVersion = Effect.fn("ControlClient.beginVersion")(function* (
|
|
301
265
|
appId: AppId,
|
|
302
|
-
|
|
266
|
+
versionId: VersionId,
|
|
267
|
+
declaration: UploadDeclaration,
|
|
303
268
|
) {
|
|
304
269
|
const request = yield* HttpClientRequest.post(
|
|
305
|
-
endpoint(config.apiUrl,
|
|
270
|
+
endpoint(config.apiUrl, `${controlVersionPath(appId, versionId)}/begin`),
|
|
306
271
|
).pipe(
|
|
307
|
-
HttpClientRequest.schemaBodyJson(
|
|
308
|
-
Effect.mapError((cause) =>
|
|
309
|
-
unavailable("The deployment request could not be encoded.", cause),
|
|
310
|
-
),
|
|
272
|
+
HttpClientRequest.schemaBodyJson(UploadDeclaration)(declaration),
|
|
273
|
+
Effect.mapError((cause) => unavailable("The version request could not be encoded.", cause)),
|
|
311
274
|
);
|
|
312
|
-
return yield*
|
|
275
|
+
return yield* executeIdempotent(request, BeginVersionResponseSchema);
|
|
313
276
|
});
|
|
314
277
|
|
|
315
|
-
const
|
|
278
|
+
const uploadVersionFile = Effect.fn("ControlClient.uploadVersionFile")(function* (
|
|
316
279
|
appId: AppId,
|
|
317
|
-
|
|
318
|
-
file:
|
|
280
|
+
versionId: VersionId,
|
|
281
|
+
file: VersionUploadFile,
|
|
319
282
|
) {
|
|
320
283
|
const baseRequest = HttpClientRequest.put(
|
|
321
|
-
endpoint(config.apiUrl,
|
|
322
|
-
).pipe(
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
284
|
+
endpoint(config.apiUrl, controlVersionFilePath(appId, versionId, file.path)),
|
|
285
|
+
).pipe(HttpClientRequest.setHeader("content-length", String(file.size)));
|
|
286
|
+
const uploadRequest =
|
|
287
|
+
file.contentEncoding === undefined
|
|
288
|
+
? baseRequest
|
|
289
|
+
: baseRequest.pipe(HttpClientRequest.setHeader("content-encoding", file.contentEncoding));
|
|
326
290
|
const request = yield* file.bytes === undefined
|
|
327
291
|
? file.sourcePath === undefined
|
|
328
|
-
? Effect.fail(unavailable(`
|
|
329
|
-
: HttpClientRequest.bodyFile(
|
|
292
|
+
? Effect.fail(unavailable(`Version file '${file.path}' has no upload source.`, {}))
|
|
293
|
+
: HttpClientRequest.bodyFile(uploadRequest, file.sourcePath, {
|
|
330
294
|
contentType: file.contentType,
|
|
331
295
|
}).pipe(
|
|
332
296
|
Effect.provideService(FileSystem.FileSystem, fileSystem),
|
|
333
297
|
Effect.mapError((cause) =>
|
|
334
|
-
unavailable(`
|
|
298
|
+
unavailable(`Version file '${file.path}' could not be opened.`, cause),
|
|
335
299
|
),
|
|
336
300
|
)
|
|
337
301
|
: Effect.succeed(
|
|
338
|
-
HttpClientRequest.bodyUint8Array(
|
|
302
|
+
HttpClientRequest.bodyUint8Array(uploadRequest, file.bytes, file.contentType),
|
|
339
303
|
);
|
|
340
|
-
const response = yield*
|
|
341
|
-
|
|
304
|
+
const response = yield* idempotentHttp.execute(authenticate(request)).pipe(
|
|
305
|
+
Effect.mapError((cause) => unavailable("The artifact upload failed.", cause)),
|
|
306
|
+
Effect.timeoutOrElse({
|
|
307
|
+
duration: "30 seconds",
|
|
308
|
+
orElse: () => Effect.fail(unavailable("The artifact upload exceeded its deadline.", {})),
|
|
309
|
+
}),
|
|
310
|
+
);
|
|
311
|
+
if (response.status !== 204) {
|
|
312
|
+
yield* decodeResponse(response, Schema.Never);
|
|
313
|
+
}
|
|
342
314
|
});
|
|
343
315
|
|
|
344
|
-
const
|
|
316
|
+
const finalizeVersion = Effect.fn("ControlClient.finalizeVersion")(function* (
|
|
345
317
|
appId: AppId,
|
|
346
|
-
|
|
318
|
+
versionId: VersionId,
|
|
347
319
|
) {
|
|
348
320
|
return yield* executeIdempotent(
|
|
349
321
|
HttpClientRequest.post(
|
|
350
|
-
endpoint(config.apiUrl,
|
|
322
|
+
endpoint(config.apiUrl, controlVersionFinalizePath(appId, versionId)),
|
|
351
323
|
),
|
|
352
|
-
|
|
324
|
+
ControlVersionSchema,
|
|
353
325
|
);
|
|
354
326
|
});
|
|
355
327
|
|
|
356
|
-
const
|
|
328
|
+
const activateVersion = Effect.fn("ControlClient.activateVersion")(function* (
|
|
357
329
|
appId: AppId,
|
|
358
|
-
|
|
330
|
+
versionId: VersionId,
|
|
359
331
|
) {
|
|
360
332
|
return yield* executeIdempotent(
|
|
361
333
|
HttpClientRequest.post(
|
|
362
|
-
endpoint(config.apiUrl,
|
|
334
|
+
endpoint(config.apiUrl, controlVersionActivatePath(appId, versionId)),
|
|
363
335
|
),
|
|
364
|
-
|
|
336
|
+
ExecutionSelection,
|
|
337
|
+
);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const getVersion = Effect.fn("ControlClient.getVersion")(function* (
|
|
341
|
+
appId: AppId,
|
|
342
|
+
versionId: VersionId,
|
|
343
|
+
) {
|
|
344
|
+
return yield* executeIdempotent(
|
|
345
|
+
HttpClientRequest.get(endpoint(config.apiUrl, controlVersionPath(appId, versionId))),
|
|
346
|
+
ControlVersionSchema,
|
|
365
347
|
);
|
|
366
348
|
});
|
|
367
349
|
|
|
368
350
|
return ControlClient.of({
|
|
369
|
-
|
|
370
|
-
|
|
351
|
+
activateVersion,
|
|
352
|
+
beginVersion,
|
|
371
353
|
createApp,
|
|
372
|
-
|
|
354
|
+
finalizeVersion,
|
|
373
355
|
getApp,
|
|
356
|
+
getVersion,
|
|
374
357
|
getAppBySlug,
|
|
375
|
-
|
|
358
|
+
uploadVersionFile,
|
|
376
359
|
});
|
|
377
360
|
}),
|
|
378
361
|
);
|
package/src/cli/deploy.ts
CHANGED
|
@@ -1,222 +1,178 @@
|
|
|
1
|
-
import { Effect,
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import type { AppSlug } from "@ignotum/contracts/control";
|
|
6
|
-
import { DeploymentId } from "@ignotum/contracts/runtime/identity";
|
|
7
|
-
import {
|
|
8
|
-
makeDeploymentInventory,
|
|
9
|
-
readArtifactDirectory,
|
|
10
|
-
sha256,
|
|
11
|
-
validateDeploymentArtifact,
|
|
12
|
-
} from "@ignotum/deployment";
|
|
13
|
-
|
|
1
|
+
import { Effect, Path, Schema } from "effect";
|
|
2
|
+
import type { AppSlug, ControlVersion } from "@ignotum/contracts/control";
|
|
3
|
+
import { VersionId, VersionNumber } from "@ignotum/contracts/runtime/identity";
|
|
4
|
+
import { IdGenerator } from "@ignotum/shared/id";
|
|
14
5
|
import { appUrl, configureApp, type AppConfiguration } from "./app-configuration.js";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
readonly serverFunctions: number;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface DeployResult extends DeploymentBuildResult {
|
|
30
|
-
readonly appUrl: string;
|
|
31
|
-
readonly deploymentId: DeploymentId;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export class BuildReplacementFailed extends Schema.TaggedError<BuildReplacementFailed>()(
|
|
35
|
-
"BuildReplacementFailed",
|
|
36
|
-
{
|
|
37
|
-
backupPath: Schema.String,
|
|
38
|
-
cause: Schema.Defect(),
|
|
39
|
-
message: Schema.String,
|
|
40
|
-
},
|
|
41
|
-
) {}
|
|
42
|
-
|
|
43
|
-
export class DeploymentUploadInterrupted extends Schema.TaggedError<DeploymentUploadInterrupted>()(
|
|
44
|
-
"DeploymentUploadInterrupted",
|
|
45
|
-
{
|
|
46
|
-
cause: Schema.Defect(),
|
|
47
|
-
deploymentId: DeploymentId,
|
|
48
|
-
message: Schema.String,
|
|
49
|
-
},
|
|
6
|
+
import { buildManagedVersion, type ManagedVersionBuildResult } from "./build/managed.js";
|
|
7
|
+
import {
|
|
8
|
+
ControlClient,
|
|
9
|
+
ControlClientUnavailable,
|
|
10
|
+
type ControlApiError,
|
|
11
|
+
type VersionUploadFile,
|
|
12
|
+
} from "./control-client.js";
|
|
13
|
+
|
|
14
|
+
export class VersionUploadInterrupted extends Schema.TaggedError<VersionUploadInterrupted>()(
|
|
15
|
+
"VersionUploadInterrupted",
|
|
16
|
+
{ cause: Schema.Defect(), versionId: VersionId, message: Schema.String },
|
|
50
17
|
) {}
|
|
51
18
|
|
|
52
|
-
export class
|
|
53
|
-
"
|
|
19
|
+
export class VersionActivationUnconfirmed extends Schema.TaggedError<VersionActivationUnconfirmed>()(
|
|
20
|
+
"VersionActivationUnconfirmed",
|
|
54
21
|
{
|
|
55
22
|
cause: Schema.Defect(),
|
|
56
|
-
|
|
23
|
+
versionId: VersionId,
|
|
24
|
+
versionNumber: VersionNumber,
|
|
57
25
|
message: Schema.String,
|
|
58
26
|
},
|
|
59
27
|
) {}
|
|
60
28
|
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const restoration = yield* Effect.result(fileSystem.rename(backupPath, buildDirectory));
|
|
87
|
-
if (Result.isFailure(restoration)) {
|
|
88
|
-
return yield* BuildReplacementFailed.make({
|
|
89
|
-
backupPath,
|
|
90
|
-
cause: restoration.failure,
|
|
91
|
-
message: `Could not install the new build or restore the previous build. The previous build remains at ${backupPath}.`,
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return yield* replacement.failure;
|
|
96
|
-
});
|
|
29
|
+
const activationError = (
|
|
30
|
+
version: ControlVersion & { readonly number: VersionNumber },
|
|
31
|
+
cause: ControlApiError | ControlClientUnavailable,
|
|
32
|
+
) =>
|
|
33
|
+
cause._tag === "ControlClientUnavailable" || cause.status >= 500
|
|
34
|
+
? VersionActivationUnconfirmed.make({
|
|
35
|
+
cause,
|
|
36
|
+
versionId: version.id,
|
|
37
|
+
versionNumber: version.number,
|
|
38
|
+
message: `Version ${version.number} (${version.id}) is ready, but activation could not be confirmed. Run ignotum deploy --resume ${version.id} to finish activation.`,
|
|
39
|
+
})
|
|
40
|
+
: cause;
|
|
41
|
+
|
|
42
|
+
const readyVersion = (version: ControlVersion) =>
|
|
43
|
+
version.status === "Ready" && version.number !== null
|
|
44
|
+
? Effect.succeed({ ...version, number: version.number })
|
|
45
|
+
: Effect.fail(
|
|
46
|
+
ControlClientUnavailable.make({
|
|
47
|
+
cause: { versionId: version.id, status: version.status },
|
|
48
|
+
message: `Version ${version.id} is not ready for activation.`,
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
97
51
|
|
|
98
|
-
export const
|
|
52
|
+
export const buildVersionArtifact = Effect.fn("Deploy.buildArtifact")(function* (
|
|
99
53
|
appDirectory: string,
|
|
100
54
|
) {
|
|
101
|
-
const fileSystem = yield* FileSystem.FileSystem;
|
|
102
55
|
const path = yield* Path.Path;
|
|
103
|
-
|
|
104
|
-
const buildDirectory = path.join(stateDirectory, "build");
|
|
105
|
-
|
|
106
|
-
yield* fileSystem.makeDirectory(stateDirectory, { recursive: true });
|
|
107
|
-
const codegen = yield* generate(appDirectory);
|
|
108
|
-
|
|
109
|
-
return yield* Effect.scoped(
|
|
110
|
-
Effect.gen(function* () {
|
|
111
|
-
const stagingDirectory = yield* Effect.acquireRelease(
|
|
112
|
-
fileSystem.makeTempDirectory({
|
|
113
|
-
directory: stateDirectory,
|
|
114
|
-
prefix: ".build-staging-",
|
|
115
|
-
}),
|
|
116
|
-
(directory) =>
|
|
117
|
-
fileSystem.remove(directory, { force: true, recursive: true }).pipe(Effect.orDie),
|
|
118
|
-
);
|
|
119
|
-
const clientOutput = path.join(stagingDirectory, "client");
|
|
120
|
-
const serverOutput = path.join(stagingDirectory, "server");
|
|
121
|
-
const client = yield* buildClient(appDirectory, clientOutput);
|
|
122
|
-
const server = yield* buildServer(appDirectory, serverOutput, codegen.functionModules);
|
|
123
|
-
const diskPayloadFiles = yield* readArtifactDirectory(stagingDirectory);
|
|
124
|
-
const payloadFiles = [...diskPayloadFiles, server.environment];
|
|
125
|
-
const inventory = yield* makeDeploymentInventory(payloadFiles);
|
|
126
|
-
yield* fileSystem.writeFile(
|
|
127
|
-
path.join(stagingDirectory, deploymentInventoryPath),
|
|
128
|
-
inventory.bytes,
|
|
129
|
-
);
|
|
130
|
-
const diskArtifactFiles = yield* readArtifactDirectory(stagingDirectory);
|
|
131
|
-
const artifactFiles = [...diskArtifactFiles, server.environment];
|
|
132
|
-
yield* validateDeploymentArtifact(artifactFiles);
|
|
133
|
-
yield* Effect.logInfo("Built deployment artifact").pipe(
|
|
134
|
-
Effect.annotateLogs({
|
|
135
|
-
files: inventory.inventory.files.length,
|
|
136
|
-
functions: server.functions,
|
|
137
|
-
}),
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
yield* replaceBuildDirectory(stateDirectory, stagingDirectory, buildDirectory);
|
|
141
|
-
|
|
142
|
-
return {
|
|
143
|
-
clientAssets: client.assets,
|
|
144
|
-
inventory: inventory.inventory,
|
|
145
|
-
inventoryBytes: inventory.bytes,
|
|
146
|
-
memoryFiles: new Map([[server.environment.path, server.environment.bytes]]),
|
|
147
|
-
outputDirectory: buildDirectory,
|
|
148
|
-
serverFunctions: server.functions,
|
|
149
|
-
} satisfies DeploymentBuildResult;
|
|
150
|
-
}),
|
|
151
|
-
);
|
|
56
|
+
return yield* buildManagedVersion(appDirectory, { applicationName: path.basename(appDirectory) });
|
|
152
57
|
});
|
|
153
58
|
|
|
154
|
-
export const
|
|
59
|
+
export const uploadVersion = Effect.fn("Deploy.upload")(function* (
|
|
155
60
|
configuration: AppConfiguration,
|
|
156
|
-
build:
|
|
61
|
+
build: ManagedVersionBuildResult,
|
|
157
62
|
) {
|
|
158
63
|
const control = yield* ControlClient;
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
build.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
64
|
+
const versionId = yield* (yield* IdGenerator).generate(VersionId);
|
|
65
|
+
const admission = yield* control.beginVersion(configuration.appId, versionId, build.declaration);
|
|
66
|
+
const files = yield* Effect.forEach(admission.targets, (target) => {
|
|
67
|
+
const publicArtifact = build.declaration.definition.artifacts.find(
|
|
68
|
+
(artifact) => artifact.path === target.path,
|
|
69
|
+
);
|
|
70
|
+
const artifact = target.protected ? build.declaration.environment?.artifact : publicArtifact;
|
|
71
|
+
const bytes =
|
|
72
|
+
target.protected && target.path === build.environment.path
|
|
73
|
+
? build.environment.bytes
|
|
74
|
+
: build.bytes.get(target.path);
|
|
75
|
+
if (
|
|
76
|
+
target.appId !== configuration.appId ||
|
|
77
|
+
target.versionId !== versionId ||
|
|
78
|
+
artifact === undefined ||
|
|
79
|
+
artifact.path !== target.path ||
|
|
80
|
+
artifact.size !== target.size ||
|
|
81
|
+
artifact.sha256 !== target.sha256 ||
|
|
82
|
+
bytes === undefined ||
|
|
83
|
+
bytes.byteLength !== target.size ||
|
|
84
|
+
(target.protected
|
|
85
|
+
? target.contentType !== "application/json; charset=utf-8" ||
|
|
86
|
+
target.contentEncoding !== undefined
|
|
87
|
+
: publicArtifact === undefined ||
|
|
88
|
+
publicArtifact.contentType !== target.contentType ||
|
|
89
|
+
publicArtifact.contentEncoding !== target.contentEncoding)
|
|
90
|
+
) {
|
|
91
|
+
return Effect.fail(
|
|
92
|
+
ControlClientUnavailable.make({
|
|
93
|
+
cause: { path: target.path },
|
|
94
|
+
message: "The API requested an artifact that does not match this build.",
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return Effect.succeed({ ...target, bytes } satisfies VersionUploadFile);
|
|
99
|
+
});
|
|
169
100
|
yield* Effect.forEach(
|
|
170
101
|
files,
|
|
171
|
-
(file) => control.
|
|
102
|
+
(file) => control.uploadVersionFile(configuration.appId, versionId, file),
|
|
172
103
|
{ concurrency: 4, discard: true },
|
|
173
104
|
).pipe(
|
|
174
105
|
Effect.catchTags({
|
|
175
106
|
ControlClientUnavailable: (cause) =>
|
|
176
|
-
|
|
177
|
-
cause,
|
|
178
|
-
deploymentId: deployment.id,
|
|
179
|
-
message: `Deployment ${deployment.id} was interrupted during upload. The active deployment was not changed.`,
|
|
180
|
-
}),
|
|
181
|
-
}),
|
|
182
|
-
);
|
|
183
|
-
|
|
184
|
-
yield* control.finalizeDeployment(configuration.appId, deployment.id).pipe(
|
|
185
|
-
Effect.catchTags({
|
|
186
|
-
ControlClientUnavailable: (cause) =>
|
|
187
|
-
DeploymentUploadInterrupted.make({
|
|
107
|
+
VersionUploadInterrupted.make({
|
|
188
108
|
cause,
|
|
189
|
-
|
|
190
|
-
message: `
|
|
109
|
+
versionId,
|
|
110
|
+
message: `Version ${versionId} was interrupted during upload. The active version was not changed.`,
|
|
191
111
|
}),
|
|
192
112
|
}),
|
|
193
113
|
);
|
|
194
|
-
|
|
195
|
-
yield* control.activateDeployment(configuration.appId, deployment.id).pipe(
|
|
114
|
+
const version = yield* control.finalizeVersion(configuration.appId, versionId).pipe(
|
|
196
115
|
Effect.catchTags({
|
|
197
116
|
ControlClientUnavailable: (cause) =>
|
|
198
|
-
|
|
117
|
+
VersionUploadInterrupted.make({
|
|
199
118
|
cause,
|
|
200
|
-
|
|
201
|
-
message: `
|
|
119
|
+
versionId,
|
|
120
|
+
message: `Version ${versionId} could not be finalized. The active version was not changed.`,
|
|
202
121
|
}),
|
|
203
122
|
}),
|
|
123
|
+
Effect.flatMap(readyVersion),
|
|
204
124
|
);
|
|
205
|
-
|
|
206
|
-
|
|
125
|
+
const selection = yield* control
|
|
126
|
+
.activateVersion(configuration.appId, versionId)
|
|
127
|
+
.pipe(Effect.mapError((cause) => activationError(version, cause)));
|
|
128
|
+
if (selection.appId !== configuration.appId || selection.versionId !== versionId) {
|
|
129
|
+
return yield* activationError(
|
|
130
|
+
version,
|
|
131
|
+
ControlClientUnavailable.make({
|
|
132
|
+
cause: selection,
|
|
133
|
+
message: "The API confirmed a different App Version.",
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
return { versionId, versionNumber: version.number, selection };
|
|
207
138
|
});
|
|
208
139
|
|
|
209
140
|
export const deploy = Effect.fn("Deploy.run")(function* (
|
|
210
141
|
appDirectory: string,
|
|
211
142
|
requestedSlug: AppSlug | undefined,
|
|
212
143
|
) {
|
|
213
|
-
const build = yield*
|
|
144
|
+
const build = yield* buildVersionArtifact(appDirectory);
|
|
214
145
|
const configuration = yield* configureApp(appDirectory, requestedSlug);
|
|
215
|
-
const
|
|
146
|
+
const activated = yield* uploadVersion(configuration, build);
|
|
147
|
+
return { ...activated, appUrl: appUrl(configuration.slug) };
|
|
148
|
+
});
|
|
216
149
|
|
|
150
|
+
export const resumeVersion = Effect.fn("Deploy.resume")(function* (
|
|
151
|
+
appDirectory: string,
|
|
152
|
+
versionId: VersionId,
|
|
153
|
+
requestedSlug?: AppSlug,
|
|
154
|
+
) {
|
|
155
|
+
const configuration = yield* configureApp(appDirectory, requestedSlug);
|
|
156
|
+
const control = yield* ControlClient;
|
|
157
|
+
const version = yield* control
|
|
158
|
+
.getVersion(configuration.appId, versionId)
|
|
159
|
+
.pipe(Effect.flatMap(readyVersion));
|
|
160
|
+
const selection = yield* control
|
|
161
|
+
.activateVersion(configuration.appId, versionId)
|
|
162
|
+
.pipe(Effect.mapError((cause) => activationError(version, cause)));
|
|
163
|
+
if (selection.appId !== configuration.appId || selection.versionId !== versionId) {
|
|
164
|
+
return yield* activationError(
|
|
165
|
+
version,
|
|
166
|
+
ControlClientUnavailable.make({
|
|
167
|
+
cause: selection,
|
|
168
|
+
message: "The API confirmed a different App Version.",
|
|
169
|
+
}),
|
|
170
|
+
);
|
|
171
|
+
}
|
|
217
172
|
return {
|
|
218
|
-
...build,
|
|
219
173
|
appUrl: appUrl(configuration.slug),
|
|
220
|
-
|
|
221
|
-
|
|
174
|
+
versionId,
|
|
175
|
+
versionNumber: version.number,
|
|
176
|
+
selection,
|
|
177
|
+
};
|
|
222
178
|
});
|