@nifrajs/cli 2.11.0 → 2.12.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 (177) hide show
  1. package/README.md +6 -0
  2. package/dist/assure-hydration.d.ts +14 -0
  3. package/dist/assure-hydration.d.ts.map +1 -0
  4. package/dist/assure-hydration.js +483 -0
  5. package/dist/assure-hydration.js.map +1 -0
  6. package/dist/assure.d.ts +35 -0
  7. package/dist/assure.d.ts.map +1 -1
  8. package/dist/assure.js +222 -1
  9. package/dist/assure.js.map +1 -1
  10. package/dist/capabilities-tool.d.ts +13 -0
  11. package/dist/capabilities-tool.d.ts.map +1 -1
  12. package/dist/capabilities-tool.js +64 -1
  13. package/dist/capabilities-tool.js.map +1 -1
  14. package/dist/check.d.ts +125 -10
  15. package/dist/check.d.ts.map +1 -1
  16. package/dist/check.js +706 -145
  17. package/dist/check.js.map +1 -1
  18. package/dist/cli.d.ts +23 -0
  19. package/dist/cli.d.ts.map +1 -1
  20. package/dist/cli.js +194 -10
  21. package/dist/cli.js.map +1 -1
  22. package/dist/contract-proof.d.ts +49 -0
  23. package/dist/contract-proof.d.ts.map +1 -0
  24. package/dist/contract-proof.js +128 -0
  25. package/dist/contract-proof.js.map +1 -0
  26. package/dist/contracts.d.ts +22 -0
  27. package/dist/contracts.d.ts.map +1 -0
  28. package/dist/contracts.js +134 -0
  29. package/dist/contracts.js.map +1 -0
  30. package/dist/diagnostics.d.ts +23 -0
  31. package/dist/diagnostics.d.ts.map +1 -0
  32. package/dist/diagnostics.js +9 -0
  33. package/dist/diagnostics.js.map +1 -0
  34. package/dist/diff-tool.d.ts +2 -0
  35. package/dist/diff-tool.d.ts.map +1 -1
  36. package/dist/diff-tool.js +1 -1
  37. package/dist/diff-tool.js.map +1 -1
  38. package/dist/docs-search.d.ts +8 -3
  39. package/dist/docs-search.d.ts.map +1 -1
  40. package/dist/docs-search.js +36 -15
  41. package/dist/docs-search.js.map +1 -1
  42. package/dist/doctor.d.ts +28 -2
  43. package/dist/doctor.d.ts.map +1 -1
  44. package/dist/doctor.js +173 -7
  45. package/dist/doctor.js.map +1 -1
  46. package/dist/examples.d.ts +1 -2
  47. package/dist/examples.d.ts.map +1 -1
  48. package/dist/examples.js +37 -16
  49. package/dist/examples.js.map +1 -1
  50. package/dist/fix-recipes.d.ts +12 -0
  51. package/dist/fix-recipes.d.ts.map +1 -0
  52. package/dist/fix-recipes.js +106 -0
  53. package/dist/fix-recipes.js.map +1 -0
  54. package/dist/init-agents.d.ts.map +1 -1
  55. package/dist/init-agents.js +60 -4
  56. package/dist/init-agents.js.map +1 -1
  57. package/dist/internal/source-facts.d.ts +15 -0
  58. package/dist/internal/source-facts.d.ts.map +1 -0
  59. package/dist/internal/source-facts.js +82 -0
  60. package/dist/internal/source-facts.js.map +1 -0
  61. package/dist/internal/typescript-import.d.ts +9 -0
  62. package/dist/internal/typescript-import.d.ts.map +1 -1
  63. package/dist/internal/typescript-import.js +38 -0
  64. package/dist/internal/typescript-import.js.map +1 -1
  65. package/dist/introspect.d.ts +6 -2
  66. package/dist/introspect.d.ts.map +1 -1
  67. package/dist/introspect.js +53 -4
  68. package/dist/introspect.js.map +1 -1
  69. package/dist/load.d.ts +6 -0
  70. package/dist/load.d.ts.map +1 -1
  71. package/dist/load.js.map +1 -1
  72. package/dist/manifest-tool.d.ts.map +1 -1
  73. package/dist/manifest-tool.js +14 -9
  74. package/dist/manifest-tool.js.map +1 -1
  75. package/dist/mcp-docs-tools.d.ts.map +1 -1
  76. package/dist/mcp-docs-tools.js +3 -0
  77. package/dist/mcp-docs-tools.js.map +1 -1
  78. package/dist/mcp-http.js +1 -1
  79. package/dist/mcp-root.d.ts +83 -0
  80. package/dist/mcp-root.d.ts.map +1 -0
  81. package/dist/mcp-root.js +221 -0
  82. package/dist/mcp-root.js.map +1 -0
  83. package/dist/mcp-run.d.ts.map +1 -1
  84. package/dist/mcp-run.js +18 -4
  85. package/dist/mcp-run.js.map +1 -1
  86. package/dist/mcp.d.ts +27 -4
  87. package/dist/mcp.d.ts.map +1 -1
  88. package/dist/mcp.js +358 -25
  89. package/dist/mcp.js.map +1 -1
  90. package/dist/project-path.d.ts +3 -0
  91. package/dist/project-path.d.ts.map +1 -0
  92. package/dist/project-path.js +36 -0
  93. package/dist/project-path.js.map +1 -0
  94. package/dist/replay.d.ts +12 -0
  95. package/dist/replay.d.ts.map +1 -0
  96. package/dist/replay.js +36 -0
  97. package/dist/replay.js.map +1 -0
  98. package/dist/rules/codes.d.ts +42 -0
  99. package/dist/rules/codes.d.ts.map +1 -0
  100. package/dist/rules/codes.js +52 -0
  101. package/dist/rules/codes.js.map +1 -0
  102. package/dist/rules/comment-markers.d.ts +10 -0
  103. package/dist/rules/comment-markers.d.ts.map +1 -0
  104. package/dist/rules/comment-markers.js +24 -0
  105. package/dist/rules/comment-markers.js.map +1 -0
  106. package/dist/rules/index.d.ts +31 -0
  107. package/dist/rules/index.d.ts.map +1 -0
  108. package/dist/rules/index.js +133 -0
  109. package/dist/rules/index.js.map +1 -0
  110. package/dist/rules/legacy.d.ts +7 -0
  111. package/dist/rules/legacy.d.ts.map +1 -0
  112. package/dist/rules/legacy.js +85 -0
  113. package/dist/rules/legacy.js.map +1 -0
  114. package/dist/rules/routes.d.ts +12 -0
  115. package/dist/rules/routes.d.ts.map +1 -0
  116. package/dist/rules/routes.js +142 -0
  117. package/dist/rules/routes.js.map +1 -0
  118. package/dist/rules/security.d.ts +10 -0
  119. package/dist/rules/security.d.ts.map +1 -0
  120. package/dist/rules/security.js +471 -0
  121. package/dist/rules/security.js.map +1 -0
  122. package/dist/search-terms.d.ts +2 -0
  123. package/dist/search-terms.d.ts.map +1 -1
  124. package/dist/search-terms.js +5 -1
  125. package/dist/search-terms.js.map +1 -1
  126. package/dist/types-search.d.ts +1 -2
  127. package/dist/types-search.d.ts.map +1 -1
  128. package/dist/types-search.js +33 -16
  129. package/dist/types-search.js.map +1 -1
  130. package/dist/verification.d.ts +9 -0
  131. package/dist/verification.d.ts.map +1 -1
  132. package/dist/verification.js +17 -1
  133. package/dist/verification.js.map +1 -1
  134. package/dist/work-graph.d.ts +123 -0
  135. package/dist/work-graph.d.ts.map +1 -0
  136. package/dist/work-graph.js +465 -0
  137. package/dist/work-graph.js.map +1 -0
  138. package/docs/examples.json +32 -2
  139. package/docs/llms-full.txt +906 -15
  140. package/docs/types.json +1682 -130
  141. package/package.json +34 -9
  142. package/src/assure-hydration.ts +606 -0
  143. package/src/assure.ts +316 -2
  144. package/src/capabilities-tool.ts +80 -1
  145. package/src/check.ts +873 -168
  146. package/src/cli.ts +198 -11
  147. package/src/contract-proof.ts +194 -0
  148. package/src/contracts.ts +157 -0
  149. package/src/diagnostics.ts +32 -0
  150. package/src/diff-tool.ts +1 -1
  151. package/src/docs-search.ts +45 -15
  152. package/src/doctor.ts +259 -8
  153. package/src/examples.ts +43 -15
  154. package/src/fix-recipes.ts +126 -0
  155. package/src/init-agents.ts +61 -4
  156. package/src/internal/source-facts.ts +133 -0
  157. package/src/internal/typescript-import.ts +29 -0
  158. package/src/introspect.ts +75 -6
  159. package/src/load.ts +6 -0
  160. package/src/manifest-tool.ts +16 -10
  161. package/src/mcp-docs-tools.ts +3 -0
  162. package/src/mcp-http.ts +1 -1
  163. package/src/mcp-root.ts +271 -0
  164. package/src/mcp-run.ts +19 -4
  165. package/src/mcp.ts +438 -32
  166. package/src/project-path.ts +36 -0
  167. package/src/replay.ts +44 -0
  168. package/src/rules/codes.ts +57 -0
  169. package/src/rules/comment-markers.ts +26 -0
  170. package/src/rules/index.ts +175 -0
  171. package/src/rules/legacy.ts +93 -0
  172. package/src/rules/routes.ts +157 -0
  173. package/src/rules/security.ts +505 -0
  174. package/src/search-terms.ts +9 -4
  175. package/src/types-search.ts +39 -15
  176. package/src/verification.ts +18 -1
  177. package/src/work-graph.ts +613 -0
@@ -0,0 +1,471 @@
1
+ import { diagnostic } from "../diagnostics.js";
2
+ import { importProjectTypeScript } from "../internal/typescript-import.js";
3
+ import { commentBlockHasMarker } from "./comment-markers.js";
4
+ /**
5
+ * A security rule that cannot run says so as an advisory finding instead of silently returning no
6
+ * findings - a report that skipped a scanner reads as "scanned and safe", which is a lie. Same
7
+ * contract the interpolated-SQL rule already keeps.
8
+ */
9
+ function didNotRun(code, title) {
10
+ return [
11
+ diagnostic({
12
+ code,
13
+ severity: "warn",
14
+ message: `${title} (${code}) did NOT run - TypeScript is not installed, so this report says nothing about it`,
15
+ fix: { recipe: "toolchain.install-typescript", command: "bun add -d typescript" },
16
+ verify: "nifra check --lints-only",
17
+ }),
18
+ ];
19
+ }
20
+ const SECRET = /(?:token|secret|apiKey|api_key|signature|hmac|password)/i;
21
+ const PII = /(?:email|phone|ssn|password|token|authorization)/i;
22
+ const REVIEWED = "@nifra-gate-reviewed";
23
+ const parseCache = new WeakMap();
24
+ // The marker counts anywhere in the contiguous comment block above the finding (or trailing on its
25
+ // line) - a human writing the multi-line justification the hatch asks for must not un-suppress the
26
+ // finding by growing the comment past two lines.
27
+ function hasReview(lines, line) {
28
+ return commentBlockHasMarker(lines, line, REVIEWED);
29
+ }
30
+ function reviewedEvidence(lines, line) {
31
+ return hasReview(lines, line) ? [REVIEWED] : [];
32
+ }
33
+ /**
34
+ * NF-S002 severity by file role. Server-side code compares real secret material - a timing oracle
35
+ * there is exploitable, so it fails the gate. Client-bundled code (route modules, .tsx/.jsx)
36
+ * compares values the client already holds, so the same shape is advisory rather than a gate
37
+ * failure. A server marker beats a client marker (`routes/x.server.ts` is server), and a plain .ts
38
+ * that cannot be classified is treated as server - fail closed.
39
+ */
40
+ function secretComparisonSeverity(file) {
41
+ const path = file.replaceAll("\\", "/");
42
+ if (/\.server\.[cm]?[tj]sx?$/.test(path))
43
+ return "error";
44
+ if (/(?:^|\/)server\//.test(path) || /(?:^|\/)backend\.[cm]?[tj]s$/.test(path))
45
+ return "error";
46
+ if (/\.[tj]sx$/.test(path) || /(?:^|\/)routes\//.test(path))
47
+ return "warn";
48
+ return "error";
49
+ }
50
+ function nameOf(ts, node) {
51
+ if (ts.isIdentifier(node))
52
+ return node.text;
53
+ if (ts.isPropertyAccessExpression(node))
54
+ return node.name.text;
55
+ return undefined;
56
+ }
57
+ /**
58
+ * Presence checks (`token === undefined`, `secret == null`, `apiKey !== ""`) and `typeof` guards are
59
+ * not equality over secret material - rewriting them to a timing-safe comparison would break the code.
60
+ */
61
+ function isPresenceComparison(ts, node) {
62
+ for (const side of [node.left, node.right]) {
63
+ if (ts.isIdentifier(side) && side.text === "undefined")
64
+ return true;
65
+ if (side.kind === ts.SyntaxKind.NullKeyword)
66
+ return true;
67
+ if (ts.isStringLiteralLike(side) && side.text === "")
68
+ return true;
69
+ if (ts.isTypeOfExpression(side))
70
+ return true;
71
+ if (ts.isVoidExpression(side))
72
+ return true;
73
+ }
74
+ return false;
75
+ }
76
+ function parse(ts, file, source) {
77
+ const kind = /\.tsx?$/.test(file) ? ts.ScriptKind.TSX : ts.ScriptKind.JS;
78
+ return ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, kind);
79
+ }
80
+ function parsedFile(ts, sources, file) {
81
+ let files = parseCache.get(sources);
82
+ if (files === undefined) {
83
+ files = new Map();
84
+ parseCache.set(sources, files);
85
+ }
86
+ const cached = files.get(file);
87
+ if (cached !== undefined)
88
+ return cached;
89
+ const source = sources.read(file);
90
+ if (source === undefined)
91
+ return undefined;
92
+ const tree = parse(ts, file, source);
93
+ const parsed = { tree, lines: tree.text.split("\n") };
94
+ files.set(file, parsed);
95
+ return parsed;
96
+ }
97
+ export const secretComparisonRule = {
98
+ code: "NF-S002",
99
+ title: "Non-constant-time secret comparison",
100
+ async scan(ctx) {
101
+ const ts = await importProjectTypeScript(ctx.root);
102
+ if (ts === undefined)
103
+ return didNotRun("NF-S002", "Non-constant-time secret comparison scan");
104
+ const findings = [];
105
+ for (const file of ctx.sources.files) {
106
+ const parsed = parsedFile(ts, ctx.sources, file);
107
+ if (parsed === undefined)
108
+ continue;
109
+ const { tree, lines } = parsed;
110
+ const visit = (node) => {
111
+ if (ts.isBinaryExpression(node) &&
112
+ [
113
+ ts.SyntaxKind.EqualsEqualsEqualsToken,
114
+ ts.SyntaxKind.ExclamationEqualsEqualsToken,
115
+ ts.SyntaxKind.EqualsEqualsToken,
116
+ ts.SyntaxKind.ExclamationEqualsToken,
117
+ ].includes(node.operatorToken.kind) &&
118
+ !isPresenceComparison(ts, node)) {
119
+ const left = nameOf(ts, node.left);
120
+ const right = nameOf(ts, node.right);
121
+ if ((left !== undefined && SECRET.test(left)) ||
122
+ (right !== undefined && SECRET.test(right))) {
123
+ const line = tree.getLineAndCharacterOfPosition(node.getStart(tree)).line + 1;
124
+ const reviewed = hasReview(lines, line);
125
+ findings.push(diagnostic({
126
+ code: "NF-S002",
127
+ severity: reviewed ? "info" : secretComparisonSeverity(file),
128
+ file,
129
+ line,
130
+ message: reviewed
131
+ ? "secret comparison is explicitly marked as reviewed"
132
+ : "secret-like values must use a length check and timing-safe comparison",
133
+ evidence: [left ?? right ?? "secret comparison", ...reviewedEvidence(lines, line)],
134
+ ...(reviewed
135
+ ? {}
136
+ : {
137
+ fix: {
138
+ recipe: "security.timing-safe-equal",
139
+ command: "nifra fix --code NF-S002",
140
+ },
141
+ }),
142
+ verify: "nifra check --lints-only",
143
+ }));
144
+ }
145
+ }
146
+ ts.forEachChild(node, visit);
147
+ };
148
+ visit(tree);
149
+ }
150
+ return findings;
151
+ },
152
+ };
153
+ export const piiLogRule = {
154
+ code: "NF-S003",
155
+ title: "Sensitive value in log call",
156
+ async scan(ctx) {
157
+ const ts = await importProjectTypeScript(ctx.root);
158
+ if (ts === undefined)
159
+ return didNotRun("NF-S003", "Sensitive-value-in-log scan");
160
+ const findings = [];
161
+ for (const file of ctx.sources.files) {
162
+ const parsed = parsedFile(ts, ctx.sources, file);
163
+ if (parsed === undefined)
164
+ continue;
165
+ const { tree, lines } = parsed;
166
+ const visit = (node) => {
167
+ if (ts.isCallExpression(node)) {
168
+ const expression = node.expression;
169
+ const callee = ts.isPropertyAccessExpression(expression)
170
+ ? `${nameOf(ts, expression.expression) ?? ""}.${expression.name.text}`
171
+ : (nameOf(ts, expression) ?? "");
172
+ if (/^(?:console|logger)\./.test(callee)) {
173
+ for (const arg of node.arguments) {
174
+ const name = nameOf(ts, arg);
175
+ if (name !== undefined && PII.test(name)) {
176
+ const line = tree.getLineAndCharacterOfPosition(node.getStart(tree)).line + 1;
177
+ const reviewed = hasReview(lines, line);
178
+ findings.push(diagnostic({
179
+ code: "NF-S003",
180
+ severity: reviewed ? "info" : "warn",
181
+ file,
182
+ line,
183
+ message: reviewed
184
+ ? "sensitive value in log call is explicitly marked as reviewed"
185
+ : "PII-shaped values must not be passed directly to log calls",
186
+ evidence: [name, ...reviewedEvidence(lines, line)],
187
+ verify: "nifra check --lints-only",
188
+ }));
189
+ }
190
+ }
191
+ }
192
+ }
193
+ ts.forEachChild(node, visit);
194
+ };
195
+ visit(tree);
196
+ }
197
+ return findings;
198
+ },
199
+ };
200
+ export const failOpenGateRule = {
201
+ code: "NF-S001",
202
+ title: "Fail-open gate",
203
+ async scan(ctx) {
204
+ const ts = await importProjectTypeScript(ctx.root);
205
+ if (ts === undefined)
206
+ return didNotRun("NF-S001", "Fail-open gate scan");
207
+ const findings = [];
208
+ for (const file of ctx.sources.files) {
209
+ const parsed = parsedFile(ts, ctx.sources, file);
210
+ if (parsed === undefined)
211
+ continue;
212
+ const { tree, lines } = parsed;
213
+ const visit = (node) => {
214
+ if (ts.isCatchClause(node)) {
215
+ let parent = node.parent;
216
+ while (parent !== undefined && !ts.isFunctionLike(parent))
217
+ parent = parent.parent;
218
+ const name = ts.isFunctionLike(parent) && parent.name && ts.isIdentifier(parent.name)
219
+ ? parent.name.text
220
+ : undefined;
221
+ if (name !== undefined && /^(?:require|assert|can|authorize)/i.test(name)) {
222
+ const text = node.block.getText(tree);
223
+ if (!/\b(?:throw|return\s+(?:false|new\s+Response|deny))/s.test(text)) {
224
+ const line = tree.getLineAndCharacterOfPosition(node.getStart(tree)).line + 1;
225
+ const reviewed = hasReview(lines, line);
226
+ findings.push(diagnostic({
227
+ code: "NF-S001",
228
+ severity: reviewed ? "info" : "error",
229
+ file,
230
+ line,
231
+ message: reviewed
232
+ ? "gate catch block is explicitly marked as reviewed"
233
+ : "gate catch blocks must rethrow or return an explicit denial",
234
+ evidence: [
235
+ name,
236
+ "catch block has no denial or rethrow",
237
+ ...reviewedEvidence(lines, line),
238
+ ],
239
+ verify: "nifra check --lints-only",
240
+ }));
241
+ }
242
+ }
243
+ }
244
+ ts.forEachChild(node, visit);
245
+ };
246
+ visit(tree);
247
+ }
248
+ return findings;
249
+ },
250
+ };
251
+ /** True when `param` is referenced anywhere inside `body`. */
252
+ function usesParam(ts, body, param) {
253
+ let used = false;
254
+ const walk = (n) => {
255
+ if (used)
256
+ return;
257
+ if (ts.isIdentifier(n) && n.text === param) {
258
+ used = true;
259
+ return;
260
+ }
261
+ ts.forEachChild(n, walk);
262
+ };
263
+ walk(body);
264
+ return used;
265
+ }
266
+ export const corsOriginPredicateRule = {
267
+ code: "NF-S004",
268
+ title: "CORS origin predicate ignores the origin",
269
+ async scan(ctx) {
270
+ const ts = await importProjectTypeScript(ctx.root);
271
+ if (ts === undefined)
272
+ return didNotRun("NF-S004", "CORS origin predicate scan");
273
+ const findings = [];
274
+ for (const file of ctx.sources.files) {
275
+ const parsed = parsedFile(ts, ctx.sources, file);
276
+ if (parsed === undefined)
277
+ continue;
278
+ const { tree, lines } = parsed;
279
+ const visit = (node) => {
280
+ if (ts.isCallExpression(node) && nameOf(ts, node.expression) === "cors") {
281
+ const arg = node.arguments[0];
282
+ if (arg !== undefined && ts.isObjectLiteralExpression(arg)) {
283
+ for (const prop of arg.properties) {
284
+ if (ts.isPropertyAssignment(prop) &&
285
+ nameOf(ts, prop.name) === "origin" &&
286
+ (ts.isArrowFunction(prop.initializer) || ts.isFunctionExpression(prop.initializer))) {
287
+ const fn = prop.initializer;
288
+ const param = fn.parameters[0]?.name;
289
+ // A predicate with no parameter, or one that never reads it, allows every origin -
290
+ // the guardrail the predicate form exists for is bypassed. A destructured parameter
291
+ // is left alone (can't cheaply prove non-use).
292
+ const ignoresOrigin = param === undefined
293
+ ? true
294
+ : ts.isIdentifier(param)
295
+ ? !usesParam(ts, fn.body, param.text)
296
+ : false;
297
+ if (ignoresOrigin) {
298
+ const line = tree.getLineAndCharacterOfPosition(prop.getStart(tree)).line + 1;
299
+ const reviewed = hasReview(lines, line);
300
+ findings.push(diagnostic({
301
+ code: "NF-S004",
302
+ severity: reviewed ? "info" : "warn",
303
+ file,
304
+ line,
305
+ message: reviewed
306
+ ? "constant CORS origin predicate is explicitly marked as reviewed"
307
+ : "CORS origin predicate never inspects the origin - it allows every origin; list explicit origins or use the argument",
308
+ evidence: ["origin predicate", ...reviewedEvidence(lines, line)],
309
+ verify: "nifra check --lints-only",
310
+ }));
311
+ }
312
+ }
313
+ }
314
+ }
315
+ }
316
+ ts.forEachChild(node, visit);
317
+ };
318
+ visit(tree);
319
+ }
320
+ return findings;
321
+ },
322
+ };
323
+ export const externalRedirectRule = {
324
+ code: "NF-S005",
325
+ title: "External redirect opt-out",
326
+ async scan(ctx) {
327
+ const ts = await importProjectTypeScript(ctx.root);
328
+ if (ts === undefined)
329
+ return didNotRun("NF-S005", "External redirect opt-out scan");
330
+ const findings = [];
331
+ for (const file of ctx.sources.files) {
332
+ const parsed = parsedFile(ts, ctx.sources, file);
333
+ if (parsed === undefined)
334
+ continue;
335
+ const { tree, lines } = parsed;
336
+ const visit = (node) => {
337
+ if (ts.isCallExpression(node) && nameOf(ts, node.expression) === "redirect") {
338
+ for (const arg of node.arguments) {
339
+ if (!ts.isObjectLiteralExpression(arg))
340
+ continue;
341
+ for (const prop of arg.properties) {
342
+ if (ts.isPropertyAssignment(prop) &&
343
+ nameOf(ts, prop.name) === "external" &&
344
+ prop.initializer.kind === ts.SyntaxKind.TrueKeyword) {
345
+ const line = tree.getLineAndCharacterOfPosition(node.getStart(tree)).line + 1;
346
+ const reviewed = hasReview(lines, line);
347
+ findings.push(diagnostic({
348
+ code: "NF-S005",
349
+ severity: reviewed ? "info" : "warn",
350
+ file,
351
+ line,
352
+ message: reviewed
353
+ ? "external redirect is explicitly marked as reviewed"
354
+ : "redirect opts out of the same-origin default - verify the target can never be derived from request input (open-redirect risk)",
355
+ evidence: ["external: true", ...reviewedEvidence(lines, line)],
356
+ verify: "nifra check --lints-only",
357
+ }));
358
+ }
359
+ }
360
+ }
361
+ }
362
+ ts.forEachChild(node, visit);
363
+ };
364
+ visit(tree);
365
+ }
366
+ return findings;
367
+ },
368
+ };
369
+ const ESCAPE_HATCHES = {
370
+ allowLengthless: "body-limit stops publishing BODY_BOUNDED evidence - lengthless (chunked) bodies bypass the Content-Length gate",
371
+ allowGlobalKey: "rate limiting collapses to one shared bucket - any client can exhaust the global allowance for everyone",
372
+ allowInProduction: "in-memory rate-limit store in production - counts are per-instance and reset on restart",
373
+ };
374
+ export const assuranceEscapeHatchRule = {
375
+ code: "NF-S006",
376
+ title: "Security escape hatch enabled",
377
+ async scan(ctx) {
378
+ const ts = await importProjectTypeScript(ctx.root);
379
+ if (ts === undefined)
380
+ return didNotRun("NF-S006", "Security escape hatch scan");
381
+ const findings = [];
382
+ for (const file of ctx.sources.files) {
383
+ const parsed = parsedFile(ts, ctx.sources, file);
384
+ if (parsed === undefined)
385
+ continue;
386
+ const { tree, lines } = parsed;
387
+ const visit = (node) => {
388
+ if (ts.isPropertyAssignment(node) && node.initializer.kind === ts.SyntaxKind.TrueKeyword) {
389
+ const name = nameOf(ts, node.name);
390
+ const consequence = name !== undefined ? ESCAPE_HATCHES[name] : undefined;
391
+ if (name !== undefined && consequence !== undefined) {
392
+ const line = tree.getLineAndCharacterOfPosition(node.getStart(tree)).line + 1;
393
+ const reviewed = hasReview(lines, line);
394
+ findings.push(diagnostic({
395
+ code: "NF-S006",
396
+ severity: reviewed ? "info" : "warn",
397
+ file,
398
+ line,
399
+ message: reviewed
400
+ ? `${name} escape hatch is explicitly marked as reviewed`
401
+ : `${name}: ${consequence}`,
402
+ evidence: [name, ...reviewedEvidence(lines, line)],
403
+ verify: "nifra check --lints-only",
404
+ }));
405
+ }
406
+ }
407
+ ts.forEachChild(node, visit);
408
+ };
409
+ visit(tree);
410
+ }
411
+ return findings;
412
+ },
413
+ };
414
+ const COOKIE_PREFIX = /^__(?:Host|Secure)-/;
415
+ export const unprefixedSecureCookieRule = {
416
+ code: "NF-S007",
417
+ title: "Secure cookie without a __Host-/__Secure- prefix",
418
+ async scan(ctx) {
419
+ const ts = await importProjectTypeScript(ctx.root);
420
+ if (ts === undefined)
421
+ return didNotRun("NF-S007", "Secure cookie prefix scan");
422
+ const findings = [];
423
+ for (const file of ctx.sources.files) {
424
+ const parsed = parsedFile(ts, ctx.sources, file);
425
+ if (parsed === undefined)
426
+ continue;
427
+ const { tree } = parsed;
428
+ const visit = (node) => {
429
+ if (ts.isCallExpression(node)) {
430
+ const callee = nameOf(ts, node.expression);
431
+ if (callee === "cookie" || callee === "serializeCookie") {
432
+ const nameArg = node.arguments[0];
433
+ const hasPrefix = nameArg !== undefined &&
434
+ ts.isStringLiteralLike(nameArg) &&
435
+ COOKIE_PREFIX.test(nameArg.text);
436
+ const secure = node.arguments.some((arg) => ts.isObjectLiteralExpression(arg) &&
437
+ arg.properties.some((prop) => ts.isPropertyAssignment(prop) &&
438
+ nameOf(ts, prop.name) === "secure" &&
439
+ prop.initializer.kind === ts.SyntaxKind.TrueKeyword));
440
+ // Only literal names are judged - a dynamic name can't be checked for a prefix.
441
+ if (secure && !hasPrefix && nameArg !== undefined && ts.isStringLiteralLike(nameArg)) {
442
+ const line = tree.getLineAndCharacterOfPosition(node.getStart(tree)).line + 1;
443
+ findings.push(diagnostic({
444
+ code: "NF-S007",
445
+ severity: "info",
446
+ file,
447
+ line,
448
+ message: "Secure cookie without a __Host-/__Secure- prefix - a prefix stops subdomains and insecure contexts from shadowing it",
449
+ evidence: [nameArg.text],
450
+ verify: "nifra check --lints-only",
451
+ }));
452
+ }
453
+ }
454
+ }
455
+ ts.forEachChild(node, visit);
456
+ };
457
+ visit(tree);
458
+ }
459
+ return findings;
460
+ },
461
+ };
462
+ export const securityRules = Object.freeze([
463
+ failOpenGateRule,
464
+ secretComparisonRule,
465
+ piiLogRule,
466
+ corsOriginPredicateRule,
467
+ externalRedirectRule,
468
+ assuranceEscapeHatchRule,
469
+ unprefixedSecureCookieRule,
470
+ ]);
471
+ //# sourceMappingURL=security.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.js","sourceRoot":"","sources":["../../src/rules/security.ts"],"names":[],"mappings":"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"}
@@ -10,6 +10,8 @@ export interface SearchTermGroup {
10
10
  readonly term: string;
11
11
  readonly variants: readonly string[];
12
12
  }
13
+ export declare const MAX_QUERY_CHARS = 256;
14
+ export declare const MAX_QUERY_TERMS = 12;
13
15
  export declare const tokenize: (s: string) => string[];
14
16
  export declare function queryTermGroups(query: string): SearchTermGroup[];
15
17
  export declare function tokenSetHas(tokens: ReadonlySet<string>, group: SearchTermGroup): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"search-terms.d.ts","sourceRoot":"","sources":["../src/search-terms.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;CACrC;AAkCD,eAAO,MAAM,QAAQ,GAAI,GAAG,MAAM,KAAG,MAAM,EAIX,CAAA;AAEhC,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,EAAE,CAKhE;AAQD,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAOxF;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAchG"}
1
+ {"version":3,"file":"search-terms.d.ts","sourceRoot":"","sources":["../src/search-terms.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;CACrC;AAkCD,eAAO,MAAM,eAAe,MAAM,CAAA;AAClC,eAAO,MAAM,eAAe,KAAK,CAAA;AAEjC,eAAO,MAAM,QAAQ,GAAI,GAAG,MAAM,KAAG,MAAM,EAIX,CAAA;AAEhC,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,EAAE,CAOhE;AAQD,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAOxF;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAchG"}
@@ -37,12 +37,16 @@ const ALIASES = {
37
37
  websockets: ["websocket", "ws"],
38
38
  ws: ["websocket", "websockets"],
39
39
  };
40
+ export const MAX_QUERY_CHARS = 256;
41
+ export const MAX_QUERY_TERMS = 12;
40
42
  export const tokenize = (s) => s
41
43
  .toLowerCase()
42
44
  .split(/[^a-z0-9]+/)
43
45
  .filter((t) => t.length > 1);
44
46
  export function queryTermGroups(query) {
45
- return [...new Set(tokenize(query))].map((term) => ({
47
+ return [...new Set(tokenize(query.slice(0, MAX_QUERY_CHARS)))]
48
+ .slice(0, MAX_QUERY_TERMS)
49
+ .map((term) => ({
46
50
  term,
47
51
  variants: [term, ...(ALIASES[term] ?? [])],
48
52
  }));
@@ -1 +1 @@
1
- {"version":3,"file":"search-terms.js","sourceRoot":"","sources":["../src/search-terms.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,MAAM,OAAO,GAAgD;IAC3D,MAAM,EAAE,CAAC,SAAS,CAAC;IACnB,OAAO,EAAE,CAAC,QAAQ,CAAC;IACnB,GAAG,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC;IAC1B,IAAI,EAAE,CAAC,gBAAgB,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,CAAC;IACxD,cAAc,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,CAAC;IACxD,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC;IAC1B,KAAK,EAAE,CAAC,SAAS,EAAE,KAAK,CAAC;IACzB,OAAO,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC;IACzB,MAAM,EAAE,CAAC,OAAO,CAAC;IACjB,MAAM,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,CAAC;IACrE,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,CAAC;IACrE,MAAM,EAAE,CAAC,SAAS,CAAC;IACnB,OAAO,EAAE,CAAC,QAAQ,CAAC;IACnB,KAAK,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC;IAC5B,MAAM,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC;IAC5B,MAAM,EAAE,CAAC,SAAS,EAAE,YAAY,EAAE,UAAU,CAAC;IAC7C,OAAO,EAAE,CAAC,QAAQ,EAAE,YAAY,EAAE,UAAU,CAAC;IAC7C,MAAM,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,OAAO,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,SAAS,CAAC;IACpE,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,SAAS,CAAC;IACpE,GAAG,EAAE,CAAC,aAAa,EAAE,QAAQ,EAAE,SAAS,CAAC;IACzC,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC;IAClC,MAAM,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,CAAC,YAAY,EAAE,QAAQ,EAAE,SAAS,CAAC;IAC7C,UAAU,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,SAAS,CAAC;IAC7C,SAAS,EAAE,CAAC,YAAY,EAAE,IAAI,CAAC;IAC/B,UAAU,EAAE,CAAC,WAAW,EAAE,IAAI,CAAC;IAC/B,EAAE,EAAE,CAAC,WAAW,EAAE,YAAY,CAAC;CAChC,CAAA;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,CAAS,EAAY,EAAE,CAC9C,CAAC;KACE,WAAW,EAAE;KACb,KAAK,CAAC,YAAY,CAAC;KACnB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;AAEhC,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAClD,IAAI;QACJ,QAAQ,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;KAC3C,CAAC,CAAC,CAAA;AACL,CAAC;AAED,SAAS,YAAY,CAAC,IAAY,EAAE,KAAa;IAC/C,IAAI,IAAI,KAAK,KAAK;QAAE,OAAO,IAAI,CAAA;IAC/B,2FAA2F;IAC3F,OAAO,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAA;AACpG,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAA2B,EAAE,KAAsB;IAC7E,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACrC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,IAAI,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAA;QAC/C,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,SAAiB,EAAE,KAAsB,EAAE,OAAe;IACtF,IAAI,IAAI,GAAG,CAAC,CAAA;IACZ,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACrC,IAAI,IAAI,GAAG,CAAC,CAAA;QACZ,IAAI,IAAI,GAAG,CAAC,CAAA;QACZ,OAAO,IAAI,GAAG,OAAO,EAAE,CAAC;YACtB,MAAM,EAAE,GAAG,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;YAC3C,IAAI,EAAE,KAAK,CAAC,CAAC;gBAAE,MAAK;YACpB,IAAI,EAAE,CAAA;YACN,IAAI,GAAG,EAAE,GAAG,OAAO,CAAC,MAAM,CAAA;QAC5B,CAAC;QACD,IAAI,IAAI,GAAG,IAAI;YAAE,IAAI,GAAG,IAAI,CAAA;IAC9B,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
1
+ {"version":3,"file":"search-terms.js","sourceRoot":"","sources":["../src/search-terms.ts"],"names":[],"mappings":"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"}
@@ -31,7 +31,6 @@ export declare function searchTypes(types: readonly TypeEntry[], query: string,
31
31
  * so this is the complete answer - there is no need to read a `.d.ts` file.
32
32
  */
33
33
  export declare function renderTypesResult(types: readonly TypeEntry[], name: string | undefined, query: string | undefined, limit: number, full?: boolean): string;
34
- /** Load the bundled type corpus (`docs/types.json`), resolved relative to this module so it works from
35
- * `src/` (dev) and `dist/` (published) alike. */
34
+ /** The published corpus is immutable, so keep one loaded array and its derived search fields. */
36
35
  export declare function loadTypesCorpus(): Promise<TypeEntry[] | undefined>;
37
36
  //# sourceMappingURL=types-search.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types-search.d.ts","sourceRoot":"","sources":["../src/types-search.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,CAAA;IAC7E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,4FAA4F;IAC5F,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,6CAA6C;IAC7C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,UAAW,SAAQ,SAAS;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAED;6BAC6B;AAC7B,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,CAGjF;AAED,oGAAoG;AACpG,wBAAgB,WAAW,CACzB,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACZ,UAAU,EAAE,CAed;AAuDD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,KAAK,EAAE,MAAM,EACb,IAAI,UAAQ,GACX,MAAM,CA2BR;AAED;iDACiD;AACjD,wBAAsB,eAAe,IAAI,OAAO,CAAC,SAAS,EAAE,GAAG,SAAS,CAAC,CAUxE"}
1
+ {"version":3,"file":"types-search.d.ts","sourceRoot":"","sources":["../src/types-search.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,CAAA;IAC7E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,4FAA4F;IAC5F,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,6CAA6C;IAC7C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,UAAW,SAAQ,SAAS;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAqBD;6BAC6B;AAC7B,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,CAGjF;AAED,oGAAoG;AACpG,wBAAgB,WAAW,CACzB,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACZ,UAAU,EAAE,CAcd;AAuDD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,KAAK,EAAE,MAAM,EACb,IAAI,UAAQ,GACX,MAAM,CA2BR;AAMD,iGAAiG;AACjG,wBAAgB,eAAe,IAAI,OAAO,CAAC,SAAS,EAAE,GAAG,SAAS,CAAC,CAalE"}
@@ -9,6 +9,19 @@
9
9
  * signature/doc body; no args → a per-package index of the available type names.
10
10
  */
11
11
  import { countBodyHits, queryTermGroups, tokenize, tokenSetHas } from "./search-terms.js";
12
+ const preparedTypesCache = new WeakMap();
13
+ const prepareTypes = (types) => {
14
+ const cached = preparedTypesCache.get(types);
15
+ if (cached !== undefined)
16
+ return cached;
17
+ const prepared = types.map((type) => ({
18
+ type,
19
+ nameTokens: new Set(tokenize(type.name)),
20
+ bodyLower: `${type.signature} ${type.doc ?? ""}`.toLowerCase(),
21
+ }));
22
+ preparedTypesCache.set(types, prepared);
23
+ return prepared;
24
+ };
12
25
  /** Exact (case-insensitive) name lookup - returns every declaration with that name (a name can exist in
13
26
  * more than one package). */
14
27
  export function lookupType(types, name) {
@@ -21,14 +34,13 @@ export function searchTypes(types, query, limit) {
21
34
  if (terms.length === 0)
22
35
  return [];
23
36
  const scored = [];
24
- for (const t of types) {
25
- const nameTokens = new Set(tokenize(t.name));
26
- const body = `${t.signature} ${t.doc ?? ""}`.toLowerCase();
37
+ for (const prepared of prepareTypes(types)) {
38
+ const t = prepared.type;
27
39
  let score = 0;
28
40
  for (const term of terms) {
29
- if (tokenSetHas(nameTokens, term))
41
+ if (tokenSetHas(prepared.nameTokens, term))
30
42
  score += 8;
31
- score += countBodyHits(body, term, 4);
43
+ score += countBodyHits(prepared.bodyLower, term, 4);
32
44
  }
33
45
  if (score > 0)
34
46
  scored.push({ ...t, score });
@@ -119,17 +131,22 @@ export function renderTypesResult(types, name, query, limit, full = false) {
119
131
  }
120
132
  /** Load the bundled type corpus (`docs/types.json`), resolved relative to this module so it works from
121
133
  * `src/` (dev) and `dist/` (published) alike. */
122
- export async function loadTypesCorpus() {
123
- const url = new URL("../docs/types.json", import.meta.url);
124
- try {
125
- const text = await Bun.file(url).text();
126
- if (text.length === 0)
134
+ let typesCorpusPromise;
135
+ /** The published corpus is immutable, so keep one loaded array and its derived search fields. */
136
+ export function loadTypesCorpus() {
137
+ typesCorpusPromise ??= (async () => {
138
+ const url = new URL("../docs/types.json", import.meta.url);
139
+ try {
140
+ const text = await Bun.file(url).text();
141
+ if (text.length === 0)
142
+ return undefined;
143
+ const parsed = JSON.parse(text);
144
+ return Array.isArray(parsed) && parsed.length > 0 ? parsed : undefined;
145
+ }
146
+ catch {
127
147
  return undefined;
128
- const parsed = JSON.parse(text);
129
- return Array.isArray(parsed) && parsed.length > 0 ? parsed : undefined;
130
- }
131
- catch {
132
- return undefined;
133
- }
148
+ }
149
+ })();
150
+ return typesCorpusPromise;
134
151
  }
135
152
  //# sourceMappingURL=types-search.js.map