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.
- package/README.md +6 -6
- package/dist/cli/bin.mjs +13263 -2689
- package/dist/cli/bin.mjs.map +1 -1
- package/dist/platform/_headers +3 -0
- package/dist/platform/_ignotum/v1.0/client.js +12 -0
- package/dist/runtime/{api-B4fwp5a2.d.ts → api-BnUKx9cj-D49Tf0rX.d.ts} +4 -3
- package/dist/runtime/api-C-tltz6s.js +31 -0
- package/dist/runtime/api-C-tltz6s.js.map +1 -0
- package/dist/runtime/artifact-BB5qzoa6.js +42 -0
- package/dist/runtime/artifact-BB5qzoa6.js.map +1 -0
- package/dist/runtime/{bootstrap-BTFScKAc.d.ts → bootstrap-DjTwGJao-DBx5zdFI.d.ts} +64 -64
- package/dist/runtime/{bootstrap-BISQSn_O.js → bootstrap-DlNIfH-r.js} +9 -9
- package/dist/runtime/bootstrap-DlNIfH-r.js.map +1 -0
- package/dist/runtime/client.d.ts +24 -20
- package/dist/runtime/client.js +9 -9
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/id-BI08JkmR.d.ts +1 -0
- package/dist/runtime/{id--1m0NuUL.js → id-BrEnxY-r.js} +118 -53
- package/dist/runtime/id-BrEnxY-r.js.map +1 -0
- package/dist/runtime/{image-Djtjy4Z8.js → image-gofzZeCm.js} +2 -2
- package/dist/runtime/image-gofzZeCm.js.map +1 -0
- package/dist/runtime/{index-D4seNHlX.d.ts → index-Biaw5vdF.d.ts} +2 -2
- package/dist/runtime/internal/api.d.ts +1 -1
- package/dist/runtime/internal/api.js +1 -30
- package/dist/runtime/internal/bootstrap.d.ts +1 -1
- package/dist/runtime/internal/bootstrap.js +1 -1
- package/dist/runtime/internal/client.d.ts +2 -2
- package/dist/runtime/internal/client.js +4 -4
- package/dist/runtime/internal/client.js.map +1 -1
- package/dist/runtime/internal/host.d.ts +4 -4
- package/dist/runtime/internal/host.js +4 -4
- package/dist/runtime/internal/routes.d.ts +1 -1
- package/dist/runtime/internal/routes.js +1 -1
- package/dist/runtime/internal/server.d.ts +3 -2
- package/dist/runtime/internal/server.js +2 -2
- package/dist/runtime/internal/server.js.map +1 -1
- package/dist/runtime/internal/types.d.ts +2 -2
- package/dist/runtime/internal/types.js +1 -1
- package/dist/runtime/{pagination-D3qd6s9N.js → pagination--f4kMKf6.js} +2 -2
- package/dist/runtime/{pagination-D3qd6s9N.js.map → pagination--f4kMKf6.js.map} +1 -1
- package/dist/runtime/pagination-BI08JkmR.d.ts +1 -0
- package/dist/runtime/{router-BSBI1oN1.js → router-C1eC9yy5.js} +462 -419
- package/dist/runtime/router-C1eC9yy5.js.map +1 -0
- package/dist/runtime/routes-DnU39R7c.js +152 -0
- package/dist/runtime/routes-DnU39R7c.js.map +1 -0
- package/dist/runtime/{schema-D1wOqiNH.js → schema-BSjepoNF.js} +271 -6
- package/dist/runtime/schema-BSjepoNF.js.map +1 -0
- package/dist/runtime/server.d.ts +5 -4
- package/dist/runtime/server.js +3 -3
- package/dist/runtime/server.js.map +1 -1
- package/dist/runtime/{sync-DzUwA8W9.js → sync-BwgS19Xy.js} +4 -16
- package/dist/runtime/sync-BwgS19Xy.js.map +1 -0
- package/dist/runtime/{sync-D-GK2sv9.d.ts → sync-By2hX5ev.d.ts} +1 -2
- package/dist/runtime/{types-DeCCyqLU-BIm7rU-0.d.ts → types-DeCCyqLU-CHx7KFtx.d.ts} +2 -2
- package/package.json +7 -1
- package/src/cli/agent-files.ts +2 -0
- package/src/cli/build/asset-names.ts +190 -0
- package/src/cli/build/managed-client.ts +151 -81
- package/src/cli/build/managed-server.ts +4 -4
- package/src/cli/build/managed.ts +5 -6
- package/src/cli/command.ts +7 -15
- package/src/cli/control-client.ts +7 -9
- package/src/cli/deploy.ts +45 -15
- package/src/cli/dev.ts +50 -99
- package/src/cli/image-assets.ts +5 -2
- package/src/cli/managed-client-boundaries.ts +6 -6
- package/src/cli/managed-dev-platform.ts +52 -66
- package/src/cli/managed-exports.ts +5 -66
- package/src/cli/new-app.ts +7 -2
- package/src/cli/path.ts +11 -0
- package/src/cli/route-codegen.ts +2 -1
- package/src/cli/route-static.ts +3 -2
- package/src/cli/upload-journal.ts +130 -0
- package/src/client/bootstrap.ts +1 -110
- package/src/client/errors.ts +1 -70
- package/src/client/files.ts +1 -233
- package/src/client/hooks.ts +1 -367
- package/src/client/id.ts +1 -263
- package/src/client/image.ts +1 -28
- package/src/client/index.ts +1 -126
- package/src/client/jsx-dev-runtime.ts +1 -2
- package/src/client/jsx-runtime.ts +1 -2
- package/src/client/managed-client.ts +1 -729
- package/src/client/managed-upgrade.ts +1 -283
- package/src/client/page-observers.ts +1 -77
- package/src/client/query.ts +1 -17
- package/src/client/recovery-journal.ts +1 -195
- package/src/client/route-boundaries.ts +1 -184
- package/src/client/router-history.ts +1 -152
- package/src/client/router-store.ts +1 -644
- package/src/client/router.ts +1 -324
- package/src/client/routes.ts +1 -28
- package/src/client/sync.ts +1 -286
- package/src/dev-runtime/id.ts +1 -1
- package/src/dev-runtime/managed-functions.ts +10 -18
- package/src/dev-runtime/managed-sync.ts +16 -16
- package/src/dev-runtime/sync.ts +6 -4
- package/src/internal/api.ts +1 -156
- package/src/internal/client.ts +1 -186
- package/src/internal/host.ts +1 -1
- package/src/internal/http-paths.ts +1 -4
- package/src/internal/routes.ts +1 -45
- package/src/internal/server.ts +1 -3
- package/src/internal/types.ts +1 -2
- package/src/server/index.ts +1 -27
- package/dist/cli/managed-release-WHYLZwaS.mjs +0 -10902
- package/dist/cli/managed-release-WHYLZwaS.mjs.map +0 -1
- package/dist/cli/managed-release.d.mts +0 -1
- package/dist/cli/managed-release.mjs +0 -14
- package/dist/cli/managed-release.mjs.map +0 -1
- package/dist/runtime/bootstrap-BISQSn_O.js.map +0 -1
- package/dist/runtime/descriptor-CTY4mtwS-cS43EbwS.js +0 -269
- package/dist/runtime/descriptor-CTY4mtwS-cS43EbwS.js.map +0 -1
- package/dist/runtime/id--1m0NuUL.js.map +0 -1
- package/dist/runtime/id-CBOt2kDo.d.ts +0 -1
- package/dist/runtime/identity-QjtJRxBD.d.ts +0 -2
- package/dist/runtime/image-Djtjy4Z8.js.map +0 -1
- package/dist/runtime/internal/api.js.map +0 -1
- package/dist/runtime/pagination-CBOt2kDo.d.ts +0 -1
- package/dist/runtime/router-BSBI1oN1.js.map +0 -1
- package/dist/runtime/routes-Ce8TVP-T.js +0 -690
- package/dist/runtime/routes-Ce8TVP-T.js.map +0 -1
- package/dist/runtime/schema-D1wOqiNH.js.map +0 -1
- package/dist/runtime/sync-DzUwA8W9.js.map +0 -1
- package/src/cli/managed-release-bin.ts +0 -13
- package/src/cli/managed-release.ts +0 -260
|
@@ -1,70 +1,63 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
2
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { Effect, Path, Schema } from "effect";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
3
|
+
import { Effect, FileSystem, Path, Schema } from "effect";
|
|
4
|
+
import { ServingSelection, type PlatformRevision } from "@ignotum/contracts/deployment";
|
|
5
|
+
import { VersionId } from "@ignotum/contracts/runtime/identity";
|
|
6
|
+
import { bundledGuests } from "@ignotum/platform/guests";
|
|
7
|
+
import type { BundledGuest } from "@ignotum/platform/artifacts";
|
|
8
|
+
import { compilerExports } from "@ignotum/platform/compiler-exports";
|
|
9
|
+
import { platformImportsPlugin } from "@ignotum/platform/compiler";
|
|
8
10
|
import { runViteBuild } from "./build/artifact.js";
|
|
9
11
|
|
|
10
|
-
export
|
|
12
|
+
export interface LocalPlatform {
|
|
13
|
+
readonly revision: PlatformRevision;
|
|
14
|
+
readonly browser: Uint8Array;
|
|
15
|
+
readonly guest: BundledGuest;
|
|
16
|
+
}
|
|
11
17
|
|
|
12
|
-
export const
|
|
13
|
-
|
|
18
|
+
export const loadLocalPlatform = Effect.fn("ManagedDev.loadPlatform")(function* () {
|
|
19
|
+
const fs = yield* FileSystem.FileSystem;
|
|
20
|
+
const revision = { major: 1, minor: 0 } as const;
|
|
21
|
+
const guest = bundledGuests.find(
|
|
22
|
+
(candidate) =>
|
|
23
|
+
candidate.manifest.revision.major === revision.major &&
|
|
24
|
+
candidate.manifest.revision.minor === revision.minor,
|
|
25
|
+
);
|
|
26
|
+
if (guest === undefined) return yield* Effect.die("The local platform guest is missing.");
|
|
27
|
+
const browser = yield* fs.readFile(
|
|
28
|
+
fileURLToPath(new URL("../../dist/platform/_ignotum/v1.0/client.js", import.meta.url)),
|
|
29
|
+
);
|
|
30
|
+
return { revision, browser, guest } satisfies LocalPlatform;
|
|
31
|
+
});
|
|
14
32
|
|
|
15
|
-
export const
|
|
16
|
-
const digest =
|
|
17
|
-
return
|
|
18
|
-
formatVersion: 1,
|
|
19
|
-
appId: `app_${digest(appDirectory)}`,
|
|
20
|
-
versionId: `dep_${digest(appDirectory)}`,
|
|
21
|
-
activationGeneration: 1,
|
|
22
|
-
platformReleaseId: `rel_${digest(provider.browser.sha256 + provider.guest.sha256)}`,
|
|
23
|
-
platformGeneration: 1,
|
|
24
|
-
dataHistoryId: `hist_${digest(appDirectory)}`,
|
|
25
|
-
});
|
|
33
|
+
export const localVersionId = (appDirectory: string) => {
|
|
34
|
+
const digest = createHash("sha256").update(appDirectory).digest("hex").slice(0, 24);
|
|
35
|
+
return VersionId.make(`dep_${digest}`);
|
|
26
36
|
};
|
|
27
37
|
|
|
28
|
-
export const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
export const localServingSelection = (
|
|
39
|
+
appDirectory: string,
|
|
40
|
+
deploymentEpoch = 1,
|
|
41
|
+
activationGeneration = 1,
|
|
42
|
+
) => {
|
|
43
|
+
const digest = createHash("sha256").update(appDirectory).digest("hex").slice(0, 24);
|
|
44
|
+
return Schema.decodeUnknownSync(ServingSelection)({
|
|
33
45
|
formatVersion: 1,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
clientApiVersion: 1,
|
|
41
|
-
protocolVersion: 1,
|
|
42
|
-
},
|
|
43
|
-
guest: {
|
|
44
|
-
graph: {
|
|
45
|
-
sha256: provider.guest.sha256,
|
|
46
|
-
entry: provider.guest.manifest.entries["ignotum/internal/host"],
|
|
47
|
-
},
|
|
48
|
-
serverApiVersion: 1,
|
|
49
|
-
protocolVersion: 1,
|
|
50
|
-
},
|
|
51
|
-
gateway: { implementation: provider.browser.sha256, browserProtocols: [1] },
|
|
52
|
-
host: { implementation: provider.guest.sha256, guestProtocols: [1] },
|
|
53
|
-
capabilities: ["operations", "routing", "images"],
|
|
54
|
-
persistentState: [],
|
|
55
|
-
publication: { state: "Ready", eligible: true },
|
|
46
|
+
appId: `app_${digest}`,
|
|
47
|
+
versionNumber: 1,
|
|
48
|
+
platform: { major: 1, minor: 0 },
|
|
49
|
+
activationGeneration,
|
|
50
|
+
deploymentEpoch,
|
|
51
|
+
dataHistoryId: `hist_${digest}`,
|
|
56
52
|
});
|
|
53
|
+
};
|
|
57
54
|
|
|
58
|
-
/** Development tools
|
|
59
|
-
export const buildLocalBrowserTools = Effect.fn("ManagedDev.buildBrowserTools")(function* (
|
|
60
|
-
provider: LocalManagedRelease,
|
|
61
|
-
selection: ExecutionSelection,
|
|
62
|
-
) {
|
|
55
|
+
/** Development tools use the same single platform module as application code. */
|
|
56
|
+
export const buildLocalBrowserTools = Effect.fn("ManagedDev.buildBrowserTools")(function* () {
|
|
63
57
|
const path = yield* Path.Path;
|
|
64
|
-
const require = createRequire(import.meta.resolve("@prefresh/vite"));
|
|
65
58
|
const entries = {
|
|
66
|
-
core:
|
|
67
|
-
utils:
|
|
59
|
+
core: fileURLToPath(import.meta.resolve("@prefresh/core")),
|
|
60
|
+
utils: fileURLToPath(import.meta.resolve("@prefresh/utils")),
|
|
68
61
|
debug: fileURLToPath(import.meta.resolve("preact/debug")),
|
|
69
62
|
};
|
|
70
63
|
const sources = new Map([
|
|
@@ -80,23 +73,15 @@ export const buildLocalBrowserTools = Effect.fn("ManagedDev.buildBrowserTools")(
|
|
|
80
73
|
logLevel: "warn",
|
|
81
74
|
define: { "process.env.NODE_ENV": JSON.stringify("development") },
|
|
82
75
|
plugins: [
|
|
76
|
+
platformImportsPlugin(compilerExports),
|
|
83
77
|
{
|
|
84
|
-
name: "ignotum:local-
|
|
78
|
+
name: "ignotum:local-browser-tools",
|
|
85
79
|
generateBundle(_options, bundle) {
|
|
86
80
|
for (const item of Object.values(bundle))
|
|
87
81
|
if (item.type === "chunk") files.set(item.fileName, item.code);
|
|
88
82
|
},
|
|
89
|
-
load(id)
|
|
90
|
-
|
|
91
|
-
},
|
|
92
|
-
resolveId(id) {
|
|
93
|
-
if (sources.has(`\0${id}`)) return `\0${id}`;
|
|
94
|
-
if (managedPreactPeers.some((peer) => peer.specifier === id)) {
|
|
95
|
-
const module = provider.resolve("Browser", id);
|
|
96
|
-
if (module === undefined) this.error(`Missing managed rendering peer ${id}.`);
|
|
97
|
-
return { id: localPlatformUrl(selection.platformReleaseId, module), external: true };
|
|
98
|
-
}
|
|
99
|
-
},
|
|
83
|
+
load: (id) => sources.get(id),
|
|
84
|
+
resolveId: (id) => (sources.has(`\0${id}`) ? `\0${id}` : undefined),
|
|
100
85
|
},
|
|
101
86
|
],
|
|
102
87
|
build: {
|
|
@@ -105,6 +90,7 @@ export const buildLocalBrowserTools = Effect.fn("ManagedDev.buildBrowserTools")(
|
|
|
105
90
|
target: "es2020",
|
|
106
91
|
rolldownOptions: {
|
|
107
92
|
input: { core: "ignotum:dev-core", utils: "ignotum:dev-utils", debug: "ignotum:dev-debug" },
|
|
93
|
+
external: ["ignotum/platform"],
|
|
108
94
|
preserveEntrySignatures: "strict",
|
|
109
95
|
output: {
|
|
110
96
|
format: "es",
|
|
@@ -2,74 +2,13 @@ import { Effect, FileSystem, Path, Schema } from "effect";
|
|
|
2
2
|
import { satisfies, validRange } from "semver";
|
|
3
3
|
import type { Plugin } from "vite";
|
|
4
4
|
|
|
5
|
-
export interface ManagedExport {
|
|
6
|
-
readonly specifier: string;
|
|
7
|
-
/** Relative to the SDK src directory, or a Preact package specifier. */
|
|
8
|
-
readonly source: string;
|
|
9
|
-
readonly fileName: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
5
|
export const managedPreactVersion = "10.29.8";
|
|
13
6
|
export const managedPreactPeers = [
|
|
14
|
-
{ specifier: "preact",
|
|
15
|
-
{ specifier: "preact/hooks",
|
|
16
|
-
{ specifier: "preact/jsx-runtime",
|
|
17
|
-
{
|
|
18
|
-
|
|
19
|
-
source: "preact/jsx-dev-runtime",
|
|
20
|
-
fileName: "preact-jsx-dev-runtime",
|
|
21
|
-
},
|
|
22
|
-
] as const satisfies ReadonlyArray<ManagedExport>;
|
|
23
|
-
|
|
24
|
-
export const managedBrowserExports = [
|
|
25
|
-
{ specifier: "ignotum/client", source: "client/index.ts", fileName: "client" },
|
|
26
|
-
{
|
|
27
|
-
specifier: "ignotum/client/jsx-runtime",
|
|
28
|
-
source: "client/jsx-runtime.ts",
|
|
29
|
-
fileName: "jsx-runtime",
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
specifier: "ignotum/client/jsx-dev-runtime",
|
|
33
|
-
source: "client/jsx-dev-runtime.ts",
|
|
34
|
-
fileName: "jsx-dev-runtime",
|
|
35
|
-
},
|
|
36
|
-
{ specifier: "ignotum/internal/api", source: "internal/api.ts", fileName: "api" },
|
|
37
|
-
{ specifier: "ignotum/internal/client", source: "internal/client.ts", fileName: "bindings" },
|
|
38
|
-
{ specifier: "ignotum/internal/routes", source: "internal/routes.ts", fileName: "routes" },
|
|
39
|
-
{ specifier: "ignotum/internal/bootstrap", source: "client/bootstrap.ts", fileName: "bootstrap" },
|
|
40
|
-
...managedPreactPeers,
|
|
41
|
-
] as const satisfies ReadonlyArray<ManagedExport>;
|
|
42
|
-
|
|
43
|
-
export const managedGuestExports = [
|
|
44
|
-
{ specifier: "ignotum/server", source: "server/index.ts", fileName: "server" },
|
|
45
|
-
{ specifier: "ignotum/internal/server", source: "internal/server.ts", fileName: "bindings" },
|
|
46
|
-
{ specifier: "ignotum/internal/api", source: "internal/api.ts", fileName: "api" },
|
|
47
|
-
{ specifier: "ignotum/internal/host", source: "internal/host.ts", fileName: "host" },
|
|
48
|
-
] as const satisfies ReadonlyArray<ManagedExport>;
|
|
49
|
-
|
|
50
|
-
export const managedCompatibility = {
|
|
51
|
-
clientApiVersion: 1,
|
|
52
|
-
serverApiVersion: 1,
|
|
53
|
-
protocolVersions: [1],
|
|
54
|
-
capabilities: ["operations", "routing", "images"],
|
|
55
|
-
} as const;
|
|
56
|
-
|
|
57
|
-
export const resolveLocalManagedExport = Effect.fn("Deploy.resolveLocalManagedExport")(
|
|
58
|
-
function* (options: {
|
|
59
|
-
readonly target: "browser" | "guest";
|
|
60
|
-
readonly sdkSourceRoot: string;
|
|
61
|
-
readonly specifier: string;
|
|
62
|
-
}) {
|
|
63
|
-
const path = yield* Path.Path;
|
|
64
|
-
const entries = options.target === "browser" ? managedBrowserExports : managedGuestExports;
|
|
65
|
-
const entry = entries.find((entry) => entry.specifier === options.specifier);
|
|
66
|
-
return entry === undefined
|
|
67
|
-
? undefined
|
|
68
|
-
: entry.source.startsWith("preact")
|
|
69
|
-
? entry.source
|
|
70
|
-
: path.resolve(options.sdkSourceRoot, entry.source);
|
|
71
|
-
},
|
|
72
|
-
);
|
|
7
|
+
{ specifier: "preact", fileName: "preact" },
|
|
8
|
+
{ specifier: "preact/hooks", fileName: "preact-hooks" },
|
|
9
|
+
{ specifier: "preact/jsx-runtime", fileName: "preact-jsx-runtime" },
|
|
10
|
+
{ specifier: "preact/jsx-dev-runtime", fileName: "preact-jsx-dev-runtime" },
|
|
11
|
+
] as const;
|
|
73
12
|
|
|
74
13
|
export class ManagedPeerError extends Schema.TaggedError<ManagedPeerError>()("ManagedPeerError", {
|
|
75
14
|
packageName: Schema.String,
|
package/src/cli/new-app.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Effect, FileSystem, Path, Schema, String } from "effect";
|
|
2
2
|
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
|
|
3
|
+
import cliPackageJson from "../../package.json" with { type: "json" };
|
|
3
4
|
|
|
4
5
|
import { agentAppFiles, type AppFile } from "./agent-files.js";
|
|
5
6
|
import { generateClientRoutes } from "./route-codegen.js";
|
|
@@ -52,7 +53,7 @@ const packageJson = (appName: string): string =>
|
|
|
52
53
|
typecheck: "ignotum codegen && tsc --noEmit",
|
|
53
54
|
},
|
|
54
55
|
dependencies: {
|
|
55
|
-
ignotum:
|
|
56
|
+
ignotum: cliPackageJson.version,
|
|
56
57
|
},
|
|
57
58
|
devDependencies: {
|
|
58
59
|
typescript: "^7.0.2",
|
|
@@ -256,7 +257,7 @@ After generating bindings, run \`npx tsc --noEmit\` to check the app.
|
|
|
256
257
|
Sign in with \`npx ignotum auth login\`, then run \`npx ignotum deploy --app my-app\`.
|
|
257
258
|
Later deployments use \`npx ignotum deploy\`. The command prints the hosted URL and confirms
|
|
258
259
|
that the numbered App Version is live. If activation cannot be confirmed, follow the printed
|
|
259
|
-
\`ignotum deploy --resume
|
|
260
|
+
\`ignotum deploy --resume\` command to finish without creating another version.
|
|
260
261
|
|
|
261
262
|
## Coding agents
|
|
262
263
|
|
|
@@ -277,6 +278,10 @@ export const appFiles = (appName: string): ReadonlyArray<AppFile> => [
|
|
|
277
278
|
{ content: schema, path: "server/schema.ts" },
|
|
278
279
|
{ content: utils, path: "shared/utils.ts" },
|
|
279
280
|
{ content: packageJson(appName), path: "package.json" },
|
|
281
|
+
{
|
|
282
|
+
content: `minimumReleaseAgeExclude:\n - ignotum@${cliPackageJson.version}\nallowBuilds:\n msgpackr-extract: false\n`,
|
|
283
|
+
path: "pnpm-workspace.yaml",
|
|
284
|
+
},
|
|
280
285
|
{ content: tsconfig, path: "tsconfig.json" },
|
|
281
286
|
{ content: gitignore, path: ".gitignore" },
|
|
282
287
|
{ content: readme(appName), path: "README.md" },
|
package/src/cli/path.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Path } from "effect";
|
|
2
|
+
|
|
3
|
+
export const PathContainment = {
|
|
4
|
+
contains(path: Path.Path, directory: string, child: string): boolean {
|
|
5
|
+
const relative = path.relative(directory, child);
|
|
6
|
+
return (
|
|
7
|
+
relative === "" ||
|
|
8
|
+
(!path.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path.sep}`))
|
|
9
|
+
);
|
|
10
|
+
},
|
|
11
|
+
};
|
package/src/cli/route-codegen.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
writeOutput,
|
|
16
16
|
} from "./codegen.js";
|
|
17
17
|
import { createRouteExtractor, InvalidRouteSource, sourceError } from "./route-static.js";
|
|
18
|
+
import { PathContainment } from "./path.js";
|
|
18
19
|
|
|
19
20
|
export interface DiscoveredClientModule {
|
|
20
21
|
readonly path: string;
|
|
@@ -54,7 +55,7 @@ export const discoverClientRoutes = Effect.fn("Routes.discoverClientRoutes")(fun
|
|
|
54
55
|
const real = yield* fs
|
|
55
56
|
.realPath(directory)
|
|
56
57
|
.pipe(Effect.mapError(() => diagnostic(directory, "Cannot read route directory.")));
|
|
57
|
-
if (
|
|
58
|
+
if (!PathContainment.contains(path, clientDirectory, real))
|
|
58
59
|
return yield* diagnostic(directory, "Route trees cannot escape client/ through symlinks.");
|
|
59
60
|
if (seen.has(real)) return yield* diagnostic(directory, "Repeated or cyclic route directory.");
|
|
60
61
|
seen.add(real);
|
package/src/cli/route-static.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
StaticMetadata,
|
|
6
6
|
UrlValueDescriptor,
|
|
7
7
|
} from "@ignotum/contracts/app-definition";
|
|
8
|
+
import { PathContainment } from "./path.js";
|
|
8
9
|
|
|
9
10
|
export class InvalidRouteSource extends Schema.TaggedError<InvalidRouteSource>()(
|
|
10
11
|
"InvalidRouteSource",
|
|
@@ -126,8 +127,8 @@ export const createRouteExtractor = Effect.fn("Routes.createExtractor")(function
|
|
|
126
127
|
.realPath(file)
|
|
127
128
|
.pipe(Effect.mapError(() => fail("Cannot read source module.")));
|
|
128
129
|
if (
|
|
129
|
-
!
|
|
130
|
-
|
|
130
|
+
!PathContainment.contains(path, appDirectory, real) ||
|
|
131
|
+
PathContainment.contains(path, path.join(appDirectory, "server"), real) ||
|
|
131
132
|
!/\.(ts|tsx)$/.test(real) ||
|
|
132
133
|
real.endsWith(".d.ts")
|
|
133
134
|
)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { Effect, FileSystem, Path, Schema } from "effect";
|
|
2
|
+
import { UploadDeclaration } from "@ignotum/contracts/app-definition";
|
|
3
|
+
import { EnvironmentArtifact, Sha256 } from "@ignotum/contracts/version";
|
|
4
|
+
import { AppId, VersionId } from "@ignotum/contracts/runtime/identity";
|
|
5
|
+
import * as Versioned from "@ignotum/contracts/versioned";
|
|
6
|
+
import { encodeCanonical, sha256, utf8Bytes } from "@ignotum/versions";
|
|
7
|
+
import { AppConfigurationInvalid, type AppConfiguration } from "./app-configuration.js";
|
|
8
|
+
import type { ManagedVersionBuildResult } from "./build/managed.js";
|
|
9
|
+
import { readEnvironmentRaw } from "./environment.js";
|
|
10
|
+
|
|
11
|
+
const UploadJournalV1 = Schema.Struct({
|
|
12
|
+
formatVersion: Schema.Literal(1),
|
|
13
|
+
appId: AppId,
|
|
14
|
+
versionId: VersionId,
|
|
15
|
+
apiUrl: Schema.String,
|
|
16
|
+
declarationSha256: Sha256,
|
|
17
|
+
environmentNonce: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{43}$/)),
|
|
18
|
+
hasEnvironmentDefinition: Schema.Boolean,
|
|
19
|
+
});
|
|
20
|
+
const UploadJournal = Versioned.initial(UploadJournalV1);
|
|
21
|
+
export type UploadJournal = typeof UploadJournal.Type;
|
|
22
|
+
const invalid = () =>
|
|
23
|
+
AppConfigurationInvalid.make({
|
|
24
|
+
message:
|
|
25
|
+
"The saved upload does not match this app, build, or environment. Run ignotum deploy to start a new upload.",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const saveUpload = Effect.fn("UploadJournal.save")(function* (
|
|
29
|
+
appDirectory: string,
|
|
30
|
+
configuration: AppConfiguration,
|
|
31
|
+
versionId: VersionId,
|
|
32
|
+
build: ManagedVersionBuildResult,
|
|
33
|
+
) {
|
|
34
|
+
const fs = yield* FileSystem.FileSystem;
|
|
35
|
+
const path = yield* Path.Path;
|
|
36
|
+
const environment = yield* Schema.decodeEffect(Schema.fromJsonString(EnvironmentArtifact))(
|
|
37
|
+
new TextDecoder().decode(build.environment.bytes),
|
|
38
|
+
).pipe(Effect.mapError(invalid));
|
|
39
|
+
const journal: UploadJournal = {
|
|
40
|
+
formatVersion: 1,
|
|
41
|
+
appId: configuration.appId,
|
|
42
|
+
versionId,
|
|
43
|
+
apiUrl: configuration.apiUrl.href,
|
|
44
|
+
declarationSha256: yield* sha256(build.declarationBytes),
|
|
45
|
+
environmentNonce: environment.nonce,
|
|
46
|
+
hasEnvironmentDefinition: yield* fs.exists(path.join(appDirectory, "server/env.ts")),
|
|
47
|
+
};
|
|
48
|
+
const directory = path.join(appDirectory, ".ignotum");
|
|
49
|
+
yield* fs.makeDirectory(directory, { recursive: true });
|
|
50
|
+
yield* Effect.scoped(
|
|
51
|
+
Effect.gen(function* () {
|
|
52
|
+
const temporary = yield* fs.makeTempFileScoped({ directory, prefix: ".upload-" });
|
|
53
|
+
yield* fs.writeFileString(temporary, encodeCanonical(UploadJournal, journal) + "\n");
|
|
54
|
+
yield* fs.rename(temporary, path.join(directory, "upload.json"));
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
return journal;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export const loadUpload = Effect.fn("UploadJournal.load")(function* (
|
|
61
|
+
appDirectory: string,
|
|
62
|
+
configuration: AppConfiguration,
|
|
63
|
+
) {
|
|
64
|
+
const fs = yield* FileSystem.FileSystem;
|
|
65
|
+
const path = yield* Path.Path;
|
|
66
|
+
const text = yield* fs.readFileString(path.join(appDirectory, ".ignotum/upload.json")).pipe(
|
|
67
|
+
Effect.mapError(() =>
|
|
68
|
+
AppConfigurationInvalid.make({
|
|
69
|
+
message: "No saved upload is available. Run ignotum deploy first.",
|
|
70
|
+
}),
|
|
71
|
+
),
|
|
72
|
+
);
|
|
73
|
+
const journal = yield* Schema.decodeEffect(Schema.fromJsonString(UploadJournal))(text).pipe(
|
|
74
|
+
Effect.mapError(invalid),
|
|
75
|
+
);
|
|
76
|
+
if (journal.appId !== configuration.appId || journal.apiUrl !== configuration.apiUrl.href)
|
|
77
|
+
return yield* invalid();
|
|
78
|
+
return journal;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
export const restoreUploadBuild = Effect.fn("UploadJournal.restoreBuild")(function* (
|
|
82
|
+
appDirectory: string,
|
|
83
|
+
journal: UploadJournal,
|
|
84
|
+
) {
|
|
85
|
+
const fs = yield* FileSystem.FileSystem;
|
|
86
|
+
const path = yield* Path.Path;
|
|
87
|
+
const outputDirectory = path.join(appDirectory, ".ignotum/build");
|
|
88
|
+
const declarationBytes = yield* fs.readFile(path.join(outputDirectory, "declaration.json"));
|
|
89
|
+
if ((yield* sha256(declarationBytes)) !== journal.declarationSha256) return yield* invalid();
|
|
90
|
+
const declaration = yield* Schema.decodeEffect(Schema.fromJsonString(UploadDeclaration))(
|
|
91
|
+
new TextDecoder().decode(declarationBytes),
|
|
92
|
+
).pipe(Effect.mapError(invalid));
|
|
93
|
+
const entries = yield* Effect.forEach(
|
|
94
|
+
declaration.definition.artifacts,
|
|
95
|
+
Effect.fn(function* (artifact) {
|
|
96
|
+
const bytes = yield* fs.readFile(path.join(outputDirectory, artifact.path));
|
|
97
|
+
if (bytes.byteLength !== artifact.size || (yield* sha256(bytes)) !== artifact.sha256)
|
|
98
|
+
return yield* invalid();
|
|
99
|
+
return [artifact.path, bytes] as const;
|
|
100
|
+
}),
|
|
101
|
+
);
|
|
102
|
+
const raw = yield* readEnvironmentRaw(appDirectory, journal.hasEnvironmentDefinition);
|
|
103
|
+
const environmentBytes = utf8Bytes(
|
|
104
|
+
encodeCanonical(EnvironmentArtifact, {
|
|
105
|
+
formatVersion: 1,
|
|
106
|
+
nonce: journal.environmentNonce,
|
|
107
|
+
values: raw.raw,
|
|
108
|
+
}) + "\n",
|
|
109
|
+
);
|
|
110
|
+
const artifact = declaration.environment?.artifact;
|
|
111
|
+
if (
|
|
112
|
+
artifact === undefined ||
|
|
113
|
+
artifact.size !== environmentBytes.byteLength ||
|
|
114
|
+
artifact.sha256 !== (yield* sha256(environmentBytes))
|
|
115
|
+
)
|
|
116
|
+
return yield* invalid();
|
|
117
|
+
return {
|
|
118
|
+
declaration,
|
|
119
|
+
declarationBytes,
|
|
120
|
+
outputDirectory,
|
|
121
|
+
bytes: new Map(entries),
|
|
122
|
+
environment: { path: artifact.path, bytes: environmentBytes },
|
|
123
|
+
} satisfies ManagedVersionBuildResult;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
export const clearUpload = Effect.fn("UploadJournal.clear")(function* (appDirectory: string) {
|
|
127
|
+
const fs = yield* FileSystem.FileSystem;
|
|
128
|
+
const path = yield* Path.Path;
|
|
129
|
+
yield* fs.remove(path.join(appDirectory, ".ignotum/upload.json"), { force: true });
|
|
130
|
+
});
|
package/src/client/bootstrap.ts
CHANGED
|
@@ -1,110 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { createElement, Fragment, render, type ComponentChildren } from "preact";
|
|
3
|
-
import { useLayoutEffect, useState } from "preact/hooks";
|
|
4
|
-
import { mountManagedSync } from "./sync.js";
|
|
5
|
-
import type { ManagedBrowserClient } from "./managed-client.js";
|
|
6
|
-
import { RouterContext, useRouterSnapshot } from "./router.js";
|
|
7
|
-
// oxlint-disable-next-line anti-slop-effect/no-service-constructor-imports -- Each browser mount owns its router and releases it on disposal.
|
|
8
|
-
import { RouterStore, type ClientRegistration } from "./router-store.js";
|
|
9
|
-
|
|
10
|
-
import { Branch, LoadFailure } from "./route-boundaries.js";
|
|
11
|
-
|
|
12
|
-
const RouterView = ({ router }: { readonly router: RouterStore }): ComponentChildren => {
|
|
13
|
-
const snapshot = useRouterSnapshot(router);
|
|
14
|
-
useLayoutEffect(() => {
|
|
15
|
-
router.rendered(snapshot.commit);
|
|
16
|
-
}, [router, snapshot]);
|
|
17
|
-
return createElement(
|
|
18
|
-
RouterContext.Provider,
|
|
19
|
-
{ value: router },
|
|
20
|
-
snapshot.branch === undefined || snapshot.location === undefined
|
|
21
|
-
? createElement("div", { role: "status" }, snapshot.failure === undefined ? "Loading…" : null)
|
|
22
|
-
: createElement(Branch, {
|
|
23
|
-
branch: snapshot.branch,
|
|
24
|
-
location: snapshot.location,
|
|
25
|
-
router,
|
|
26
|
-
failure: snapshot.failure,
|
|
27
|
-
}),
|
|
28
|
-
snapshot.failure !== undefined && snapshot.branch === undefined
|
|
29
|
-
? createElement(LoadFailure, { failure: snapshot.failure, router })
|
|
30
|
-
: null,
|
|
31
|
-
);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const mounts = new WeakSet<Element>();
|
|
35
|
-
|
|
36
|
-
const ManagedStatus = ({ client }: { readonly client: ManagedBrowserClient }) => {
|
|
37
|
-
const [snapshot, setSnapshot] = useState(client.upgrade.getSnapshot);
|
|
38
|
-
useLayoutEffect(
|
|
39
|
-
() => client.upgrade.subscribe(() => setSnapshot(client.upgrade.getSnapshot())),
|
|
40
|
-
[client],
|
|
41
|
-
);
|
|
42
|
-
if (snapshot.phase === "ready" && snapshot.recovery.length === 0) return null;
|
|
43
|
-
return createElement(
|
|
44
|
-
"aside",
|
|
45
|
-
{
|
|
46
|
-
"data-ignotum-update": snapshot.phase,
|
|
47
|
-
role: snapshot.phase === "error" ? "alert" : "status",
|
|
48
|
-
},
|
|
49
|
-
snapshot.message ?? (snapshot.phase === "connecting" ? "Connecting to the app…" : null),
|
|
50
|
-
snapshot.recovery.length === 0
|
|
51
|
-
? null
|
|
52
|
-
: createElement(
|
|
53
|
-
"ul",
|
|
54
|
-
null,
|
|
55
|
-
snapshot.recovery.map((message) => createElement("li", { key: message }, message)),
|
|
56
|
-
),
|
|
57
|
-
snapshot.phase === "error"
|
|
58
|
-
? createElement("button", { type: "button", onClick: () => client.retry() }, "Reload app")
|
|
59
|
-
: null,
|
|
60
|
-
snapshot.recovery.length === 0
|
|
61
|
-
? null
|
|
62
|
-
: createElement(
|
|
63
|
-
"button",
|
|
64
|
-
{ type: "button", onClick: client.upgrade.dismissRecovery },
|
|
65
|
-
"Dismiss",
|
|
66
|
-
),
|
|
67
|
-
);
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
/** The platform owns mounting. Generated registrations contain no Preact render/bootstrap code. */
|
|
71
|
-
export const mountClient = (registration: ClientRegistration, selection: ExecutionSelection) => {
|
|
72
|
-
const root = window.document.getElementById("app");
|
|
73
|
-
if (root === null) throw new Error("Ignotum mount element is missing.");
|
|
74
|
-
if (mounts.has(root)) throw new Error("The Ignotum client is already mounted.");
|
|
75
|
-
const router = new RouterStore(registration, selection, window);
|
|
76
|
-
const managed = mountManagedSync(selection, window, {
|
|
77
|
-
stopAdmission: router.stopForUpgrade,
|
|
78
|
-
reload: router.upgrade,
|
|
79
|
-
});
|
|
80
|
-
router.setAdmission({
|
|
81
|
-
validate: managed.client.validate,
|
|
82
|
-
recoverLoad: managed.client.recoverLoad,
|
|
83
|
-
});
|
|
84
|
-
render(
|
|
85
|
-
createElement(
|
|
86
|
-
Fragment,
|
|
87
|
-
null,
|
|
88
|
-
createElement(ManagedStatus, { client: managed.client }),
|
|
89
|
-
createElement(RouterView, { router }),
|
|
90
|
-
),
|
|
91
|
-
root,
|
|
92
|
-
);
|
|
93
|
-
const ready = managed.client
|
|
94
|
-
.whenReady()
|
|
95
|
-
.then((accepted) => (accepted ? router.start() : { status: "cancelled" as const }));
|
|
96
|
-
mounts.add(root);
|
|
97
|
-
let disposed = false;
|
|
98
|
-
return {
|
|
99
|
-
ready,
|
|
100
|
-
upgrade: () => managed.client.upgrade.require(),
|
|
101
|
-
dispose: () => {
|
|
102
|
-
if (disposed) return;
|
|
103
|
-
disposed = true;
|
|
104
|
-
mounts.delete(root);
|
|
105
|
-
router.dispose();
|
|
106
|
-
managed.dispose();
|
|
107
|
-
render(null, root);
|
|
108
|
-
},
|
|
109
|
-
};
|
|
110
|
-
};
|
|
1
|
+
export * from "@ignotum/platform/versions/1/browser/bootstrap";
|
package/src/client/errors.ts
CHANGED
|
@@ -1,70 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Cause, Effect, ErrorReporter, Schema } from "effect";
|
|
3
|
-
|
|
4
|
-
import { FunctionAddress } from "@ignotum/contracts/runtime/sync";
|
|
5
|
-
|
|
6
|
-
export class ConnectionUnavailable extends Schema.TaggedError<ConnectionUnavailable>()(
|
|
7
|
-
"ConnectionUnavailable",
|
|
8
|
-
{
|
|
9
|
-
cause: Schema.optional(Schema.Defect()),
|
|
10
|
-
message: Schema.String,
|
|
11
|
-
},
|
|
12
|
-
) {}
|
|
13
|
-
|
|
14
|
-
export class PlatformUpgrading extends Schema.TaggedError<PlatformUpgrading>()(
|
|
15
|
-
"PlatformUpgrading",
|
|
16
|
-
{
|
|
17
|
-
message: Schema.String,
|
|
18
|
-
},
|
|
19
|
-
) {}
|
|
20
|
-
|
|
21
|
-
export class InvalidMutationArguments extends Schema.TaggedError<InvalidMutationArguments>()(
|
|
22
|
-
"InvalidMutationArguments",
|
|
23
|
-
{
|
|
24
|
-
cause: Schema.Defect(),
|
|
25
|
-
function: FunctionAddress,
|
|
26
|
-
message: Schema.String,
|
|
27
|
-
},
|
|
28
|
-
) {}
|
|
29
|
-
|
|
30
|
-
export class InvalidServerMessage extends Schema.TaggedError<InvalidServerMessage>()(
|
|
31
|
-
"InvalidServerMessage",
|
|
32
|
-
{
|
|
33
|
-
cause: Schema.Defect(),
|
|
34
|
-
message: Schema.String,
|
|
35
|
-
},
|
|
36
|
-
) {}
|
|
37
|
-
|
|
38
|
-
export class ServerError extends Schema.TaggedError<ServerError>()("ServerError", {
|
|
39
|
-
code: Schema.Literals([
|
|
40
|
-
"PlatformUpgrading",
|
|
41
|
-
"SessionExpired",
|
|
42
|
-
"InvalidMessage",
|
|
43
|
-
"Unavailable",
|
|
44
|
-
"InvalidArguments",
|
|
45
|
-
"InvocationIdConflict",
|
|
46
|
-
"ResourceLimitExceeded",
|
|
47
|
-
]),
|
|
48
|
-
message: Schema.String,
|
|
49
|
-
}) {}
|
|
50
|
-
|
|
51
|
-
export const ClientInfrastructureError = Schema.Union([
|
|
52
|
-
PlatformUpgrading,
|
|
53
|
-
ConnectionUnavailable,
|
|
54
|
-
InvalidMutationArguments,
|
|
55
|
-
InvalidServerMessage,
|
|
56
|
-
ServerError,
|
|
57
|
-
]);
|
|
58
|
-
export type ClientInfrastructureError = typeof ClientInfrastructureError.Type;
|
|
59
|
-
|
|
60
|
-
const browserErrorReporter = ErrorReporter.make(({ attributes, error }) => {
|
|
61
|
-
globalThis.console.error("Ignotum infrastructure error", error, attributes);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
export const clientErrorReporterLayer = ErrorReporter.layer([browserErrorReporter]);
|
|
65
|
-
|
|
66
|
-
export const reportClientError = Effect.fn("ClientErrorReporter.report")(function* (
|
|
67
|
-
error: ClientInfrastructureError,
|
|
68
|
-
) {
|
|
69
|
-
yield* ErrorReporter.report(Cause.fail(error));
|
|
70
|
-
});
|
|
1
|
+
export * from "@ignotum/platform/versions/1/browser/errors";
|