@mandujs/core 0.41.2 → 0.43.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 (88) hide show
  1. package/package.json +21 -4
  2. package/src/auth/__tests__/login.test.ts +420 -419
  3. package/src/auth/__tests__/reset.test.ts +296 -296
  4. package/src/brain/adapters/anthropic-oauth.ts +421 -420
  5. package/src/brain/adapters/index.ts +2 -1
  6. package/src/brain/adapters/ollama.ts +1 -1
  7. package/src/brain/adapters/openai-oauth.ts +534 -533
  8. package/src/brain/brain.ts +2 -1
  9. package/src/brain/redactor.ts +196 -196
  10. package/src/bundler/__tests__/cli-bench-utils.test.ts +149 -149
  11. package/src/bundler/__tests__/cold-start.test.ts +504 -504
  12. package/src/bundler/__tests__/fast-refresh.test.ts +607 -606
  13. package/src/bundler/__tests__/hdr.test.ts +1 -1
  14. package/src/bundler/analyzer.ts +958 -958
  15. package/src/bundler/build.ts +104 -14
  16. package/src/bundler/dev.ts +125 -0
  17. package/src/bundler/hmr-types.ts +1 -0
  18. package/src/bundler/plugins/__tests__/react-compiler-lint.test.ts +110 -0
  19. package/src/bundler/plugins/index.ts +14 -0
  20. package/src/bundler/plugins/react-compiler-lint.ts +253 -0
  21. package/src/bundler/plugins/react-compiler.ts +162 -0
  22. package/src/bundler/types.ts +12 -0
  23. package/src/change/integrity.ts +2 -1
  24. package/src/client/index.ts +10 -0
  25. package/src/client/island.ts +38 -11
  26. package/src/client/router.ts +6 -1
  27. package/src/config/mandu.ts +57 -0
  28. package/src/config/validate.ts +42 -0
  29. package/src/content/collection.ts +844 -809
  30. package/src/content/content-layer.ts +316 -314
  31. package/src/content/content.test.ts +433 -433
  32. package/src/content/digest.ts +133 -133
  33. package/src/content/generate-types.ts +168 -168
  34. package/src/content/index.ts +6 -1
  35. package/src/content/llms-txt.ts +277 -277
  36. package/src/contract/define.ts +474 -474
  37. package/src/contract/route-helpers.ts +2 -1
  38. package/src/contract/zod-utils.ts +158 -155
  39. package/src/db/index.ts +513 -513
  40. package/src/desktop/__tests__/smoke.test.ts +100 -100
  41. package/src/desktop/webview-fallback.ts +583 -583
  42. package/src/desktop/window.ts +3 -1
  43. package/src/dev-error-overlay/overlay-client.ts +300 -300
  44. package/src/devtools/ai/mcp-connector.ts +499 -498
  45. package/src/devtools/client/components/kitchen-root.tsx +7 -2
  46. package/src/email/resend.ts +163 -163
  47. package/src/guard/__tests__/tsgolint-bridge.test.ts +347 -0
  48. package/src/guard/ast-analyzer.ts +806 -806
  49. package/src/guard/graph.ts +898 -898
  50. package/src/guard/index.ts +16 -0
  51. package/src/guard/statistics.ts +578 -578
  52. package/src/guard/tsgolint-bridge.ts +512 -0
  53. package/src/i18n/locale-resolver.ts +214 -214
  54. package/src/id/__tests__/id.test.ts +120 -120
  55. package/src/intent/index.ts +321 -321
  56. package/src/island/index.ts +39 -23
  57. package/src/kitchen/api/contract-api.ts +15 -8
  58. package/src/kitchen/kitchen-ui.ts +2137 -2137
  59. package/src/lockfile/index.ts +3 -2
  60. package/src/middleware/oauth/__tests__/oauth.test.ts +575 -574
  61. package/src/middleware/rate-limit/__tests__/rate-limit.test.ts +642 -642
  62. package/src/middleware/secure/index.ts +417 -417
  63. package/src/observability/event-bus.ts +2 -2
  64. package/src/observability/metrics.ts +334 -334
  65. package/src/observability/tracing.ts +694 -694
  66. package/src/openapi/generator.ts +1 -1
  67. package/src/perf/user-marks.ts +553 -553
  68. package/src/plugins/registry.ts +387 -387
  69. package/src/resource/ddl/diff.ts +392 -392
  70. package/src/resource/ddl/snapshot.ts +448 -447
  71. package/src/resource/generator-schema.ts +477 -476
  72. package/src/resource/parser.ts +4 -2
  73. package/src/resource/schema.ts +1 -1
  74. package/src/router/fs-patterns.ts +422 -422
  75. package/src/runtime/fast-refresh-types.ts +126 -128
  76. package/src/runtime/image-handler.ts +206 -195
  77. package/src/runtime/router.test.ts +476 -476
  78. package/src/runtime/security.ts +155 -155
  79. package/src/runtime/server.ts +36 -19
  80. package/src/runtime/session-key.ts +328 -328
  81. package/src/scheduler/__tests__/scheduler.test.ts +514 -514
  82. package/src/seo/resolve/index.ts +353 -353
  83. package/src/spec/load.ts +1 -1
  84. package/src/testing/reporter.ts +676 -676
  85. package/src/testing/server.ts +196 -196
  86. package/src/testing/snapshot.ts +444 -444
  87. package/src/utils/__tests__/lru-cache.test.ts +186 -186
  88. package/src/utils/bun.ts +8 -8
@@ -0,0 +1,347 @@
1
+ /**
2
+ * Mandu Guard — oxlint type-aware bridge tests (Follow-up E).
3
+ *
4
+ * Coverage:
5
+ * 1. extractRuleId — normalizes oxlint's `plugin(rule)` shape.
6
+ * 2. mapOxlintSeverity — oxlint severity vocabulary → Mandu `Severity`.
7
+ * 3. translateDiagnostic — shape + filename resolution.
8
+ * 4. resolveOxlintBinary — returns undefined for missing binaries.
9
+ * 5. runTsgolint — skips gracefully when binary missing.
10
+ * 6. runTsgolint — severity=off short-circuit.
11
+ * 7. runTsgolint — real oxlint invocation (when available) on a
12
+ * fixture with `const x: any = 1` + JSON round-trip.
13
+ *
14
+ * Fixtures are isolated under `os.tmpdir()` and scrubbed in afterEach.
15
+ * Tests that need a real oxlint binary probe `findRepoOxlint()` and
16
+ * skip themselves with an in-place assertion when the binary isn't
17
+ * available — keeping the suite green on contributors' machines where
18
+ * oxlint may not be installed yet.
19
+ */
20
+
21
+ import { describe, it, expect, beforeEach, afterEach } from "bun:test";
22
+ import fs from "fs/promises";
23
+ import path from "path";
24
+ import os from "os";
25
+
26
+ import {
27
+ extractRuleId,
28
+ mapOxlintSeverity,
29
+ translateDiagnostic,
30
+ resolveOxlintBinary,
31
+ runTsgolint,
32
+ } from "../tsgolint-bridge";
33
+
34
+ // ────────────────────────────────────────────────────────────────────────
35
+ // Helpers
36
+ // ────────────────────────────────────────────────────────────────────────
37
+
38
+ async function mkFixtureRoot(): Promise<string> {
39
+ return fs.mkdtemp(path.join(os.tmpdir(), "mandu-tsgolint-"));
40
+ }
41
+
42
+ async function writeFile(root: string, rel: string, content: string): Promise<void> {
43
+ const abs = path.join(root, rel);
44
+ await fs.mkdir(path.dirname(abs), { recursive: true });
45
+ await fs.writeFile(abs, content, "utf-8");
46
+ }
47
+
48
+ /**
49
+ * Locate the repo root so real-binary tests can point the bridge at
50
+ * the already-installed `node_modules/.bin/oxlint` without copying
51
+ * shim files (Bun's bin shims embed lockfile metadata and can't
52
+ * survive a plain `copyFile` onto a test fixture).
53
+ *
54
+ * When the walk-up fails (detached tree / fresh clone without install)
55
+ * the caller gracefully asserts the `oxlint-not-installed` skip path
56
+ * instead, keeping the suite green.
57
+ */
58
+ async function findRepoRoot(): Promise<string | undefined> {
59
+ let dir = __dirname;
60
+ for (let i = 0; i < 10; i++) {
61
+ const bin = path.join(
62
+ dir,
63
+ "node_modules",
64
+ ".bin",
65
+ process.platform === "win32" ? "oxlint.exe" : "oxlint",
66
+ );
67
+ try {
68
+ await fs.access(bin);
69
+ return dir;
70
+ } catch {
71
+ /* keep walking */
72
+ }
73
+ const parent = path.dirname(dir);
74
+ if (parent === dir) break;
75
+ dir = parent;
76
+ }
77
+ return undefined;
78
+ }
79
+
80
+ // ────────────────────────────────────────────────────────────────────────
81
+ // Pure helpers
82
+ // ────────────────────────────────────────────────────────────────────────
83
+
84
+ describe("extractRuleId", () => {
85
+ it("normalizes typescript-eslint plugin shape to `typescript/<rule>`", () => {
86
+ expect(extractRuleId("typescript-eslint(no-floating-promises)")).toBe(
87
+ "typescript/no-floating-promises",
88
+ );
89
+ expect(extractRuleId("typescript-eslint(no-explicit-any)")).toBe(
90
+ "typescript/no-explicit-any",
91
+ );
92
+ });
93
+
94
+ it("returns bare rule name for eslint / unprefixed codes", () => {
95
+ expect(extractRuleId("no-debugger")).toBe("no-debugger");
96
+ expect(extractRuleId("eslint(no-debugger)")).toBe("no-debugger");
97
+ });
98
+
99
+ it("preserves other plugin prefixes verbatim", () => {
100
+ expect(extractRuleId("unicorn(no-array-sort)")).toBe("unicorn/no-array-sort");
101
+ });
102
+
103
+ it("returns 'unknown' for empty or undefined codes", () => {
104
+ expect(extractRuleId(undefined)).toBe("unknown");
105
+ expect(extractRuleId("")).toBe("unknown");
106
+ });
107
+ });
108
+
109
+ describe("mapOxlintSeverity", () => {
110
+ it("maps oxlint severity strings to Mandu severities", () => {
111
+ expect(mapOxlintSeverity("error")).toBe("error");
112
+ expect(mapOxlintSeverity("warning")).toBe("warn");
113
+ expect(mapOxlintSeverity("advice")).toBe("info");
114
+ });
115
+
116
+ it("defaults unknown / undefined severity to 'warn'", () => {
117
+ expect(mapOxlintSeverity(undefined)).toBe("warn");
118
+ expect(mapOxlintSeverity("unexpected")).toBe("warn");
119
+ });
120
+ });
121
+
122
+ describe("translateDiagnostic", () => {
123
+ it("maps filename / line / column / rule / severity correctly", () => {
124
+ const v = translateDiagnostic(
125
+ {
126
+ message: "Unexpected any.",
127
+ code: "typescript-eslint(no-explicit-any)",
128
+ severity: "error",
129
+ filename: "src/foo.ts",
130
+ labels: [{ span: { offset: 9, length: 3, line: 2, column: 10 } }],
131
+ help: "Use unknown instead.",
132
+ url: "https://oxc.rs/...",
133
+ },
134
+ "/root",
135
+ );
136
+ expect(v.ruleName).toBe("typescript/no-explicit-any");
137
+ expect(v.ruleDescription).toBe("Unexpected any.");
138
+ expect(v.severity).toBe("error");
139
+ expect(v.line).toBe(2);
140
+ expect(v.column).toBe(10);
141
+ expect(v.filePath).toBe(path.join("/root", "src/foo.ts"));
142
+ expect(v.suggestions).toContain("Use unknown instead.");
143
+ expect(v.suggestions.some((s) => s.includes("https://oxc.rs"))).toBe(true);
144
+ });
145
+
146
+ it("falls back to line 1 column 1 when labels are missing", () => {
147
+ const v = translateDiagnostic(
148
+ {
149
+ message: "X",
150
+ code: "typescript-eslint(y)",
151
+ severity: "warning",
152
+ filename: "src/y.ts",
153
+ },
154
+ "/root",
155
+ );
156
+ expect(v.line).toBe(1);
157
+ expect(v.column).toBe(1);
158
+ expect(v.severity).toBe("warn");
159
+ });
160
+
161
+ it("applies severity override when provided", () => {
162
+ const v = translateDiagnostic(
163
+ {
164
+ message: "X",
165
+ code: "typescript-eslint(y)",
166
+ severity: "warning",
167
+ filename: "src/y.ts",
168
+ },
169
+ "/root",
170
+ "error",
171
+ );
172
+ expect(v.severity).toBe("error");
173
+ });
174
+ });
175
+
176
+ // ────────────────────────────────────────────────────────────────────────
177
+ // resolveOxlintBinary
178
+ // ────────────────────────────────────────────────────────────────────────
179
+
180
+ describe("resolveOxlintBinary", () => {
181
+ let root: string;
182
+
183
+ beforeEach(async () => {
184
+ root = await mkFixtureRoot();
185
+ });
186
+
187
+ afterEach(async () => {
188
+ await fs.rm(root, { recursive: true, force: true });
189
+ });
190
+
191
+ it("returns undefined when node_modules/.bin/oxlint is absent", async () => {
192
+ const found = await resolveOxlintBinary(root);
193
+ expect(found).toBeUndefined();
194
+ });
195
+
196
+ it("returns the resolved path when the binary exists", async () => {
197
+ const binDir = path.join(root, "node_modules", ".bin");
198
+ await fs.mkdir(binDir, { recursive: true });
199
+ const binName = process.platform === "win32" ? "oxlint.exe" : "oxlint";
200
+ await fs.writeFile(path.join(binDir, binName), "");
201
+ const found = await resolveOxlintBinary(root);
202
+ expect(found).toBe(path.join(binDir, binName));
203
+ });
204
+ });
205
+
206
+ // ────────────────────────────────────────────────────────────────────────
207
+ // runTsgolint — integration
208
+ // ────────────────────────────────────────────────────────────────────────
209
+
210
+ describe("runTsgolint", () => {
211
+ let root: string;
212
+
213
+ beforeEach(async () => {
214
+ root = await mkFixtureRoot();
215
+ });
216
+
217
+ afterEach(async () => {
218
+ await fs.rm(root, { recursive: true, force: true });
219
+ });
220
+
221
+ it("skips gracefully when oxlint binary is missing (no throw)", async () => {
222
+ const result = await runTsgolint({ projectRoot: root });
223
+ expect(result.skipped).toBe("oxlint-not-installed");
224
+ expect(result.violations).toEqual([]);
225
+ expect(result.summary.elapsedMs).toBe(0);
226
+ expect(result.summary.filesAnalyzed).toBe(0);
227
+ });
228
+
229
+ it("short-circuits when severity is 'off' without touching the filesystem", async () => {
230
+ const result = await runTsgolint({ projectRoot: root, severity: "off" });
231
+ expect(result.skipped).toBe("severity-off");
232
+ expect(result.violations).toEqual([]);
233
+ });
234
+
235
+ it("runs oxlint on a real fixture and round-trips an explicit-any violation", async () => {
236
+ const repoRoot = await findRepoRoot();
237
+ if (!repoRoot) {
238
+ // Environment without an installed oxlint — assert the skip
239
+ // reason as a sanity check and bail early. Keeps the suite
240
+ // passing on fresh clones.
241
+ const result = await runTsgolint({ projectRoot: root });
242
+ expect(result.skipped).toBe("oxlint-not-installed");
243
+ return;
244
+ }
245
+
246
+ // We anchor `projectRoot` at the repo root so `resolveOxlintBinary()`
247
+ // finds the lockfile-aware bin shim. The fixture lives in an
248
+ // isolated subdir at `<repoRoot>/.tmp-tsgolint-*` and we pass only
249
+ // that file path as `paths`, so oxlint lints just our fixture.
250
+ const fixtureSubdir = `.tmp-tsgolint-fixture-${process.pid}-${Date.now()}`;
251
+ const fixtureDir = path.join(repoRoot, fixtureSubdir);
252
+ await fs.mkdir(fixtureDir, { recursive: true });
253
+
254
+ try {
255
+ await writeFile(
256
+ fixtureDir,
257
+ ".oxlintrc.json",
258
+ JSON.stringify({
259
+ rules: { "typescript/no-explicit-any": "error" },
260
+ }),
261
+ );
262
+ await writeFile(
263
+ fixtureDir,
264
+ "bad.ts",
265
+ "const x: any = 1;\nexport { x };\n",
266
+ );
267
+
268
+ // Anchor projectRoot at repoRoot so the bin-shim resolves;
269
+ // point paths at the fixture file (absolute path so oxlint
270
+ // honors it regardless of cwd).
271
+ const result = await runTsgolint({
272
+ projectRoot: repoRoot,
273
+ configPath: path.join(fixtureDir, ".oxlintrc.json"),
274
+ paths: [path.join(fixtureDir, "bad.ts")],
275
+ });
276
+
277
+ expect(result.skipped).toBeUndefined();
278
+ expect(result.violations.length).toBeGreaterThanOrEqual(1);
279
+
280
+ const hit = result.violations.find(
281
+ (v) => v.ruleName === "typescript/no-explicit-any",
282
+ );
283
+ expect(hit).toBeDefined();
284
+ expect(hit?.severity).toBe("error");
285
+ expect(hit?.filePath.endsWith("bad.ts")).toBe(true);
286
+ expect(hit?.line).toBeGreaterThan(0);
287
+
288
+ expect(result.summary.filesAnalyzed).toBeGreaterThanOrEqual(1);
289
+ expect(result.summary.rulesEnabled).toContain(
290
+ "typescript/no-explicit-any",
291
+ );
292
+ expect(result.summary.elapsedMs).toBeGreaterThan(0);
293
+ } finally {
294
+ await fs.rm(fixtureDir, { recursive: true, force: true });
295
+ }
296
+ });
297
+
298
+ it("filters diagnostics by the `rules` allowlist when provided", async () => {
299
+ const repoRoot = await findRepoRoot();
300
+ if (!repoRoot) {
301
+ // Skip — same graceful-degradation pattern as above.
302
+ return;
303
+ }
304
+
305
+ const fixtureSubdir = `.tmp-tsgolint-filter-${process.pid}-${Date.now()}`;
306
+ const fixtureDir = path.join(repoRoot, fixtureSubdir);
307
+ await fs.mkdir(fixtureDir, { recursive: true });
308
+
309
+ try {
310
+ await writeFile(
311
+ fixtureDir,
312
+ ".oxlintrc.json",
313
+ JSON.stringify({
314
+ rules: {
315
+ "typescript/no-explicit-any": "error",
316
+ "no-debugger": "error",
317
+ },
318
+ }),
319
+ );
320
+ await writeFile(
321
+ fixtureDir,
322
+ "mixed.ts",
323
+ "const x: any = 1;\ndebugger;\nexport { x };\n",
324
+ );
325
+
326
+ const result = await runTsgolint({
327
+ projectRoot: repoRoot,
328
+ configPath: path.join(fixtureDir, ".oxlintrc.json"),
329
+ paths: [path.join(fixtureDir, "mixed.ts")],
330
+ rules: ["typescript/no-explicit-any"],
331
+ });
332
+
333
+ // Only typescript/no-explicit-any should survive the post-filter;
334
+ // `no-debugger` hits oxlint but the bridge drops them.
335
+ for (const v of result.violations) {
336
+ expect(v.ruleName).toBe("typescript/no-explicit-any");
337
+ }
338
+ // diagnosticsReceived counts pre-filter and should exceed
339
+ // post-filter violation count when both rules fired.
340
+ expect(result.summary.diagnosticsReceived).toBeGreaterThanOrEqual(
341
+ result.violations.length,
342
+ );
343
+ } finally {
344
+ await fs.rm(fixtureDir, { recursive: true, force: true });
345
+ }
346
+ });
347
+ });