@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.
Files changed (55) hide show
  1. package/README.md +127 -5
  2. package/assets/document-controller.js +18 -1
  3. package/assets/document-runtime.js +189 -1
  4. package/assets/viewer.js +74 -7
  5. package/package.json +6 -13
  6. package/src/analyze.mjs +897 -123
  7. package/src/anchors.mjs +5 -0
  8. package/src/build-options.d.ts +6 -1
  9. package/src/build-runtime.mjs +4 -1
  10. package/src/build-styles.mjs +49 -6
  11. package/src/build.mjs +13 -7
  12. package/src/cli.mjs +10 -4
  13. package/src/component-registry.mjs +229 -0
  14. package/src/dependencies.mjs +2 -2
  15. package/src/dom-payload-browser.mjs +28 -0
  16. package/src/dom-payload-json.mjs +12 -0
  17. package/src/dom-payload.mjs +50 -0
  18. package/src/framework-routes.mjs +44 -89
  19. package/src/generate.mjs +20 -7
  20. package/src/graph-sources.mjs +54 -0
  21. package/src/index.d.ts +13 -1
  22. package/src/index.mjs +1 -1
  23. package/src/integrity.mjs +4 -2
  24. package/src/library-entry.mjs +5 -0
  25. package/src/lucide-source.mjs +18 -0
  26. package/src/next-loader.cjs +7 -1
  27. package/src/next.cjs +5 -0
  28. package/src/next.mjs +4 -3
  29. package/src/node-hashes.mjs +13 -9
  30. package/src/render-index.mjs +6 -4
  31. package/src/route-lazy.mjs +80 -0
  32. package/src/route-tree.mjs +15 -3
  33. package/src/routing.mjs +1 -1
  34. package/src/schema-definition.mjs +8 -1
  35. package/src/source-attributes.mjs +24 -0
  36. package/src/source-charts.mjs +38 -0
  37. package/src/source-collections.mjs +241 -0
  38. package/src/source-entry.mjs +50 -0
  39. package/src/source-environment.mjs +26 -0
  40. package/src/source-media.mjs +21 -0
  41. package/src/source-query-evidence.mjs +64 -0
  42. package/src/source-redirects.mjs +388 -0
  43. package/src/source-resolution.mjs +268 -0
  44. package/src/static-primitives.mjs +48 -0
  45. package/src/static-query.mjs +39 -11
  46. package/src/tab-scenes.mjs +214 -0
  47. package/src/tanstack-routes.mjs +143 -0
  48. package/src/transform.mjs +2 -2
  49. package/src/ui-preview.mjs +225 -0
  50. package/src/ui-state-machine.mjs +76 -0
  51. package/src/ui-structure.mjs +645 -0
  52. package/src/upload.mjs +28 -6
  53. package/src/validate.mjs +19 -4
  54. package/src/vite.d.ts +4 -1
  55. package/src/vite.mjs +25 -10
@@ -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
- function literal(node, module, seen = new Set()) {
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 imported = module.imports.get(tag);
160
- if (imported) {
161
- const target = resolveModule(module.file, imported.specifier);
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) throw Error("Invalid nested React route");
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) throw Error("Invalid nested React route");
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
- for (const module of modules.values()) {
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 modules.values()) {
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 { sha256 } from "./integrity.mjs";
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"), document.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: document.nodes, hashes: document.hashes }, null, 2) + "\n",
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(document.html),
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: document.nodes,
81
+ nodes: dom.nodes,
80
82
  ...(document.sourceMappingsHash?{sourceMappingsHash:document.sourceMappingsHash}:{}),
81
83
  }),
82
84
  document: hashObject("iam/document/v1", body),
@@ -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
+ }
@@ -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
  });
@@ -1,27 +1,29 @@
1
- import { canonical, sealNodes, sealDocument } from './integrity.mjs';
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 of node.childNodes)full.children.push(visit(child,nodeId,depth+1));return nodeId;
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 {JSDOM}=dependency('jsdom').value,dom=new JSDOM('<!doctype html><html><body>'+document.html+'</body></html>');
24
- try{return expandDomNodes(document,dom.window.document.body);}finally{dom.window.close();}
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
- document.nodes = nodes.map(({nodeId,kind,elementKey,synthetic,source}) => ({
39
- ...(kind === 'text' ? {nodeId} : {}),
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} : {}),