modality-ts 0.0.16 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/README.md +18 -14
  2. package/dist/check/check-model.d.ts +4 -0
  3. package/dist/check/check-model.d.ts.map +1 -0
  4. package/dist/check/check-model.js +159 -0
  5. package/dist/check/check-model.js.map +1 -0
  6. package/dist/check/index.d.ts +2 -2
  7. package/dist/check/index.d.ts.map +1 -1
  8. package/dist/check/index.js +2 -2
  9. package/dist/check/index.js.map +1 -1
  10. package/dist/check/model-api.d.ts.map +1 -0
  11. package/dist/check/model-api.js +8 -0
  12. package/dist/check/model-api.js.map +1 -0
  13. package/dist/check/native.d.ts +16 -0
  14. package/dist/check/native.d.ts.map +1 -0
  15. package/dist/check/native.js +143 -0
  16. package/dist/check/native.js.map +1 -0
  17. package/dist/check/serialize-properties.d.ts +4 -0
  18. package/dist/check/serialize-properties.d.ts.map +1 -0
  19. package/dist/check/serialize-properties.js +4 -0
  20. package/dist/check/serialize-properties.js.map +1 -0
  21. package/dist/cli/features/check/command.d.ts.map +1 -1
  22. package/dist/cli/features/check/command.js +25 -6
  23. package/dist/cli/features/check/command.js.map +1 -1
  24. package/dist/cli/features/extract/command.d.ts.map +1 -1
  25. package/dist/cli/features/extract/command.js +180 -193
  26. package/dist/cli/features/extract/command.js.map +1 -1
  27. package/dist/cli/features/extract/project.d.ts +36 -0
  28. package/dist/cli/features/extract/project.d.ts.map +1 -0
  29. package/dist/cli/features/extract/project.js +783 -0
  30. package/dist/cli/features/extract/project.js.map +1 -0
  31. package/dist/cli/runtime/index.d.ts.map +1 -1
  32. package/dist/cli/runtime/index.js +2 -1
  33. package/dist/cli/runtime/index.js.map +1 -1
  34. package/dist/core/artifacts/index.d.ts +3 -0
  35. package/dist/core/artifacts/index.d.ts.map +1 -1
  36. package/dist/core/artifacts/index.js +238 -0
  37. package/dist/core/artifacts/index.js.map +1 -1
  38. package/dist/core/index.d.ts +1 -0
  39. package/dist/core/index.d.ts.map +1 -1
  40. package/dist/core/index.js +1 -0
  41. package/dist/core/index.js.map +1 -1
  42. package/dist/core/ir/eval.d.ts +6 -0
  43. package/dist/core/ir/eval.d.ts.map +1 -0
  44. package/dist/core/ir/eval.js +104 -0
  45. package/dist/core/ir/eval.js.map +1 -0
  46. package/dist/core/ir/types.d.ts +83 -0
  47. package/dist/core/ir/types.d.ts.map +1 -1
  48. package/dist/core/props/index.d.ts +23 -60
  49. package/dist/core/props/index.d.ts.map +1 -1
  50. package/dist/core/props/index.js +177 -116
  51. package/dist/core/props/index.js.map +1 -1
  52. package/dist/core/report/types.d.ts +7 -0
  53. package/dist/core/report/types.d.ts.map +1 -1
  54. package/dist/extract/engine/pipeline/index.d.ts +4 -0
  55. package/dist/extract/engine/pipeline/index.d.ts.map +1 -1
  56. package/dist/extract/engine/pipeline/index.js +39 -15
  57. package/dist/extract/engine/pipeline/index.js.map +1 -1
  58. package/dist/extract/engine/spi/index.d.ts +32 -0
  59. package/dist/extract/engine/spi/index.d.ts.map +1 -1
  60. package/dist/extract/engine/ts/react-source-transitions.d.ts +3 -0
  61. package/dist/extract/engine/ts/react-source-transitions.d.ts.map +1 -1
  62. package/dist/extract/engine/ts/react-source-transitions.js +18 -0
  63. package/dist/extract/engine/ts/react-source-transitions.js.map +1 -1
  64. package/dist/extract/sources/router/index.d.ts.map +1 -1
  65. package/dist/extract/sources/router/index.js +5 -0
  66. package/dist/extract/sources/router/index.js.map +1 -1
  67. package/dist/extract/sources/router/module-roles.d.ts +7 -0
  68. package/dist/extract/sources/router/module-roles.d.ts.map +1 -0
  69. package/dist/extract/sources/router/module-roles.js +153 -0
  70. package/dist/extract/sources/router/module-roles.js.map +1 -0
  71. package/dist/modality-ts-native.zip +0 -0
  72. package/native/modality-checker.darwin-arm64.node +0 -0
  73. package/native/modality-checker.darwin-x64.node +0 -0
  74. package/native/modality-checker.linux-arm64-gnu.node +0 -0
  75. package/native/modality-checker.linux-x64-gnu.node +0 -0
  76. package/native/modality-checker.win32-x64-msvc.node +0 -0
  77. package/package.json +14 -4
  78. package/dist/check/diagnostics/bounds.d.ts +0 -5
  79. package/dist/check/diagnostics/bounds.d.ts.map +0 -1
  80. package/dist/check/diagnostics/bounds.js +0 -25
  81. package/dist/check/diagnostics/bounds.js.map +0 -1
  82. package/dist/check/diagnostics/vacuity.d.ts +0 -3
  83. package/dist/check/diagnostics/vacuity.d.ts.map +0 -1
  84. package/dist/check/diagnostics/vacuity.js +0 -22
  85. package/dist/check/diagnostics/vacuity.js.map +0 -1
  86. package/dist/check/engine/check-model.d.ts +0 -7
  87. package/dist/check/engine/check-model.d.ts.map +0 -1
  88. package/dist/check/engine/check-model.js +0 -527
  89. package/dist/check/engine/check-model.js.map +0 -1
  90. package/dist/check/engine/initial-states.d.ts +0 -3
  91. package/dist/check/engine/initial-states.d.ts.map +0 -1
  92. package/dist/check/engine/initial-states.js +0 -11
  93. package/dist/check/engine/initial-states.js.map +0 -1
  94. package/dist/check/engine/model-api.d.ts.map +0 -1
  95. package/dist/check/engine/model-api.js +0 -17
  96. package/dist/check/engine/model-api.js.map +0 -1
  97. package/dist/check/engine/mounts.d.ts +0 -3
  98. package/dist/check/engine/mounts.d.ts.map +0 -1
  99. package/dist/check/engine/mounts.js +0 -13
  100. package/dist/check/engine/mounts.js.map +0 -1
  101. package/dist/check/engine/stabilize.d.ts +0 -4
  102. package/dist/check/engine/stabilize.d.ts.map +0 -1
  103. package/dist/check/engine/stabilize.js +0 -104
  104. package/dist/check/engine/stabilize.js.map +0 -1
  105. package/dist/check/engine/state-utils.d.ts +0 -13
  106. package/dist/check/engine/state-utils.d.ts.map +0 -1
  107. package/dist/check/engine/state-utils.js +0 -43
  108. package/dist/check/engine/state-utils.js.map +0 -1
  109. package/dist/check/engine/transitions.d.ts +0 -12
  110. package/dist/check/engine/transitions.d.ts.map +0 -1
  111. package/dist/check/engine/transitions.js +0 -42
  112. package/dist/check/engine/transitions.js.map +0 -1
  113. package/dist/check/properties/checked-state.d.ts +0 -3
  114. package/dist/check/properties/checked-state.d.ts.map +0 -1
  115. package/dist/check/properties/checked-state.js +0 -21
  116. package/dist/check/properties/checked-state.js.map +0 -1
  117. package/dist/check/properties/finalize.d.ts +0 -5
  118. package/dist/check/properties/finalize.d.ts.map +0 -1
  119. package/dist/check/properties/finalize.js +0 -107
  120. package/dist/check/properties/finalize.js.map +0 -1
  121. package/dist/check/properties/leads-to.d.ts +0 -8
  122. package/dist/check/properties/leads-to.d.ts.map +0 -1
  123. package/dist/check/properties/leads-to.js +0 -70
  124. package/dist/check/properties/leads-to.js.map +0 -1
  125. package/dist/check/properties/observe.d.ts +0 -6
  126. package/dist/check/properties/observe.d.ts.map +0 -1
  127. package/dist/check/properties/observe.js +0 -111
  128. package/dist/check/properties/observe.js.map +0 -1
  129. package/dist/check/properties/reachable-from.d.ts +0 -10
  130. package/dist/check/properties/reachable-from.d.ts.map +0 -1
  131. package/dist/check/properties/reachable-from.js +0 -19
  132. package/dist/check/properties/reachable-from.js.map +0 -1
  133. package/dist/check/runtime/domains.d.ts +0 -5
  134. package/dist/check/runtime/domains.d.ts.map +0 -1
  135. package/dist/check/runtime/domains.js +0 -53
  136. package/dist/check/runtime/domains.js.map +0 -1
  137. package/dist/check/runtime/effects.d.ts +0 -9
  138. package/dist/check/runtime/effects.d.ts.map +0 -1
  139. package/dist/check/runtime/effects.js +0 -86
  140. package/dist/check/runtime/effects.js.map +0 -1
  141. package/dist/check/runtime/expr.d.ts +0 -7
  142. package/dist/check/runtime/expr.d.ts.map +0 -1
  143. package/dist/check/runtime/expr.js +0 -49
  144. package/dist/check/runtime/expr.js.map +0 -1
  145. package/dist/check/runtime/navigation.d.ts +0 -7
  146. package/dist/check/runtime/navigation.d.ts.map +0 -1
  147. package/dist/check/runtime/navigation.js +0 -60
  148. package/dist/check/runtime/navigation.js.map +0 -1
  149. package/dist/check/runtime/opaque.d.ts +0 -3
  150. package/dist/check/runtime/opaque.d.ts.map +0 -1
  151. package/dist/check/runtime/opaque.js +0 -72
  152. package/dist/check/runtime/opaque.js.map +0 -1
  153. package/dist/check/runtime/paths.d.ts +0 -4
  154. package/dist/check/runtime/paths.d.ts.map +0 -1
  155. package/dist/check/runtime/paths.js +0 -28
  156. package/dist/check/runtime/paths.js.map +0 -1
  157. package/dist/check/runtime/pending.d.ts +0 -9
  158. package/dist/check/runtime/pending.d.ts.map +0 -1
  159. package/dist/check/runtime/pending.js +0 -5
  160. package/dist/check/runtime/pending.js.map +0 -1
  161. package/dist/check/runtime/tokens.d.ts +0 -7
  162. package/dist/check/runtime/tokens.d.ts.map +0 -1
  163. package/dist/check/runtime/tokens.js +0 -36
  164. package/dist/check/runtime/tokens.js.map +0 -1
  165. package/dist/check/traces/step-facts.d.ts +0 -3
  166. package/dist/check/traces/step-facts.d.ts.map +0 -1
  167. package/dist/check/traces/step-facts.js +0 -35
  168. package/dist/check/traces/step-facts.js.map +0 -1
  169. package/dist/check/traces/trace.d.ts +0 -13
  170. package/dist/check/traces/trace.d.ts.map +0 -1
  171. package/dist/check/traces/trace.js +0 -47
  172. package/dist/check/traces/trace.js.map +0 -1
  173. /package/dist/check/{engine/model-api.d.ts → model-api.d.ts} +0 -0
@@ -0,0 +1,783 @@
1
+ import { readFile, stat } from "node:fs/promises";
2
+ import { dirname, extname, join, resolve } from "node:path";
3
+ import * as ts from "typescript";
4
+ function parseModuleDirectives(sourceText) {
5
+ const directives = [];
6
+ const lines = sourceText.split(/\r?\n/).slice(0, 5);
7
+ for (const line of lines) {
8
+ const trimmed = line.trim();
9
+ if (trimmed === '"use client";' || trimmed === "'use client';")
10
+ directives.push("use client");
11
+ if (trimmed === '"use server";' || trimmed === "'use server';")
12
+ directives.push("use server");
13
+ }
14
+ return directives;
15
+ }
16
+ function isManifestFile(path) {
17
+ return path.endsWith("routes.ts");
18
+ }
19
+ function isPascalCase(name) {
20
+ return /^[A-Z][A-Za-z0-9]*$/.test(name);
21
+ }
22
+ function isHookName(name) {
23
+ return (name.startsWith("use") && name.length > 3 && /^[A-Z]/.test(name[3] ?? ""));
24
+ }
25
+ function isLocalImportSpecifier(specifier) {
26
+ return specifier.startsWith(".") || specifier.startsWith("~/");
27
+ }
28
+ function createSourceFile(fileName, sourceText) {
29
+ const kind = [".tsx", ".jsx"].includes(extname(fileName))
30
+ ? ts.ScriptKind.TSX
31
+ : ts.ScriptKind.TS;
32
+ return ts.createSourceFile(fileName, sourceText, ts.ScriptTarget.Latest, true, kind);
33
+ }
34
+ function defaultClassification() {
35
+ return { defaultContext: "unknown" };
36
+ }
37
+ function classifyModule(adapter, path, text, route) {
38
+ if (adapter?.classifyModule) {
39
+ return adapter.classifyModule({ fileName: path, sourceText: text, route });
40
+ }
41
+ const directives = parseModuleDirectives(text);
42
+ if (directives.includes("use client"))
43
+ return { defaultContext: "client", directives };
44
+ if (directives.includes("use server"))
45
+ return { defaultContext: "server", serverOnly: true, directives };
46
+ return defaultClassification();
47
+ }
48
+ function moduleEntryExports(adapter, path, text, route) {
49
+ if (adapter?.moduleEntryExports) {
50
+ return adapter.moduleEntryExports({
51
+ fileName: path,
52
+ sourceText: text,
53
+ route,
54
+ });
55
+ }
56
+ return inferDefaultEntryExports(text, path);
57
+ }
58
+ function inferDefaultEntryExports(text, path) {
59
+ const sourceFile = createSourceFile(path, text);
60
+ const entries = [];
61
+ const seen = new Set();
62
+ const add = (name, reason) => {
63
+ const key = name === "default" ? "default" : name;
64
+ if (seen.has(key))
65
+ return;
66
+ seen.add(key);
67
+ entries.push({ name, context: "client", reason });
68
+ };
69
+ for (const statement of sourceFile.statements) {
70
+ if (ts.isExportAssignment(statement)) {
71
+ add("default", "default export");
72
+ continue;
73
+ }
74
+ if (!ts.canHaveModifiers(statement))
75
+ continue;
76
+ const exported = statement.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword);
77
+ if (!exported)
78
+ continue;
79
+ const isDefault = statement.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.DefaultKeyword);
80
+ if (isDefault) {
81
+ add("default", "default export");
82
+ continue;
83
+ }
84
+ const name = topLevelDeclName(statement);
85
+ if (!name)
86
+ continue;
87
+ if (isPascalCase(name) || isHookName(name))
88
+ add(name, "exported client symbol");
89
+ }
90
+ return entries;
91
+ }
92
+ function isAnonymousDefaultExport(statement) {
93
+ return (ts.canHaveModifiers(statement) &&
94
+ statement.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.DefaultKeyword) === true &&
95
+ !topLevelDeclName(statement) &&
96
+ (ts.isFunctionDeclaration(statement) ||
97
+ ts.isClassDeclaration(statement) ||
98
+ ts.isVariableStatement(statement)));
99
+ }
100
+ function topLevelDeclName(statement) {
101
+ if (ts.isFunctionDeclaration(statement) && statement.name)
102
+ return statement.name.text;
103
+ if (ts.isVariableStatement(statement)) {
104
+ const decl = statement.declarationList.declarations[0];
105
+ if (decl && ts.isIdentifier(decl.name))
106
+ return decl.name.text;
107
+ }
108
+ if (ts.isClassDeclaration(statement) && statement.name)
109
+ return statement.name.text;
110
+ if (ts.isInterfaceDeclaration(statement))
111
+ return statement.name.text;
112
+ if (ts.isTypeAliasDeclaration(statement))
113
+ return statement.name.text;
114
+ if (ts.isEnumDeclaration(statement))
115
+ return statement.name.text;
116
+ return undefined;
117
+ }
118
+ function exportContextForDecl(name, isDefault, entryExports) {
119
+ if (isDefault) {
120
+ const entry = entryExports.find((item) => item.name === "default");
121
+ return entry?.context ?? "client";
122
+ }
123
+ const entry = entryExports.find((item) => item.name === name);
124
+ return entry?.context ?? "unknown";
125
+ }
126
+ function seedsForModule(record, broadEntry, isEntry) {
127
+ const render = new Set();
128
+ const interaction = new Set();
129
+ if (record.isManifest)
130
+ return { render, interaction };
131
+ const sourceFile = createSourceFile(record.path, record.text);
132
+ const hasEntryExports = record.entryExports.length > 0;
133
+ const useBroadEntry = broadEntry && isEntry;
134
+ for (const statement of sourceFile.statements) {
135
+ const name = topLevelDeclName(statement);
136
+ const isDefault = (ts.canHaveModifiers(statement) &&
137
+ statement.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.DefaultKeyword)) ??
138
+ false;
139
+ const exported = isDefault ||
140
+ ((ts.canHaveModifiers(statement) &&
141
+ statement.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword)) ??
142
+ false) ||
143
+ ts.isExportAssignment(statement);
144
+ if (ts.isTypeAliasDeclaration(statement) ||
145
+ ts.isInterfaceDeclaration(statement) ||
146
+ ts.isEnumDeclaration(statement)) {
147
+ if (name)
148
+ record.typeDecls.add(name);
149
+ continue;
150
+ }
151
+ if (!name && !ts.isExportAssignment(statement)) {
152
+ if (isAnonymousDefaultExport(statement)) {
153
+ render.add("default");
154
+ interaction.add("default");
155
+ }
156
+ continue;
157
+ }
158
+ if (useBroadEntry || (broadEntry && !hasEntryExports)) {
159
+ if (name) {
160
+ render.add(name);
161
+ interaction.add(name);
162
+ }
163
+ else if (ts.isExportAssignment(statement)) {
164
+ render.add("default");
165
+ interaction.add("default");
166
+ }
167
+ continue;
168
+ }
169
+ const declName = ts.isExportAssignment(statement) ? "default" : name;
170
+ if (!declName)
171
+ continue;
172
+ const context = exportContextForDecl(declName, isDefault || ts.isExportAssignment(statement), record.entryExports);
173
+ if (context === "server") {
174
+ render.add(declName);
175
+ continue;
176
+ }
177
+ if (context === "type") {
178
+ record.typeDecls.add(declName);
179
+ continue;
180
+ }
181
+ if (context === "client" || context === "shared" || context === "unknown") {
182
+ render.add(declName);
183
+ interaction.add(declName);
184
+ }
185
+ }
186
+ if (!useBroadEntry && render.size === 0 && interaction.size === 0) {
187
+ for (const statement of sourceFile.statements) {
188
+ if (isAnonymousDefaultExport(statement)) {
189
+ render.add("default");
190
+ interaction.add("default");
191
+ continue;
192
+ }
193
+ const name = topLevelDeclName(statement);
194
+ if (!name)
195
+ continue;
196
+ if (ts.isTypeAliasDeclaration(statement) ||
197
+ ts.isInterfaceDeclaration(statement) ||
198
+ ts.isEnumDeclaration(statement)) {
199
+ record.typeDecls.add(name);
200
+ continue;
201
+ }
202
+ render.add(name);
203
+ interaction.add(name);
204
+ }
205
+ }
206
+ return { render, interaction };
207
+ }
208
+ function parseImportBindings(node, localOnly = false) {
209
+ if (!ts.isStringLiteral(node.moduleSpecifier))
210
+ return [];
211
+ const specifier = node.moduleSpecifier.text;
212
+ if (localOnly && !isLocalImportSpecifier(specifier))
213
+ return [];
214
+ const isTypeOnly = node.importClause?.isTypeOnly === true ||
215
+ (node.importClause?.namedBindings &&
216
+ ts.isNamedImports(node.importClause.namedBindings) &&
217
+ node.importClause.namedBindings.elements.every((el) => el.isTypeOnly));
218
+ const bindings = [];
219
+ const clause = node.importClause;
220
+ if (!clause) {
221
+ bindings.push({ local: "*", specifier, isTypeOnly: false });
222
+ return bindings;
223
+ }
224
+ if (clause.name) {
225
+ bindings.push({
226
+ local: clause.name.text,
227
+ specifier,
228
+ isTypeOnly: clause.isTypeOnly === true,
229
+ });
230
+ }
231
+ if (clause.namedBindings) {
232
+ if (ts.isNamespaceImport(clause.namedBindings)) {
233
+ bindings.push({
234
+ local: clause.namedBindings.name.text,
235
+ specifier,
236
+ isTypeOnly: false,
237
+ });
238
+ }
239
+ else if (ts.isNamedImports(clause.namedBindings)) {
240
+ for (const element of clause.namedBindings.elements) {
241
+ const local = (element.name ?? element.propertyName)?.text;
242
+ if (!local)
243
+ continue;
244
+ bindings.push({
245
+ local,
246
+ imported: element.propertyName?.text ?? local,
247
+ specifier,
248
+ isTypeOnly: element.isTypeOnly === true || clause.isTypeOnly === true,
249
+ });
250
+ }
251
+ }
252
+ }
253
+ return bindings;
254
+ }
255
+ function collectImportBindings(sourceFile, localOnly = false) {
256
+ const bindings = [];
257
+ for (const statement of sourceFile.statements) {
258
+ if (ts.isImportDeclaration(statement))
259
+ bindings.push(...parseImportBindings(statement, localOnly));
260
+ }
261
+ return bindings;
262
+ }
263
+ function referencedIdentifiers(node, sourceFile) {
264
+ const ids = new Set();
265
+ const visit = (current) => {
266
+ if (ts.isIdentifier(current) && current.parent !== node) {
267
+ if (current.parent &&
268
+ ts.isPropertyAccessExpression(current.parent) &&
269
+ current.parent.expression === current) {
270
+ // keep root identifier only
271
+ }
272
+ else if (!(ts.isPropertyAccessExpression(current.parent) &&
273
+ current.parent.name === current)) {
274
+ ids.add(current.text);
275
+ }
276
+ }
277
+ if (ts.isJsxOpeningElement(current) ||
278
+ ts.isJsxSelfClosingElement(current)) {
279
+ const tag = current.tagName;
280
+ if (ts.isIdentifier(tag) && isPascalCase(tag.text))
281
+ ids.add(tag.text);
282
+ }
283
+ ts.forEachChild(current, visit);
284
+ };
285
+ visit(node);
286
+ return ids;
287
+ }
288
+ function findTopLevelDeclaration(sourceFile, name) {
289
+ for (const statement of sourceFile.statements) {
290
+ if (ts.isExportAssignment(statement) && name === "default")
291
+ return statement;
292
+ if (ts.isExportDeclaration(statement) && statement.exportClause) {
293
+ if (ts.isNamedExports(statement.exportClause)) {
294
+ const matches = statement.exportClause.elements.some((element) => (element.name ?? element.propertyName)?.text === name);
295
+ if (matches)
296
+ return statement;
297
+ }
298
+ }
299
+ const declName = topLevelDeclName(statement);
300
+ if (declName === name)
301
+ return statement;
302
+ if (name === "default" &&
303
+ ts.canHaveModifiers(statement) &&
304
+ statement.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.DefaultKeyword)) {
305
+ return statement;
306
+ }
307
+ }
308
+ return undefined;
309
+ }
310
+ function resolveReExportTarget(statement, localName) {
311
+ if (!statement.moduleSpecifier ||
312
+ !ts.isStringLiteral(statement.moduleSpecifier))
313
+ return undefined;
314
+ if (!statement.exportClause || !ts.isNamedExports(statement.exportClause))
315
+ return undefined;
316
+ const element = statement.exportClause.elements.find((entry) => (entry.name ?? entry.propertyName)?.text === localName);
317
+ if (!element)
318
+ return undefined;
319
+ return {
320
+ specifier: statement.moduleSpecifier.text,
321
+ exportedName: element.propertyName?.text ?? localName,
322
+ };
323
+ }
324
+ async function followDeclarationReference(record, declName, tsconfig, ensureModule, warnings) {
325
+ const sourceFile = createSourceFile(record.path, record.text);
326
+ const decl = findTopLevelDeclaration(sourceFile, declName);
327
+ if (!decl || !ts.isExportDeclaration(decl))
328
+ return { module: record, declName };
329
+ const reexport = resolveReExportTarget(decl, declName);
330
+ if (!reexport) {
331
+ warnings.push(`Over-approximating unresolved re-export ${declName} in ${record.path}`);
332
+ return { module: record, declName };
333
+ }
334
+ const importedPath = await resolveImportPath(dirname(record.path), reexport.specifier, tsconfig);
335
+ if (!importedPath)
336
+ return undefined;
337
+ const target = await ensureModule(importedPath);
338
+ if (!target)
339
+ return undefined;
340
+ return followDeclarationReference(target, reexport.exportedName, tsconfig, ensureModule, warnings);
341
+ }
342
+ function classifyImportEdge(adapter, ctx) {
343
+ if (adapter?.classifyImportEdge)
344
+ return adapter.classifyImportEdge(ctx);
345
+ return ctx.isTypeOnly ? "type" : "unknown";
346
+ }
347
+ function isServerOnlyTarget(adapter, path, classification) {
348
+ if (adapter?.isServerOnlyModule?.(path))
349
+ return true;
350
+ return classification.serverOnly === true;
351
+ }
352
+ function statementIncludedInSurface(statement, declNames, importLocals, typeDeclNames, typeImportLocals) {
353
+ if (ts.isImportDeclaration(statement)) {
354
+ const bindings = parseImportBindings(statement, false);
355
+ return bindings.some((binding) => binding.isTypeOnly
356
+ ? typeImportLocals.has(binding.local)
357
+ : importLocals.has(binding.local));
358
+ }
359
+ if (ts.isExportDeclaration(statement) && statement.exportClause) {
360
+ if (ts.isNamedExports(statement.exportClause)) {
361
+ return statement.exportClause.elements.some((element) => {
362
+ const local = (element.name ?? element.propertyName)?.text;
363
+ return local ? declNames.has(local) : false;
364
+ });
365
+ }
366
+ }
367
+ if (ts.isExportAssignment(statement))
368
+ return declNames.has("default");
369
+ if (isAnonymousDefaultExport(statement))
370
+ return declNames.has("default");
371
+ const name = topLevelDeclName(statement);
372
+ if (name && declNames.has(name))
373
+ return true;
374
+ return (name !== undefined &&
375
+ typeDeclNames.has(name) &&
376
+ (ts.isTypeAliasDeclaration(statement) ||
377
+ ts.isInterfaceDeclaration(statement) ||
378
+ ts.isEnumDeclaration(statement)));
379
+ }
380
+ function buildSurfaceText(sourceText, sourceFile, declNames, importLocals, typeDeclNames, typeImportLocals) {
381
+ const includedLines = new Set();
382
+ const lines = sourceText.split(/\r?\n/);
383
+ for (const statement of sourceFile.statements) {
384
+ if (!statementIncludedInSurface(statement, declNames, importLocals, typeDeclNames, typeImportLocals)) {
385
+ continue;
386
+ }
387
+ const start = sourceFile.getLineAndCharacterOfPosition(statement.getStart());
388
+ const end = sourceFile.getLineAndCharacterOfPosition(statement.getEnd());
389
+ for (let line = start.line; line <= end.line; line += 1) {
390
+ includedLines.add(line);
391
+ }
392
+ }
393
+ return lines
394
+ .map((line, index) => (includedLines.has(index) ? line : ""))
395
+ .join("\n");
396
+ }
397
+ function collectTypeReferences(node) {
398
+ const refs = new Set();
399
+ const visit = (current) => {
400
+ if (ts.isTypeReferenceNode(current) && ts.isIdentifier(current.typeName)) {
401
+ refs.add(current.typeName.text);
402
+ }
403
+ ts.forEachChild(current, visit);
404
+ };
405
+ visit(node);
406
+ return refs;
407
+ }
408
+ function expandTypeDependencies(record) {
409
+ const sourceFile = createSourceFile(record.path, record.text);
410
+ let changed = true;
411
+ while (changed) {
412
+ changed = false;
413
+ for (const typeName of [...record.typeDecls]) {
414
+ const decl = findTopLevelDeclaration(sourceFile, typeName);
415
+ if (!decl)
416
+ continue;
417
+ for (const ref of collectTypeReferences(decl)) {
418
+ const refDecl = findTopLevelDeclaration(sourceFile, ref);
419
+ if (refDecl &&
420
+ (ts.isTypeAliasDeclaration(refDecl) ||
421
+ ts.isInterfaceDeclaration(refDecl) ||
422
+ ts.isEnumDeclaration(refDecl)) &&
423
+ !record.typeDecls.has(ref)) {
424
+ record.typeDecls.add(ref);
425
+ changed = true;
426
+ }
427
+ }
428
+ }
429
+ }
430
+ }
431
+ function syncReferencedImports(record, surface) {
432
+ const declSet = surface === "render" ? record.renderDecls : record.interactionDecls;
433
+ const importSet = surface === "render" ? record.renderImports : record.interactionImports;
434
+ const sourceFile = createSourceFile(record.path, record.text);
435
+ const importBindings = collectImportBindings(sourceFile, false);
436
+ for (const declName of declSet) {
437
+ const decl = findTopLevelDeclaration(sourceFile, declName);
438
+ if (!decl)
439
+ continue;
440
+ for (const ref of referencedIdentifiers(decl, sourceFile)) {
441
+ const binding = importBindings.find((item) => item.local === ref);
442
+ if (binding)
443
+ importSet.add(binding.local);
444
+ }
445
+ }
446
+ }
447
+ function routeForFile(path, inventory, manifestDir) {
448
+ if (!inventory || !manifestDir)
449
+ return undefined;
450
+ const resolved = resolve(path);
451
+ return inventory.routes.find((node) => {
452
+ if (!node.file)
453
+ return false;
454
+ return resolve(manifestDir, node.file) === resolved;
455
+ });
456
+ }
457
+ export async function sourceWithReachableImports(entries, tsconfig, adapter, inventory) {
458
+ const warnings = [];
459
+ const modules = new Map();
460
+ const manifestEntry = entries.find((entry) => isManifestFile(entry.path));
461
+ const manifestDir = manifestEntry
462
+ ? dirname(resolve(manifestEntry.path))
463
+ : undefined;
464
+ const broadEntry = !inventory || inventory.routes.length === 0;
465
+ const entryPaths = new Set(entries.map((entry) => resolve(entry.path)));
466
+ const ensureModule = async (path, text) => {
467
+ const canonical = resolve(path);
468
+ const existing = modules.get(canonical);
469
+ if (existing)
470
+ return existing;
471
+ const sourceText = text ?? (await readFile(canonical, "utf8"));
472
+ const route = routeForFile(canonical, inventory, manifestDir);
473
+ const classification = classifyModule(adapter, canonical, sourceText, route);
474
+ const entryExports = moduleEntryExports(adapter, canonical, sourceText, route);
475
+ const record = {
476
+ path: canonical,
477
+ text: sourceText,
478
+ classification,
479
+ entryExports,
480
+ route,
481
+ isManifest: isManifestFile(canonical),
482
+ renderDecls: new Set(),
483
+ interactionDecls: new Set(),
484
+ renderImports: new Set(),
485
+ interactionImports: new Set(),
486
+ typeDecls: new Set(),
487
+ typeImports: new Set(),
488
+ };
489
+ modules.set(canonical, record);
490
+ return record;
491
+ };
492
+ for (const entry of entries) {
493
+ await ensureModule(entry.path, entry.text);
494
+ }
495
+ const queue = [];
496
+ for (const record of modules.values()) {
497
+ const seeds = seedsForModule(record, broadEntry, entryPaths.has(resolve(record.path)));
498
+ for (const name of seeds.render)
499
+ record.renderDecls.add(name);
500
+ for (const name of seeds.interaction)
501
+ record.interactionDecls.add(name);
502
+ if (record.renderDecls.size > 0)
503
+ queue.push({ path: record.path, surface: "render" });
504
+ if (record.interactionDecls.size > 0)
505
+ queue.push({ path: record.path, surface: "interaction" });
506
+ }
507
+ const fixpoint = (record, surface, declName) => {
508
+ const declSet = surface === "render" ? record.renderDecls : record.interactionDecls;
509
+ if (declSet.has(declName))
510
+ return;
511
+ declSet.add(declName);
512
+ queue.push({ path: record.path, surface });
513
+ };
514
+ while (queue.length > 0) {
515
+ const next = queue.shift();
516
+ if (!next)
517
+ break;
518
+ const record = modules.get(resolve(next.path));
519
+ if (!record || record.isManifest)
520
+ continue;
521
+ const declSet = next.surface === "render" ? record.renderDecls : record.interactionDecls;
522
+ const importSet = next.surface === "render"
523
+ ? record.renderImports
524
+ : record.interactionImports;
525
+ const sourceFile = createSourceFile(record.path, record.text);
526
+ const importBindings = collectImportBindings(sourceFile, true);
527
+ for (const declName of [...declSet]) {
528
+ const decl = findTopLevelDeclaration(sourceFile, declName);
529
+ if (!decl)
530
+ continue;
531
+ const refs = referencedIdentifiers(decl, sourceFile);
532
+ for (const ref of refs) {
533
+ const localDecl = findTopLevelDeclaration(sourceFile, ref);
534
+ if (localDecl) {
535
+ if (ts.isExportDeclaration(localDecl)) {
536
+ const resolved = await followDeclarationReference(record, ref, tsconfig, ensureModule, warnings);
537
+ if (resolved)
538
+ fixpoint(resolved.module, next.surface, resolved.declName);
539
+ continue;
540
+ }
541
+ fixpoint(record, next.surface, ref);
542
+ continue;
543
+ }
544
+ const binding = importBindings.find((item) => item.local === ref);
545
+ if (!binding)
546
+ continue;
547
+ if (binding.isTypeOnly ||
548
+ (binding.local === ref && binding.isTypeOnly)) {
549
+ record.typeImports.add(binding.local);
550
+ const importedPath = await resolveImportPath(dirname(record.path), binding.specifier, tsconfig);
551
+ if (!importedPath)
552
+ continue;
553
+ const target = await ensureModule(importedPath);
554
+ if (!target)
555
+ continue;
556
+ const targetFile = createSourceFile(target.path, target.text);
557
+ const importedName = binding.imported ?? binding.local;
558
+ const typeDecl = findTopLevelDeclaration(targetFile, importedName);
559
+ if (typeDecl && topLevelDeclName(typeDecl))
560
+ target.typeDecls.add(topLevelDeclName(typeDecl));
561
+ continue;
562
+ }
563
+ const edgeContext = classifyImportEdge(adapter, {
564
+ importer: record.path,
565
+ specifier: binding.specifier,
566
+ imported: binding.imported,
567
+ isTypeOnly: binding.isTypeOnly,
568
+ importerContext: record.classification.defaultContext,
569
+ surface: next.surface,
570
+ });
571
+ if (edgeContext === "type") {
572
+ record.typeImports.add(binding.local);
573
+ continue;
574
+ }
575
+ const importedPath = await resolveImportPath(dirname(record.path), binding.specifier, tsconfig);
576
+ if (!importedPath)
577
+ continue;
578
+ const target = await ensureModule(importedPath);
579
+ if (!target)
580
+ continue;
581
+ if (next.surface === "interaction" &&
582
+ isServerOnlyTarget(adapter, target.path, target.classification)) {
583
+ warnings.push(`Client import skipped server-only module ${target.path} from ${record.path}`);
584
+ continue;
585
+ }
586
+ if (binding.local === "*" || !binding.imported) {
587
+ warnings.push(`Over-approximating namespace import ${binding.specifier} in ${record.path}`);
588
+ const broadSeeds = seedsForModule(target, true, false);
589
+ for (const name of broadSeeds.render)
590
+ target.renderDecls.add(name);
591
+ for (const name of broadSeeds.interaction)
592
+ target.interactionDecls.add(name);
593
+ importSet.add(binding.local);
594
+ queue.push({ path: target.path, surface: next.surface });
595
+ if (next.surface === "render" && isPascalCase(ref)) {
596
+ for (const name of broadSeeds.interaction)
597
+ target.interactionDecls.add(name);
598
+ queue.push({ path: target.path, surface: "interaction" });
599
+ }
600
+ continue;
601
+ }
602
+ importSet.add(binding.local);
603
+ const importedName = binding.imported ?? binding.local;
604
+ const resolved = await followDeclarationReference(target, importedName, tsconfig, ensureModule, warnings);
605
+ if (!resolved)
606
+ continue;
607
+ const promoteInteraction = isPascalCase(ref) &&
608
+ (resolved.module.classification.defaultContext === "client" ||
609
+ resolved.module.classification.defaultContext === "shared" ||
610
+ resolved.module.classification.defaultContext === "unknown" ||
611
+ !adapter?.classifyModule);
612
+ if (promoteInteraction) {
613
+ resolved.module.interactionDecls.add(resolved.declName);
614
+ resolved.module.renderDecls.add(resolved.declName);
615
+ queue.push({ path: resolved.module.path, surface: "interaction" });
616
+ queue.push({ path: resolved.module.path, surface: "render" });
617
+ }
618
+ else {
619
+ fixpoint(resolved.module, next.surface, resolved.declName);
620
+ }
621
+ }
622
+ }
623
+ }
624
+ for (const record of [...modules.values()]) {
625
+ expandTypeDependencies(record);
626
+ syncReferencedImports(record, "render");
627
+ syncReferencedImports(record, "interaction");
628
+ }
629
+ const effectApiProvenance = [];
630
+ const sources = [];
631
+ for (const record of [...modules.values()].sort((left, right) => left.path.localeCompare(right.path))) {
632
+ const sourceFile = createSourceFile(record.path, record.text);
633
+ const included = record.isManifest ||
634
+ record.renderDecls.size > 0 ||
635
+ record.interactionDecls.size > 0 ||
636
+ record.typeDecls.size > 0;
637
+ const renderText = record.isManifest
638
+ ? ""
639
+ : buildSurfaceText(record.text, sourceFile, record.renderDecls, record.renderImports, record.typeDecls, record.typeImports);
640
+ const interactionText = record.isManifest
641
+ ? ""
642
+ : buildSurfaceText(record.text, sourceFile, record.interactionDecls, record.interactionImports, record.typeDecls, record.typeImports);
643
+ if (interactionText) {
644
+ for (const entry of discoverFetchOps(interactionText, record.path)) {
645
+ effectApiProvenance.push(entry);
646
+ }
647
+ }
648
+ sources.push({
649
+ path: record.path,
650
+ text: record.text,
651
+ renderText,
652
+ interactionText,
653
+ included,
654
+ ...(record.classification.serverOnly && interactionText.length === 0
655
+ ? { excludedReason: "server-only module" }
656
+ : {}),
657
+ });
658
+ }
659
+ const renderSurface = sources.some((entry) => entry.renderText.length > 0);
660
+ if (!renderSurface && entries.length > 0) {
661
+ warnings.push("No render surface found for requested extraction entries");
662
+ }
663
+ if (!sources.some((entry) => entry.interactionText.length > 0)) {
664
+ warnings.push("No interaction surface found; extraction may produce a zero-transition client model");
665
+ }
666
+ const effectApis = [
667
+ ...new Set(effectApiProvenance.map((entry) => entry.opId)),
668
+ ].sort();
669
+ return { sources, warnings, effectApiProvenance, effectApis };
670
+ }
671
+ function discoverFetchOps(sourceText, fileName) {
672
+ const sourceFile = createSourceFile(fileName, sourceText);
673
+ const ops = [];
674
+ const visit = (node) => {
675
+ if (ts.isCallExpression(node) &&
676
+ ts.isIdentifier(node.expression) &&
677
+ node.expression.text === "fetch") {
678
+ const op = fetchOpId(node);
679
+ if (op) {
680
+ const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
681
+ ops.push({
682
+ opId: op,
683
+ source: {
684
+ file: fileName,
685
+ line: start.line + 1,
686
+ column: start.character + 1,
687
+ },
688
+ });
689
+ }
690
+ }
691
+ ts.forEachChild(node, visit);
692
+ };
693
+ visit(sourceFile);
694
+ return ops;
695
+ }
696
+ function fetchOpId(call) {
697
+ const first = call.arguments[0];
698
+ if (!first)
699
+ return undefined;
700
+ const path = fetchPathValue(first);
701
+ if (!path)
702
+ return undefined;
703
+ const method = fetchMethodValue(call.arguments[1]) ?? "GET";
704
+ return `${method} ${path}`;
705
+ }
706
+ function fetchPathValue(expression) {
707
+ if (ts.isStringLiteral(expression) ||
708
+ ts.isNoSubstitutionTemplateLiteral(expression))
709
+ return normalizeFetchPath(expression.text);
710
+ if (ts.isTemplateExpression(expression)) {
711
+ let value = expression.head.text;
712
+ for (const span of expression.templateSpans)
713
+ value += `:id${span.literal.text}`;
714
+ return normalizeFetchPath(value);
715
+ }
716
+ return undefined;
717
+ }
718
+ function normalizeFetchPath(path) {
719
+ return (path.startsWith("/") ? path : `/${path}`).replace(/\/:param(?=\/|$)/g, "/:id");
720
+ }
721
+ function fetchMethodValue(expression) {
722
+ if (!expression || !ts.isObjectLiteralExpression(expression))
723
+ return undefined;
724
+ for (const prop of expression.properties) {
725
+ if (ts.isPropertyAssignment(prop) &&
726
+ ts.isIdentifier(prop.name) &&
727
+ prop.name.text === "method" &&
728
+ ts.isStringLiteral(prop.initializer)) {
729
+ return prop.initializer.text.toUpperCase();
730
+ }
731
+ }
732
+ return undefined;
733
+ }
734
+ async function resolveImportPath(baseDir, specifier, tsconfig) {
735
+ if (specifier.startsWith("./+types/") || specifier.startsWith("../+types/"))
736
+ return undefined;
737
+ const bases = importBases(baseDir, specifier, tsconfig);
738
+ for (const base of bases) {
739
+ const resolved = await firstExistingModulePath(base);
740
+ if (resolved)
741
+ return resolved;
742
+ }
743
+ return undefined;
744
+ }
745
+ function importBases(baseDir, specifier, tsconfig) {
746
+ if (specifier.startsWith("."))
747
+ return [resolve(baseDir, specifier)];
748
+ const matches = tsconfig.paths.flatMap((entry) => {
749
+ if (!specifier.startsWith(entry.prefix) ||
750
+ !specifier.endsWith(entry.suffix))
751
+ return [];
752
+ const star = specifier.slice(entry.prefix.length, specifier.length - entry.suffix.length);
753
+ return entry.targets.map((target) => resolve(target.replace("*", star)));
754
+ });
755
+ if (matches.length > 0)
756
+ return matches;
757
+ return tsconfig.baseUrl ? [resolve(tsconfig.baseUrl, specifier)] : [];
758
+ }
759
+ async function firstExistingModulePath(base) {
760
+ const candidates = /\.[cm]?[jt]sx?$/.test(base)
761
+ ? [base]
762
+ : [
763
+ `${base}.ts`,
764
+ `${base}.tsx`,
765
+ `${base}.mts`,
766
+ `${base}.cts`,
767
+ join(base, "index.ts"),
768
+ join(base, "index.tsx"),
769
+ ];
770
+ for (const candidate of candidates) {
771
+ try {
772
+ const candidateStat = await stat(candidate);
773
+ if (candidateStat.isFile())
774
+ return candidate;
775
+ }
776
+ catch (error) {
777
+ if (error.code !== "ENOENT")
778
+ throw error;
779
+ }
780
+ }
781
+ return undefined;
782
+ }
783
+ //# sourceMappingURL=project.js.map