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
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import MagicString from "magic-string";
|
|
2
|
+
import remapping from "@jridgewell/remapping";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { posix } from "node:path";
|
|
5
|
+
import { Graph, Schema } from "effect";
|
|
6
|
+
import { parseSync } from "oxc-parser";
|
|
7
|
+
|
|
8
|
+
export interface ClientOutputChunk {
|
|
9
|
+
readonly fileName: string;
|
|
10
|
+
readonly code: string;
|
|
11
|
+
readonly sourceMap?: string;
|
|
12
|
+
readonly imports: readonly string[];
|
|
13
|
+
readonly dynamicImports: readonly string[];
|
|
14
|
+
readonly modules: Readonly<Record<string, { readonly renderedLength: number }>>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class AssetNamingError extends Schema.TaggedError<AssetNamingError>()("AssetNamingError", {
|
|
18
|
+
message: Schema.String,
|
|
19
|
+
}) {}
|
|
20
|
+
|
|
21
|
+
/** The bundler must merge these source groups before assigning final-byte hashes. */
|
|
22
|
+
export const cyclicChunkGroups = (
|
|
23
|
+
chunks: readonly ClientOutputChunk[],
|
|
24
|
+
): readonly (readonly string[])[] => {
|
|
25
|
+
const ids = new Map<string, Graph.NodeIndex>();
|
|
26
|
+
const byIndex = new Map<Graph.NodeIndex, ClientOutputChunk>();
|
|
27
|
+
const graph = Graph.directed<string, void>((mutable) => {
|
|
28
|
+
for (const chunk of chunks) {
|
|
29
|
+
const index = Graph.addNode(mutable, chunk.fileName);
|
|
30
|
+
ids.set(chunk.fileName, index);
|
|
31
|
+
byIndex.set(index, chunk);
|
|
32
|
+
}
|
|
33
|
+
for (const chunk of chunks) {
|
|
34
|
+
for (const dependency of [...chunk.imports, ...chunk.dynamicImports]) {
|
|
35
|
+
const target = ids.get(dependency);
|
|
36
|
+
if (target !== undefined)
|
|
37
|
+
Graph.addEdge(mutable, ids.get(chunk.fileName)!, target, undefined);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
return Graph.stronglyConnectedComponents(graph)
|
|
42
|
+
.filter((component) => component.length > 1)
|
|
43
|
+
.map((component) =>
|
|
44
|
+
[...new Set(component.flatMap((index) => Object.keys(byIndex.get(index)!.modules)))].sort(),
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export interface NamedClientChunk {
|
|
49
|
+
readonly fileName: string;
|
|
50
|
+
readonly code: string;
|
|
51
|
+
readonly sha256: string;
|
|
52
|
+
readonly sourceMap?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Call only after CSS/media URLs and chunk grouping have been finalized. */
|
|
56
|
+
export const nameClientChunks = (
|
|
57
|
+
chunks: readonly ClientOutputChunk[],
|
|
58
|
+
entry: string,
|
|
59
|
+
): ReadonlyMap<string, NamedClientChunk> => {
|
|
60
|
+
if (cyclicChunkGroups(chunks).length !== 0) {
|
|
61
|
+
throw AssetNamingError.make({
|
|
62
|
+
message: "Merge cyclic emitted chunks before hashing their bytes.",
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
const sources = new Map(chunks.map((chunk) => [chunk.fileName, chunk]));
|
|
66
|
+
const named = new Map<string, NamedClientChunk>();
|
|
67
|
+
const visiting = new Set<string>();
|
|
68
|
+
const name = (fileName: string): NamedClientChunk => {
|
|
69
|
+
const found = named.get(fileName);
|
|
70
|
+
if (found !== undefined) return found;
|
|
71
|
+
const chunk = sources.get(fileName);
|
|
72
|
+
if (chunk === undefined || visiting.has(fileName)) {
|
|
73
|
+
throw AssetNamingError.make({ message: `Unresolved or cyclic app chunk '${fileName}'.` });
|
|
74
|
+
}
|
|
75
|
+
visiting.add(fileName);
|
|
76
|
+
const parsed = parseSync(fileName, chunk.code);
|
|
77
|
+
if (parsed.errors.length > 0)
|
|
78
|
+
throw AssetNamingError.make({ message: `Invalid app chunk '${fileName}'.` });
|
|
79
|
+
const requests = [
|
|
80
|
+
...parsed.module.staticImports.map((item) => item.moduleRequest),
|
|
81
|
+
...parsed.module.staticExports.flatMap((item) =>
|
|
82
|
+
item.entries.flatMap((item) => (item.moduleRequest === null ? [] : [item.moduleRequest])),
|
|
83
|
+
),
|
|
84
|
+
...parsed.module.dynamicImports.map((item) => {
|
|
85
|
+
const expression = parseSync(
|
|
86
|
+
"import.js",
|
|
87
|
+
chunk.code.slice(item.moduleRequest.start, item.moduleRequest.end),
|
|
88
|
+
).program.body[0];
|
|
89
|
+
const value =
|
|
90
|
+
expression?.type === "ExpressionStatement" ? expression.expression : undefined;
|
|
91
|
+
const address =
|
|
92
|
+
value?.type === "Literal"
|
|
93
|
+
? value.value
|
|
94
|
+
: value?.type === "TemplateLiteral" &&
|
|
95
|
+
value.expressions.length === 0 &&
|
|
96
|
+
value.quasis.length === 1
|
|
97
|
+
? value.quasis[0]!.value.cooked
|
|
98
|
+
: undefined;
|
|
99
|
+
if (address === undefined)
|
|
100
|
+
throw AssetNamingError.make({
|
|
101
|
+
message: `App dynamic import in '${fileName}' must have a literal module address.`,
|
|
102
|
+
});
|
|
103
|
+
return { ...item.moduleRequest, value: Schema.decodeUnknownSync(Schema.String)(address) };
|
|
104
|
+
}),
|
|
105
|
+
];
|
|
106
|
+
const edited = new MagicString(chunk.code);
|
|
107
|
+
const replacements = new Map<number, { readonly end: number; readonly text: string }>();
|
|
108
|
+
for (const request of requests) {
|
|
109
|
+
if (!request.value.startsWith(".")) continue;
|
|
110
|
+
const dependency = posix.normalize(posix.join(posix.dirname(fileName), request.value));
|
|
111
|
+
const target = name(dependency);
|
|
112
|
+
if (dependency === entry)
|
|
113
|
+
throw AssetNamingError.make({
|
|
114
|
+
message: "An app dependency cannot import the publication entry.",
|
|
115
|
+
});
|
|
116
|
+
const relative = posix.relative(posix.dirname(fileName), target.fileName);
|
|
117
|
+
replacements.set(request.start, {
|
|
118
|
+
end: request.end,
|
|
119
|
+
text: JSON.stringify(relative.startsWith(".") ? relative : `./${relative}`),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
for (const [start, replacement] of [...replacements].sort(([a], [b]) => b - a)) {
|
|
123
|
+
edited.overwrite(start, replacement.end, replacement.text);
|
|
124
|
+
}
|
|
125
|
+
const code = edited.toString();
|
|
126
|
+
const sha256 = createHash("sha256").update(code).digest("hex");
|
|
127
|
+
const outputName =
|
|
128
|
+
fileName === entry ? "entry.js" : fileName.replace(/\.(?:m?js)$/, `-${sha256}.js`);
|
|
129
|
+
if (fileName !== entry && outputName === fileName)
|
|
130
|
+
throw AssetNamingError.make({
|
|
131
|
+
message: `App chunk '${fileName}' must have a JavaScript extension.`,
|
|
132
|
+
});
|
|
133
|
+
const sourceMap =
|
|
134
|
+
chunk.sourceMap === undefined
|
|
135
|
+
? undefined
|
|
136
|
+
: remapping(
|
|
137
|
+
[edited.generateMap({ hires: true, source: fileName }).toString(), chunk.sourceMap],
|
|
138
|
+
() => null,
|
|
139
|
+
).toString();
|
|
140
|
+
const output = { fileName: outputName, code, sha256, sourceMap };
|
|
141
|
+
const collision = [...named.values()].find(
|
|
142
|
+
(other) => other.fileName === outputName && other.code !== code,
|
|
143
|
+
);
|
|
144
|
+
if (collision !== undefined)
|
|
145
|
+
throw AssetNamingError.make({ message: `Conflicting public path '${outputName}'.` });
|
|
146
|
+
named.set(fileName, output);
|
|
147
|
+
visiting.delete(fileName);
|
|
148
|
+
return output;
|
|
149
|
+
};
|
|
150
|
+
for (const chunk of chunks) name(chunk.fileName);
|
|
151
|
+
return named;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/** Group source cycles before rendering so final-byte dependency hashing terminates. */
|
|
155
|
+
export const clientCycleGroupsPlugin = (appDirectory: string): import("vite").Plugin => {
|
|
156
|
+
const groups = new Map<string, string>();
|
|
157
|
+
return {
|
|
158
|
+
name: "ignotum:client-cycle-groups",
|
|
159
|
+
buildEnd() {
|
|
160
|
+
groups.clear();
|
|
161
|
+
const sources = [...this.getModuleIds()].flatMap((id) => {
|
|
162
|
+
const info = this.getModuleInfo(id);
|
|
163
|
+
return info === null
|
|
164
|
+
? []
|
|
165
|
+
: [
|
|
166
|
+
{
|
|
167
|
+
fileName: id,
|
|
168
|
+
code: "",
|
|
169
|
+
imports: info.importedIds,
|
|
170
|
+
dynamicImports: info.dynamicallyImportedIds,
|
|
171
|
+
modules: { [id]: { renderedLength: 1 } },
|
|
172
|
+
},
|
|
173
|
+
];
|
|
174
|
+
});
|
|
175
|
+
for (const ids of cyclicChunkGroups(sources)) {
|
|
176
|
+
const identity = ids
|
|
177
|
+
.map((id) => posix.relative(appDirectory.replaceAll("\\", "/"), id.replaceAll("\\", "/")))
|
|
178
|
+
.sort();
|
|
179
|
+
const name = `cycle-${createHash("sha256").update(JSON.stringify(identity)).digest("hex").slice(0, 16)}`;
|
|
180
|
+
for (const id of ids) groups.set(id, name);
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
outputOptions(options) {
|
|
184
|
+
return {
|
|
185
|
+
...options,
|
|
186
|
+
codeSplitting: { groups: [{ name: (id: string) => groups.get(id) ?? null }] },
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { Effect, FileSystem, Option, Path,
|
|
3
|
+
import { Effect, FileSystem, Option, Path, Schema } from "effect";
|
|
4
4
|
import tailwindcss from "@tailwindcss/vite";
|
|
5
5
|
import { defaultClientConditions, normalizePath, type Plugin } from "vite";
|
|
6
6
|
import {
|
|
@@ -16,7 +16,9 @@ import { InvalidClientFile } from "../client-config.js";
|
|
|
16
16
|
import { clientStylesSource } from "../client-styles.js";
|
|
17
17
|
import { createImageAssetsPlugin } from "../image-assets.js";
|
|
18
18
|
import { managedClientBoundariesPlugin } from "../managed-client-boundaries.js";
|
|
19
|
-
import {
|
|
19
|
+
import { platformImportsPlugin } from "@ignotum/platform/compiler";
|
|
20
|
+
import { compilerExports } from "@ignotum/platform/compiler-exports";
|
|
21
|
+
import { clientCycleGroupsPlugin, nameClientChunks } from "./asset-names.js";
|
|
20
22
|
import { moduleBoundariesPlugin } from "../module-boundaries.js";
|
|
21
23
|
import { generateClientRoutes } from "../route-codegen.js";
|
|
22
24
|
import { InvalidBuildOutput, runViteBuild, sourceMapPathTransform } from "./artifact.js";
|
|
@@ -38,14 +40,7 @@ interface ClientChunk {
|
|
|
38
40
|
|
|
39
41
|
const literal = Schema.encodeSync(Schema.fromJsonString(Schema.String));
|
|
40
42
|
const globalStylesId = "virtual:ignotum/managed-global.css";
|
|
41
|
-
|
|
42
|
-
const stylesheetFileName = (source: string | Uint8Array): string => {
|
|
43
|
-
// Vite removes its internal hash-update marker before writing CSS assets.
|
|
44
|
-
const finalSource = Predicate.isString(source)
|
|
45
|
-
? source.replace(/\/\*\$vite\$:\d+\*\//, "")
|
|
46
|
-
: source;
|
|
47
|
-
return `assets/${createHash("sha256").update(finalSource).digest("hex")}/style.css`;
|
|
48
|
-
};
|
|
43
|
+
const globalEntryId = "virtual:ignotum/managed-global.js";
|
|
49
44
|
|
|
50
45
|
/** Compile route definitions and app dependencies without embedding browser runtime code. */
|
|
51
46
|
export const buildManagedClient = Effect.fn("Deploy.buildManagedClient")(function* (
|
|
@@ -74,16 +69,34 @@ export const buildManagedClient = Effect.fn("Deploy.buildManagedClient")(functio
|
|
|
74
69
|
}
|
|
75
70
|
const discovery = yield* generateClientRoutes(appDirectory);
|
|
76
71
|
const stage = yield* fs.makeTempDirectoryScoped({ prefix: "ignotum-managed-client-" });
|
|
77
|
-
const images = yield* createImageAssetsPlugin(appDirectory);
|
|
72
|
+
const images = yield* createImageAssetsPlugin(appDirectory, true);
|
|
78
73
|
const boundaries = yield* moduleBoundariesPlugin("Client", appDirectory);
|
|
79
|
-
const platform = yield* managedClientBoundariesPlugin(appDirectory);
|
|
80
|
-
const peers = yield* managedPreactPeerPlugin({ resolvePeer: (specifier) => specifier });
|
|
74
|
+
const platform = yield* managedClientBoundariesPlugin(appDirectory, true);
|
|
81
75
|
const entrySources = new Map<string, string>();
|
|
82
76
|
const input: Record<string, string> = {};
|
|
83
|
-
input.global =
|
|
77
|
+
input.global = globalEntryId;
|
|
84
78
|
const sourceEntries = new Map<AppModuleId, string>();
|
|
85
|
-
|
|
86
|
-
const
|
|
79
|
+
const segmentName = (segment: string): string => {
|
|
80
|
+
const escape = (value: string) =>
|
|
81
|
+
value.replace(/[^a-zA-Z0-9.-]/g, (value) => `_${value.codePointAt(0)!.toString(16)}_`);
|
|
82
|
+
const parameter = /^\[([^\]]+)\]$/.exec(segment);
|
|
83
|
+
return parameter ? `_param_${escape(parameter[1]!)}` : escape(segment);
|
|
84
|
+
};
|
|
85
|
+
for (const module of discovery.modules) {
|
|
86
|
+
const relative = normalizePath(
|
|
87
|
+
path.relative(path.join(appDirectory, "client"), module.path),
|
|
88
|
+
).replace(/\.[^.]+$/, "");
|
|
89
|
+
const ownership =
|
|
90
|
+
module.kind === "layout"
|
|
91
|
+
? path.dirname(relative) === "."
|
|
92
|
+
? "root"
|
|
93
|
+
: normalizePath(path.dirname(relative))
|
|
94
|
+
: relative;
|
|
95
|
+
const name = `${module.kind === "route" ? "routes" : "layouts"}/${ownership.split("/").map(segmentName).join("/")}`;
|
|
96
|
+
if (Object.hasOwn(input, name))
|
|
97
|
+
return yield* InvalidBuildOutput.make({
|
|
98
|
+
message: `Conflicting client module ownership '${name}'.`,
|
|
99
|
+
});
|
|
87
100
|
const id = `virtual:ignotum/managed-client/${name}`;
|
|
88
101
|
input[name] = id;
|
|
89
102
|
sourceEntries.set(module.id, `${name}.mjs`);
|
|
@@ -115,9 +128,11 @@ export const buildManagedClient = Effect.fn("Deploy.buildManagedClient")(functio
|
|
|
115
128
|
name: "ignotum:managed-client-entries",
|
|
116
129
|
enforce: "post",
|
|
117
130
|
resolveId(id) {
|
|
118
|
-
if (id === globalStylesId || entrySources.has(`\0${id}`))
|
|
131
|
+
if (id === globalStylesId || id === globalEntryId || entrySources.has(`\0${id}`))
|
|
132
|
+
return `\0${id}`;
|
|
119
133
|
},
|
|
120
134
|
load(id) {
|
|
135
|
+
if (id === `\0${globalEntryId}`) return `import ${literal(globalStylesId)};`;
|
|
121
136
|
if (id === `\0${globalStylesId}`) return clientStylesSource;
|
|
122
137
|
return entrySources.get(id);
|
|
123
138
|
},
|
|
@@ -139,7 +154,13 @@ export const buildManagedClient = Effect.fn("Deploy.buildManagedClient")(functio
|
|
|
139
154
|
isEntry: item.isEntry,
|
|
140
155
|
});
|
|
141
156
|
if (item.map === null) {
|
|
142
|
-
|
|
157
|
+
const applicationRoot = `${normalizePath(appDirectory)}/`;
|
|
158
|
+
if (
|
|
159
|
+
Object.entries(item.modules).some(
|
|
160
|
+
([id, module]) =>
|
|
161
|
+
normalizePath(id).startsWith(applicationRoot) && module.renderedLength > 0,
|
|
162
|
+
)
|
|
163
|
+
)
|
|
143
164
|
this.error(`Missing source map for app implementation '${item.fileName}'.`);
|
|
144
165
|
continue;
|
|
145
166
|
}
|
|
@@ -163,7 +184,7 @@ export const buildManagedClient = Effect.fn("Deploy.buildManagedClient")(functio
|
|
|
163
184
|
"import.meta.env.DEV": "false",
|
|
164
185
|
"import.meta.env.PROD": "true",
|
|
165
186
|
},
|
|
166
|
-
base: "/_ignotum/",
|
|
187
|
+
base: "/_ignotum/assets/",
|
|
167
188
|
oxc: { jsx: { importSource: "ignotum/client", runtime: "automatic", development: false } },
|
|
168
189
|
resolve: {
|
|
169
190
|
alias: [
|
|
@@ -177,11 +198,21 @@ export const buildManagedClient = Effect.fn("Deploy.buildManagedClient")(functio
|
|
|
177
198
|
),
|
|
178
199
|
tsconfigPaths: true,
|
|
179
200
|
},
|
|
180
|
-
plugins: [
|
|
201
|
+
plugins: [
|
|
202
|
+
privateMaps,
|
|
203
|
+
images.plugin,
|
|
204
|
+
boundaries,
|
|
205
|
+
platform,
|
|
206
|
+
platformImportsPlugin(compilerExports),
|
|
207
|
+
clientCycleGroupsPlugin(appDirectory),
|
|
208
|
+
collect,
|
|
209
|
+
tailwindcss(),
|
|
210
|
+
],
|
|
181
211
|
build: {
|
|
182
212
|
emptyOutDir: false,
|
|
183
213
|
outDir: stage,
|
|
184
214
|
modulePreload: false,
|
|
215
|
+
cssCodeSplit: false,
|
|
185
216
|
sourcemap: "hidden",
|
|
186
217
|
target: "es2022",
|
|
187
218
|
rolldownOptions: {
|
|
@@ -189,18 +220,31 @@ export const buildManagedClient = Effect.fn("Deploy.buildManagedClient")(functio
|
|
|
189
220
|
preserveEntrySignatures: "strict",
|
|
190
221
|
output: {
|
|
191
222
|
entryFileNames: "[name].mjs",
|
|
192
|
-
chunkFileNames:
|
|
223
|
+
chunkFileNames: (chunk) => {
|
|
224
|
+
const owners = chunk.moduleIds
|
|
225
|
+
.map((id) => {
|
|
226
|
+
const normalized = normalizePath(id);
|
|
227
|
+
const dependency = normalized.lastIndexOf("/node_modules/");
|
|
228
|
+
return dependency < 0
|
|
229
|
+
? normalized
|
|
230
|
+
.replace(normalizePath(appDirectory), "app")
|
|
231
|
+
.replace(encodeURIComponent(normalizePath(appDirectory)), "app")
|
|
232
|
+
: normalized.slice(dependency + 14);
|
|
233
|
+
})
|
|
234
|
+
.sort();
|
|
235
|
+
return `shared/chunk-${createHash("sha256").update(JSON.stringify(owners)).digest("hex").slice(0, 16)}.mjs`;
|
|
236
|
+
},
|
|
193
237
|
assetFileNames: (asset) =>
|
|
194
238
|
asset.names.some((name) => name.endsWith(".css"))
|
|
195
|
-
?
|
|
196
|
-
: "
|
|
239
|
+
? "styles.css"
|
|
240
|
+
: "media/[name]-[hash][extname]",
|
|
197
241
|
sourcemapPathTransform: yield* sourceMapPathTransform(appDirectory),
|
|
198
242
|
},
|
|
199
243
|
},
|
|
200
244
|
},
|
|
201
245
|
});
|
|
202
246
|
const chunks = output.flatMap((item) =>
|
|
203
|
-
item.type === "chunk"
|
|
247
|
+
item.type === "chunk" && item.fileName !== "global.mjs"
|
|
204
248
|
? [
|
|
205
249
|
{
|
|
206
250
|
fileName: item.fileName,
|
|
@@ -214,6 +258,7 @@ export const buildManagedClient = Effect.fn("Deploy.buildManagedClient")(functio
|
|
|
214
258
|
);
|
|
215
259
|
const files = new Map<string, Uint8Array>();
|
|
216
260
|
for (const item of output) {
|
|
261
|
+
if (item.fileName === "global.mjs") continue;
|
|
217
262
|
if (
|
|
218
263
|
item.type === "asset" &&
|
|
219
264
|
!item.fileName.endsWith(".css") &&
|
|
@@ -224,53 +269,81 @@ export const buildManagedClient = Effect.fn("Deploy.buildManagedClient")(functio
|
|
|
224
269
|
});
|
|
225
270
|
files.set(item.fileName, yield* fs.readFile(path.join(stage, item.fileName)));
|
|
226
271
|
}
|
|
272
|
+
for (const [name, contents] of [...files]) {
|
|
273
|
+
if (!name.endsWith(".css")) continue;
|
|
274
|
+
files.delete(name);
|
|
275
|
+
files.set(`styles-${yield* sha256(contents)}.css`, contents);
|
|
276
|
+
}
|
|
227
277
|
for (const [name, bytes] of maps) files.set(name, bytes);
|
|
228
|
-
const
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
...chunk.imports.flatMap((dependency) => stylesFor(dependency, visited)),
|
|
238
|
-
]),
|
|
239
|
-
].sort();
|
|
278
|
+
const styles = [...files.keys()].filter((name) => name.endsWith(".css")).sort();
|
|
279
|
+
const registration = `const registration = {
|
|
280
|
+
applicationName: ${literal(appName)},
|
|
281
|
+
routes: ${JSON.stringify(discovery.routes)},
|
|
282
|
+
layouts: ${JSON.stringify(discovery.layouts)},
|
|
283
|
+
styles: ${JSON.stringify(styles.map((name) => `/_ignotum/assets/${name}`))},
|
|
284
|
+
modules: new Map([
|
|
285
|
+
${discovery.modules.map((module) => ` [${literal(module.id)}, { load: () => import(${literal(`./${sourceEntries.get(module.id)!}`)}), styles: [] }]`).join(",\n")}
|
|
286
|
+
])
|
|
240
287
|
};
|
|
241
|
-
|
|
242
|
-
const routeStyles = new Set(
|
|
243
|
-
chunks.filter((chunk) => chunk.fileName !== "global.mjs").flatMap((chunk) => chunk.styles),
|
|
244
|
-
);
|
|
245
|
-
const styles = [...files.keys()]
|
|
246
|
-
.filter((name) => name.endsWith(".css") && (globalCss.has(name) || !routeStyles.has(name)))
|
|
247
|
-
.sort();
|
|
248
|
-
const registration = `import { mountClient } from "ignotum/internal/bootstrap";
|
|
249
|
-
const registration = {
|
|
250
|
-
applicationName: ${literal(appName)},
|
|
251
|
-
routes: ${JSON.stringify(discovery.routes)},
|
|
252
|
-
layouts: ${JSON.stringify(discovery.layouts)},
|
|
253
|
-
styles: ${JSON.stringify(styles.map((name) => `/_ignotum/${name}`))},
|
|
254
|
-
modules: new Map([
|
|
255
|
-
${discovery.modules
|
|
256
|
-
.map((module) => {
|
|
257
|
-
const name = sourceEntries.get(module.id)!;
|
|
258
|
-
return ` [${literal(module.id)}, { load: () => import(${literal(`./${name}`)}), styles: ${JSON.stringify(stylesFor(name).map((style) => `/_ignotum/${style}`))} }]`;
|
|
259
|
-
})
|
|
260
|
-
.join(",\n")}
|
|
261
|
-
])
|
|
262
|
-
};
|
|
263
|
-
export default function mount(selection) { return mountClient(registration, selection); }
|
|
288
|
+
export default registration;
|
|
264
289
|
`;
|
|
265
|
-
files.set("registration.mjs", utf8Bytes(registration));
|
|
266
290
|
chunks.push({
|
|
267
|
-
fileName: "
|
|
268
|
-
imports: [
|
|
291
|
+
fileName: "entry.mjs",
|
|
292
|
+
imports: [],
|
|
269
293
|
dynamicImports: [...sourceEntries.values()],
|
|
270
294
|
styles,
|
|
271
295
|
isEntry: true,
|
|
272
296
|
});
|
|
273
|
-
|
|
297
|
+
const names = yield* Effect.try({
|
|
298
|
+
try: () =>
|
|
299
|
+
nameClientChunks(
|
|
300
|
+
[
|
|
301
|
+
...output.flatMap((item) =>
|
|
302
|
+
item.type === "chunk" && item.fileName !== "global.mjs"
|
|
303
|
+
? [
|
|
304
|
+
{
|
|
305
|
+
fileName: item.fileName,
|
|
306
|
+
code: new TextDecoder().decode(files.get(item.fileName)!),
|
|
307
|
+
imports: item.imports,
|
|
308
|
+
dynamicImports: item.dynamicImports,
|
|
309
|
+
modules: {},
|
|
310
|
+
sourceMap: maps.has(`${item.fileName}.map`)
|
|
311
|
+
? new TextDecoder().decode(maps.get(`${item.fileName}.map`))
|
|
312
|
+
: undefined,
|
|
313
|
+
},
|
|
314
|
+
]
|
|
315
|
+
: [],
|
|
316
|
+
),
|
|
317
|
+
{
|
|
318
|
+
fileName: "entry.mjs",
|
|
319
|
+
code: registration,
|
|
320
|
+
imports: [],
|
|
321
|
+
dynamicImports: [...sourceEntries.values()],
|
|
322
|
+
modules: {},
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
"entry.mjs",
|
|
326
|
+
),
|
|
327
|
+
catch: (cause) =>
|
|
328
|
+
InvalidBuildOutput.make({ message: `Client dependency naming failed: ${String(cause)}` }),
|
|
329
|
+
});
|
|
330
|
+
for (const [original, named] of names) {
|
|
331
|
+
files.delete(original);
|
|
332
|
+
files.delete(`${original}.map`);
|
|
333
|
+
maps.delete(`${original}.map`);
|
|
334
|
+
files.set(named.fileName, utf8Bytes(named.code));
|
|
335
|
+
if (named.sourceMap !== undefined)
|
|
336
|
+
maps.set(`${named.fileName}.map`, utf8Bytes(named.sourceMap));
|
|
337
|
+
}
|
|
338
|
+
for (const [id, original] of sourceEntries) sourceEntries.set(id, names.get(original)!.fileName);
|
|
339
|
+
const finalChunks = chunks.map((chunk) => ({
|
|
340
|
+
...chunk,
|
|
341
|
+
fileName: names.get(chunk.fileName)!.fileName,
|
|
342
|
+
imports: chunk.imports.map((name) => names.get(name)?.fileName ?? name),
|
|
343
|
+
dynamicImports: chunk.dynamicImports.map((name) => names.get(name)?.fileName ?? name),
|
|
344
|
+
styles: [],
|
|
345
|
+
}));
|
|
346
|
+
const knownChunks = new Map(finalChunks.map((chunk) => [chunk.fileName, chunk]));
|
|
274
347
|
let icon: AppArtifactPath | undefined;
|
|
275
348
|
const iconPath = path.join(appDirectory, "client/icon.svg");
|
|
276
349
|
if (yield* fs.exists(iconPath)) {
|
|
@@ -280,23 +353,16 @@ export default function mount(selection) { return mountClient(registration, sele
|
|
|
280
353
|
message: "The client icon must be a regular file.",
|
|
281
354
|
});
|
|
282
355
|
const bytes = yield* fs.readFile(iconPath);
|
|
283
|
-
icon = AppArtifactPath.make(`
|
|
356
|
+
icon = AppArtifactPath.make(`media/icon-${yield* sha256(bytes)}.svg`);
|
|
284
357
|
files.set(icon, bytes);
|
|
285
358
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const inventory = yield* Effect.forEach(
|
|
289
|
-
[...files].sort(([a], [b]) => a.localeCompare(b)),
|
|
290
|
-
([name, bytes]) => sha256(bytes).pipe(Effect.map((digest) => ({ name, digest }))),
|
|
291
|
-
);
|
|
292
|
-
const graphDigest = yield* sha256(utf8Bytes(JSON.stringify(inventory)));
|
|
293
|
-
const moduleId = (name: string) => AppModuleId.make(`app/client/${graphDigest}/${name}`);
|
|
294
|
-
const modulePath = (name: string) => AppArtifactPath.make(`client/${graphDigest}/${name}`);
|
|
359
|
+
const moduleId = (name: string) => AppModuleId.make(`app/client/${name}`);
|
|
360
|
+
const modulePath = (name: string) => AppArtifactPath.make(name);
|
|
295
361
|
const artifacts: AppArtifact[] = [];
|
|
296
362
|
const bytes = new Map<string, Uint8Array>();
|
|
297
363
|
const modules: AppModule[] = [];
|
|
298
364
|
const imagePaths = [...images.assets.keys()];
|
|
299
|
-
for (const chunk of
|
|
365
|
+
for (const chunk of finalChunks) {
|
|
300
366
|
const source = files.get(chunk.fileName)!;
|
|
301
367
|
const id = moduleId(chunk.fileName);
|
|
302
368
|
const artifact = {
|
|
@@ -317,7 +383,7 @@ export default function mount(selection) { return mountClient(registration, sele
|
|
|
317
383
|
} else {
|
|
318
384
|
const kind = imported.startsWith("ignotum/internal/")
|
|
319
385
|
? "Compiler"
|
|
320
|
-
: imported.startsWith("ignotum/client")
|
|
386
|
+
: imported.startsWith("ignotum/client") || imported === "ignotum/platform"
|
|
321
387
|
? "Runtime"
|
|
322
388
|
: "RenderingPeer";
|
|
323
389
|
imports.push(
|
|
@@ -327,7 +393,7 @@ export default function mount(selection) { return mountClient(registration, sele
|
|
|
327
393
|
}
|
|
328
394
|
const map = maps.get(`${chunk.fileName}.map`);
|
|
329
395
|
const mapPath = modulePath(`${chunk.fileName}.map`);
|
|
330
|
-
const moduleStyles = chunk.fileName === "
|
|
396
|
+
const moduleStyles = chunk.fileName === "entry.js" ? styles : [];
|
|
331
397
|
const assetSources = [
|
|
332
398
|
new TextDecoder().decode(source),
|
|
333
399
|
...moduleStyles.map((style) => new TextDecoder().decode(files.get(style))),
|
|
@@ -338,7 +404,9 @@ export default function mount(selection) { return mountClient(registration, sele
|
|
|
338
404
|
imports,
|
|
339
405
|
styles: moduleStyles.map((name) => AppArtifactPath.make(name)),
|
|
340
406
|
assets: imagePaths
|
|
341
|
-
.filter((name) =>
|
|
407
|
+
.filter((name) =>
|
|
408
|
+
assetSources.some((contents) => contents.includes(`/_ignotum/assets/${name}`)),
|
|
409
|
+
)
|
|
342
410
|
.map((name) => AppArtifactPath.make(name)),
|
|
343
411
|
};
|
|
344
412
|
const module = map === undefined ? baseModule : { ...baseModule, sourceMap: mapPath };
|
|
@@ -358,7 +426,7 @@ export default function mount(selection) { return mountClient(registration, sele
|
|
|
358
426
|
}
|
|
359
427
|
}
|
|
360
428
|
for (const [name, contents] of [...files].sort(([a], [b]) => a.localeCompare(b))) {
|
|
361
|
-
if (knownChunks.has(name) || name === "
|
|
429
|
+
if (knownChunks.has(name) || name === "entry.js" || name.endsWith(".map")) continue;
|
|
362
430
|
const kind = name === icon ? "Icon" : name.endsWith(".css") ? "Style" : "Image";
|
|
363
431
|
const contentType =
|
|
364
432
|
kind === "Icon"
|
|
@@ -369,7 +437,7 @@ export default function mount(selection) { return mountClient(registration, sele
|
|
|
369
437
|
if (contentType === undefined)
|
|
370
438
|
return yield* InvalidBuildOutput.make({ message: `Unknown client asset '${name}'.` });
|
|
371
439
|
const reference = yield* artifactReference(name, contents);
|
|
372
|
-
if (kind === "Style" && name !== `
|
|
440
|
+
if (kind === "Style" && name !== `styles-${reference.sha256}.css`)
|
|
373
441
|
return yield* InvalidBuildOutput.make({
|
|
374
442
|
message: "The bundler changed stylesheet bytes after their content identity was assigned.",
|
|
375
443
|
});
|
|
@@ -387,9 +455,11 @@ export default function mount(selection) { return mountClient(registration, sele
|
|
|
387
455
|
exportName: "default",
|
|
388
456
|
});
|
|
389
457
|
const baseClient = {
|
|
390
|
-
registration: { module: moduleId("
|
|
458
|
+
registration: { module: moduleId("entry.js"), exportName: "default" },
|
|
391
459
|
graph: {
|
|
392
|
-
entries:
|
|
460
|
+
entries: finalChunks
|
|
461
|
+
.filter((chunk) => chunk.isEntry)
|
|
462
|
+
.map((chunk) => moduleId(chunk.fileName)),
|
|
393
463
|
modules,
|
|
394
464
|
},
|
|
395
465
|
routes: discovery.routes.map((route) => ({ ...route, page: reference(route.page.module) })),
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
validateAppModuleBytes,
|
|
20
20
|
validateManagedServerDefinition,
|
|
21
21
|
} from "@ignotum/versions";
|
|
22
|
-
import {
|
|
22
|
+
import { guestEntries } from "@ignotum/platform/registry";
|
|
23
23
|
import { moduleBoundariesPlugin } from "../module-boundaries.js";
|
|
24
24
|
import { InvalidBuildOutput, runViteBuild, sourceMapPathTransform } from "./artifact.js";
|
|
25
25
|
import type { ServerDiscoveryManifest } from "./server.js";
|
|
@@ -36,9 +36,9 @@ export interface ManagedServerBuildResult {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
const literal = Schema.encodeSync(Schema.fromJsonString(Schema.String));
|
|
39
|
-
const externals =
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
const externals = Object.keys(guestEntries).filter(
|
|
40
|
+
(specifier) => specifier !== "ignotum/internal/host",
|
|
41
|
+
);
|
|
42
42
|
|
|
43
43
|
export const compileManagedServer = Effect.fn("Deploy.compileManagedServer")(function* (
|
|
44
44
|
appDirectory: string,
|
package/src/cli/build/managed.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Effect, FileSystem, Path, Schema } from "effect";
|
|
2
2
|
import { UploadDeclaration } from "@ignotum/contracts/app-definition";
|
|
3
|
-
import {
|
|
3
|
+
import { PlatformRequirement } from "@ignotum/contracts/deployment";
|
|
4
4
|
import { encodeCanonical, validateUploadDeclaration, utf8Bytes } from "@ignotum/versions";
|
|
5
5
|
import { generate } from "../codegen.js";
|
|
6
6
|
import { replaceBuildDirectory } from "./output.js";
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
import { buildManagedClient } from "./managed-client.js";
|
|
9
9
|
import { buildManagedServer } from "./server.js";
|
|
10
10
|
|
|
@@ -35,10 +35,9 @@ export const buildManagedVersion = Effect.fn("Deploy.buildManagedVersion")(funct
|
|
|
35
35
|
);
|
|
36
36
|
const client = yield* buildManagedClient(appDirectory, staging, options);
|
|
37
37
|
const server = yield* buildManagedServer(appDirectory, staging, generated.functionModules);
|
|
38
|
-
const requirements = yield* Schema.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
capabilities: managedCompatibility.capabilities,
|
|
38
|
+
const requirements = yield* Schema.decodeEffect(PlatformRequirement)({
|
|
39
|
+
major: 1,
|
|
40
|
+
minimumMinor: 0,
|
|
42
41
|
});
|
|
43
42
|
const declaration = yield* validateUploadDeclaration({
|
|
44
43
|
formatVersion: 1,
|