@tinkerlandai/indiana-iam 0.2.0 → 0.4.0
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 +127 -5
- package/assets/document-controller.js +18 -1
- package/assets/document-runtime.js +189 -1
- package/assets/viewer.js +74 -7
- package/package.json +6 -13
- package/src/analyze.mjs +897 -123
- package/src/anchors.mjs +5 -0
- package/src/build-options.d.ts +6 -1
- package/src/build-runtime.mjs +4 -1
- package/src/build-styles.mjs +49 -6
- package/src/build.mjs +13 -7
- package/src/cli.mjs +10 -4
- package/src/component-registry.mjs +229 -0
- package/src/dependencies.mjs +2 -2
- package/src/dom-payload-browser.mjs +28 -0
- package/src/dom-payload-json.mjs +12 -0
- package/src/dom-payload.mjs +50 -0
- package/src/framework-routes.mjs +44 -89
- package/src/generate.mjs +20 -7
- package/src/graph-sources.mjs +54 -0
- package/src/index.d.ts +13 -1
- package/src/index.mjs +1 -1
- package/src/integrity.mjs +4 -2
- package/src/library-entry.mjs +5 -0
- package/src/lucide-source.mjs +18 -0
- package/src/next-loader.cjs +7 -1
- package/src/next.cjs +5 -0
- package/src/next.mjs +4 -3
- package/src/node-hashes.mjs +13 -9
- package/src/render-index.mjs +6 -4
- package/src/route-lazy.mjs +80 -0
- package/src/route-tree.mjs +15 -3
- package/src/routing.mjs +1 -1
- package/src/schema-definition.mjs +8 -1
- package/src/source-attributes.mjs +24 -0
- package/src/source-charts.mjs +38 -0
- package/src/source-collections.mjs +241 -0
- package/src/source-entry.mjs +50 -0
- package/src/source-environment.mjs +26 -0
- package/src/source-media.mjs +21 -0
- package/src/source-query-evidence.mjs +64 -0
- package/src/source-redirects.mjs +388 -0
- package/src/source-resolution.mjs +268 -0
- package/src/static-primitives.mjs +48 -0
- package/src/static-query.mjs +39 -11
- package/src/tab-scenes.mjs +214 -0
- package/src/tanstack-routes.mjs +143 -0
- package/src/transform.mjs +2 -2
- package/src/ui-preview.mjs +225 -0
- package/src/ui-state-machine.mjs +76 -0
- package/src/ui-structure.mjs +645 -0
- package/src/upload.mjs +28 -6
- package/src/validate.mjs +19 -4
- package/src/vite.d.ts +4 -1
- package/src/vite.mjs +25 -10
package/src/framework-routes.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { tanstackRoutes } from "./tanstack-routes.mjs";
|
|
2
|
+
import { routeLazyComponent } from "./route-lazy.mjs";
|
|
1
3
|
import { pagesRoute } from "./routing.mjs";
|
|
2
4
|
import { routeCompositions } from "./route-composition.mjs";
|
|
3
5
|
import { compositionIdentity, flattenComposition } from "./render-index.mjs";
|
|
@@ -16,11 +18,15 @@ export function externalImport(module, name) {
|
|
|
16
18
|
/** Source-only adapters. Merchant modules, loaders and configuration are never executed. */
|
|
17
19
|
export function frameworkRoutes({
|
|
18
20
|
modules,
|
|
21
|
+
discoveryModules = modules,
|
|
22
|
+
entry,
|
|
19
23
|
resolveModule,
|
|
20
24
|
ts,
|
|
21
25
|
sourceLocation,
|
|
22
26
|
navigationPaths,
|
|
23
27
|
gap,
|
|
28
|
+
resolution,
|
|
29
|
+
inlineComponent,
|
|
24
30
|
}) {
|
|
25
31
|
const files = [...modules.keys()];
|
|
26
32
|
const app = routeCompositions(files, navigationPaths);
|
|
@@ -48,6 +54,7 @@ export function frameworkRoutes({
|
|
|
48
54
|
app.entries.length ||
|
|
49
55
|
(pages.length && (explicitNextPages || (!reactRouting && !reactBootstrap)))
|
|
50
56
|
) {
|
|
57
|
+
if (entry) throw Error("entry is for browser applications; scope Next.js by its app root");
|
|
51
58
|
const entries = [...app.entries];
|
|
52
59
|
const hardRoutes = new Set(
|
|
53
60
|
entries.filter((e) => e.entryMode === "hard").map((e) => e.route),
|
|
@@ -118,87 +125,17 @@ export function frameworkRoutes({
|
|
|
118
125
|
return node;
|
|
119
126
|
};
|
|
120
127
|
const external = externalImport;
|
|
121
|
-
|
|
122
|
-
node = unwrap(node);
|
|
123
|
-
if (!node || seen.size > 16) return undefined;
|
|
124
|
-
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node))
|
|
125
|
-
return node.text;
|
|
126
|
-
if (ts.isIdentifier(node)) {
|
|
127
|
-
if (seen.has(node.text)) return undefined;
|
|
128
|
-
return literal(
|
|
129
|
-
module.bindings.get(node.text),
|
|
130
|
-
module,
|
|
131
|
-
new Set([...seen, node.text]),
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
if (
|
|
135
|
-
ts.isPropertyAccessExpression(node) &&
|
|
136
|
-
ts.isIdentifier(node.expression)
|
|
137
|
-
) {
|
|
138
|
-
const declaration = module.source.statements.find(
|
|
139
|
-
(s) => ts.isEnumDeclaration(s) && s.name.text === node.expression.text,
|
|
140
|
-
);
|
|
141
|
-
const member = declaration?.members.find(
|
|
142
|
-
(m) =>
|
|
143
|
-
m.name.getText(module.source).replace(/^['"]|['"]$/g, "") ===
|
|
144
|
-
node.name.text,
|
|
145
|
-
);
|
|
146
|
-
if (member) return literal(member.initializer, module, seen);
|
|
147
|
-
const record = unwrap(module.bindings.get(node.expression.text));
|
|
148
|
-
if (record && ts.isObjectLiteralExpression(record))
|
|
149
|
-
return literal(
|
|
150
|
-
record.properties.find((p) => p.name?.text === node.name.text)
|
|
151
|
-
?.initializer,
|
|
152
|
-
module,
|
|
153
|
-
seen,
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
return undefined;
|
|
157
|
-
}
|
|
128
|
+
const literal = (node, module, seen = new Set()) => resolution.string(node, module, seen);
|
|
158
129
|
function component(tag, module) {
|
|
159
|
-
const
|
|
160
|
-
if (
|
|
161
|
-
|
|
162
|
-
return target
|
|
163
|
-
? {
|
|
164
|
-
file: target.file,
|
|
165
|
-
symbol:
|
|
166
|
-
imported.name === "default"
|
|
167
|
-
? target.defaultName
|
|
168
|
-
: (target.exports.get(imported.name) ?? imported.name),
|
|
169
|
-
}
|
|
170
|
-
: null;
|
|
171
|
-
}
|
|
172
|
-
const local = unwrap(module.bindings.get(tag));
|
|
173
|
-
// React.lazy(() => import("literal")) is inspected, never invoked.
|
|
174
|
-
if (
|
|
175
|
-
local &&
|
|
176
|
-
ts.isCallExpression(local) &&
|
|
177
|
-
external(module, local.expression.getText(module.source))?.specifier ===
|
|
178
|
-
"react" &&
|
|
179
|
-
external(module, local.expression.getText(module.source))?.name === "lazy"
|
|
180
|
-
) {
|
|
181
|
-
const fn = local.arguments[0],
|
|
182
|
-
body = fn && ts.isArrowFunction(fn) ? unwrap(fn.body) : null;
|
|
183
|
-
if (
|
|
184
|
-
body &&
|
|
185
|
-
ts.isCallExpression(body) &&
|
|
186
|
-
body.expression.kind === ts.SyntaxKind.ImportKeyword &&
|
|
187
|
-
body.arguments.length === 1
|
|
188
|
-
) {
|
|
189
|
-
const specifier = literal(body.arguments[0], module),
|
|
190
|
-
target =
|
|
191
|
-
typeof specifier === "string" &&
|
|
192
|
-
resolveModule(module.file, specifier);
|
|
193
|
-
if (target) return { file: target.file, symbol: target.defaultName };
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
return local ? { file: module.file, symbol: tag } : null;
|
|
130
|
+
const ref = resolution.component(module, tag);
|
|
131
|
+
if (ref?.wrapped) gap("LAZY_WRAPPER_UNOBSERVED", module.file, tag + ": only a statically forwarded successful module is projected; retry/reload behavior is not executed");
|
|
132
|
+
return ref ? { file: ref.module.file, symbol: ref.name } : null;
|
|
197
133
|
}
|
|
198
|
-
function jsxComponent(node, module) {
|
|
134
|
+
function jsxComponent(node, module, route) {
|
|
199
135
|
node = unwrap(node);
|
|
200
136
|
if (!node) return null;
|
|
201
|
-
if (ts.isJsxExpression(node)) return jsxComponent(node.expression, module);
|
|
137
|
+
if (ts.isJsxExpression(node)) return jsxComponent(node.expression, module, route);
|
|
138
|
+
if (inlineComponent && route !== undefined && (ts.isArrowFunction(node) || ts.isFunctionExpression(node) || ts.isMethodDeclaration(node))) return inlineComponent(module, node, route);
|
|
202
139
|
if (ts.isJsxElement(node)) {
|
|
203
140
|
const name = node.openingElement.tagName.getText(module.source),
|
|
204
141
|
imported = external(module, name);
|
|
@@ -209,13 +146,14 @@ export function frameworkRoutes({
|
|
|
209
146
|
const children = node.children.filter(
|
|
210
147
|
(n) => !ts.isJsxText(n) || n.text.trim(),
|
|
211
148
|
);
|
|
212
|
-
return children.length === 1 ? jsxComponent(children[0], module) : null;
|
|
149
|
+
return children.length === 1 ? jsxComponent(children[0], module, route) : null;
|
|
213
150
|
}
|
|
214
151
|
return component(name, module);
|
|
215
152
|
}
|
|
216
153
|
if (ts.isJsxSelfClosingElement(node))
|
|
217
154
|
return component(node.tagName.getText(module.source), module);
|
|
218
155
|
if (ts.isIdentifier(node)) return component(node.text, module);
|
|
156
|
+
if (ts.isPropertyAccessExpression(node)) return component(node.getText(module.source), module);
|
|
219
157
|
return null;
|
|
220
158
|
}
|
|
221
159
|
function join(parent, child) {
|
|
@@ -321,7 +259,8 @@ export function frameworkRoutes({
|
|
|
321
259
|
node = unwrap(node);
|
|
322
260
|
if (
|
|
323
261
|
!ts.isObjectLiteralExpression(node) ||
|
|
324
|
-
node.properties.some((p) => !ts.isPropertyAssignment(p))
|
|
262
|
+
node.properties.some((p) => !(ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p) || ts.isMethodDeclaration(p)) || ts.isComputedPropertyName(p.name)) ||
|
|
263
|
+
new Set(node.properties.map(p => p.name.text)).size !== node.properties.length
|
|
325
264
|
) {
|
|
326
265
|
gap(
|
|
327
266
|
"ROUTES_UNRESOLVED",
|
|
@@ -331,7 +270,7 @@ export function frameworkRoutes({
|
|
|
331
270
|
continue;
|
|
332
271
|
}
|
|
333
272
|
const properties = new Map(
|
|
334
|
-
node.properties.map((p) => [p.name.text, p.initializer]),
|
|
273
|
+
node.properties.map((p) => [p.name.text, ts.isMethodDeclaration(p) ? p : ts.isShorthandPropertyAssignment(p) ? p.name : p.initializer]),
|
|
335
274
|
);
|
|
336
275
|
const rawPath = properties.has("path")
|
|
337
276
|
? literal(properties.get("path"), module)
|
|
@@ -345,11 +284,19 @@ export function frameworkRoutes({
|
|
|
345
284
|
continue;
|
|
346
285
|
}
|
|
347
286
|
const route = join(parent, rawPath);
|
|
348
|
-
if (route === null)
|
|
287
|
+
if (route === null) {
|
|
288
|
+
if (typeof rawPath === "string" && /\?/.test(rawPath)) {
|
|
289
|
+
gap("ROUTE_PATTERN_UNSUPPORTED", module.file, rawPath + ": optional route parameters require an explicit expansion adapter");
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
throw Error("Invalid nested React route");
|
|
293
|
+
}
|
|
349
294
|
const target = jsxComponent(
|
|
350
295
|
properties.get("element") ?? properties.get("Component"),
|
|
351
296
|
module,
|
|
352
|
-
|
|
297
|
+
route,
|
|
298
|
+
) ?? (!properties.has("element") && !properties.has("Component") && properties.has("lazy")
|
|
299
|
+
? routeLazyComponent(properties.get("lazy"), module, { ts, resolution, resolveModule }) : null);
|
|
353
300
|
const index = properties.get("index")?.kind === ts.SyntaxKind.TrueKeyword;
|
|
354
301
|
if (
|
|
355
302
|
properties.has("index") &&
|
|
@@ -455,8 +402,15 @@ export function frameworkRoutes({
|
|
|
455
402
|
target = jsxComponent(
|
|
456
403
|
props.get("element") ?? props.get("Component"),
|
|
457
404
|
module,
|
|
405
|
+
route,
|
|
458
406
|
);
|
|
459
|
-
if (route === null)
|
|
407
|
+
if (route === null) {
|
|
408
|
+
if (typeof raw === "string" && /\?/.test(raw)) {
|
|
409
|
+
gap("ROUTE_PATTERN_UNSUPPORTED", module.file, raw + ": optional route parameters require an explicit expansion adapter");
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
throw Error("Invalid nested React route");
|
|
413
|
+
}
|
|
460
414
|
const nested =
|
|
461
415
|
ts.isJsxElement(child) &&
|
|
462
416
|
child.children.some(
|
|
@@ -500,7 +454,8 @@ export function frameworkRoutes({
|
|
|
500
454
|
add(module, child, route, target, ancestors);
|
|
501
455
|
}
|
|
502
456
|
}
|
|
503
|
-
|
|
457
|
+
const tanstack = tanstackRoutes({ discoveryModules, ts, resolution, gap, component: jsxComponent, add, externalImport });
|
|
458
|
+
for (const module of discoveryModules.values()) {
|
|
504
459
|
if (
|
|
505
460
|
/(^|\/)(__tests__|tests?|stories)(\/|$)|\.(test|spec|stories)\./.test(
|
|
506
461
|
module.file,
|
|
@@ -517,7 +472,7 @@ export function frameworkRoutes({
|
|
|
517
472
|
["react-router", "react-router-dom"].includes(imported?.specifier) &&
|
|
518
473
|
["createBrowserRouter", "createHashRouter"].includes(imported.name)
|
|
519
474
|
) {
|
|
520
|
-
if (++routers > 1)
|
|
475
|
+
if (++routers > 1 || tanstack)
|
|
521
476
|
throw Error(
|
|
522
477
|
"Multiple React route authorities require an explicit adapter",
|
|
523
478
|
);
|
|
@@ -559,7 +514,7 @@ export function frameworkRoutes({
|
|
|
559
514
|
["react-router", "react-router-dom"].includes(imported?.specifier) &&
|
|
560
515
|
imported.name === "Routes"
|
|
561
516
|
) {
|
|
562
|
-
if (++routers > 1)
|
|
517
|
+
if (++routers > 1 || tanstack)
|
|
563
518
|
throw Error(
|
|
564
519
|
"Multiple React route authorities require an explicit adapter",
|
|
565
520
|
);
|
|
@@ -571,9 +526,9 @@ export function frameworkRoutes({
|
|
|
571
526
|
}
|
|
572
527
|
visit(module.source);
|
|
573
528
|
}
|
|
574
|
-
if (!routers) {
|
|
529
|
+
if (!routers && !tanstack) {
|
|
575
530
|
let roots = 0;
|
|
576
|
-
for (const module of
|
|
531
|
+
for (const module of discoveryModules.values()) {
|
|
577
532
|
if (
|
|
578
533
|
/(^|\/)(__tests__|tests?|stories)(\/|$)|\.(test|spec|stories)\./.test(
|
|
579
534
|
module.file,
|
|
@@ -609,7 +564,7 @@ export function frameworkRoutes({
|
|
|
609
564
|
}
|
|
610
565
|
}
|
|
611
566
|
return {
|
|
612
|
-
framework: routers ? "react-router" : "react-spa",
|
|
567
|
+
framework: tanstack ? "tanstack-router" : routers ? "react-router" : "react-spa",
|
|
613
568
|
entries,
|
|
614
569
|
pageFiles: [...new Set(entries.map((e) => e.sourceFile))],
|
|
615
570
|
unsupported: [],
|
package/src/generate.mjs
CHANGED
|
@@ -2,7 +2,8 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { analyze } from "./analyze.mjs";
|
|
4
4
|
import { validate } from "./validate.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { documentDom, packDocumentDom, assertDomBudget } from "./dom-payload.mjs";
|
|
6
|
+
import { sha256, sealDocument } from "./integrity.mjs";
|
|
6
7
|
import { attachBuildStyles, shareDocumentStyles } from "./build-styles.mjs";
|
|
7
8
|
import { releasePath } from "./release-metadata.mjs";
|
|
8
9
|
import { shareDocumentScripts, documentScript } from "./shared-scripts.mjs";
|
|
@@ -11,9 +12,9 @@ import { refreshRenderHashes } from "./render-index.mjs";
|
|
|
11
12
|
export const IAM_MAX_BYTES = 8_388_608;
|
|
12
13
|
|
|
13
14
|
/** Scans the source checkout. The result is the input of {@link generateAppManifest}. */
|
|
14
|
-
export async function analyzeSource({ root, name, repository = "", commit = "", navigationPaths = [], automaticAnchors = false }) {
|
|
15
|
+
export async function analyzeSource({ root, name, repository = "", commit = "", navigationPaths = [], entry, automaticAnchors = false }) {
|
|
15
16
|
root = await fs.realpath(root);
|
|
16
|
-
return analyze({ root, name: name || path.basename(root), repository, commit, navigationPaths, automaticAnchors });
|
|
17
|
+
return analyze({ root, name: name || path.basename(root), repository, commit, navigationPaths, entry, automaticAnchors });
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -27,10 +28,12 @@ export async function generateAppManifest({
|
|
|
27
28
|
repository,
|
|
28
29
|
commit,
|
|
29
30
|
navigationPaths,
|
|
31
|
+
entry,
|
|
30
32
|
automaticAnchors = false,
|
|
31
33
|
releaseId,
|
|
32
34
|
buildCss = [],
|
|
33
35
|
publicAssets = [],
|
|
36
|
+
referencedImages = false,
|
|
34
37
|
optimizePublicImages = false,
|
|
35
38
|
rootFont = "",
|
|
36
39
|
omitSvgResources = false,
|
|
@@ -39,16 +42,22 @@ export async function generateAppManifest({
|
|
|
39
42
|
root = await fs.realpath(root);
|
|
40
43
|
const manifest = analysis
|
|
41
44
|
? structuredClone(analysis)
|
|
42
|
-
: await analyzeSource({ root, name, repository, commit, navigationPaths, automaticAnchors });
|
|
45
|
+
: await analyzeSource({ root, name, repository, commit, navigationPaths, entry, automaticAnchors });
|
|
46
|
+
for (const document of manifest.documents) if (document.domPayload) {
|
|
47
|
+
Object.assign(document, documentDom(document));
|
|
48
|
+
delete document.domPayload;
|
|
49
|
+
sealDocument(document);
|
|
50
|
+
}
|
|
43
51
|
if (releaseId) {
|
|
44
52
|
releasePath(releaseId);
|
|
45
53
|
manifest.release.runtimeBinding = { schemaVersion: 1, releaseId };
|
|
46
54
|
}
|
|
47
|
-
if (buildCss.length || publicAssets.length)
|
|
55
|
+
if (buildCss.length || publicAssets.length || referencedImages)
|
|
48
56
|
await attachBuildStyles(manifest, {
|
|
49
57
|
root,
|
|
50
58
|
files: buildCss,
|
|
51
59
|
publicFiles: publicAssets,
|
|
60
|
+
referencedImages,
|
|
52
61
|
optimizePublicImages,
|
|
53
62
|
rootFont,
|
|
54
63
|
omitSvgResources,
|
|
@@ -57,6 +66,9 @@ export async function generateAppManifest({
|
|
|
57
66
|
else if (rootFont) throw new Error("rootFont requires build CSS");
|
|
58
67
|
shareDocumentStyles(manifest);
|
|
59
68
|
shareDocumentScripts(manifest);
|
|
69
|
+
assertDomBudget(manifest.documents);
|
|
70
|
+
if (Buffer.byteLength(JSON.stringify(manifest)) > 4_194_304)
|
|
71
|
+
for (const document of manifest.documents) if (packDocumentDom(document)) sealDocument(document);
|
|
60
72
|
refreshRenderHashes(manifest);
|
|
61
73
|
const validation = await validate(manifest);
|
|
62
74
|
const serialized = JSON.stringify(manifest) + "\n",
|
|
@@ -83,15 +95,16 @@ export async function writeAppManifestOutput({ root, out, result }) {
|
|
|
83
95
|
await fs.writeFile(path.join(out, "styles", style.styleKey + ".css"), style.css);
|
|
84
96
|
}
|
|
85
97
|
for (const document of manifest.documents) {
|
|
98
|
+
const decoded = documentDom(document);
|
|
86
99
|
const folder = path.join(out, "documents", document.documentKey);
|
|
87
100
|
await fs.mkdir(folder, { recursive: true });
|
|
88
101
|
await Promise.all([
|
|
89
|
-
fs.writeFile(path.join(folder, "page.html"),
|
|
102
|
+
fs.writeFile(path.join(folder, "page.html"), decoded.html),
|
|
90
103
|
fs.writeFile(path.join(folder, "page.css"), document.css),
|
|
91
104
|
fs.writeFile(path.join(folder, "page.js"), documentScript(manifest, document)),
|
|
92
105
|
fs.writeFile(
|
|
93
106
|
path.join(folder, "dom-map.json"),
|
|
94
|
-
JSON.stringify({ rootNodeId: document.rootNodeId, nodes:
|
|
107
|
+
JSON.stringify({ rootNodeId: document.rootNodeId, nodes: decoded.nodes, hashes: document.hashes }, null, 2) + "\n",
|
|
95
108
|
),
|
|
96
109
|
]);
|
|
97
110
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { canonical } from "./integrity.mjs";
|
|
2
|
+
/** Lossless references to repeated source locations; semantic identities and hashes stay unchanged. */
|
|
3
|
+
export function internGraphSources(graph) {
|
|
4
|
+
const rows = [
|
|
5
|
+
"variables",
|
|
6
|
+
"bindings",
|
|
7
|
+
"transitions",
|
|
8
|
+
"states",
|
|
9
|
+
"navigation",
|
|
10
|
+
"uncovered",
|
|
11
|
+
].flatMap((key) => graph[key]),
|
|
12
|
+
counts = new Map();
|
|
13
|
+
for (const row of rows) {
|
|
14
|
+
const key = canonical(row.source);
|
|
15
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
16
|
+
}
|
|
17
|
+
const pool = [],
|
|
18
|
+
indices = new Map();
|
|
19
|
+
for (const row of rows) {
|
|
20
|
+
const key = canonical(row.source);
|
|
21
|
+
if (counts.get(key) < 2) continue;
|
|
22
|
+
let index = indices.get(key);
|
|
23
|
+
if (index === undefined) {
|
|
24
|
+
index = pool.length;
|
|
25
|
+
indices.set(key, index);
|
|
26
|
+
pool.push(row.source);
|
|
27
|
+
}
|
|
28
|
+
row.source = index;
|
|
29
|
+
}
|
|
30
|
+
if (pool.length) graph.sourcePool = pool;
|
|
31
|
+
const scopes = [],
|
|
32
|
+
scopeIndices = new Map(),
|
|
33
|
+
scopeCounts = new Map();
|
|
34
|
+
for (const v of graph.variables)
|
|
35
|
+
scopeCounts.set(v.scopeKey, (scopeCounts.get(v.scopeKey) ?? 0) + 1);
|
|
36
|
+
for (const v of graph.variables)
|
|
37
|
+
if (scopeCounts.get(v.scopeKey) > 1) {
|
|
38
|
+
let i = scopeIndices.get(v.scopeKey);
|
|
39
|
+
if (i === undefined) {
|
|
40
|
+
i = scopes.length;
|
|
41
|
+
scopeIndices.set(v.scopeKey, i);
|
|
42
|
+
scopes.push(v.scopeKey);
|
|
43
|
+
}
|
|
44
|
+
v.scopeKey = i;
|
|
45
|
+
}
|
|
46
|
+
if (scopes.length) graph.scopePool = scopes;
|
|
47
|
+
}
|
|
48
|
+
export function graphSource(graph, row) {
|
|
49
|
+
return typeof row?.source === "number"
|
|
50
|
+
? Number.isInteger(row.source)
|
|
51
|
+
? graph.sourcePool?.[row.source]
|
|
52
|
+
: undefined
|
|
53
|
+
: row?.source;
|
|
54
|
+
}
|
package/src/index.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export type ValidationCounts = Record<string, unknown>;
|
|
|
20
20
|
|
|
21
21
|
export type GenerateOptions = {
|
|
22
22
|
root: string;
|
|
23
|
+
/** Browser entry relative to root. Defaults to the single module script in index.html. */
|
|
24
|
+
entry?: string;
|
|
23
25
|
automaticAnchors?: boolean;
|
|
24
26
|
/** A previous {@link analyzeSource} result; scanned again when omitted. */
|
|
25
27
|
analysis?: AppManifest;
|
|
@@ -30,6 +32,8 @@ export type GenerateOptions = {
|
|
|
30
32
|
releaseId?: string;
|
|
31
33
|
buildCss?: string[];
|
|
32
34
|
publicAssets?: string[];
|
|
35
|
+
/** Package only projected local raster references; large images use the bounded build preview profile. */
|
|
36
|
+
referencedImages?: boolean;
|
|
33
37
|
optimizePublicImages?: boolean;
|
|
34
38
|
rootFont?: string;
|
|
35
39
|
omitSvgResources?: boolean;
|
|
@@ -47,6 +51,8 @@ export type GeneratedAppManifest = {
|
|
|
47
51
|
export declare const IAM_MAX_BYTES: 8388608;
|
|
48
52
|
export declare function analyzeSource(options: {
|
|
49
53
|
root: string;
|
|
54
|
+
/** Browser entry relative to root. Defaults to the single module script in index.html. */
|
|
55
|
+
entry?: string;
|
|
50
56
|
automaticAnchors?: boolean;
|
|
51
57
|
name?: string;
|
|
52
58
|
repository?: string;
|
|
@@ -82,6 +88,8 @@ export type BuildLogger = Pick<Console, "log" | "warn">;
|
|
|
82
88
|
export type BuildReleaseOptions = {
|
|
83
89
|
strict?: boolean;
|
|
84
90
|
logger?: BuildLogger;
|
|
91
|
+
/** Browser entry relative to root. Defaults to the single module script in index.html. */
|
|
92
|
+
entry?: string;
|
|
85
93
|
automaticAnchors?: boolean;
|
|
86
94
|
};
|
|
87
95
|
export declare function prepareBuildRelease(
|
|
@@ -114,10 +122,13 @@ export declare function finishBuildRelease(
|
|
|
114
122
|
} | null>;
|
|
115
123
|
|
|
116
124
|
export declare const DEFAULT_BASE_URL: "https://tinker.cash";
|
|
125
|
+
export declare const DEFAULT_CLIENT: "web";
|
|
117
126
|
export declare function uploadAppManifest(options: {
|
|
118
127
|
serialized: string;
|
|
119
128
|
digest: string;
|
|
120
129
|
label: string;
|
|
130
|
+
/** Defaults to `web`; each client of an App environment has its own current version. */
|
|
131
|
+
client?: string;
|
|
121
132
|
apiKey: string;
|
|
122
133
|
baseUrl?: string;
|
|
123
134
|
fetch?: typeof globalThis.fetch;
|
|
@@ -133,9 +144,10 @@ export declare function uploadSettings(
|
|
|
133
144
|
apiKey?: string;
|
|
134
145
|
baseUrl?: string;
|
|
135
146
|
upload?: "production" | "always" | false | true;
|
|
147
|
+
client?: string;
|
|
136
148
|
},
|
|
137
149
|
env?: Record<string, string | undefined>,
|
|
138
|
-
): { enabled: boolean; apiKey: string; baseUrl: string; missingKey: boolean };
|
|
150
|
+
): { enabled: boolean; apiKey: string; baseUrl: string; client: string; missingKey: boolean };
|
|
139
151
|
export declare function buildLabel(
|
|
140
152
|
root: string,
|
|
141
153
|
env?: Record<string, string | undefined>,
|
package/src/index.mjs
CHANGED
|
@@ -6,4 +6,4 @@ export { nextStaticMounts } from "./build-styles.mjs";
|
|
|
6
6
|
export { transformSourceForIam } from "./transform.mjs";
|
|
7
7
|
export { rebindIamSource } from "./rebind.mjs";
|
|
8
8
|
export { planAnchors, applyAnchors } from "./instrument.mjs";
|
|
9
|
-
export { uploadAppManifest, uploadSettings, buildLabel, DEFAULT_BASE_URL } from "./upload.mjs";
|
|
9
|
+
export { uploadAppManifest, uploadSettings, buildLabel, DEFAULT_BASE_URL, DEFAULT_CLIENT } from "./upload.mjs";
|
package/src/integrity.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { documentDom } from "./dom-payload.mjs";
|
|
2
3
|
|
|
3
4
|
export const sha256 = (bytes) =>
|
|
4
5
|
createHash("sha256").update(bytes).digest("hex");
|
|
@@ -70,13 +71,14 @@ export function sealNodes(nodes, rootId) {
|
|
|
70
71
|
}
|
|
71
72
|
export function sealDocument(document) {
|
|
72
73
|
const { hashes: _hashes, ...body } = document;
|
|
74
|
+
const dom = documentDom(document);
|
|
73
75
|
document.hashes = {
|
|
74
|
-
html: sha256(
|
|
76
|
+
html: sha256(dom.html),
|
|
75
77
|
css: sha256(document.css),
|
|
76
78
|
js: sha256(document.js),
|
|
77
79
|
dom: hashObject("iam/dom-map/v1", {
|
|
78
80
|
rootNodeId: document.rootNodeId,
|
|
79
|
-
nodes:
|
|
81
|
+
nodes: dom.nodes,
|
|
80
82
|
...(document.sourceMappingsHash?{sourceMappingsHash:document.sourceMappingsHash}:{}),
|
|
81
83
|
}),
|
|
82
84
|
document: hashObject("iam/document/v1", body),
|
package/src/library-entry.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import{renderGuardFor}from'./render-index.mjs';
|
|
|
8
8
|
import { documentScript } from "./shared-scripts.mjs";
|
|
9
9
|
import { documentNodes } from './node-hashes.mjs';
|
|
10
10
|
export { documentNodes as iamDocumentNodes } from './node-hashes.mjs';
|
|
11
|
+
export { documentDom as iamDocumentDom } from './dom-payload.mjs';
|
|
11
12
|
export { rebindIamSource } from './rebind.mjs';
|
|
12
13
|
export const parseIamJson = parseManifest;
|
|
13
14
|
export async function isReviewedIamScript(value) {
|
|
@@ -81,3 +82,7 @@ export async function renderIamViewer(manifest, context) {
|
|
|
81
82
|
.replace('<link rel="stylesheet" href="viewer.css" />', base ? '<link rel="stylesheet" href="'+asset(assets.css,'.css')+'" integrity="'+integrity(assets.css)+'" crossorigin="anonymous">' : '<style>' + assets.css.replaceAll(/<\/style/gi, "<\\/style") + '</style>')
|
|
82
83
|
.replace('<script src="viewer.js"></script>', '<script id="iam-context" type="application/json">' + json(context) + '</script>' + (base ? '<script src="'+asset(assets.js,'.js')+'" integrity="'+integrity(assets.js)+'" crossorigin="anonymous"></script>' : '<script>' + script + '</script>'));
|
|
83
84
|
}
|
|
85
|
+
|
|
86
|
+
export { previewUiDocument as previewIamUiDocument } from './ui-preview.mjs';
|
|
87
|
+
|
|
88
|
+
export { tabUiScenes as iamTabUiScenes } from './tab-scenes.mjs';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
// Pinned, inert geometry data. Merchant packages and React components are never imported.
|
|
3
|
+
const require = createRequire(import.meta.url),
|
|
4
|
+
catalog = require("lucide-static/icon-nodes.json");
|
|
5
|
+
export const LUCIDE_SOURCE_VERSION = "1.34.0";
|
|
6
|
+
export const LUCIDE_SOURCE = Symbol("source-lucide");
|
|
7
|
+
export function lucideSource(name) {
|
|
8
|
+
const key = name
|
|
9
|
+
.replace(/Icon$/, "")
|
|
10
|
+
.replace(/([A-Z])([A-Z][a-z])/g, "$1-$2")
|
|
11
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
|
|
12
|
+
.replace(/([a-zA-Z])(\d)/g, "$1-$2")
|
|
13
|
+
.toLowerCase();
|
|
14
|
+
return Object.hasOwn(catalog, key) ? { [LUCIDE_SOURCE]: key } : null;
|
|
15
|
+
}
|
|
16
|
+
export function lucideGeometry(value) {
|
|
17
|
+
return catalog[value?.[LUCIDE_SOURCE]] ?? null;
|
|
18
|
+
}
|
package/src/next-loader.cjs
CHANGED
|
@@ -7,10 +7,16 @@ module.exports = function indianaIamLoader(source, inputMap) {
|
|
|
7
7
|
const { root } = this.getOptions();
|
|
8
8
|
Promise.all([
|
|
9
9
|
import("./transform.mjs"),
|
|
10
|
-
fs.realpath(this.resourcePath)
|
|
10
|
+
fs.realpath(this.resourcePath).catch((error) => {
|
|
11
|
+
// Turbopack can retype loader output (e.g. icon.svg -> icon.svg.js).
|
|
12
|
+
// Such virtual modules have no original source binding to instrument.
|
|
13
|
+
if (error.code === "ENOENT" || error.code === "ENOTDIR") return null;
|
|
14
|
+
throw error;
|
|
15
|
+
}),
|
|
11
16
|
fs.realpath(root),
|
|
12
17
|
])
|
|
13
18
|
.then(([{ transformSourceForIam }, resource, directory]) => {
|
|
19
|
+
if (!resource) return done(null, source, inputMap);
|
|
14
20
|
const file = path.relative(directory, resource).split(path.sep).join("/");
|
|
15
21
|
if (file.startsWith("../") || path.isAbsolute(file))
|
|
16
22
|
return done(null, source, inputMap);
|
package/src/next.cjs
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Next transpiles next.config.ts to CommonJS before loading its plugins. */
|
|
2
|
+
exports.withIndianaIam = (nextConfig = {}, options = {}) => async (phase, context) => {
|
|
3
|
+
const { withIndianaIam } = await import("./next.mjs");
|
|
4
|
+
return withIndianaIam(nextConfig, options)(phase, context);
|
|
5
|
+
};
|
package/src/next.mjs
CHANGED
|
@@ -17,12 +17,12 @@ const sourceLoader = fileURLToPath(new URL("./next-loader.cjs", import.meta.url)
|
|
|
17
17
|
* for production deployments). Development servers and other phases get the config unchanged.
|
|
18
18
|
*/
|
|
19
19
|
export function withIndianaIam(nextConfig = {}, options = {}) {
|
|
20
|
-
const { enabled = true, root, name, automaticAnchors = true, strict = false, rootFont, omitSvgResources, logger = console, apiKey, baseUrl, upload, fetch } = options;
|
|
20
|
+
const { enabled = true, root, name, entry, automaticAnchors = true, strict = false, rootFont, omitSvgResources, logger = console, apiKey, baseUrl, upload, client, fetch } = options;
|
|
21
21
|
return async (phase, context) => {
|
|
22
22
|
const config = typeof nextConfig === "function" ? await nextConfig(phase, context) : nextConfig;
|
|
23
23
|
if (phase !== PRODUCTION_BUILD || !enabled) return config;
|
|
24
24
|
const directory = path.resolve(root ?? process.cwd());
|
|
25
|
-
const prepared = await prepareBuildRelease({ root: directory, name, automaticAnchors, strict, logger });
|
|
25
|
+
const prepared = await prepareBuildRelease({ root: directory, name, entry, automaticAnchors, strict, logger });
|
|
26
26
|
if (!prepared) return config;
|
|
27
27
|
const { releaseId } = prepared;
|
|
28
28
|
const previous = config.compiler?.runAfterProductionCompile;
|
|
@@ -56,13 +56,14 @@ export function withIndianaIam(nextConfig = {}, options = {}) {
|
|
|
56
56
|
root: build.projectDir,
|
|
57
57
|
releaseId,
|
|
58
58
|
name,
|
|
59
|
+
entry,
|
|
59
60
|
automaticAnchors,
|
|
60
61
|
cssDirectory: distDir + "/static",
|
|
61
62
|
mounts: nextStaticMounts(distDir, config.basePath ?? ""),
|
|
62
63
|
publicDirectory: "public",
|
|
63
64
|
rootFont,
|
|
64
65
|
omitSvgResources,
|
|
65
|
-
upload: { apiKey, baseUrl, upload, fetch },
|
|
66
|
+
upload: { apiKey, baseUrl, upload, client, fetch },
|
|
66
67
|
strict,
|
|
67
68
|
logger,
|
|
68
69
|
});
|
package/src/node-hashes.mjs
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { documentDom } from "./dom-payload.mjs";
|
|
2
|
+
import { canonical, identity, sealNodes, sealDocument } from './integrity.mjs';
|
|
2
3
|
import { dependency } from './dependencies.mjs';
|
|
3
4
|
import { sourceMappingsHash } from './compact-dom.mjs';
|
|
4
5
|
|
|
5
6
|
/** HTML already owns the DOM values and ordering. Store only the source map
|
|
6
7
|
* in preorder, rather than repeating attributes, text and every tree edge. */
|
|
7
|
-
export function expandDomNodes(document, body) {
|
|
8
|
+
export function expandDomNodes(document, body, deriveTextId) {
|
|
8
9
|
if(document.nodeEncoding!=='html-preorder-v1'||document.nodeHashMode!=='derived-v1'||!document.sourceMappingsHash||document.nodes.length>12000)throw Error('Invalid HTML-derived DOM mode');
|
|
9
10
|
let index=0;const result=[];
|
|
10
|
-
function visit(node,parentId,depth){
|
|
11
|
+
function visit(node,parentId,depth,childIndex=0){
|
|
11
12
|
if(depth>128||![1,3].includes(node.nodeType))throw Error('Invalid HTML-derived DOM structure');
|
|
12
13
|
const ref=document.nodes[index++];
|
|
13
14
|
if(!ref||Object.keys(ref).some(key=>!['nodeId','elementKey','source','synthetic'].includes(key)))throw Error('Mixed HTML-derived DOM values');
|
|
14
|
-
const element=node.nodeType===1,nodeId=element?node.getAttribute('data-iam-node'):ref.nodeId;
|
|
15
|
+
const element=node.nodeType===1,nodeId=element?node.getAttribute('data-iam-node'):ref.nodeId??deriveTextId?.(document.pageKey,parentId,childIndex);
|
|
15
16
|
if(!/^node_[a-f0-9]{64}$/.test(nodeId??'')||element&&Object.hasOwn(ref,'nodeId')||Object.hasOwn(ref,'synthetic')&&ref.synthetic!==true)throw Error('HTML-derived DOM identity differs');
|
|
16
17
|
const full={...ref,nodeId,synthetic:ref.synthetic??false,parentId,kind:element?'element':'text',tag:element?node.tagName.toLowerCase():null,text:element?null:node.data,attributes:element?Object.fromEntries([...node.attributes].filter(a=>!a.name.startsWith('data-iam-')).map(a=>[a.name,a.value])):{},children:[]};
|
|
17
|
-
result.push(full);for(const child
|
|
18
|
+
result.push(full);for(const [i,child]of [...node.childNodes].entries())full.children.push(visit(child,nodeId,depth+1,i));return nodeId;
|
|
18
19
|
}
|
|
19
20
|
if(body.childNodes.length!==1||visit(body.firstChild,null,0)!==document.rootNodeId||index!==document.nodes.length)throw Error('HTML-derived DOM coverage differs');
|
|
20
21
|
return result;
|
|
21
22
|
}
|
|
22
23
|
function domNodes(document){
|
|
23
|
-
const {
|
|
24
|
-
|
|
24
|
+
const decoded = {...document, ...documentDom(document)};
|
|
25
|
+
const {JSDOM}=dependency('jsdom').value,dom=new JSDOM('<!doctype html><html><body>'+decoded.html+'</body></html>');
|
|
26
|
+
try{return expandDomNodes(decoded,dom.window.document.body,(page,parentId,index)=>identity('node',{page,parentId,index,kind:3}));}finally{dom.window.close();}
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
/** Node hashes are pure derived values. IDs, source mappings and algorithms never change. */
|
|
@@ -32,11 +34,13 @@ export function documentNodes(document) {
|
|
|
32
34
|
}
|
|
33
35
|
/** Serialize the producer's in-memory DOM into the single admitted wire representation. */
|
|
34
36
|
export function storeDocumentNodes(document, nodes, elements) {
|
|
37
|
+
delete document.domPayload;
|
|
35
38
|
document.sourceMappingsHash = sourceMappingsHash(nodes, elements);
|
|
36
39
|
document.nodeEncoding = 'html-preorder-v1';
|
|
37
40
|
document.nodeHashMode = 'derived-v1';
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
const byId=new Map(nodes.map(node=>[node.nodeId,node]));
|
|
42
|
+
document.nodes = nodes.map(({nodeId,parentId,kind,elementKey,synthetic,source}) => ({
|
|
43
|
+
...(kind === 'text' && nodeId!==identity('node',{page:document.pageKey,parentId,index:byId.get(parentId)?.children.indexOf(nodeId)??0,kind:3}) ? {nodeId} : {}),
|
|
40
44
|
elementKey,
|
|
41
45
|
...(synthetic ? {synthetic:true} : {}),
|
|
42
46
|
...(source && canonical(source) !== canonical(elements.get(elementKey)?.source) ? {source} : {}),
|