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
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { Effect, Path, Schema } from "effect";
|
|
5
|
+
import { ExecutionSelection, PlatformRelease } from "@ignotum/contracts/platform";
|
|
6
|
+
import { managedPreactPeers } from "./managed-exports.js";
|
|
7
|
+
import { localManagedReleaseProvider } from "./managed-release.js";
|
|
8
|
+
import { runViteBuild } from "./build/artifact.js";
|
|
9
|
+
|
|
10
|
+
export type LocalManagedRelease = Effect.Success<ReturnType<typeof localManagedReleaseProvider>>;
|
|
11
|
+
|
|
12
|
+
export const localPlatformUrl = (release: string, module: string) =>
|
|
13
|
+
`/_ignotum/platform/${release}/${module}`;
|
|
14
|
+
|
|
15
|
+
export const localExecutionSelection = (appDirectory: string, provider: LocalManagedRelease) => {
|
|
16
|
+
const digest = (value: string) => createHash("sha256").update(value).digest("hex").slice(0, 24);
|
|
17
|
+
return Schema.decodeUnknownSync(ExecutionSelection)({
|
|
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
|
+
});
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const localPlatformRelease = (
|
|
29
|
+
provider: LocalManagedRelease,
|
|
30
|
+
selection: ExecutionSelection,
|
|
31
|
+
) =>
|
|
32
|
+
Schema.decodeUnknownSync(PlatformRelease)({
|
|
33
|
+
formatVersion: 1,
|
|
34
|
+
id: selection.platformReleaseId,
|
|
35
|
+
browser: {
|
|
36
|
+
graph: {
|
|
37
|
+
sha256: provider.browser.sha256,
|
|
38
|
+
entry: provider.browser.manifest.entries["ignotum/internal/bootstrap"],
|
|
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 },
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
/** Development tools share the selected renderer; they never enter application modules. */
|
|
59
|
+
export const buildLocalBrowserTools = Effect.fn("ManagedDev.buildBrowserTools")(function* (
|
|
60
|
+
provider: LocalManagedRelease,
|
|
61
|
+
selection: ExecutionSelection,
|
|
62
|
+
) {
|
|
63
|
+
const path = yield* Path.Path;
|
|
64
|
+
const require = createRequire(import.meta.resolve("@prefresh/vite"));
|
|
65
|
+
const entries = {
|
|
66
|
+
core: require.resolve("@prefresh/core"),
|
|
67
|
+
utils: require.resolve("@prefresh/utils"),
|
|
68
|
+
debug: fileURLToPath(import.meta.resolve("preact/debug")),
|
|
69
|
+
};
|
|
70
|
+
const sources = new Map([
|
|
71
|
+
["\0ignotum:dev-core", `import ${JSON.stringify(entries.core)};`],
|
|
72
|
+
["\0ignotum:dev-utils", `export { flush } from ${JSON.stringify(entries.utils)};`],
|
|
73
|
+
["\0ignotum:dev-debug", `import ${JSON.stringify(entries.debug)};`],
|
|
74
|
+
]);
|
|
75
|
+
const files = new Map<string, string>();
|
|
76
|
+
yield* runViteBuild("local browser tools", {
|
|
77
|
+
configFile: false,
|
|
78
|
+
root: path.dirname(entries.core),
|
|
79
|
+
publicDir: false,
|
|
80
|
+
logLevel: "warn",
|
|
81
|
+
define: { "process.env.NODE_ENV": JSON.stringify("development") },
|
|
82
|
+
plugins: [
|
|
83
|
+
{
|
|
84
|
+
name: "ignotum:local-tooling-renderer",
|
|
85
|
+
generateBundle(_options, bundle) {
|
|
86
|
+
for (const item of Object.values(bundle))
|
|
87
|
+
if (item.type === "chunk") files.set(item.fileName, item.code);
|
|
88
|
+
},
|
|
89
|
+
load(id) {
|
|
90
|
+
return sources.get(id);
|
|
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
|
+
},
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
build: {
|
|
103
|
+
write: false,
|
|
104
|
+
minify: false,
|
|
105
|
+
target: "es2020",
|
|
106
|
+
rolldownOptions: {
|
|
107
|
+
input: { core: "ignotum:dev-core", utils: "ignotum:dev-utils", debug: "ignotum:dev-debug" },
|
|
108
|
+
preserveEntrySignatures: "strict",
|
|
109
|
+
output: {
|
|
110
|
+
format: "es",
|
|
111
|
+
entryFileNames: "[name].js",
|
|
112
|
+
chunkFileNames: "chunks/[name]-[hash].js",
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
return files;
|
|
118
|
+
});
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { Effect, FileSystem, Path, Schema } from "effect";
|
|
2
|
+
import { satisfies, validRange } from "semver";
|
|
3
|
+
import type { Plugin } from "vite";
|
|
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
|
+
export const managedPreactVersion = "10.29.8";
|
|
13
|
+
export const managedPreactPeers = [
|
|
14
|
+
{ specifier: "preact", source: "preact", fileName: "preact" },
|
|
15
|
+
{ specifier: "preact/hooks", source: "preact/hooks", fileName: "preact-hooks" },
|
|
16
|
+
{ specifier: "preact/jsx-runtime", source: "preact/jsx-runtime", fileName: "preact-jsx-runtime" },
|
|
17
|
+
{
|
|
18
|
+
specifier: "preact/jsx-dev-runtime",
|
|
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
|
+
);
|
|
73
|
+
|
|
74
|
+
export class ManagedPeerError extends Schema.TaggedError<ManagedPeerError>()("ManagedPeerError", {
|
|
75
|
+
packageName: Schema.String,
|
|
76
|
+
message: Schema.String,
|
|
77
|
+
}) {}
|
|
78
|
+
|
|
79
|
+
export const validateManagedPreactPeer = (packageName: string, declaredRange: string): void => {
|
|
80
|
+
if (validRange(declaredRange) === null || !satisfies(managedPreactVersion, declaredRange)) {
|
|
81
|
+
throw ManagedPeerError.make({
|
|
82
|
+
packageName,
|
|
83
|
+
message: `${packageName} requires Preact ${declaredRange}, but the managed runtime supplies ${managedPreactVersion}. Choose a library version whose Preact peer range includes ${managedPreactVersion}.`,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const PackageMetadata = Schema.Struct({
|
|
89
|
+
name: Schema.optional(Schema.String),
|
|
90
|
+
peerDependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
|
91
|
+
bundledDependencies: Schema.optional(Schema.Union([Schema.Boolean, Schema.Array(Schema.String)])),
|
|
92
|
+
bundleDependencies: Schema.optional(Schema.Union([Schema.Boolean, Schema.Array(Schema.String)])),
|
|
93
|
+
dependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const SourceMapMetadata = Schema.Struct({ sources: Schema.Array(Schema.String) });
|
|
97
|
+
|
|
98
|
+
/** Compiler-only resolution: author modules must continue to import ignotum/client. */
|
|
99
|
+
export const managedPreactPeerPlugin = Effect.fn("Deploy.managedPreactPeerPlugin")(
|
|
100
|
+
function* (options: { readonly resolvePeer: (specifier: string) => string }) {
|
|
101
|
+
const fs = yield* FileSystem.FileSystem;
|
|
102
|
+
const path = yield* Path.Path;
|
|
103
|
+
const runPromise = Effect.runPromiseWith(yield* Effect.context<never>());
|
|
104
|
+
const owners = new Map<string, typeof PackageMetadata.Type | undefined>();
|
|
105
|
+
const sourceMapSources = Effect.fn("Deploy.managedPeerSourceMapSources")(function* (
|
|
106
|
+
code: string,
|
|
107
|
+
id: string,
|
|
108
|
+
) {
|
|
109
|
+
const sources: string[] = [];
|
|
110
|
+
for (const match of code.matchAll(/^\s*\/\/[#@]\s*sourceMappingURL=(\S+)\s*$/gm)) {
|
|
111
|
+
const url = match[1]!;
|
|
112
|
+
let json: string;
|
|
113
|
+
if (url.startsWith("data:application/json;") && url.includes(";base64,")) {
|
|
114
|
+
json = Buffer.from(url.slice(url.indexOf(",") + 1), "base64").toString("utf8");
|
|
115
|
+
} else if (!url.includes(":") && path.isAbsolute(id)) {
|
|
116
|
+
const mapPath = path.resolve(path.dirname(id), url);
|
|
117
|
+
if (!(yield* fs.exists(mapPath))) continue;
|
|
118
|
+
json = yield* fs.readFileString(mapPath);
|
|
119
|
+
} else continue;
|
|
120
|
+
const map = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(SourceMapMetadata))(
|
|
121
|
+
json,
|
|
122
|
+
);
|
|
123
|
+
sources.push(...map.sources);
|
|
124
|
+
}
|
|
125
|
+
return sources;
|
|
126
|
+
});
|
|
127
|
+
const ownerOf = Effect.fn("Deploy.managedPeerOwner")(function* (id: string) {
|
|
128
|
+
const clean = id.split("?", 1)[0] ?? id;
|
|
129
|
+
if (!path.isAbsolute(clean) || !clean.replaceAll("\\", "/").includes("/node_modules/"))
|
|
130
|
+
return undefined;
|
|
131
|
+
let directory = path.dirname(clean);
|
|
132
|
+
if (owners.has(directory)) return owners.get(directory);
|
|
133
|
+
const start = directory;
|
|
134
|
+
while (path.dirname(directory) !== directory && path.basename(directory) !== "node_modules") {
|
|
135
|
+
const manifest = path.join(directory, "package.json");
|
|
136
|
+
if (yield* fs.exists(manifest)) {
|
|
137
|
+
const metadata = yield* Schema.decodeUnknownEffect(
|
|
138
|
+
Schema.fromJsonString(PackageMetadata),
|
|
139
|
+
)(yield* fs.readFileString(manifest));
|
|
140
|
+
if (
|
|
141
|
+
metadata.name !== undefined ||
|
|
142
|
+
path.basename(path.dirname(directory)) === "node_modules"
|
|
143
|
+
) {
|
|
144
|
+
owners.set(start, metadata);
|
|
145
|
+
return metadata;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
directory = path.dirname(directory);
|
|
149
|
+
}
|
|
150
|
+
owners.set(start, undefined);
|
|
151
|
+
return undefined;
|
|
152
|
+
});
|
|
153
|
+
const checkOwner = (owner: typeof PackageMetadata.Type | undefined, importer: string) => {
|
|
154
|
+
if (owner === undefined) return;
|
|
155
|
+
const name = owner.name ?? importer;
|
|
156
|
+
const range = owner.peerDependencies?.preact;
|
|
157
|
+
if (range !== undefined) validateManagedPreactPeer(name, range);
|
|
158
|
+
for (const bundled of [owner.bundledDependencies, owner.bundleDependencies]) {
|
|
159
|
+
if (
|
|
160
|
+
(bundled === true && owner.dependencies?.preact !== undefined) ||
|
|
161
|
+
(Array.isArray(bundled) && bundled.includes("preact"))
|
|
162
|
+
) {
|
|
163
|
+
throw ManagedPeerError.make({
|
|
164
|
+
packageName: name,
|
|
165
|
+
message: `${name} bundles a private Preact renderer. Use a distribution that declares Preact as an external peer dependency.`,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
return {
|
|
171
|
+
name: "ignotum:managed-preact-peers",
|
|
172
|
+
enforce: "pre",
|
|
173
|
+
async resolveId(specifier, importer) {
|
|
174
|
+
if (specifier !== "preact" && !specifier.startsWith("preact/")) return;
|
|
175
|
+
const owner = importer === undefined ? undefined : await runPromise(ownerOf(importer));
|
|
176
|
+
const name = owner?.name ?? importer ?? "application";
|
|
177
|
+
if (!managedPreactPeers.some((entry) => entry.specifier === specifier)) {
|
|
178
|
+
throw ManagedPeerError.make({
|
|
179
|
+
packageName: name,
|
|
180
|
+
message: `${name} imports unsupported Preact entry ${specifier}. Supported peer entries: ${managedPreactPeers.map((entry) => entry.specifier).join(", ")}.`,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
checkOwner(owner, name);
|
|
184
|
+
if (owner?.peerDependencies?.preact === undefined) {
|
|
185
|
+
throw ManagedPeerError.make({
|
|
186
|
+
packageName: name,
|
|
187
|
+
message: `${name} imports ${specifier} without a Preact peer declaration. App source must import ignotum/client; UI libraries must declare Preact as a peer dependency.`,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return { id: options.resolvePeer(specifier), external: true };
|
|
191
|
+
},
|
|
192
|
+
async transform(code, id) {
|
|
193
|
+
const owner = await runPromise(ownerOf(id));
|
|
194
|
+
checkOwner(owner, id);
|
|
195
|
+
if (owner?.name === "preact") {
|
|
196
|
+
throw ManagedPeerError.make({
|
|
197
|
+
packageName: "preact",
|
|
198
|
+
message: `The app graph contains a private Preact renderer at ${id}. Remove direct renderer paths and use a library with external Preact peer imports.`,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
// Source maps retain renderer provenance even when a library vendors it into one file.
|
|
202
|
+
const sources = [
|
|
203
|
+
...this.getCombinedSourcemap().sources,
|
|
204
|
+
...(await runPromise(sourceMapSources(code, id))),
|
|
205
|
+
];
|
|
206
|
+
if (
|
|
207
|
+
sources.some((source) =>
|
|
208
|
+
/(?:^|\/)preact\/(?:src|dist)\//.test(source.replaceAll("\\", "/")),
|
|
209
|
+
)
|
|
210
|
+
) {
|
|
211
|
+
throw ManagedPeerError.make({
|
|
212
|
+
packageName: owner?.name ?? id,
|
|
213
|
+
message: `${id} embeds a Preact renderer in its source map. Use a distribution that leaves Preact external.`,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
} satisfies Plugin;
|
|
218
|
+
},
|
|
219
|
+
);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { NodeRuntime, NodeServices } from "@effect/platform-node";
|
|
3
|
+
import { Config, Effect, Path } from "effect";
|
|
4
|
+
import { writeManagedReleaseGraphs } from "./managed-release.js";
|
|
5
|
+
|
|
6
|
+
Effect.gen(function* () {
|
|
7
|
+
const path = yield* Path.Path;
|
|
8
|
+
const sdkDirectory = fileURLToPath(new URL("../../", import.meta.url));
|
|
9
|
+
const output = yield* Config.string("IGNOTUM_MANAGED_RELEASE_DIRECTORY").pipe(
|
|
10
|
+
Config.withDefault(path.join(sdkDirectory, "dist/managed")),
|
|
11
|
+
);
|
|
12
|
+
yield* writeManagedReleaseGraphs(sdkDirectory, output);
|
|
13
|
+
}).pipe(Effect.provide(NodeServices.layer), NodeRuntime.runMain);
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { Effect, FileSystem, Path, Predicate, Schema } from "effect";
|
|
3
|
+
import type { Plugin } from "vite";
|
|
4
|
+
import { normalizePath } from "vite";
|
|
5
|
+
import { ManagedReleaseManifest } from "@ignotum/contracts/managed-release";
|
|
6
|
+
import {
|
|
7
|
+
encodeManagedReleaseManifest,
|
|
8
|
+
validateManagedReleaseGraph,
|
|
9
|
+
} from "@ignotum/versions/managed-release";
|
|
10
|
+
import { sha256, utf8Bytes } from "@ignotum/versions";
|
|
11
|
+
import {
|
|
12
|
+
managedBrowserExports,
|
|
13
|
+
managedGuestExports,
|
|
14
|
+
managedPreactVersion,
|
|
15
|
+
} from "./managed-exports.js";
|
|
16
|
+
import { InvalidBuildOutput, runViteBuild } from "./build/artifact.js";
|
|
17
|
+
|
|
18
|
+
interface EmittedModule {
|
|
19
|
+
readonly path: string;
|
|
20
|
+
readonly bytes: Uint8Array;
|
|
21
|
+
readonly imports: ReadonlyArray<string>;
|
|
22
|
+
readonly exports: ReadonlyArray<string>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Release publication uses SDK sources; installed local development uses packed SDK entries. */
|
|
26
|
+
export const buildManagedReleaseGraph = Effect.fn("ManagedRelease.buildGraph")(function* (
|
|
27
|
+
sdkDirectory: string,
|
|
28
|
+
kind: "Browser" | "Guest",
|
|
29
|
+
inputKind: "Source" | "Packed" = "Source",
|
|
30
|
+
) {
|
|
31
|
+
const path = yield* Path.Path;
|
|
32
|
+
if (kind === "Browser") {
|
|
33
|
+
const fs = yield* FileSystem.FileSystem;
|
|
34
|
+
const preact = yield* Schema.decodeEffect(
|
|
35
|
+
Schema.fromJsonString(Schema.Struct({ version: Schema.String })),
|
|
36
|
+
)(yield* fs.readFileString(fileURLToPath(import.meta.resolve("preact/package.json"))));
|
|
37
|
+
if (preact.version !== managedPreactVersion)
|
|
38
|
+
return yield* InvalidBuildOutput.make({
|
|
39
|
+
message: `The installed Preact version ${preact.version} differs from compiler peer metadata ${managedPreactVersion}. Update and validate the shared managed export mapping before publishing.`,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
const prefix = `ignotum/platform/${kind.toLowerCase()}/`;
|
|
43
|
+
const entries = kind === "Browser" ? managedBrowserExports : managedGuestExports;
|
|
44
|
+
const modules: Array<EmittedModule> = [];
|
|
45
|
+
const files: Array<{ path: string; bytes: Uint8Array }> = [];
|
|
46
|
+
const rendererRoots = new Set<string>();
|
|
47
|
+
const plugin: Plugin = {
|
|
48
|
+
name: "ignotum:managed-release-ownership",
|
|
49
|
+
enforce: "pre",
|
|
50
|
+
resolveId(id) {
|
|
51
|
+
if (id.startsWith("node:") || id.startsWith("@effect/platform-node"))
|
|
52
|
+
this.error(`Managed ${kind} modules cannot import Node capabilities: ${id}.`);
|
|
53
|
+
},
|
|
54
|
+
generateBundle(_options, bundle) {
|
|
55
|
+
for (const item of Object.values(bundle)) {
|
|
56
|
+
if (item.type === "asset") {
|
|
57
|
+
if (!item.fileName.endsWith(".js.map"))
|
|
58
|
+
this.error(`Unexpected managed release asset '${item.fileName}'.`);
|
|
59
|
+
const artifact = {
|
|
60
|
+
path: item.fileName,
|
|
61
|
+
bytes: Predicate.isString(item.source) ? utf8Bytes(item.source) : item.source,
|
|
62
|
+
};
|
|
63
|
+
const existing = files.findIndex((file) => file.path === artifact.path);
|
|
64
|
+
if (existing < 0) files.push(artifact);
|
|
65
|
+
else files[existing] = artifact;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
for (const [id, module] of Object.entries(item.modules)) {
|
|
69
|
+
if (module.renderedLength === 0) continue;
|
|
70
|
+
const normalized = normalizePath(id);
|
|
71
|
+
if (
|
|
72
|
+
normalized.includes("/src/cli/") ||
|
|
73
|
+
normalized.includes("/platform-node/") ||
|
|
74
|
+
(kind === "Browser" &&
|
|
75
|
+
(normalized.includes("/runtime/src/guest.") ||
|
|
76
|
+
normalized.includes("/ignotum/src/server/") ||
|
|
77
|
+
normalized.includes("/ignotum/src/internal/host.")))
|
|
78
|
+
)
|
|
79
|
+
this.error(`Unexpected implementation in managed ${kind} graph: ${normalized}.`);
|
|
80
|
+
const marker = "/node_modules/preact/";
|
|
81
|
+
const renderer = normalized.lastIndexOf(marker);
|
|
82
|
+
if (renderer >= 0) rendererRoots.add(normalized.slice(0, renderer + marker.length));
|
|
83
|
+
}
|
|
84
|
+
const bytes = utf8Bytes(item.code);
|
|
85
|
+
if (!files.some((file) => file.path === `${item.fileName}.map`)) {
|
|
86
|
+
// Generated re-export facades have no source locations. Keep an explicit empty map.
|
|
87
|
+
const sourceMap =
|
|
88
|
+
item.map?.toString() ??
|
|
89
|
+
JSON.stringify({
|
|
90
|
+
version: 3,
|
|
91
|
+
file: item.fileName,
|
|
92
|
+
sources: [],
|
|
93
|
+
names: [],
|
|
94
|
+
mappings: "",
|
|
95
|
+
});
|
|
96
|
+
files.push({ path: `${item.fileName}.map`, bytes: utf8Bytes(sourceMap) });
|
|
97
|
+
}
|
|
98
|
+
modules.push({
|
|
99
|
+
path: item.fileName,
|
|
100
|
+
bytes,
|
|
101
|
+
imports: [...new Set([...item.imports, ...item.dynamicImports])].sort(),
|
|
102
|
+
exports: [...item.exports].sort(),
|
|
103
|
+
});
|
|
104
|
+
files.push({ path: item.fileName, bytes });
|
|
105
|
+
}
|
|
106
|
+
if (kind === "Browser" && rendererRoots.size !== 1)
|
|
107
|
+
this.error(
|
|
108
|
+
`Managed browser release must contain one Preact package, found ${rendererRoots.size}.`,
|
|
109
|
+
);
|
|
110
|
+
if (kind === "Guest" && rendererRoots.size !== 0)
|
|
111
|
+
this.error("Managed guest release cannot contain a renderer.");
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
const input = Object.fromEntries(
|
|
115
|
+
entries.map((entry) => [
|
|
116
|
+
entry.fileName,
|
|
117
|
+
entry.source.startsWith("preact")
|
|
118
|
+
? fileURLToPath(import.meta.resolve(entry.source))
|
|
119
|
+
: inputKind === "Source"
|
|
120
|
+
? path.join(sdkDirectory, "src", entry.source)
|
|
121
|
+
: path.join(
|
|
122
|
+
sdkDirectory,
|
|
123
|
+
"dist/runtime",
|
|
124
|
+
`${entry.specifier.slice("ignotum/".length)}.js`,
|
|
125
|
+
),
|
|
126
|
+
]),
|
|
127
|
+
);
|
|
128
|
+
yield* runViteBuild(`managed ${kind} release`, {
|
|
129
|
+
configFile: false,
|
|
130
|
+
root: sdkDirectory,
|
|
131
|
+
publicDir: false,
|
|
132
|
+
logLevel: "warn",
|
|
133
|
+
mode: "production",
|
|
134
|
+
define: { "process.env.NODE_ENV": JSON.stringify("production") },
|
|
135
|
+
plugins: [plugin],
|
|
136
|
+
resolve: {
|
|
137
|
+
alias: managedBrowserExports
|
|
138
|
+
.filter((entry) => entry.specifier.startsWith("preact"))
|
|
139
|
+
.map((entry) => ({
|
|
140
|
+
find: new RegExp(`^${entry.specifier}$`),
|
|
141
|
+
replacement: fileURLToPath(import.meta.resolve(entry.source)),
|
|
142
|
+
})),
|
|
143
|
+
conditions: ["@ignotum/source", "module", "browser", "production"],
|
|
144
|
+
dedupe: ["preact"],
|
|
145
|
+
noExternal: true,
|
|
146
|
+
},
|
|
147
|
+
build: {
|
|
148
|
+
write: false,
|
|
149
|
+
sourcemap: "hidden",
|
|
150
|
+
minify: "oxc",
|
|
151
|
+
target: "es2020",
|
|
152
|
+
modulePreload: false,
|
|
153
|
+
rolldownOptions: {
|
|
154
|
+
input,
|
|
155
|
+
preserveEntrySignatures: "strict",
|
|
156
|
+
output: {
|
|
157
|
+
format: "es",
|
|
158
|
+
entryFileNames: `${prefix}[name].js`,
|
|
159
|
+
chunkFileNames: `${prefix}chunks/[name]-[hash].js`,
|
|
160
|
+
sourcemapPathTransform: (source) => {
|
|
161
|
+
const normalized = normalizePath(source);
|
|
162
|
+
const dependency = normalized.lastIndexOf("/node_modules/");
|
|
163
|
+
return dependency >= 0
|
|
164
|
+
? `ignotum://dependency/${normalized.slice(dependency + "/node_modules/".length)}`
|
|
165
|
+
: `ignotum://platform/${normalized.replace(/^(?:\.\.\/)+/, "")}`;
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
const descriptions = yield* Effect.forEach(modules, (module) =>
|
|
172
|
+
Effect.gen(function* () {
|
|
173
|
+
const sourceMap = files.find((file) => file.path === `${module.path}.map`);
|
|
174
|
+
if (sourceMap === undefined)
|
|
175
|
+
return yield* new InvalidBuildOutput({ message: `Missing source map for ${module.path}.` });
|
|
176
|
+
return {
|
|
177
|
+
path: module.path,
|
|
178
|
+
sha256: yield* sha256(module.bytes),
|
|
179
|
+
size: module.bytes.byteLength,
|
|
180
|
+
imports: module.imports,
|
|
181
|
+
exports: module.exports,
|
|
182
|
+
sourceMap: {
|
|
183
|
+
path: sourceMap.path,
|
|
184
|
+
sha256: yield* sha256(sourceMap.bytes),
|
|
185
|
+
size: sourceMap.bytes.byteLength,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}),
|
|
189
|
+
);
|
|
190
|
+
const manifest = yield* Schema.decodeUnknownEffect(ManagedReleaseManifest)({
|
|
191
|
+
formatVersion: 1,
|
|
192
|
+
kind,
|
|
193
|
+
entries: Object.fromEntries(
|
|
194
|
+
entries.map((entry) => [entry.specifier, `${prefix}${entry.fileName}.js`]),
|
|
195
|
+
),
|
|
196
|
+
modules: descriptions.sort((a, b) => a.path.localeCompare(b.path)),
|
|
197
|
+
});
|
|
198
|
+
const manifestBytes = encodeManagedReleaseManifest(manifest);
|
|
199
|
+
const verified = yield* validateManagedReleaseGraph(manifestBytes, files);
|
|
200
|
+
return { ...verified, manifestBytes };
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
export const buildManagedReleaseGraphs = Effect.fn("ManagedRelease.buildGraphs")(function* (
|
|
204
|
+
sdkDirectory: string,
|
|
205
|
+
inputKind: "Source" | "Packed" = "Source",
|
|
206
|
+
) {
|
|
207
|
+
// Build sequentially to bound compiler memory. Each graph has its own private dependencies.
|
|
208
|
+
const browser = yield* buildManagedReleaseGraph(sdkDirectory, "Browser", inputKind);
|
|
209
|
+
const guest = yield* buildManagedReleaseGraph(sdkDirectory, "Guest", inputKind);
|
|
210
|
+
return { browser, guest };
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
/** Fixtures and local development resolve exactly the entries that publication verifies. */
|
|
214
|
+
export const localManagedReleaseProvider = Effect.fn("ManagedRelease.localProvider")(function* (
|
|
215
|
+
sdkDirectory: string,
|
|
216
|
+
) {
|
|
217
|
+
const fs = yield* FileSystem.FileSystem;
|
|
218
|
+
const path = yield* Path.Path;
|
|
219
|
+
const hasWorkspaceSources = yield* fs.exists(
|
|
220
|
+
path.resolve(sdkDirectory, "../contracts/src/schema/index.ts"),
|
|
221
|
+
);
|
|
222
|
+
const graphs = yield* buildManagedReleaseGraphs(
|
|
223
|
+
sdkDirectory,
|
|
224
|
+
hasWorkspaceSources ? "Source" : "Packed",
|
|
225
|
+
);
|
|
226
|
+
return {
|
|
227
|
+
...graphs,
|
|
228
|
+
resolve: (kind: "Browser" | "Guest", specifier: string) =>
|
|
229
|
+
(kind === "Browser" ? graphs.browser : graphs.guest).manifest.entries[specifier],
|
|
230
|
+
read: (kind: "Browser" | "Guest", modulePath: string) =>
|
|
231
|
+
(kind === "Browser" ? graphs.browser : graphs.guest).files.get(modulePath)?.slice(),
|
|
232
|
+
};
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
export const writeManagedReleaseGraphs = Effect.fn("ManagedRelease.writeGraphs")(function* (
|
|
236
|
+
sdkDirectory: string,
|
|
237
|
+
outputDirectory: string,
|
|
238
|
+
) {
|
|
239
|
+
const fs = yield* FileSystem.FileSystem;
|
|
240
|
+
const path = yield* Path.Path;
|
|
241
|
+
const graphs = yield* buildManagedReleaseGraphs(sdkDirectory);
|
|
242
|
+
for (const graph of [graphs.browser, graphs.guest]) {
|
|
243
|
+
const directory = path.join(outputDirectory, graph.sha256);
|
|
244
|
+
yield* fs.makeDirectory(directory, { recursive: true });
|
|
245
|
+
for (const [name, bytes] of graph.files) {
|
|
246
|
+
const target = path.join(directory, name);
|
|
247
|
+
yield* fs.makeDirectory(path.dirname(target), { recursive: true });
|
|
248
|
+
yield* fs.writeFile(target, bytes);
|
|
249
|
+
}
|
|
250
|
+
yield* fs.writeFile(path.join(directory, "manifest.json"), graph.manifestBytes);
|
|
251
|
+
yield* Effect.logInfo("Built managed runtime graph", {
|
|
252
|
+
kind: graph.manifest.kind,
|
|
253
|
+
sha256: graph.sha256,
|
|
254
|
+
modules: graph.manifest.modules.length,
|
|
255
|
+
moduleBytes: graph.manifest.modules.reduce((sum, module) => sum + module.size, 0),
|
|
256
|
+
directory,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return graphs;
|
|
260
|
+
});
|
|
@@ -17,6 +17,8 @@ interface PackageEntry {
|
|
|
17
17
|
const packageEntries: ReadonlyArray<PackageEntry> = [
|
|
18
18
|
{ graph: "Client", specifier: "ignotum/client" },
|
|
19
19
|
{ graph: "Client", specifier: "ignotum/internal/api" },
|
|
20
|
+
{ graph: "Client", specifier: "ignotum/internal/client" },
|
|
21
|
+
{ graph: "Client", specifier: "ignotum/internal/routes" },
|
|
20
22
|
{ graph: "Server", specifier: "ignotum/server" },
|
|
21
23
|
{ graph: "Server", specifier: "ignotum/internal/host" },
|
|
22
24
|
{ graph: "Server", specifier: "ignotum/internal/server" },
|
|
@@ -93,6 +95,8 @@ export const moduleBoundariesPlugin = Effect.fn("Deploy.moduleBoundariesPlugin")
|
|
|
93
95
|
const clientDirectory = yield* canonicalModuleId(`${normalizedApp}/client`);
|
|
94
96
|
const serverDirectory = yield* canonicalModuleId(`${normalizedApp}/server`);
|
|
95
97
|
const generatedApi = yield* canonicalModuleId(`${normalizedApp}/_generated/api.ts`);
|
|
98
|
+
const generatedClient = yield* canonicalModuleId(`${normalizedApp}/_generated/client.ts`);
|
|
99
|
+
const generatedRoutes = yield* canonicalModuleId(`${normalizedApp}/_generated/routes.ts`);
|
|
96
100
|
const generatedServer = yield* canonicalModuleId(`${normalizedApp}/_generated/server.ts`);
|
|
97
101
|
|
|
98
102
|
const reject = (specifier: string, importer: string, reason: string): never => {
|
|
@@ -126,7 +130,13 @@ export const moduleBoundariesPlugin = Effect.fn("Deploy.moduleBoundariesPlugin")
|
|
|
126
130
|
reject(specifier, importer, "the resolved module belongs to the server");
|
|
127
131
|
}
|
|
128
132
|
|
|
129
|
-
if (
|
|
133
|
+
if (
|
|
134
|
+
graph === "Server" &&
|
|
135
|
+
(isWithin(cleaned, clientDirectory) ||
|
|
136
|
+
cleaned === generatedApi ||
|
|
137
|
+
cleaned === generatedClient ||
|
|
138
|
+
cleaned === generatedRoutes)
|
|
139
|
+
) {
|
|
130
140
|
reject(specifier, importer, "the resolved module belongs to the client");
|
|
131
141
|
}
|
|
132
142
|
};
|