ignotum 0.0.16 → 0.0.18

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 (126) hide show
  1. package/README.md +6 -6
  2. package/dist/cli/bin.mjs +13263 -2689
  3. package/dist/cli/bin.mjs.map +1 -1
  4. package/dist/platform/_headers +3 -0
  5. package/dist/platform/_ignotum/v1.0/client.js +12 -0
  6. package/dist/runtime/{api-B4fwp5a2.d.ts → api-BnUKx9cj-D49Tf0rX.d.ts} +4 -3
  7. package/dist/runtime/api-C-tltz6s.js +31 -0
  8. package/dist/runtime/api-C-tltz6s.js.map +1 -0
  9. package/dist/runtime/artifact-BB5qzoa6.js +42 -0
  10. package/dist/runtime/artifact-BB5qzoa6.js.map +1 -0
  11. package/dist/runtime/{bootstrap-BTFScKAc.d.ts → bootstrap-DjTwGJao-DBx5zdFI.d.ts} +64 -64
  12. package/dist/runtime/{bootstrap-BISQSn_O.js → bootstrap-DlNIfH-r.js} +9 -9
  13. package/dist/runtime/bootstrap-DlNIfH-r.js.map +1 -0
  14. package/dist/runtime/client.d.ts +24 -20
  15. package/dist/runtime/client.js +9 -9
  16. package/dist/runtime/client.js.map +1 -1
  17. package/dist/runtime/id-BI08JkmR.d.ts +1 -0
  18. package/dist/runtime/{id--1m0NuUL.js → id-BrEnxY-r.js} +118 -53
  19. package/dist/runtime/id-BrEnxY-r.js.map +1 -0
  20. package/dist/runtime/{image-Djtjy4Z8.js → image-gofzZeCm.js} +2 -2
  21. package/dist/runtime/image-gofzZeCm.js.map +1 -0
  22. package/dist/runtime/{index-D4seNHlX.d.ts → index-Biaw5vdF.d.ts} +2 -2
  23. package/dist/runtime/internal/api.d.ts +1 -1
  24. package/dist/runtime/internal/api.js +1 -30
  25. package/dist/runtime/internal/bootstrap.d.ts +1 -1
  26. package/dist/runtime/internal/bootstrap.js +1 -1
  27. package/dist/runtime/internal/client.d.ts +2 -2
  28. package/dist/runtime/internal/client.js +4 -4
  29. package/dist/runtime/internal/client.js.map +1 -1
  30. package/dist/runtime/internal/host.d.ts +4 -4
  31. package/dist/runtime/internal/host.js +4 -4
  32. package/dist/runtime/internal/routes.d.ts +1 -1
  33. package/dist/runtime/internal/routes.js +1 -1
  34. package/dist/runtime/internal/server.d.ts +3 -2
  35. package/dist/runtime/internal/server.js +2 -2
  36. package/dist/runtime/internal/server.js.map +1 -1
  37. package/dist/runtime/internal/types.d.ts +2 -2
  38. package/dist/runtime/internal/types.js +1 -1
  39. package/dist/runtime/{pagination-D3qd6s9N.js → pagination--f4kMKf6.js} +2 -2
  40. package/dist/runtime/{pagination-D3qd6s9N.js.map → pagination--f4kMKf6.js.map} +1 -1
  41. package/dist/runtime/pagination-BI08JkmR.d.ts +1 -0
  42. package/dist/runtime/{router-BSBI1oN1.js → router-C1eC9yy5.js} +462 -419
  43. package/dist/runtime/router-C1eC9yy5.js.map +1 -0
  44. package/dist/runtime/routes-DnU39R7c.js +152 -0
  45. package/dist/runtime/routes-DnU39R7c.js.map +1 -0
  46. package/dist/runtime/{schema-D1wOqiNH.js → schema-BSjepoNF.js} +271 -6
  47. package/dist/runtime/schema-BSjepoNF.js.map +1 -0
  48. package/dist/runtime/server.d.ts +5 -4
  49. package/dist/runtime/server.js +3 -3
  50. package/dist/runtime/server.js.map +1 -1
  51. package/dist/runtime/{sync-DzUwA8W9.js → sync-BwgS19Xy.js} +4 -16
  52. package/dist/runtime/sync-BwgS19Xy.js.map +1 -0
  53. package/dist/runtime/{sync-D-GK2sv9.d.ts → sync-By2hX5ev.d.ts} +1 -2
  54. package/dist/runtime/{types-DeCCyqLU-BIm7rU-0.d.ts → types-DeCCyqLU-CHx7KFtx.d.ts} +2 -2
  55. package/package.json +7 -1
  56. package/src/cli/agent-files.ts +2 -0
  57. package/src/cli/build/asset-names.ts +190 -0
  58. package/src/cli/build/managed-client.ts +151 -81
  59. package/src/cli/build/managed-server.ts +4 -4
  60. package/src/cli/build/managed.ts +5 -6
  61. package/src/cli/command.ts +7 -15
  62. package/src/cli/control-client.ts +7 -9
  63. package/src/cli/deploy.ts +45 -15
  64. package/src/cli/dev.ts +50 -99
  65. package/src/cli/image-assets.ts +5 -2
  66. package/src/cli/managed-client-boundaries.ts +6 -6
  67. package/src/cli/managed-dev-platform.ts +52 -66
  68. package/src/cli/managed-exports.ts +5 -66
  69. package/src/cli/new-app.ts +7 -2
  70. package/src/cli/path.ts +11 -0
  71. package/src/cli/route-codegen.ts +2 -1
  72. package/src/cli/route-static.ts +3 -2
  73. package/src/cli/upload-journal.ts +130 -0
  74. package/src/client/bootstrap.ts +1 -110
  75. package/src/client/errors.ts +1 -70
  76. package/src/client/files.ts +1 -233
  77. package/src/client/hooks.ts +1 -367
  78. package/src/client/id.ts +1 -263
  79. package/src/client/image.ts +1 -28
  80. package/src/client/index.ts +1 -126
  81. package/src/client/jsx-dev-runtime.ts +1 -2
  82. package/src/client/jsx-runtime.ts +1 -2
  83. package/src/client/managed-client.ts +1 -729
  84. package/src/client/managed-upgrade.ts +1 -283
  85. package/src/client/page-observers.ts +1 -77
  86. package/src/client/query.ts +1 -17
  87. package/src/client/recovery-journal.ts +1 -195
  88. package/src/client/route-boundaries.ts +1 -184
  89. package/src/client/router-history.ts +1 -152
  90. package/src/client/router-store.ts +1 -644
  91. package/src/client/router.ts +1 -324
  92. package/src/client/routes.ts +1 -28
  93. package/src/client/sync.ts +1 -286
  94. package/src/dev-runtime/id.ts +1 -1
  95. package/src/dev-runtime/managed-functions.ts +10 -18
  96. package/src/dev-runtime/managed-sync.ts +16 -16
  97. package/src/dev-runtime/sync.ts +6 -4
  98. package/src/internal/api.ts +1 -156
  99. package/src/internal/client.ts +1 -186
  100. package/src/internal/host.ts +1 -1
  101. package/src/internal/http-paths.ts +1 -4
  102. package/src/internal/routes.ts +1 -45
  103. package/src/internal/server.ts +1 -3
  104. package/src/internal/types.ts +1 -2
  105. package/src/server/index.ts +1 -27
  106. package/dist/cli/managed-release-WHYLZwaS.mjs +0 -10902
  107. package/dist/cli/managed-release-WHYLZwaS.mjs.map +0 -1
  108. package/dist/cli/managed-release.d.mts +0 -1
  109. package/dist/cli/managed-release.mjs +0 -14
  110. package/dist/cli/managed-release.mjs.map +0 -1
  111. package/dist/runtime/bootstrap-BISQSn_O.js.map +0 -1
  112. package/dist/runtime/descriptor-CTY4mtwS-cS43EbwS.js +0 -269
  113. package/dist/runtime/descriptor-CTY4mtwS-cS43EbwS.js.map +0 -1
  114. package/dist/runtime/id--1m0NuUL.js.map +0 -1
  115. package/dist/runtime/id-CBOt2kDo.d.ts +0 -1
  116. package/dist/runtime/identity-QjtJRxBD.d.ts +0 -2
  117. package/dist/runtime/image-Djtjy4Z8.js.map +0 -1
  118. package/dist/runtime/internal/api.js.map +0 -1
  119. package/dist/runtime/pagination-CBOt2kDo.d.ts +0 -1
  120. package/dist/runtime/router-BSBI1oN1.js.map +0 -1
  121. package/dist/runtime/routes-Ce8TVP-T.js +0 -690
  122. package/dist/runtime/routes-Ce8TVP-T.js.map +0 -1
  123. package/dist/runtime/schema-D1wOqiNH.js.map +0 -1
  124. package/dist/runtime/sync-DzUwA8W9.js.map +0 -1
  125. package/src/cli/managed-release-bin.ts +0 -13
  126. package/src/cli/managed-release.ts +0 -260
@@ -14,7 +14,6 @@ import {
14
14
  } from "./auth-client.js";
15
15
  import { ControlClient } from "./control-client.js";
16
16
  import { deploy as runDeploy, resumeVersion } from "./deploy.js";
17
- import { VersionId } from "@ignotum/contracts/runtime/identity";
18
17
  import { resetDevDatabase } from "../dev-runtime/dev-database.js";
19
18
  import { dev as runDev } from "./dev.js";
20
19
  import { createApp } from "./new-app.js";
@@ -145,10 +144,9 @@ const dev = Command.make(
145
144
  const deploy = Command.make(
146
145
  "deploy",
147
146
  {
148
- resume: Flag.string("resume").pipe(
149
- Flag.withDescription("Finish activation of an already uploaded version."),
150
- Flag.withSchema(VersionId),
151
- Flag.optional,
147
+ resume: Flag.boolean("resume").pipe(
148
+ Flag.withDescription("Resume the saved upload or activation."),
149
+ Flag.withDefault(false),
152
150
  ),
153
151
  app: Flag.string("app").pipe(
154
152
  Flag.withDescription("Create or link this app slug on the first deploy."),
@@ -159,12 +157,8 @@ const deploy = Command.make(
159
157
  Effect.fn("deploy")(function* ({ app, resume }) {
160
158
  const path = yield* Path.Path;
161
159
  const terminal = yield* Terminal.Terminal;
162
- if (Option.isSome(resume)) {
163
- const result = yield* resumeVersion(
164
- path.resolve("."),
165
- resume.value,
166
- Option.getOrUndefined(app),
167
- ).pipe(
160
+ if (resume) {
161
+ const result = yield* resumeVersion(path.resolve("."), Option.getOrUndefined(app)).pipe(
168
162
  // @effect-diagnostics-next-line strictEffectProvide:off This command owns the authenticated control client.
169
163
  Effect.provide(ControlClient.layer),
170
164
  Effect.mapError((error) =>
@@ -172,7 +166,7 @@ const deploy = Command.make(
172
166
  ),
173
167
  );
174
168
  yield* terminal.display(
175
- `Version ${result.versionNumber} is live.\n\nApp: ${result.appUrl}\nVersion ID: ${result.versionId}\n`,
169
+ `Version ${result.versionNumber} is live.\n\nApp: ${result.appUrl}\n`,
176
170
  );
177
171
  return;
178
172
  }
@@ -184,9 +178,7 @@ const deploy = Command.make(
184
178
  ),
185
179
  );
186
180
 
187
- yield* terminal.display(
188
- `Version ${result.versionNumber} is live.\n\nApp: ${result.appUrl}\nVersion ID: ${result.versionId}\n`,
189
- );
181
+ yield* terminal.display(`Version ${result.versionNumber} is live.\n\nApp: ${result.appUrl}\n`);
190
182
  }),
191
183
  ).pipe(Command.withDescription("Build and deploy the application to Ignotum Cloud."));
192
184
 
@@ -23,8 +23,8 @@ import {
23
23
  controlVersionPath,
24
24
  } from "@ignotum/contracts/control";
25
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";
26
+ import { ServingSelection } from "@ignotum/contracts/platform";
27
+ import type { AppId, VersionId, VersionNumber } from "@ignotum/contracts/runtime/identity";
28
28
  import {
29
29
  Config,
30
30
  Context,
@@ -111,8 +111,8 @@ export interface VersionUploadFile {
111
111
  interface ControlClientService {
112
112
  readonly activateVersion: (
113
113
  appId: AppId,
114
- versionId: VersionId,
115
- ) => Effect.Effect<ExecutionSelection, ControlApiError | ControlClientUnavailable>;
114
+ number: VersionNumber,
115
+ ) => Effect.Effect<ServingSelection, ControlApiError | ControlClientUnavailable>;
116
116
  readonly beginVersion: (
117
117
  appId: AppId,
118
118
  versionId: VersionId,
@@ -329,13 +329,11 @@ export const controlClientLayer = Layer.effect(
329
329
 
330
330
  const activateVersion = Effect.fn("ControlClient.activateVersion")(function* (
331
331
  appId: AppId,
332
- versionId: VersionId,
332
+ number: VersionNumber,
333
333
  ) {
334
334
  return yield* executeIdempotent(
335
- HttpClientRequest.post(
336
- endpoint(config.apiUrl, controlVersionActivatePath(appId, versionId)),
337
- ),
338
- ExecutionSelection,
335
+ HttpClientRequest.post(endpoint(config.apiUrl, controlVersionActivatePath(appId, number))),
336
+ ServingSelection,
339
337
  );
340
338
  });
341
339
 
package/src/cli/deploy.ts CHANGED
@@ -11,6 +11,8 @@ import {
11
11
  type VersionUploadFile,
12
12
  } from "./control-client.js";
13
13
 
14
+ import { saveUpload, loadUpload, restoreUploadBuild, clearUpload } from "./upload-journal.js";
15
+
14
16
  export class VersionUploadInterrupted extends Schema.TaggedError<VersionUploadInterrupted>()(
15
17
  "VersionUploadInterrupted",
16
18
  { cause: Schema.Defect(), versionId: VersionId, message: Schema.String },
@@ -35,7 +37,7 @@ const activationError = (
35
37
  cause,
36
38
  versionId: version.id,
37
39
  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.`,
40
+ message: `Version ${version.number} is ready, but activation could not be confirmed. Run ignotum deploy --resume to finish activation.`,
39
41
  })
40
42
  : cause;
41
43
 
@@ -45,7 +47,8 @@ const readyVersion = (version: ControlVersion) =>
45
47
  : Effect.fail(
46
48
  ControlClientUnavailable.make({
47
49
  cause: { versionId: version.id, status: version.status },
48
- message: `Version ${version.id} is not ready for activation.`,
50
+ message:
51
+ "The saved upload is not ready for activation. Run ignotum deploy to start a new upload.",
49
52
  }),
50
53
  );
51
54
 
@@ -59,10 +62,22 @@ export const buildVersionArtifact = Effect.fn("Deploy.buildArtifact")(function*
59
62
  export const uploadVersion = Effect.fn("Deploy.upload")(function* (
60
63
  configuration: AppConfiguration,
61
64
  build: ManagedVersionBuildResult,
65
+ savedVersionId?: VersionId,
62
66
  ) {
63
67
  const control = yield* ControlClient;
64
- const versionId = yield* (yield* IdGenerator).generate(VersionId);
65
- const admission = yield* control.beginVersion(configuration.appId, versionId, build.declaration);
68
+ const versionId = savedVersionId ?? (yield* (yield* IdGenerator).generate(VersionId));
69
+ const admission = yield* control
70
+ .beginVersion(configuration.appId, versionId, build.declaration)
71
+ .pipe(
72
+ Effect.catchTags({
73
+ ControlClientUnavailable: (cause) =>
74
+ VersionUploadInterrupted.make({
75
+ cause,
76
+ versionId,
77
+ message: "Upload admission could not be confirmed. Run ignotum deploy --resume.",
78
+ }),
79
+ }),
80
+ );
66
81
  const files = yield* Effect.forEach(admission.targets, (target) => {
67
82
  const publicArtifact = build.declaration.definition.artifacts.find(
68
83
  (artifact) => artifact.path === target.path,
@@ -110,7 +125,7 @@ export const uploadVersion = Effect.fn("Deploy.upload")(function* (
110
125
  VersionUploadInterrupted.make({
111
126
  cause,
112
127
  versionId,
113
- message: `Version ${versionId} was interrupted during upload. The active version was not changed.`,
128
+ message: "Upload interrupted. Run ignotum deploy --resume.",
114
129
  }),
115
130
  }),
116
131
  );
@@ -120,15 +135,15 @@ export const uploadVersion = Effect.fn("Deploy.upload")(function* (
120
135
  VersionUploadInterrupted.make({
121
136
  cause,
122
137
  versionId,
123
- message: `Version ${versionId} could not be finalized. The active version was not changed.`,
138
+ message: "Upload finalization could not be confirmed. Run ignotum deploy --resume.",
124
139
  }),
125
140
  }),
126
141
  Effect.flatMap(readyVersion),
127
142
  );
128
143
  const selection = yield* control
129
- .activateVersion(configuration.appId, versionId)
144
+ .activateVersion(configuration.appId, version.number)
130
145
  .pipe(Effect.mapError((cause) => activationError(version, cause)));
131
- if (selection.appId !== configuration.appId || selection.versionId !== versionId) {
146
+ if (selection.appId !== configuration.appId || selection.versionNumber !== version.number) {
132
147
  return yield* activationError(
133
148
  version,
134
149
  ControlClientUnavailable.make({
@@ -146,24 +161,38 @@ export const deploy = Effect.fn("Deploy.run")(function* (
146
161
  ) {
147
162
  const build = yield* buildVersionArtifact(appDirectory);
148
163
  const configuration = yield* configureApp(appDirectory, requestedSlug);
149
- const activated = yield* uploadVersion(configuration, build);
164
+ const versionId = yield* (yield* IdGenerator).generate(VersionId);
165
+ yield* saveUpload(appDirectory, configuration, versionId, build);
166
+ const activated = yield* uploadVersion(configuration, build, versionId);
167
+ yield* clearUpload(appDirectory);
150
168
  return { ...activated, appUrl: appUrl(configuration.slug) };
151
169
  });
152
170
 
153
171
  export const resumeVersion = Effect.fn("Deploy.resume")(function* (
154
172
  appDirectory: string,
155
- versionId: VersionId,
156
173
  requestedSlug?: AppSlug,
157
174
  ) {
158
175
  const configuration = yield* configureApp(appDirectory, requestedSlug);
159
176
  const control = yield* ControlClient;
160
- const version = yield* control
161
- .getVersion(configuration.appId, versionId)
162
- .pipe(Effect.flatMap(readyVersion));
177
+ const journal = yield* loadUpload(appDirectory, configuration);
178
+ const versionId = journal.versionId;
179
+ const existing = yield* control.getVersion(configuration.appId, versionId).pipe(
180
+ Effect.catchTags({
181
+ ControlApiError: (error) =>
182
+ error.status === 404 ? Effect.succeed(undefined) : Effect.fail(error),
183
+ }),
184
+ );
185
+ if (existing === undefined || existing.status === "Uploading") {
186
+ const build = yield* restoreUploadBuild(appDirectory, journal);
187
+ const activated = yield* uploadVersion(configuration, build, versionId);
188
+ yield* clearUpload(appDirectory);
189
+ return { ...activated, appUrl: appUrl(configuration.slug) };
190
+ }
191
+ const version = yield* readyVersion(existing);
163
192
  const selection = yield* control
164
- .activateVersion(configuration.appId, versionId)
193
+ .activateVersion(configuration.appId, version.number)
165
194
  .pipe(Effect.mapError((cause) => activationError(version, cause)));
166
- if (selection.appId !== configuration.appId || selection.versionId !== versionId) {
195
+ if (selection.appId !== configuration.appId || selection.versionNumber !== version.number) {
167
196
  return yield* activationError(
168
197
  version,
169
198
  ControlClientUnavailable.make({
@@ -172,6 +201,7 @@ export const resumeVersion = Effect.fn("Deploy.resume")(function* (
172
201
  }),
173
202
  );
174
203
  }
204
+ yield* clearUpload(appDirectory);
175
205
  return {
176
206
  appUrl: appUrl(configuration.slug),
177
207
  versionId,
package/src/cli/dev.ts CHANGED
@@ -4,7 +4,11 @@ import tailwindcss from "@tailwindcss/vite";
4
4
  import { Crypto, Effect, FileSystem, Option, Path, Schema, Scope } from "effect";
5
5
  import { createServer, normalizePath, type Plugin, type ViteDevServer } from "vite";
6
6
  import { ChildProcessSpawner } from "effect/unstable/process";
7
- import { ExecutionSelection, RuntimeRequirements } from "@ignotum/contracts/platform";
7
+ import {
8
+ PlatformRequirement,
9
+ revisionPrefix,
10
+ ServingSelection,
11
+ } from "@ignotum/contracts/deployment";
8
12
  import { managedSelectionPath } from "@ignotum/contracts/managed-sync";
9
13
  import { renderDocument } from "@ignotum/shared/document";
10
14
  import { compilePatterns, matchLocation, resolveMetadata } from "@ignotum/shared/routing";
@@ -15,14 +19,14 @@ import { generateClientRoutes, type ClientRouteDiscovery } from "./route-codegen
15
19
  import { createImageAssetsPlugin } from "./image-assets.js";
16
20
  import { managedClientBoundariesPlugin } from "./managed-client-boundaries.js";
17
21
  import { moduleBoundariesPlugin } from "./module-boundaries.js";
18
- import { managedBrowserExports, managedPreactPeerPlugin } from "./managed-exports.js";
19
- import { localManagedReleaseProvider } from "./managed-release.js";
22
+ import { platformImportsPlugin } from "@ignotum/platform/compiler";
23
+ import { compilerExports } from "@ignotum/platform/compiler-exports";
20
24
  import {
21
25
  buildLocalBrowserTools,
22
- localExecutionSelection,
23
- localPlatformRelease,
24
- localPlatformUrl,
25
- type LocalManagedRelease,
26
+ loadLocalPlatform,
27
+ localServingSelection,
28
+ localVersionId,
29
+ type LocalPlatform,
26
30
  } from "./managed-dev-platform.js";
27
31
  import { acquireDevDatabaseLock, devDatabasePath } from "../dev-runtime/dev-database.js";
28
32
  import { ignotumManagedSyncPlugin } from "../dev-runtime/sync.js";
@@ -48,12 +52,10 @@ const toolingPrefix = "/_ignotum/development/tools/";
48
52
 
49
53
  export interface ManagedDevOptions extends DevOptions {
50
54
  readonly applicationName?: string;
51
- readonly sdkDirectory?: string;
52
- readonly provider?: LocalManagedRelease;
55
+ readonly provider?: LocalPlatform;
53
56
  }
54
57
 
55
58
  export const renderDevRegistration = (discovery: ClientRouteDiscovery, applicationName: string) => `
56
- import { mountClient } from "ignotum/internal/bootstrap";
57
59
  import ${JSON.stringify(`${toolingPrefix}debug.js`)};
58
60
  import ${JSON.stringify(stylesId)};
59
61
  const registration = {
@@ -62,18 +64,12 @@ const registration = {
62
64
  layouts: ${JSON.stringify(discovery.layouts)},
63
65
  modules: new Map([${discovery.modules.map((module) => `[${JSON.stringify(module.id)}, { load: () => import(${JSON.stringify(`/@fs/${normalizePath(module.path)}`)}) }]`).join(",\n")}]),
64
66
  };
65
- let mounted;
66
- export default function mount(selection) {
67
- import.meta.hot.data.selection = selection;
68
- mounted?.dispose();
69
- mounted = mountClient(registration, selection);
70
- return mounted;
71
- }
72
67
  if (import.meta.hot) {
73
- import.meta.hot.accept();
74
- import.meta.hot.dispose(() => mounted?.dispose());
75
- if (import.meta.hot.data.selection) mount(import.meta.hot.data.selection);
68
+ import.meta.hot.accept((next) => {
69
+ window.dispatchEvent(new CustomEvent("ignotum:dev-registration", { detail: next.default }));
70
+ });
76
71
  }
72
+ export default registration;
77
73
  `;
78
74
 
79
75
  /** Internal assembly until all CLI producers switch together. The caller owns its Effect scope. */
@@ -85,7 +81,6 @@ export const acquireManagedDevServer = Effect.fn("ManagedDev.acquireServer")(fun
85
81
  const appDirectory = yield* fs.realPath(options.appDirectory);
86
82
  const appName = options.applicationName ?? path.basename(appDirectory);
87
83
  const clientDirectory = path.join(appDirectory, "client");
88
- const sdkDirectory = options.sdkDirectory ?? fileURLToPath(new URL("../../", import.meta.url));
89
84
  const validateFiles = Effect.fn("ManagedDev.validateFiles")(function* () {
90
85
  for (const publicPath of [
91
86
  path.join(appDirectory, "public"),
@@ -114,9 +109,9 @@ export const acquireManagedDevServer = Effect.fn("ManagedDev.acquireServer")(fun
114
109
  let icon = yield* validateFiles();
115
110
  let discovery = yield* generateClientRoutes(appDirectory);
116
111
  let patterns = yield* compilePatterns(discovery.routes, discovery.layouts);
117
- let provider = options.provider ?? (yield* localManagedReleaseProvider(sdkDirectory));
118
- let selection = localExecutionSelection(appDirectory, provider);
119
- let tools = yield* buildLocalBrowserTools(provider, selection);
112
+ let provider = options.provider ?? (yield* loadLocalPlatform());
113
+ let selection = localServingSelection(appDirectory);
114
+ let tools = yield* buildLocalBrowserTools();
120
115
  const runPromise = Effect.runPromiseWith(
121
116
  yield* Effect.context<
122
117
  | FileSystem.FileSystem
@@ -133,48 +128,14 @@ export const acquireManagedDevServer = Effect.fn("ManagedDev.acquireServer")(fun
133
128
  let work = Promise.resolve();
134
129
  const images = yield* createImageAssetsPlugin(appDirectory);
135
130
  const boundaries = yield* moduleBoundariesPlugin("Client", appDirectory);
136
- const platform = yield* managedClientBoundariesPlugin(appDirectory);
137
- const peers = yield* managedPreactPeerPlugin({ resolvePeer: (specifier) => specifier });
138
- // These checks run before the resolver turns supported externals into selected graph URLs.
139
- const originalPlatformResolve = platform.resolveId;
140
- platform.resolveId = function (specifier, importer) {
141
- if (importer === `\0${registrationId}` && specifier === "ignotum/internal/bootstrap")
142
- return {
143
- id: localPlatformUrl(selection.platformReleaseId, provider.resolve("Browser", specifier)!),
144
- external: true as const,
145
- };
146
- const resolved = originalPlatformResolve.call(this, specifier, importer);
147
- if (resolved !== undefined && resolved.external) {
148
- const module = provider.resolve("Browser", specifier);
149
- if (module === undefined) this.error(`The local platform does not supply ${specifier}.`);
150
- return { id: localPlatformUrl(selection.platformReleaseId, module), external: true as const };
151
- }
152
- return resolved;
153
- };
154
- const originalPeerResolve = peers.resolveId;
155
- peers.resolveId = async function (specifier, importer) {
156
- const resolved = await originalPeerResolve.call(this, specifier, importer);
157
- return resolved === undefined
158
- ? undefined
159
- : {
160
- id: localPlatformUrl(
161
- selection.platformReleaseId,
162
- provider.resolve("Browser", specifier)!,
163
- ),
164
- external: true as const,
165
- };
166
- };
131
+ const platform = yield* managedClientBoundariesPlugin(appDirectory, true);
167
132
  const databasePath = yield* devDatabasePath(appDirectory);
168
133
  yield* fs.makeDirectory(path.dirname(databasePath), { recursive: true });
169
134
  yield* acquireDevDatabaseLock(appDirectory);
170
135
  let loader = yield* createManagedFunctionLoader(appDirectory, {
171
136
  guest: provider.guest,
172
- release: localPlatformRelease(provider, selection),
173
- requirements: Schema.decodeUnknownSync(RuntimeRequirements)({
174
- clientApiVersion: 1,
175
- serverApiVersion: 1,
176
- capabilities: ["operations", "routing", "images"],
177
- }),
137
+ platform: selection.platform,
138
+ requirements: Schema.decodeUnknownSync(PlatformRequirement)({ major: 1, minimumMinor: 0 }),
178
139
  });
179
140
  const compileServer = Effect.fn("ManagedDev.compileServer")(function* () {
180
141
  const generated = yield* generate(appDirectory);
@@ -188,6 +149,7 @@ export const acquireManagedDevServer = Effect.fn("ManagedDev.acquireServer")(fun
188
149
  yield* compileServer();
189
150
  const sync = ignotumManagedSyncPlugin(appDirectory, databasePath, {
190
151
  selection: () => selection,
152
+ versionId: localVersionId(appDirectory),
191
153
  isServerDependency: (file) => {
192
154
  const relative = normalizePath(path.relative(appDirectory, file));
193
155
  return (
@@ -232,7 +194,7 @@ export const acquireManagedDevServer = Effect.fn("ManagedDev.acquireServer")(fun
232
194
  id: `${toolingPrefix}${id.slice("@prefresh/".length)}.js`,
233
195
  external: true as const,
234
196
  };
235
- if (id.startsWith(toolingPrefix) || id.startsWith("/_ignotum/platform/"))
197
+ if (id.startsWith(toolingPrefix) || id.startsWith("/_ignotum/v"))
236
198
  return { id, external: true as const };
237
199
  },
238
200
  load(id) {
@@ -247,11 +209,7 @@ export const acquireManagedDevServer = Effect.fn("ManagedDev.acquireServer")(fun
247
209
  // Prefresh adds optimizer entries. Its tools are already compiled against the managed renderer.
248
210
  config.optimizeDeps.include = [];
249
211
  config.optimizeDeps.noDiscovery = true;
250
- config.optimizeDeps.exclude = [
251
- ...managedBrowserExports.map((entry) => entry.specifier),
252
- "@prefresh/core",
253
- "@prefresh/utils",
254
- ];
212
+ config.optimizeDeps.exclude = ["ignotum/platform", "@prefresh/core", "@prefresh/utils"];
255
213
  },
256
214
  configureServer(server) {
257
215
  viteServer = server;
@@ -335,17 +293,9 @@ export const acquireManagedDevServer = Effect.fn("ManagedDev.acquireServer")(fun
335
293
  send(JSON.stringify({ protocolVersion: 1, selection }), "application/json");
336
294
  return;
337
295
  }
338
- const platformPrefix = `/_ignotum/platform/${selection.platformReleaseId}/`;
339
- if (url.pathname.startsWith(platformPrefix)) {
340
- const modulePath = url.pathname.slice(platformPrefix.length);
341
- const allowed = provider.browser.manifest.modules.some(
342
- (module) => module.path === modulePath,
343
- );
344
- const bytes = allowed ? provider.read("Browser", modulePath) : undefined;
345
- if (bytes === undefined) {
346
- response.statusCode = 404;
347
- response.end();
348
- } else send(bytes, "text/javascript", true);
296
+ const browserEntry = `${revisionPrefix(selection.platform)}/client.js`;
297
+ if (url.pathname === browserEntry) {
298
+ send(provider.browser, "text/javascript", true);
349
299
  return;
350
300
  }
351
301
  if (url.pathname.startsWith(toolingPrefix)) {
@@ -392,20 +342,21 @@ export const acquireManagedDevServer = Effect.fn("ManagedDev.acquireServer")(fun
392
342
  outcome: outcome._tag,
393
343
  }),
394
344
  );
395
- const imports = Object.fromEntries(
396
- Object.entries(provider.browser.manifest.entries).map(([specifier, module]) => [
397
- specifier,
398
- localPlatformUrl(selection.platformReleaseId, module),
399
- ]),
400
- );
345
+ const browserEntry = `${revisionPrefix(selection.platform)}/client.js`;
401
346
  const html = renderDocument({
402
347
  metadata,
403
348
  selection,
404
- importMap: { imports },
405
- browserEntry: imports["ignotum/internal/bootstrap"]!,
349
+ importMap: {
350
+ imports: {
351
+ "ignotum/platform": browserEntry,
352
+ "/@id/ignotum/platform": browserEntry,
353
+ },
354
+ },
355
+ browserEntry,
406
356
  registration: { module: moduleUrl(registrationId), exportName: "default" },
407
357
  styles: [],
408
358
  icon: icon?.url,
359
+ development: true,
409
360
  });
410
361
  response.statusCode =
411
362
  outcome._tag === "NotFound" ? 404 : outcome._tag === "Invalid" ? 400 : 200;
@@ -433,7 +384,7 @@ export const acquireManagedDevServer = Effect.fn("ManagedDev.acquireServer")(fun
433
384
  images.plugin,
434
385
  boundaries,
435
386
  platform,
436
- peers,
387
+ platformImportsPlugin(compilerExports),
437
388
  sync,
438
389
  await prefresh(),
439
390
  tailwindcss(),
@@ -476,21 +427,21 @@ export const acquireManagedDevServer = Effect.fn("ManagedDev.acquireServer")(fun
476
427
  server,
477
428
  selection: () => selection,
478
429
  settled: () => work,
479
- replacePlatform: (next: LocalManagedRelease) => {
430
+ replacePlatform: (next: LocalPlatform) => {
480
431
  const replace = async () => {
481
- const nextSelection = Schema.decodeUnknownSync(ExecutionSelection)({
482
- ...localExecutionSelection(appDirectory, next),
483
- platformGeneration: selection.platformGeneration + 1,
484
- });
485
- const nextTools = await runPromise(buildLocalBrowserTools(next, nextSelection));
432
+ const nextSelection = localServingSelection(
433
+ appDirectory,
434
+ selection.deploymentEpoch + 1,
435
+ selection.activationGeneration + 1,
436
+ );
437
+ const nextTools = await runPromise(buildLocalBrowserTools());
486
438
  const nextLoader = await runPromise(
487
439
  createManagedFunctionLoader(appDirectory, {
488
440
  guest: next.guest,
489
- release: localPlatformRelease(next, nextSelection),
490
- requirements: Schema.decodeUnknownSync(RuntimeRequirements)({
491
- clientApiVersion: 1,
492
- serverApiVersion: 1,
493
- capabilities: ["operations", "routing", "images"],
441
+ platform: nextSelection.platform,
442
+ requirements: Schema.decodeUnknownSync(PlatformRequirement)({
443
+ major: 1,
444
+ minimumMinor: 0,
494
445
  }),
495
446
  }),
496
447
  );
@@ -506,7 +457,7 @@ export const acquireManagedDevServer = Effect.fn("ManagedDev.acquireServer")(fun
506
457
  loader = nextLoader;
507
458
  provider = next;
508
459
  tools = nextTools;
509
- selection = Schema.decodeUnknownSync(ExecutionSelection)(nextSelection);
460
+ selection = Schema.decodeUnknownSync(ServingSelection)(nextSelection);
510
461
  serverDiagnostic = undefined;
511
462
  server.environments.client.moduleGraph.invalidateAll();
512
463
  // Managed sync checks the new selection even for sockets with no active queries.
@@ -8,7 +8,6 @@ import { normalizePath } from "vite";
8
8
  import type { Plugin, ResolvedConfig, ViteDevServer } from "vite";
9
9
 
10
10
  const imageModulePrefix = "\0ignotum:image:";
11
- const assetUrlPrefix = "/_ignotum/";
12
11
  const imageModuleId = (sourcePath: string): string =>
13
12
  `${imageModulePrefix}${encodeURIComponent(normalizePath(sourcePath))}.js`;
14
13
 
@@ -147,7 +146,9 @@ const cssImageNodes = (parsed: ReturnType<typeof valueParser>): ReadonlyArray<va
147
146
 
148
147
  export const createImageAssetsPlugin = Effect.fn("Deploy.createImageAssetsPlugin")(function* (
149
148
  appDirectory: string,
149
+ deployment = false,
150
150
  ) {
151
+ const assetUrlPrefix = deployment ? "/_ignotum/assets/" : "/_ignotum/";
151
152
  const fileSystem = yield* FileSystem.FileSystem;
152
153
  const path = yield* Path.Path;
153
154
  const runPromise = Effect.runPromiseWith(yield* Effect.context<Crypto.Crypto>());
@@ -267,7 +268,9 @@ export const createImageAssetsPlugin = Effect.fn("Deploy.createImageAssetsPlugin
267
268
 
268
269
  const digest = yield* sha256(bytes);
269
270
  const format = imageFormats[extension];
270
- const artifactPath = `assets/${digest}/image.${format.canonicalExtension}`;
271
+ const artifactPath = deployment
272
+ ? `media/${path.basename(absolutePath, path.extname(absolutePath)).replace(/[^a-zA-Z0-9.-]/g, "_")}-${digest}.${format.canonicalExtension}`
273
+ : `assets/${digest}/image.${format.canonicalExtension}`;
271
274
  let asset = assets.get(artifactPath);
272
275
  if (asset === undefined) {
273
276
  asset = {
@@ -1,18 +1,16 @@
1
1
  import { Effect, FileSystem, Path, Schema } from "effect";
2
2
  import { normalizePath, type Plugin } from "vite";
3
- import { managedBrowserExports } from "./managed-exports.js";
3
+ import { compilerExports } from "@ignotum/platform/compiler-exports";
4
4
  import { InvalidBuildOutput } from "./build/artifact.js";
5
5
 
6
6
  const PackageMetadata = Schema.Struct({ name: Schema.optional(Schema.String) });
7
7
  const externals = new Set<string>(
8
- managedBrowserExports
9
- .filter((entry) => entry.specifier.startsWith("ignotum/"))
10
- .map((entry) => entry.specifier),
8
+ Object.keys(compilerExports).filter((specifier) => specifier.startsWith("ignotum/")),
11
9
  );
12
10
 
13
11
  /** Resolve before workspace conditions or package aliases can embed SDK implementation. */
14
12
  export const managedClientBoundariesPlugin = Effect.fn("Deploy.managedClientBoundariesPlugin")(
15
- function* (appDirectory: string) {
13
+ function* (appDirectory: string, sharedImports = false) {
16
14
  const fs = yield* FileSystem.FileSystem;
17
15
  const path = yield* Path.Path;
18
16
  const runPromise = Effect.runPromiseWith(yield* Effect.context<never>());
@@ -70,7 +68,9 @@ export const managedClientBoundariesPlugin = Effect.fn("Deploy.managedClientBoun
70
68
  message: `Managed client module '${importer ?? "<entry>"}' imports unsupported platform entry '${specifier}'. Compiler-owned entries must come from generated bindings.`,
71
69
  });
72
70
  }
73
- if (externals.has(specifier)) return { id: specifier, external: true };
71
+ if (externals.has(specifier))
72
+ return sharedImports ? undefined : { id: specifier, external: true };
73
+ if (sharedImports && specifier === "ignotum/platform") return;
74
74
  if (
75
75
  specifier === "ignotum" ||
76
76
  specifier.startsWith("ignotum/") ||