@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
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/** Bounded AST references only. No merchant module, getter, factory or callback is invoked. */
|
|
2
|
+
export function sourceResolution({ ts, resolveModule, resolveBinding }) {
|
|
3
|
+
const unwrap = (node) => {
|
|
4
|
+
while (
|
|
5
|
+
node &&
|
|
6
|
+
(ts.isParenthesizedExpression(node) ||
|
|
7
|
+
ts.isAsExpression(node) ||
|
|
8
|
+
ts.isTypeAssertionExpression(node) ||
|
|
9
|
+
ts.isSatisfiesExpression(node) ||
|
|
10
|
+
ts.isNonNullExpression(node) ||
|
|
11
|
+
ts.isAwaitExpression(node))
|
|
12
|
+
)
|
|
13
|
+
node = node.expression;
|
|
14
|
+
return node;
|
|
15
|
+
};
|
|
16
|
+
function reference(node, module, seen = new Set()) {
|
|
17
|
+
node = unwrap(node);
|
|
18
|
+
if (!node || seen.size >= 32) return null;
|
|
19
|
+
const key = module.file + ":" + node.pos + ":" + node.end;
|
|
20
|
+
if (seen.has(key)) return null;
|
|
21
|
+
seen = new Set([...seen, key]);
|
|
22
|
+
if (ts.isIdentifier(node)) {
|
|
23
|
+
const ref = resolveBinding(module, node.text);
|
|
24
|
+
return ref && reference(ref.node, ref.module, seen);
|
|
25
|
+
}
|
|
26
|
+
if (
|
|
27
|
+
ts.isPropertyAccessExpression(node) ||
|
|
28
|
+
ts.isElementAccessExpression(node)
|
|
29
|
+
) {
|
|
30
|
+
if (node.questionDotToken) return null;
|
|
31
|
+
const name = ts.isPropertyAccessExpression(node)
|
|
32
|
+
? node.name.text
|
|
33
|
+
: string(node.argumentExpression, module, seen);
|
|
34
|
+
if (typeof name !== "string") return null;
|
|
35
|
+
if (ts.isIdentifier(node.expression)) {
|
|
36
|
+
const imported = module.imports.get(node.expression.text);
|
|
37
|
+
if (imported?.name === "*") {
|
|
38
|
+
const target = resolveModule(module.file, imported.specifier);
|
|
39
|
+
const ref =
|
|
40
|
+
target && resolveBinding(target, target.exports.get(name) ?? name);
|
|
41
|
+
return ref && reference(ref.node, ref.module, seen);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const record = reference(node.expression, module, seen);
|
|
45
|
+
if (!record) return null;
|
|
46
|
+
if (ts.isEnumDeclaration(record.node)) {
|
|
47
|
+
const member = record.node.members.find(
|
|
48
|
+
(member) => member.name.text === name,
|
|
49
|
+
);
|
|
50
|
+
return member && reference(member.initializer, record.module, seen);
|
|
51
|
+
}
|
|
52
|
+
if (
|
|
53
|
+
!ts.isObjectLiteralExpression(record.node) ||
|
|
54
|
+
record.node.properties.some(
|
|
55
|
+
(property) =>
|
|
56
|
+
!ts.isPropertyAssignment(property) ||
|
|
57
|
+
(property.name && ts.isComputedPropertyName(property.name)),
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
return null;
|
|
61
|
+
const fields = record.node.properties.filter(
|
|
62
|
+
(property) => property.name.text === name,
|
|
63
|
+
);
|
|
64
|
+
return fields.length === 1
|
|
65
|
+
? reference(fields[0].initializer, record.module, seen)
|
|
66
|
+
: null;
|
|
67
|
+
}
|
|
68
|
+
return { node, module };
|
|
69
|
+
}
|
|
70
|
+
function string(node, module, seen = new Set()) {
|
|
71
|
+
const ref = reference(node, module, seen);
|
|
72
|
+
if (!ref) return undefined;
|
|
73
|
+
node = ref.node;
|
|
74
|
+
module = ref.module;
|
|
75
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node))
|
|
76
|
+
return node.text;
|
|
77
|
+
const key = module.file + ":string:" + node.pos;
|
|
78
|
+
if (seen.has(key) || seen.size >= 32) return undefined;
|
|
79
|
+
seen = new Set([...seen, key]);
|
|
80
|
+
if (ts.isTemplateExpression(node)) {
|
|
81
|
+
let result = node.head.text;
|
|
82
|
+
for (const span of node.templateSpans) {
|
|
83
|
+
const value = string(span.expression, module, seen);
|
|
84
|
+
if (value === undefined) return undefined;
|
|
85
|
+
result += value + span.literal.text;
|
|
86
|
+
if (result.length > 2048) return undefined;
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
if (
|
|
91
|
+
ts.isBinaryExpression(node) &&
|
|
92
|
+
node.operatorToken.kind === ts.SyntaxKind.PlusToken
|
|
93
|
+
) {
|
|
94
|
+
const left = string(node.left, module, seen),
|
|
95
|
+
right = string(node.right, module, seen);
|
|
96
|
+
return left !== undefined &&
|
|
97
|
+
right !== undefined &&
|
|
98
|
+
left.length + right.length <= 2048
|
|
99
|
+
? left + right
|
|
100
|
+
: undefined;
|
|
101
|
+
}
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
function external(module, node) {
|
|
105
|
+
node = unwrap(node);
|
|
106
|
+
if (node && ts.isIdentifier(node)) return module.imports.get(node.text);
|
|
107
|
+
if (
|
|
108
|
+
node &&
|
|
109
|
+
ts.isPropertyAccessExpression(node) &&
|
|
110
|
+
ts.isIdentifier(node.expression)
|
|
111
|
+
) {
|
|
112
|
+
const owner = module.imports.get(node.expression.text);
|
|
113
|
+
if (owner && ["*", "default"].includes(owner.name))
|
|
114
|
+
return { specifier: owner.specifier, name: node.name.text };
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
function functionNode(node) {
|
|
119
|
+
node = unwrap(node);
|
|
120
|
+
return node &&
|
|
121
|
+
(ts.isFunctionDeclaration(node) ||
|
|
122
|
+
ts.isFunctionExpression(node) ||
|
|
123
|
+
ts.isArrowFunction(node))
|
|
124
|
+
? node
|
|
125
|
+
: null;
|
|
126
|
+
}
|
|
127
|
+
function returned(fn) {
|
|
128
|
+
if (!fn?.body) return null;
|
|
129
|
+
if (!ts.isBlock(fn.body)) return fn.body;
|
|
130
|
+
return fn.body.statements.length === 1 &&
|
|
131
|
+
ts.isReturnStatement(fn.body.statements[0])
|
|
132
|
+
? fn.body.statements[0].expression
|
|
133
|
+
: null;
|
|
134
|
+
}
|
|
135
|
+
// Prove only the successful module return of a forwarding function. Catch
|
|
136
|
+
// side effects are opaque and must rethrow; finally/alternate module returns refuse.
|
|
137
|
+
function forwards(node, module, parameters, depth = 0) {
|
|
138
|
+
node = unwrap(node);
|
|
139
|
+
if (!node || depth > 8 || !ts.isCallExpression(node)) return false;
|
|
140
|
+
if (
|
|
141
|
+
ts.isIdentifier(node.expression) &&
|
|
142
|
+
parameters.has(node.expression.text)
|
|
143
|
+
)
|
|
144
|
+
return node.arguments.length === 0;
|
|
145
|
+
if (!ts.isIdentifier(node.expression)) return false;
|
|
146
|
+
const ref = resolveBinding(module, node.expression.text),
|
|
147
|
+
fn = ref && functionNode(ref.node);
|
|
148
|
+
if (
|
|
149
|
+
!fn ||
|
|
150
|
+
fn.asteriskToken ||
|
|
151
|
+
fn.parameters.length !== 1 ||
|
|
152
|
+
fn.parameters[0].dotDotDotToken ||
|
|
153
|
+
!ts.isIdentifier(fn.parameters[0].name) ||
|
|
154
|
+
node.arguments.length !== 1 ||
|
|
155
|
+
!ts.isIdentifier(node.arguments[0]) ||
|
|
156
|
+
!parameters.has(node.arguments[0].text)
|
|
157
|
+
)
|
|
158
|
+
return false;
|
|
159
|
+
const nested = new Set([fn.parameters[0].name.text]);
|
|
160
|
+
let result = returned(fn);
|
|
161
|
+
if (
|
|
162
|
+
!result &&
|
|
163
|
+
fn.body &&
|
|
164
|
+
ts.isBlock(fn.body) &&
|
|
165
|
+
fn.body.statements.length === 1
|
|
166
|
+
) {
|
|
167
|
+
const statement = fn.body.statements[0];
|
|
168
|
+
if (
|
|
169
|
+
!ts.isTryStatement(statement) ||
|
|
170
|
+
statement.finallyBlock ||
|
|
171
|
+
!statement.catchClause ||
|
|
172
|
+
!ts.isThrowStatement(statement.catchClause.block.statements.at(-1))
|
|
173
|
+
)
|
|
174
|
+
return false;
|
|
175
|
+
let overrides = false;
|
|
176
|
+
const visit = (item) => {
|
|
177
|
+
if (ts.isReturnStatement(item)) overrides = true;
|
|
178
|
+
ts.forEachChild(item, visit);
|
|
179
|
+
};
|
|
180
|
+
visit(statement.catchClause);
|
|
181
|
+
if (
|
|
182
|
+
overrides ||
|
|
183
|
+
statement.tryBlock.statements.length !== 1 ||
|
|
184
|
+
!ts.isReturnStatement(statement.tryBlock.statements[0])
|
|
185
|
+
)
|
|
186
|
+
return false;
|
|
187
|
+
result = statement.tryBlock.statements[0].expression;
|
|
188
|
+
}
|
|
189
|
+
return forwards(result, ref.module, nested, depth + 1);
|
|
190
|
+
}
|
|
191
|
+
function lazyFactory(call, module) {
|
|
192
|
+
if (call.arguments.length !== 1) return null;
|
|
193
|
+
const imported = external(module, call.expression);
|
|
194
|
+
if (imported?.specifier === "react" && imported.name === "lazy")
|
|
195
|
+
return { node: call.arguments[0], module, wrapped: false };
|
|
196
|
+
if (!ts.isIdentifier(call.expression)) return null;
|
|
197
|
+
const ref = resolveBinding(module, call.expression.text),
|
|
198
|
+
fn = ref && functionNode(ref.node);
|
|
199
|
+
if (
|
|
200
|
+
!fn ||
|
|
201
|
+
fn.asteriskToken ||
|
|
202
|
+
fn.parameters.length !== 1 ||
|
|
203
|
+
fn.parameters[0].dotDotDotToken ||
|
|
204
|
+
!ts.isIdentifier(fn.parameters[0].name)
|
|
205
|
+
)
|
|
206
|
+
return null;
|
|
207
|
+
if (fn.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword)) return null;
|
|
208
|
+
const value = unwrap(returned(fn));
|
|
209
|
+
if (!value || !ts.isCallExpression(value) || value.arguments.length !== 1)
|
|
210
|
+
return null;
|
|
211
|
+
const wrapper = external(ref.module, value.expression),
|
|
212
|
+
callback = functionNode(value.arguments[0]);
|
|
213
|
+
if (
|
|
214
|
+
wrapper?.specifier !== "react" ||
|
|
215
|
+
wrapper.name !== "lazy" ||
|
|
216
|
+
!callback ||
|
|
217
|
+
callback.asteriskToken ||
|
|
218
|
+
callback.parameters.length ||
|
|
219
|
+
!forwards(
|
|
220
|
+
returned(callback),
|
|
221
|
+
ref.module,
|
|
222
|
+
new Set([fn.parameters[0].name.text]),
|
|
223
|
+
)
|
|
224
|
+
)
|
|
225
|
+
return null;
|
|
226
|
+
return { node: call.arguments[0], module, wrapped: true };
|
|
227
|
+
}
|
|
228
|
+
function component(module, name) {
|
|
229
|
+
if (typeof name !== "string" || !name) return null;
|
|
230
|
+
const parts = name.split(".");
|
|
231
|
+
let ref;
|
|
232
|
+
if (parts.length === 2) {
|
|
233
|
+
const imported = module.imports.get(parts[0]),
|
|
234
|
+
target =
|
|
235
|
+
imported &&
|
|
236
|
+
["*", "default"].includes(imported.name) &&
|
|
237
|
+
resolveModule(module.file, imported.specifier);
|
|
238
|
+
ref =
|
|
239
|
+
target &&
|
|
240
|
+
resolveBinding(target, target.exports.get(parts[1]) ?? parts[1]);
|
|
241
|
+
} else if (parts.length === 1) ref = resolveBinding(module, name);
|
|
242
|
+
if (!ref) return null;
|
|
243
|
+
const node = unwrap(ref.node);
|
|
244
|
+
if (node && ts.isCallExpression(node)) {
|
|
245
|
+
const factory = lazyFactory(node, ref.module),
|
|
246
|
+
callback = factory && functionNode(factory.node),
|
|
247
|
+
body = callback && unwrap(returned(callback));
|
|
248
|
+
if (
|
|
249
|
+
body &&
|
|
250
|
+
!callback.asteriskToken &&
|
|
251
|
+
!callback.parameters.length &&
|
|
252
|
+
ts.isCallExpression(body) &&
|
|
253
|
+
body.expression.kind === ts.SyntaxKind.ImportKeyword &&
|
|
254
|
+
body.arguments.length === 1
|
|
255
|
+
) {
|
|
256
|
+
const specifier = string(body.arguments[0], factory.module),
|
|
257
|
+
target =
|
|
258
|
+
typeof specifier === "string" &&
|
|
259
|
+
resolveModule(factory.module.file, specifier);
|
|
260
|
+
const resolved = target && resolveBinding(target, target.defaultName);
|
|
261
|
+
if (resolved)
|
|
262
|
+
return { ...resolved, lazy: true, wrapped: factory.wrapped };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return ref;
|
|
266
|
+
}
|
|
267
|
+
return { reference, string, component };
|
|
268
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** A finite source-only React Router matcher. No merchant modules or regular expressions run. */
|
|
2
|
+
export function sourceMatchPath(input, pathname) {
|
|
3
|
+
const options = typeof input === "string" ? { path: input } : input;
|
|
4
|
+
if (
|
|
5
|
+
!options ||
|
|
6
|
+
typeof options.path !== "string" ||
|
|
7
|
+
typeof pathname !== "string" ||
|
|
8
|
+
options.path.length > 1024 ||
|
|
9
|
+
pathname.length > 4000
|
|
10
|
+
)
|
|
11
|
+
throw Error("Unsupported source path");
|
|
12
|
+
const parts = options.path.split("/").filter(Boolean),
|
|
13
|
+
names = [];
|
|
14
|
+
let pattern = "^";
|
|
15
|
+
for (const [index, part] of parts.entries()) {
|
|
16
|
+
if (part === "*" && index === parts.length - 1) {
|
|
17
|
+
names.push("*");
|
|
18
|
+
pattern += "(?:/(.*))?";
|
|
19
|
+
} else if (/^:[A-Za-z_][\w-]*\??$/.test(part)) {
|
|
20
|
+
names.push(part.slice(1).replace(/\?$/, ""));
|
|
21
|
+
pattern += part.endsWith("?") ? "(?:/([^/]+))?" : "/([^/]+)";
|
|
22
|
+
} else if (/^[\w.~-]+$/.test(part))
|
|
23
|
+
pattern += "/" + part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
24
|
+
else throw Error("Unsupported source pattern");
|
|
25
|
+
}
|
|
26
|
+
pattern += options.end === false ? "(?=/|$)" : "/*$";
|
|
27
|
+
const match = new RegExp(pattern, options.caseSensitive ? "" : "i").exec(
|
|
28
|
+
pathname,
|
|
29
|
+
);
|
|
30
|
+
if (!match) return null;
|
|
31
|
+
return {
|
|
32
|
+
params: Object.fromEntries(
|
|
33
|
+
names.map((name, index) => [
|
|
34
|
+
name,
|
|
35
|
+
match[index + 1] === undefined
|
|
36
|
+
? undefined
|
|
37
|
+
: decodeURIComponent(match[index + 1]),
|
|
38
|
+
]),
|
|
39
|
+
),
|
|
40
|
+
pathname: match[0] || "/",
|
|
41
|
+
pathnameBase: (match[0] || "/").replace(/\/+$/, "") || "/",
|
|
42
|
+
pattern: {
|
|
43
|
+
path: options.path,
|
|
44
|
+
caseSensitive: options.caseSensitive ?? false,
|
|
45
|
+
end: options.end ?? true,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
package/src/static-query.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import {sourceMatchPath} from './static-primitives.mjs';
|
|
1
2
|
// Abstract interpretation of bounded, synchronous, read-only source helpers.
|
|
2
3
|
// No import(), eval, native callback invocation, await or object mutation.
|
|
3
|
-
export function staticQuery({ ts, node, module, context, binding, unknown, redact, sensitive }) {
|
|
4
|
+
export function staticQuery({ ts, node, module, context, binding, unknown, redact, sensitive, reference, isReference, imported, project }) {
|
|
4
5
|
let steps = 0;
|
|
5
6
|
const closure = Symbol("source-function"), writable = Symbol("function-locals"), fail = () => { throw unknown; };
|
|
6
7
|
const tick = () => { if (++steps > 6000) fail(); };
|
|
@@ -9,7 +10,9 @@ export function staticQuery({ ts, node, module, context, binding, unknown, redac
|
|
|
9
10
|
const scalar = value => value === null || ["string", "boolean", "number", "undefined"].includes(typeof value);
|
|
10
11
|
function clean(value, depth = 0) {
|
|
11
12
|
tick();
|
|
12
|
-
if (depth > 20
|
|
13
|
+
if (depth > 20) fail();
|
|
14
|
+
if(isReference?.(value))return value;
|
|
15
|
+
if(value===unknown||value?.[closure]){if(depth>0)return unknown;fail();}
|
|
13
16
|
if (typeof value === "number" && !Number.isFinite(value)) fail();
|
|
14
17
|
if (typeof value === "string") return redact(value);
|
|
15
18
|
if (Array.isArray(value)) { if (value.length > 256) fail(); return value.map(v => clean(v, depth + 1)); }
|
|
@@ -20,6 +23,7 @@ export function staticQuery({ ts, node, module, context, binding, unknown, redac
|
|
|
20
23
|
function read(n, m, env, seen = new Set(), depth = 0) {
|
|
21
24
|
tick(); n = unwrap(n); if (!n || depth > 24) fail();
|
|
22
25
|
const ev = child => read(child, m, env, seen, depth + 1);
|
|
26
|
+
const opaque=!(ts.isIdentifier(n)&&Object.hasOwn(env,n.text))&&reference?.(n,m);if(opaque)return opaque;
|
|
23
27
|
if (ts.isStringLiteralLike(n)) return redact(n.text);
|
|
24
28
|
if (ts.isNumericLiteral(n)) return Number(n.text);
|
|
25
29
|
if (n.kind === ts.SyntaxKind.TrueKeyword) return true;
|
|
@@ -38,11 +42,11 @@ export function staticQuery({ ts, node, module, context, binding, unknown, redac
|
|
|
38
42
|
if (ts.isObjectLiteralExpression(n)) {
|
|
39
43
|
const value = Object.create(null);
|
|
40
44
|
for (const p of n.properties) {
|
|
41
|
-
if (ts.isShorthandPropertyAssignment(p)) { value[p.name.text] = sensitive(p.name.text) ? unknown : ev(p.name);
|
|
45
|
+
if (ts.isShorthandPropertyAssignment(p)) { try{value[p.name.text] = sensitive(p.name.text) ? unknown : ev(p.name);}catch{value[p.name.text]=unknown;}continue; }
|
|
42
46
|
if (!ts.isPropertyAssignment(p) || !p.name || ts.isComputedPropertyName(p.name)) fail();
|
|
43
47
|
const key = p.name.text; if (!key || ["__proto__", "constructor", "prototype"].includes(key)) fail();
|
|
44
48
|
// Keep unrelated async/mutating helpers opaque, never evaluate their bodies.
|
|
45
|
-
value[key] = sensitive(key) ? unknown : ev(p.initializer);
|
|
49
|
+
try{value[key] = sensitive(key) ? unknown : ev(p.initializer);}catch{value[key]=unknown;}
|
|
46
50
|
}
|
|
47
51
|
return value;
|
|
48
52
|
}
|
|
@@ -50,8 +54,11 @@ export function staticQuery({ ts, node, module, context, binding, unknown, redac
|
|
|
50
54
|
const object = ev(n.expression), key = ts.isPropertyAccessExpression(n) ? n.name.text : ev(n.argumentExpression);
|
|
51
55
|
if (object == null && n.questionDotToken) return undefined;
|
|
52
56
|
if (object == null || ["__proto__", "constructor", "prototype"].includes(key) || sensitive(String(key))) fail();
|
|
53
|
-
|
|
57
|
+
const result=Object.hasOwn(Object(object), key) || key === "length" ? object[key] : undefined;
|
|
58
|
+
if(result===unknown)fail();return result;
|
|
54
59
|
}
|
|
60
|
+
if(ts.isTemplateExpression(n)){let text=n.head.text;for(const span of n.templateSpans){const v=ev(span.expression);if(!scalar(v)||v===unknown)fail();text+=String(v)+span.literal.text;if(text.length>4000)fail();}return redact(text);}
|
|
61
|
+
if(ts.isRegularExpressionLiteral(n)){const raw=n.getText(m.source);if(raw==='/[?#]/')return {sourceRegex:'query-separator'};if(raw==='/\\/+$/')return {sourceRegex:'trailing-slashes'};if(raw==='/^./')return {sourceRegex:'first-character'};fail();}
|
|
55
62
|
if (ts.isTypeOfExpression(n)) return typeof ev(n.expression);
|
|
56
63
|
if (ts.isPrefixUnaryExpression(n)) { const value = ev(n.operand); if (n.operator === ts.SyntaxKind.ExclamationToken) return !value; if (n.operator === ts.SyntaxKind.MinusToken && typeof value === "number") return -value; fail(); }
|
|
57
64
|
if (ts.isBinaryExpression(n)) {
|
|
@@ -67,15 +74,34 @@ export function staticQuery({ ts, node, module, context, binding, unknown, redac
|
|
|
67
74
|
}
|
|
68
75
|
if (ts.isConditionalExpression(n)) return ev(ev(n.condition) ? n.whenTrue : n.whenFalse);
|
|
69
76
|
if (ts.isCallExpression(n)) {
|
|
77
|
+
const projected=project?.(n,m,env);if(Array.isArray(projected))return projected[0];
|
|
70
78
|
if (n.arguments.length > 8) fail();
|
|
79
|
+
const primitive=imported?.(m,n.expression.getText(m.source));
|
|
80
|
+
if(primitive?.specifier==='react'&&primitive.name==='useMemo'&&n.arguments.length===2){const fn=ev(n.arguments[0]);return call(fn,[],depth+1);}
|
|
71
81
|
const args = n.arguments.map(ev);
|
|
82
|
+
if(ts.isPropertyAccessExpression(n.expression)&&ts.isIdentifier(n.expression.expression)&&n.expression.expression.text==='Object'&&!Object.hasOwn(env,'Object')&&!binding(m,'Object')){
|
|
83
|
+
const method=n.expression.name.text,target=args[0];
|
|
84
|
+
if(!plain(target)||isReference?.(target)||Object.keys(target).length>256)fail();
|
|
85
|
+
if(method==='keys'&&args.length===1)return Object.keys(target);
|
|
86
|
+
if(method==='hasOwn'&&args.length===2&&['string','number'].includes(typeof args[1]))return Object.hasOwn(target,args[1]);
|
|
87
|
+
fail();
|
|
88
|
+
}
|
|
89
|
+
if(['react-router','react-router-dom'].includes(primitive?.specifier)&&primitive.name==='matchPath'&&args.length===2)return sourceMatchPath(args[0],args[1]);
|
|
90
|
+
if(ts.isIdentifier(n.expression)&&n.expression.text==='String'&&args.length===1&&scalar(args[0]))return String(args[0]);
|
|
72
91
|
if (ts.isPropertyAccessExpression(n.expression)) {
|
|
73
92
|
const target = ev(n.expression.expression), method = n.expression.name.text;
|
|
74
|
-
if (Array.isArray(target) && ["find", "filter", "map"].includes(method) && args.length === 1 && args[0]?.[closure]) {
|
|
93
|
+
if (Array.isArray(target) && ["find", "filter", "map", "some", "every", "flatMap"].includes(method) && args.length === 1 && args[0]?.[closure]) {
|
|
75
94
|
if (target.length > 256) fail();
|
|
76
95
|
const result = [];
|
|
77
|
-
for (let i = 0; i < target.length; i++) { const value = call(args[0], [target[i], i], depth + 1); if (method === "find" && value) return target[i]; if (method === "filter" && value) result.push(target[i]); if (method === "map") result.push(value); }
|
|
78
|
-
return method === "find" ? undefined : result;
|
|
96
|
+
for (let i = 0; i < target.length; i++) { const value = call(args[0], [target[i], i], depth + 1); if (method === "find" && value) return target[i]; if (method === "filter" && value) result.push(target[i]); if (method === "map") result.push(value); if(method==="flatMap"){if(!Array.isArray(value))fail();result.push(...value);} if(method==="some"&&value)return true;if(method==="every"&&!value)return false; }
|
|
97
|
+
return method === "find" ? undefined : method==="some"?false:method==="every"?true:result;
|
|
98
|
+
}
|
|
99
|
+
if(typeof target==='string'&&target.length<=4000){
|
|
100
|
+
if(['startsWith','endsWith','includes','toLowerCase','toUpperCase','trim','slice'].includes(method)&&args.every(v=>['string','number'].includes(typeof v)))return String.prototype[method].apply(target,args);
|
|
101
|
+
if(method==='split'&&args[0]?.sourceRegex==='query-separator'&&args[1]===1)return target.split(/[?#]/,1);
|
|
102
|
+
if(method==='replace'&&args[0]?.sourceRegex==='trailing-slashes'&&args[1]==='')return target.replace(/\/+$/,'');
|
|
103
|
+
if(method==='replaceAll'&&args.length===2&&typeof args[0]==='string'&&args[0].length&&typeof args[1]==='string'){const result=target.split(args[0]).join(args[1]);if(result.length>4000)fail();return result;}
|
|
104
|
+
if(method==='replace'&&args[0]?.sourceRegex==='first-character'&&args[1]?.[closure]){const first=call(args[1],[target.charAt(0)],depth+1);if(typeof first!=='string')fail();return first+target.slice(1);}
|
|
79
105
|
}
|
|
80
106
|
}
|
|
81
107
|
return call(ev(n.expression), args, depth + 1);
|
|
@@ -86,9 +112,11 @@ export function staticQuery({ ts, node, module, context, binding, unknown, redac
|
|
|
86
112
|
tick(); if (!fn?.[closure] || depth > 24 || fn.node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword)) fail();
|
|
87
113
|
const env = { ...fn.env, [writable]: new Set() };
|
|
88
114
|
for (let i = 0; i < fn.node.parameters.length; i++) {
|
|
89
|
-
const p = fn.node.parameters[i]; if (
|
|
90
|
-
|
|
91
|
-
env[writable].add(p.name.text);
|
|
115
|
+
const p = fn.node.parameters[i]; if (p.dotDotDotToken) fail();
|
|
116
|
+
const value=args[i] === undefined && p.initializer ? read(p.initializer, fn.module, env, new Set(), depth + 1) : args[i];
|
|
117
|
+
if(ts.isIdentifier(p.name)){env[p.name.text]=value;env[writable].add(p.name.text);}
|
|
118
|
+
else if(ts.isArrayBindingPattern(p.name)&&Array.isArray(value)&&p.name.elements.length<=8){for(const [index,field] of p.name.elements.entries()){if(ts.isOmittedExpression(field))continue;if(!ts.isIdentifier(field.name)||field.dotDotDotToken)fail();env[field.name.text]=value[index]===undefined&&field.initializer?read(field.initializer,fn.module,env,new Set(),depth+1):value[index];env[writable].add(field.name.text);}}
|
|
119
|
+
else fail();
|
|
92
120
|
}
|
|
93
121
|
if (!ts.isBlock(fn.node.body)) return read(fn.node.body, fn.module, env, new Set(), depth + 1);
|
|
94
122
|
const result = statements(fn.node.body.statements, fn.module, env, depth + 1); return result?.value;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { JSDOM, VirtualConsole } from "jsdom";
|
|
2
|
+
import { documentDom } from "./dom-payload.mjs";
|
|
3
|
+
import { createStateMachine } from "./ui-state-machine.mjs";
|
|
4
|
+
import { previewUiDocument } from "./ui-preview.mjs";
|
|
5
|
+
import {graphSource} from './graph-sources.mjs';
|
|
6
|
+
|
|
7
|
+
/** Build thumbnail states and edges by dispatching the IAM's actual scalar transitions. */
|
|
8
|
+
export function tabUiScenes(manifest, tabKey) {
|
|
9
|
+
const graph = manifest.routeTree,
|
|
10
|
+
tab = graph.ui?.tabs.find((t) => t.tabKey === tabKey);
|
|
11
|
+
if (!tab || !tab.variableKey) throw Error("Unknown controlled source Tab");
|
|
12
|
+
const render = graph.renders.find((r) => r.renderKey === tab.renderKey),
|
|
13
|
+
document = manifest.documents.find(
|
|
14
|
+
(d) => d.documentKey === render.documentKey,
|
|
15
|
+
);
|
|
16
|
+
const dom = new JSDOM(documentDom(document).html, {
|
|
17
|
+
virtualConsole: new VirtualConsole(),
|
|
18
|
+
});
|
|
19
|
+
try {
|
|
20
|
+
const doc = dom.window.document,
|
|
21
|
+
nodes = new Map(
|
|
22
|
+
[...doc.querySelectorAll("[data-iam-node]")].map((n) => [
|
|
23
|
+
n.getAttribute("data-iam-node"),
|
|
24
|
+
n,
|
|
25
|
+
]),
|
|
26
|
+
);
|
|
27
|
+
const ancestors = [];
|
|
28
|
+
let current = nodes.get(tab.nodeId);
|
|
29
|
+
while (current && ancestors.length < 128) {
|
|
30
|
+
ancestors.push(current);
|
|
31
|
+
current = current.parentElement;
|
|
32
|
+
}
|
|
33
|
+
const conditions = {},
|
|
34
|
+
regions = {},
|
|
35
|
+
values = {},
|
|
36
|
+
ids = new Set(ancestors.map((n) => n.getAttribute("data-iam-node")));
|
|
37
|
+
for (const node of ancestors) {
|
|
38
|
+
if (node.hasAttribute("data-iam-condition")) {
|
|
39
|
+
const truthy = node.getAttribute("data-iam-alternative") === "true",
|
|
40
|
+
root = truthy ? node : node.previousElementSibling;
|
|
41
|
+
if (
|
|
42
|
+
!root ||
|
|
43
|
+
root.getAttribute("data-iam-condition") !==
|
|
44
|
+
node.getAttribute("data-iam-condition")
|
|
45
|
+
)
|
|
46
|
+
throw Error("Invalid source condition pair");
|
|
47
|
+
conditions[root.getAttribute("data-iam-node")] = truthy;
|
|
48
|
+
}
|
|
49
|
+
if (node.hasAttribute("data-iam-region-value")) {
|
|
50
|
+
const region = (graph.ui.regions ?? []).find((r) =>
|
|
51
|
+
r.options.some(
|
|
52
|
+
(o) => o.nodeId === node.getAttribute("data-iam-node"),
|
|
53
|
+
),
|
|
54
|
+
);
|
|
55
|
+
if (region)
|
|
56
|
+
regions[region.regionKey] = node.getAttribute(
|
|
57
|
+
"data-iam-region-value",
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
for (const binding of graph.bindings.filter(
|
|
62
|
+
(b) =>
|
|
63
|
+
b.renderKey === tab.renderKey &&
|
|
64
|
+
b.property === "visible" &&
|
|
65
|
+
ids.has(b.nodeId),
|
|
66
|
+
)) {
|
|
67
|
+
if (binding.value.op === "var") values[binding.value.key] = true;
|
|
68
|
+
if (binding.value.op === "not" && binding.value.arg.op === "var")
|
|
69
|
+
values[binding.value.arg.key] = false;
|
|
70
|
+
if (
|
|
71
|
+
binding.value.op === "eq" &&
|
|
72
|
+
binding.value.left.op === "var" &&
|
|
73
|
+
binding.value.right.op === "const"
|
|
74
|
+
)
|
|
75
|
+
values[binding.value.left.key] = binding.value.right.value;
|
|
76
|
+
}
|
|
77
|
+
const scope = nodes.get(tab.scopeNodeId ?? tab.nodeId),
|
|
78
|
+
overlay = scope?.getAttribute("role") === "dialog",
|
|
79
|
+
width = Number(
|
|
80
|
+
/\bw-\[(\d+)px\]/.exec(scope?.getAttribute("class") ?? "")?.[1] || 0,
|
|
81
|
+
);
|
|
82
|
+
const viewport = overlay
|
|
83
|
+
? {
|
|
84
|
+
width: width ? Math.max(320, width + 48) : 640,
|
|
85
|
+
height: width ? 480 : 400,
|
|
86
|
+
}
|
|
87
|
+
: { width: 480, height: 760 };
|
|
88
|
+
const base = {
|
|
89
|
+
strategy: "content",
|
|
90
|
+
conditions,
|
|
91
|
+
regions,
|
|
92
|
+
values,
|
|
93
|
+
focusNodeId: tab.scopeNodeId ?? tab.nodeId,
|
|
94
|
+
viewport,
|
|
95
|
+
};
|
|
96
|
+
const initial = createStateMachine(
|
|
97
|
+
graph,
|
|
98
|
+
tab.routeKey,
|
|
99
|
+
tab.renderKey,
|
|
100
|
+
).snapshot(),
|
|
101
|
+
scenes = [];
|
|
102
|
+
for (const item of tab.items) {
|
|
103
|
+
const machine = createStateMachine(graph, tab.routeKey, tab.renderKey);
|
|
104
|
+
machine.preview(values);
|
|
105
|
+
const transition = graph.transitions.find(
|
|
106
|
+
(t) =>
|
|
107
|
+
t.renderKey === tab.renderKey &&
|
|
108
|
+
t.trigger.nodeId === item.nodeId &&
|
|
109
|
+
t.trigger.event === "click",
|
|
110
|
+
);
|
|
111
|
+
const result = transition
|
|
112
|
+
? machine.dispatch(item.nodeId, "click", {})
|
|
113
|
+
: null;
|
|
114
|
+
if (!result || machine.snapshot()[tab.variableKey] !== item.value)
|
|
115
|
+
continue;
|
|
116
|
+
const changed = Object.fromEntries(
|
|
117
|
+
Object.entries(machine.snapshot()).filter(
|
|
118
|
+
([key, value]) => initial[key] !== value || key === tab.variableKey,
|
|
119
|
+
),
|
|
120
|
+
);
|
|
121
|
+
const options = { ...base, values: { ...values, ...changed } },
|
|
122
|
+
preview = previewUiDocument(manifest, document, options),
|
|
123
|
+
sceneDom = new JSDOM(preview.html, {
|
|
124
|
+
virtualConsole: new VirtualConsole(),
|
|
125
|
+
});
|
|
126
|
+
const focused = [
|
|
127
|
+
...sceneDom.window.document.querySelectorAll("[data-iam-node]"),
|
|
128
|
+
]
|
|
129
|
+
.find((n) => n.getAttribute("data-iam-node") === base.focusNodeId)
|
|
130
|
+
?.cloneNode(true);
|
|
131
|
+
focused
|
|
132
|
+
?.querySelectorAll("[hidden],[role=tablist]")
|
|
133
|
+
.forEach((n) => n.remove());
|
|
134
|
+
const headings = focused
|
|
135
|
+
? [...focused.querySelectorAll("h1,h2,h3,h4,label,[role=columnheader]")]
|
|
136
|
+
.map((n) => n.textContent.replace(/\s+/g, " ").trim())
|
|
137
|
+
.filter(Boolean)
|
|
138
|
+
: [];
|
|
139
|
+
const content = focused?.textContent.replace(/\s+/g, " ").trim() ?? "";
|
|
140
|
+
const fields = focused
|
|
141
|
+
? [...focused.querySelectorAll("input,textarea,select")].map((n) => ({
|
|
142
|
+
tag: n.tagName.toLowerCase(),
|
|
143
|
+
label: n.getAttribute("aria-label") ?? "",
|
|
144
|
+
placeholder: n.getAttribute("placeholder") ?? "",
|
|
145
|
+
}))
|
|
146
|
+
: [];
|
|
147
|
+
const allowedIds = new Set([
|
|
148
|
+
...ancestors.map((n) => n.getAttribute("data-iam-node")),
|
|
149
|
+
...(scope
|
|
150
|
+
? [scope, ...scope.querySelectorAll("[data-iam-node]")].map((n) =>
|
|
151
|
+
n.getAttribute("data-iam-node"),
|
|
152
|
+
)
|
|
153
|
+
: []),
|
|
154
|
+
]);
|
|
155
|
+
scenes.push({
|
|
156
|
+
assumptions: preview.assumptions.filter((a) =>
|
|
157
|
+
allowedIds.has(a.groupKey),
|
|
158
|
+
),
|
|
159
|
+
value: item.value,
|
|
160
|
+
label:
|
|
161
|
+
nodes.get(item.nodeId)?.textContent.replace(/\s+/g, " ").trim() ||
|
|
162
|
+
item.value,
|
|
163
|
+
options,
|
|
164
|
+
headings: [...new Set(headings)].slice(0, 8),
|
|
165
|
+
content: content.slice(0, 1000),
|
|
166
|
+
fields,
|
|
167
|
+
collections: preview.collectionChoices.filter((c) => {
|
|
168
|
+
const row = (graph.ui.collections ?? []).find(
|
|
169
|
+
(r) => r.collectionKey === c.collectionKey,
|
|
170
|
+
),
|
|
171
|
+
node = row && nodes.get(row.nodeId);
|
|
172
|
+
return node && (scope === node || scope?.contains(node));
|
|
173
|
+
}),
|
|
174
|
+
unobserved: preview.unobserved.filter((u) =>
|
|
175
|
+
allowedIds.has(u.groupKey),
|
|
176
|
+
),
|
|
177
|
+
transitionKey: result.transitionKey,
|
|
178
|
+
changedNodeIds: tab.contentNodeIds ?? [],
|
|
179
|
+
});
|
|
180
|
+
sceneDom.window.close();
|
|
181
|
+
}
|
|
182
|
+
const edges = [];
|
|
183
|
+
for (const from of scenes)
|
|
184
|
+
for (const to of scenes) {
|
|
185
|
+
if (from.value === to.value) continue;
|
|
186
|
+
const item = tab.items.find((i) => i.value === to.value),
|
|
187
|
+
machine = createStateMachine(graph, tab.routeKey, tab.renderKey);
|
|
188
|
+
machine.preview(from.options.values);
|
|
189
|
+
const result = machine.dispatch(item.nodeId, "click", {});
|
|
190
|
+
if (!result || machine.snapshot()[tab.variableKey] !== to.value)
|
|
191
|
+
continue;
|
|
192
|
+
const transition = graph.transitions.find(
|
|
193
|
+
(t) => t.transitionKey === result.transitionKey,
|
|
194
|
+
);
|
|
195
|
+
edges.push({
|
|
196
|
+
from: from.value,
|
|
197
|
+
to: to.value,
|
|
198
|
+
transitionKey: result.transitionKey,
|
|
199
|
+
event: transition.trigger.event,
|
|
200
|
+
variableKey: tab.variableKey,
|
|
201
|
+
writes: transition.stateWrites.map((w) => ({
|
|
202
|
+
variableKey: w.variableKey,
|
|
203
|
+
label: graph.variables.find((v) => v.variableKey === w.variableKey)
|
|
204
|
+
.label,
|
|
205
|
+
value: machine.snapshot()[w.variableKey],
|
|
206
|
+
})),
|
|
207
|
+
sourceRefs: [graphSource(graph,transition), tab.scopeSource],
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return { tabKey, scenes, edges };
|
|
211
|
+
} finally {
|
|
212
|
+
dom.window.close();
|
|
213
|
+
}
|
|
214
|
+
}
|