@nifrajs/cli 2.14.1 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/assure-hydration.d.ts.map +1 -1
- package/dist/assure-hydration.js +6 -1
- package/dist/assure-hydration.js.map +1 -1
- package/dist/assure.d.ts.map +1 -1
- package/dist/assure.js +3 -2
- package/dist/assure.js.map +1 -1
- package/dist/check-diagnostics.d.ts +165 -0
- package/dist/check-diagnostics.d.ts.map +1 -0
- package/dist/check-diagnostics.js +1009 -0
- package/dist/check-diagnostics.js.map +1 -0
- package/dist/check-scan.d.ts +245 -0
- package/dist/check-scan.d.ts.map +1 -0
- package/dist/check-scan.js +1599 -0
- package/dist/check-scan.js.map +1 -0
- package/dist/check.d.ts +4 -324
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +8 -2346
- package/dist/check.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +285 -10
- package/dist/cli.js.map +1 -1
- package/dist/command-catalog.d.ts +27 -1
- package/dist/command-catalog.d.ts.map +1 -1
- package/dist/command-catalog.js +142 -2
- package/dist/command-catalog.js.map +1 -1
- package/dist/doctor.d.ts +42 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +64 -7
- package/dist/doctor.js.map +1 -1
- package/dist/env-file.d.ts +17 -0
- package/dist/env-file.d.ts.map +1 -0
- package/dist/env-file.js +113 -0
- package/dist/env-file.js.map +1 -0
- package/dist/fix-recipes.d.ts.map +1 -1
- package/dist/fix-recipes.js +91 -15
- package/dist/fix-recipes.js.map +1 -1
- package/dist/frontend-guidance.d.ts +57 -0
- package/dist/frontend-guidance.d.ts.map +1 -0
- package/dist/frontend-guidance.js +195 -0
- package/dist/frontend-guidance.js.map +1 -0
- package/dist/internal/reserved-segment-codemod.d.ts +36 -0
- package/dist/internal/reserved-segment-codemod.d.ts.map +1 -0
- package/dist/internal/reserved-segment-codemod.js +134 -0
- package/dist/internal/reserved-segment-codemod.js.map +1 -0
- package/dist/internal/typescript-import.d.ts +1 -1
- package/dist/internal/typescript-import.d.ts.map +1 -1
- package/dist/internal/typescript-import.js +62 -11
- package/dist/internal/typescript-import.js.map +1 -1
- package/dist/introspect.d.ts +26 -1
- package/dist/introspect.d.ts.map +1 -1
- package/dist/introspect.js +74 -24
- package/dist/introspect.js.map +1 -1
- package/dist/levels-tool.d.ts.map +1 -1
- package/dist/levels-tool.js +6 -0
- package/dist/levels-tool.js.map +1 -1
- package/dist/load.d.ts.map +1 -1
- package/dist/load.js +4 -1
- package/dist/load.js.map +1 -1
- package/dist/mcp-context.d.ts +48 -0
- package/dist/mcp-context.d.ts.map +1 -0
- package/dist/mcp-context.js +215 -0
- package/dist/mcp-context.js.map +1 -0
- package/dist/mcp-docs-tools.d.ts.map +1 -1
- package/dist/mcp-docs-tools.js +31 -0
- package/dist/mcp-docs-tools.js.map +1 -1
- package/dist/mcp-exec.d.ts +74 -0
- package/dist/mcp-exec.d.ts.map +1 -0
- package/dist/mcp-exec.js +844 -0
- package/dist/mcp-exec.js.map +1 -0
- package/dist/mcp-http.js +1 -1
- package/dist/mcp-http.js.map +1 -1
- package/dist/mcp-io.d.ts +26 -0
- package/dist/mcp-io.d.ts.map +1 -0
- package/dist/mcp-io.js +100 -0
- package/dist/mcp-io.js.map +1 -0
- package/dist/mcp-reflect.d.ts +16 -0
- package/dist/mcp-reflect.d.ts.map +1 -0
- package/dist/mcp-reflect.js +101 -0
- package/dist/mcp-reflect.js.map +1 -0
- package/dist/mcp-root.d.ts +7 -0
- package/dist/mcp-root.d.ts.map +1 -1
- package/dist/mcp-root.js +51 -3
- package/dist/mcp-root.js.map +1 -1
- package/dist/mcp.d.ts +10 -84
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +13 -1170
- package/dist/mcp.js.map +1 -1
- package/dist/openapi-tool.d.ts +5 -10
- package/dist/openapi-tool.d.ts.map +1 -1
- package/dist/openapi-tool.js +21 -2
- package/dist/openapi-tool.js.map +1 -1
- package/dist/openapi-types.d.ts +21 -0
- package/dist/openapi-types.d.ts.map +1 -0
- package/dist/openapi-types.js +214 -0
- package/dist/openapi-types.js.map +1 -0
- package/dist/port.d.ts.map +1 -1
- package/dist/port.js +2 -1
- package/dist/port.js.map +1 -1
- package/dist/project-facts.d.ts +2 -1
- package/dist/project-facts.d.ts.map +1 -1
- package/dist/project-facts.js.map +1 -1
- package/dist/recipes/3.0.0.d.ts +10 -0
- package/dist/recipes/3.0.0.d.ts.map +1 -0
- package/dist/recipes/3.0.0.js +23 -0
- package/dist/recipes/3.0.0.js.map +1 -0
- package/dist/recipes/index.d.ts.map +1 -1
- package/dist/recipes/index.js +2 -0
- package/dist/recipes/index.js.map +1 -1
- package/dist/release-verification.d.ts +4 -1
- package/dist/release-verification.d.ts.map +1 -1
- package/dist/release-verification.js +6 -123
- package/dist/release-verification.js.map +1 -1
- package/dist/rules/codes.d.ts +4 -0
- package/dist/rules/codes.d.ts.map +1 -1
- package/dist/rules/codes.js +4 -0
- package/dist/rules/codes.js.map +1 -1
- package/dist/rules/islands.d.ts +4 -0
- package/dist/rules/islands.d.ts.map +1 -0
- package/dist/rules/islands.js +155 -0
- package/dist/rules/islands.js.map +1 -0
- package/dist/rules/nano.d.ts +6 -0
- package/dist/rules/nano.d.ts.map +1 -0
- package/dist/rules/nano.js +264 -0
- package/dist/rules/nano.js.map +1 -0
- package/dist/rules/routes.d.ts.map +1 -1
- package/dist/rules/routes.js +17 -30
- package/dist/rules/routes.js.map +1 -1
- package/dist/rules/security.d.ts.map +1 -1
- package/dist/rules/security.js +38 -4
- package/dist/rules/security.js.map +1 -1
- package/dist/scaffold.d.ts +12 -6
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +149 -10
- package/dist/scaffold.js.map +1 -1
- package/dist/search-terms.d.ts.map +1 -1
- package/dist/search-terms.js +5 -3
- package/dist/search-terms.js.map +1 -1
- package/dist/stylex-migrate.d.ts +38 -0
- package/dist/stylex-migrate.d.ts.map +1 -0
- package/dist/stylex-migrate.js +1052 -0
- package/dist/stylex-migrate.js.map +1 -0
- package/dist/sync-manifest.d.ts.map +1 -1
- package/dist/sync-manifest.js +2 -1
- package/dist/sync-manifest.js.map +1 -1
- package/dist/verification-plan.d.ts +24 -0
- package/dist/verification-plan.d.ts.map +1 -0
- package/dist/verification-plan.js +116 -0
- package/dist/verification-plan.js.map +1 -0
- package/dist/work-graph.d.ts.map +1 -1
- package/dist/work-graph.js +6 -3
- package/dist/work-graph.js.map +1 -1
- package/dist/workspace-link.d.ts +37 -0
- package/dist/workspace-link.d.ts.map +1 -0
- package/dist/workspace-link.js +72 -0
- package/dist/workspace-link.js.map +1 -0
- package/docs/examples.json +37 -1
- package/docs/llms-full.txt +1598 -44
- package/docs/types.json +5157 -172
- package/package.json +9 -9
- package/src/assure-hydration.ts +6 -1
- package/src/assure.ts +4 -2
- package/src/check-diagnostics.ts +1219 -0
- package/src/check-scan.ts +1825 -0
- package/src/check.ts +47 -2731
- package/src/cli.ts +291 -10
- package/src/command-catalog.ts +177 -2
- package/src/doctor.ts +99 -8
- package/src/env-file.ts +121 -0
- package/src/fix-recipes.ts +101 -15
- package/src/frontend-guidance.ts +259 -0
- package/src/internal/reserved-segment-codemod.ts +150 -0
- package/src/internal/typescript-import.ts +61 -11
- package/src/introspect.ts +82 -21
- package/src/levels-tool.ts +6 -0
- package/src/load.ts +4 -1
- package/src/mcp-context.ts +266 -0
- package/src/mcp-docs-tools.ts +37 -0
- package/src/mcp-exec.ts +1040 -0
- package/src/mcp-http.ts +1 -1
- package/src/mcp-io.ts +118 -0
- package/src/mcp-reflect.ts +140 -0
- package/src/mcp-root.ts +62 -4
- package/src/mcp.ts +61 -1436
- package/src/openapi-tool.ts +32 -2
- package/src/openapi-types.ts +261 -0
- package/src/port.ts +2 -1
- package/src/project-facts.ts +2 -4
- package/src/recipes/3.0.0.ts +24 -0
- package/src/recipes/index.ts +2 -0
- package/src/release-verification.ts +22 -147
- package/src/rules/codes.ts +4 -0
- package/src/rules/islands.ts +171 -0
- package/src/rules/nano.ts +295 -0
- package/src/rules/routes.ts +17 -30
- package/src/rules/security.ts +40 -4
- package/src/scaffold.ts +169 -9
- package/src/search-terms.ts +7 -3
- package/src/stylex-migrate.ts +1139 -0
- package/src/sync-manifest.ts +2 -1
- package/src/verification-plan.ts +242 -0
- package/src/work-graph.ts +6 -3
- package/src/workspace-link.ts +80 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nifra_frontend` - a symptom-indexed catalog of the mistakes an agent (or a human) hits on the client
|
|
3
|
+
* side of a nifra app, across every adapter. It is the FRONTEND companion to `nifra_check`: where the
|
|
4
|
+
* check statically flags the footguns nifra's own seam owns (server-only imports, the nano lane), this
|
|
5
|
+
* catalog covers the ones a static rule can't own cheaply - hydration nondeterminism, a duplicated
|
|
6
|
+
* framework runtime, and the per-framework reactivity-loss idioms that each ecosystem's own linter is
|
|
7
|
+
* the real authority on.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately a compact reference, not an accumulated fault corpus: two kinds of entry only.
|
|
10
|
+
* - Seam entries (`adapters: ["all"]`) - the boundary nifra defines: server/client split, the loader
|
|
11
|
+
* data contract, hydration determinism, one framework copy. These point at a `nifra_*` tool to fix
|
|
12
|
+
* and verify, because nifra owns that surface.
|
|
13
|
+
* - Framework entries - the classic reactivity-loss shape for one adapter (Vue ref, Solid props,
|
|
14
|
+
* Svelte runes, React/Preact deps). These point at that framework's own ESLint plugin, which is the
|
|
15
|
+
* authority; nifra does not re-implement it, it routes you to it.
|
|
16
|
+
*
|
|
17
|
+
* One source, two heads: `nifra_frontend` (MCP) serves it to an agent; `nifra frontend` (CLI) prints it
|
|
18
|
+
* for a human. Both call {@link renderFrontendResult}, so they can never drift.
|
|
19
|
+
*/
|
|
20
|
+
import type { Framework } from "./scaffold.js";
|
|
21
|
+
/** An adapter an entry applies to, or `all` for a seam issue that is adapter-independent. */
|
|
22
|
+
export type GuidanceAdapter = Framework | "all";
|
|
23
|
+
export interface GuidanceEntry {
|
|
24
|
+
/** Stable slug, e.g. `server-import`. */
|
|
25
|
+
readonly id: string;
|
|
26
|
+
/** Which adapters this applies to. `["all"]` is a seam issue every adapter shares. */
|
|
27
|
+
readonly adapters: readonly GuidanceAdapter[];
|
|
28
|
+
/** What you observe - the searchable symptom, phrased as the failure, not the cause. */
|
|
29
|
+
readonly symptom: string;
|
|
30
|
+
/** Why it happens. */
|
|
31
|
+
readonly cause: string;
|
|
32
|
+
/** The concrete change that fixes it. */
|
|
33
|
+
readonly fix: string;
|
|
34
|
+
/** How to confirm it is fixed - a `nifra_*` tool for a seam issue, the framework's linter otherwise. */
|
|
35
|
+
readonly verify: string;
|
|
36
|
+
/** Where to go deeper - a `nifra_docs`/`nifra_example` query, or the ecosystem linter that owns it. */
|
|
37
|
+
readonly seeAlso?: string | undefined;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The catalog. Seam entries first (they are the nifra-specific value and apply everywhere), then the
|
|
41
|
+
* per-framework reactivity-loss entries. Keep it compact and high-signal - a curated fault corpus mined
|
|
42
|
+
* from real failures is operated depth and lives elsewhere, not in the public package.
|
|
43
|
+
*/
|
|
44
|
+
export declare const FRONTEND_GUIDANCE: readonly GuidanceEntry[];
|
|
45
|
+
/** Normalize a free-text adapter argument to a known adapter, or `undefined` if it is not one. */
|
|
46
|
+
export declare function parseAdapter(value: string | undefined): GuidanceAdapter | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Render the catalog. Filters by `adapter` (when it names a known one), then by a `symptom` keyword
|
|
49
|
+
* query. With no query it returns the (filtered) index; with a query it returns the best-matching
|
|
50
|
+
* entries in full, or the index plus a "no match" note when nothing scores. Pure, so both heads agree.
|
|
51
|
+
*/
|
|
52
|
+
export declare function renderFrontendResult(args: {
|
|
53
|
+
adapter?: string | undefined;
|
|
54
|
+
symptom?: string | undefined;
|
|
55
|
+
limit?: number | undefined;
|
|
56
|
+
}): string;
|
|
57
|
+
//# sourceMappingURL=frontend-guidance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontend-guidance.d.ts","sourceRoot":"","sources":["../src/frontend-guidance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAE9C,6FAA6F;AAC7F,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,KAAK,CAAA;AAE/C,MAAM,WAAW,aAAa;IAC5B,yCAAyC;IACzC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,sFAAsF;IACtF,QAAQ,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAA;IAC7C,wFAAwF;IACxF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,sBAAsB;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,yCAAyC;IACzC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,wGAAwG;IACxG,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,uGAAuG;IACvG,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACtC;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,SAAS,aAAa,EAuHrD,CAAA;AAYD,kGAAkG;AAClG,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,eAAe,GAAG,SAAS,CAGnF;AA2CD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5B,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC3B,GAAG,MAAM,CAwBT"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nifra_frontend` - a symptom-indexed catalog of the mistakes an agent (or a human) hits on the client
|
|
3
|
+
* side of a nifra app, across every adapter. It is the FRONTEND companion to `nifra_check`: where the
|
|
4
|
+
* check statically flags the footguns nifra's own seam owns (server-only imports, the nano lane), this
|
|
5
|
+
* catalog covers the ones a static rule can't own cheaply - hydration nondeterminism, a duplicated
|
|
6
|
+
* framework runtime, and the per-framework reactivity-loss idioms that each ecosystem's own linter is
|
|
7
|
+
* the real authority on.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately a compact reference, not an accumulated fault corpus: two kinds of entry only.
|
|
10
|
+
* - Seam entries (`adapters: ["all"]`) - the boundary nifra defines: server/client split, the loader
|
|
11
|
+
* data contract, hydration determinism, one framework copy. These point at a `nifra_*` tool to fix
|
|
12
|
+
* and verify, because nifra owns that surface.
|
|
13
|
+
* - Framework entries - the classic reactivity-loss shape for one adapter (Vue ref, Solid props,
|
|
14
|
+
* Svelte runes, React/Preact deps). These point at that framework's own ESLint plugin, which is the
|
|
15
|
+
* authority; nifra does not re-implement it, it routes you to it.
|
|
16
|
+
*
|
|
17
|
+
* One source, two heads: `nifra_frontend` (MCP) serves it to an agent; `nifra frontend` (CLI) prints it
|
|
18
|
+
* for a human. Both call {@link renderFrontendResult}, so they can never drift.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* The catalog. Seam entries first (they are the nifra-specific value and apply everywhere), then the
|
|
22
|
+
* per-framework reactivity-loss entries. Keep it compact and high-signal - a curated fault corpus mined
|
|
23
|
+
* from real failures is operated depth and lives elsewhere, not in the public package.
|
|
24
|
+
*/
|
|
25
|
+
export const FRONTEND_GUIDANCE = [
|
|
26
|
+
{
|
|
27
|
+
id: "server-import",
|
|
28
|
+
adapters: ["all"],
|
|
29
|
+
symptom: "A client component crashes at build or runtime with a missing Node built-in, a leaked secret, or 'module not found in the browser'.",
|
|
30
|
+
cause: "A server-only module (the DB, a secret, `node:*`, the backend file) is imported at the top level of a component, so the bundler tries to ship it to the browser.",
|
|
31
|
+
fix: "Move the access into a server-only `loader`/`action` and read the result in the component. Reach the backend through the typed `api` argument, never a top-level server-only import.",
|
|
32
|
+
verify: "nifra_check - its transitive server-import scan flags the exact import chain into a client module.",
|
|
33
|
+
seeAlso: "loader action server-only import boundary",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "hydration-mismatch",
|
|
37
|
+
adapters: ["all"],
|
|
38
|
+
symptom: "A hydration warning, text that flips on load, or 'server HTML did not match client' the first time the page becomes interactive.",
|
|
39
|
+
cause: "The first client render is not identical to the SSR HTML - usually a non-deterministic read during render: `Date.now()`, `Math.random()`, `window`/`localStorage`, or a locale/timezone the server did not have.",
|
|
40
|
+
fix: "Render deterministic markup, then apply anything client-only in an effect / `onMount` after the first paint. Pass anything the server knew (time, locale, flags) down through loader data so both renders agree.",
|
|
41
|
+
verify: "nifra_render the route and diff its HTML against the first client render; nifra_check's hydration assurance reports nondeterminism it can see.",
|
|
42
|
+
seeAlso: "hydration ssr determinism loader data",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "duplicate-runtime",
|
|
46
|
+
adapters: ["react", "preact", "solid", "vue", "svelte"],
|
|
47
|
+
symptom: "'Invalid hook call', a null context/provider, signals that never update, or a component that mounts twice.",
|
|
48
|
+
cause: "Two copies of the framework are in the bundle - the app's own dependency and a second one resolved through the adapter - so the two runtimes do not share the same module-level state.",
|
|
49
|
+
fix: "Dedupe the framework to a single version across the workspace (one entry in the lockfile). The adapter must resolve the app's copy, not bundle its own.",
|
|
50
|
+
verify: "nifra_doctor - it detects a duplicated framework runtime (NF-H002).",
|
|
51
|
+
seeAlso: "duplicate framework runtime dedupe NF-H002",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: "loader-typing",
|
|
55
|
+
adapters: ["all"],
|
|
56
|
+
symptom: "Loader data is typed `any` in the page, or a hand-written interface silently drifts from what the loader actually returns.",
|
|
57
|
+
cause: "The component types its props by hand instead of inferring them from the loader, so a change to the loader is not a type error at the use site.",
|
|
58
|
+
fix: "Type the page from the loader: `props: { data: LoaderData<typeof loader> }`. Call the backend through the typed `api` so the request/response shape is inferred, not restated.",
|
|
59
|
+
verify: "nifra_context prints the exact loader shape and typed call form; nifra_check fails on drift.",
|
|
60
|
+
seeAlso: "LoaderData typed client loader inference",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "list-key",
|
|
64
|
+
adapters: ["all"],
|
|
65
|
+
symptom: "List rows keep the wrong state on reorder/insert - a checked box, focus, or an input value jumps to a different row.",
|
|
66
|
+
cause: "The list is keyed by the array index (or not keyed at all), so add/remove/reorder maps the wrong item to an existing node.",
|
|
67
|
+
fix: "Key each row by a stable, unique id from the item, never its position in the array.",
|
|
68
|
+
verify: "The framework's own linter for JSX keys; on the vanilla nano lane, nifra_check flags an index key as NF-C022.",
|
|
69
|
+
seeAlso: "list key stable id reconcile nano",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: "vue-lost-reactivity",
|
|
73
|
+
adapters: ["vue"],
|
|
74
|
+
symptom: "A value stops updating the template after you pull it out of a ref, reactive object, or props.",
|
|
75
|
+
cause: "Destructuring a `reactive()` object or `props` copies the current value and drops the reactive link; a `ref` read in script forgets `.value`.",
|
|
76
|
+
fix: "Keep the reactive source intact: use `toRefs`/`storeToRefs` when you must destructure, access `props.x` directly, and remember `.value` on a ref in script (templates unwrap it for you).",
|
|
77
|
+
verify: "eslint-plugin-vue - it is the authority on Vue reactivity; nifra does not re-implement it.",
|
|
78
|
+
seeAlso: "eslint-plugin-vue toRefs reactivity",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: "solid-lost-reactivity",
|
|
82
|
+
adapters: ["solid"],
|
|
83
|
+
symptom: "A prop or store value renders once and never tracks updates in a Solid component.",
|
|
84
|
+
cause: "Solid props are getters, evaluated where you read them. Destructuring props (or reading them outside JSX/an effect) reads once and loses tracking.",
|
|
85
|
+
fix: "Access `props.x` at the point of use inside JSX, or `splitProps`/`mergeProps` when you need to pass a subset on. Never destructure props in the function signature.",
|
|
86
|
+
verify: "eslint-plugin-solid - the authority on Solid reactivity; nifra routes you to it, not around it.",
|
|
87
|
+
seeAlso: "eslint-plugin-solid splitProps props getters",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: "svelte-runes",
|
|
91
|
+
adapters: ["svelte"],
|
|
92
|
+
symptom: "In a Svelte 5 component, `export let` props or a `$:` statement do not behave as expected, or the compiler rejects them in runes mode.",
|
|
93
|
+
cause: "Runes mode replaces the Svelte 4 idioms: props come from `$props()`, derived values from `$derived`, side effects from `$effect`. Mixing the two models is the mismatch.",
|
|
94
|
+
fix: "In runes mode use `$props()` for inputs, `$state` for local state, `$derived` for computed values, and `$effect` for side effects - not `export let` or `$:`.",
|
|
95
|
+
verify: "eslint-plugin-svelte and the Svelte compiler warnings own this; nifra points you at them.",
|
|
96
|
+
seeAlso: "eslint-plugin-svelte runes $props $derived",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: "react-stale-deps",
|
|
100
|
+
adapters: ["react", "preact"],
|
|
101
|
+
symptom: "An effect or callback uses a stale value, fires too often, or never re-runs when a value it reads changes.",
|
|
102
|
+
cause: "The dependency array omits a value the effect/callback closes over (or the effect has no array), so the closure captures an old render's value.",
|
|
103
|
+
fix: "List every reactive value the effect reads in its deps, or hold the mutable value in a ref when it must not trigger a re-run. Do not silence the rule by emptying the array.",
|
|
104
|
+
verify: "eslint-plugin-react-hooks (`exhaustive-deps`) is the authority on effect deps; nifra does not duplicate it.",
|
|
105
|
+
seeAlso: "eslint-plugin-react-hooks exhaustive-deps effect closure",
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: "prefer-nano-or-island",
|
|
109
|
+
adapters: ["vanilla"],
|
|
110
|
+
symptom: "A vanilla page needs local state or a list a human edits, and hand-written DOM updates are getting fragile.",
|
|
111
|
+
cause: "The islands lane is imperative and has no state primitive, so each update is hand-wired and a list means rebuilding the container.",
|
|
112
|
+
fix: "Use the nano lane: `signal` + `computed(fn, [deps])` for state, `bindList` for a keyed list, `resource` for async. Every reactive edge is a visible call, so its mistakes are static lints.",
|
|
113
|
+
verify: "nifra_check runs NF-C021 (discarded disposer), NF-C022 (index key), NF-C023 (missing dep) over the nano code.",
|
|
114
|
+
seeAlso: "nano signal computed bindList resource islands",
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
const ADAPTER_ALIASES = {
|
|
118
|
+
all: "all",
|
|
119
|
+
react: "react",
|
|
120
|
+
preact: "preact",
|
|
121
|
+
solid: "solid",
|
|
122
|
+
vue: "vue",
|
|
123
|
+
svelte: "svelte",
|
|
124
|
+
vanilla: "vanilla",
|
|
125
|
+
};
|
|
126
|
+
/** Normalize a free-text adapter argument to a known adapter, or `undefined` if it is not one. */
|
|
127
|
+
export function parseAdapter(value) {
|
|
128
|
+
if (value === undefined)
|
|
129
|
+
return undefined;
|
|
130
|
+
return ADAPTER_ALIASES[value.trim().toLowerCase()];
|
|
131
|
+
}
|
|
132
|
+
const appliesTo = (entry, adapter) => adapter === "all" || entry.adapters.includes("all") || entry.adapters.includes(adapter);
|
|
133
|
+
/** Keyword score of an entry against a query - counts distinct query words found in its text. */
|
|
134
|
+
function score(entry, words) {
|
|
135
|
+
const hay = `${entry.id} ${entry.symptom} ${entry.cause} ${entry.fix} ${entry.seeAlso ?? ""}`.toLowerCase();
|
|
136
|
+
let hits = 0;
|
|
137
|
+
for (const w of words)
|
|
138
|
+
if (hay.includes(w))
|
|
139
|
+
hits++;
|
|
140
|
+
return hits;
|
|
141
|
+
}
|
|
142
|
+
function renderEntry(entry) {
|
|
143
|
+
const scope = entry.adapters.includes("all") ? "all adapters" : entry.adapters.join(", ");
|
|
144
|
+
const parts = [
|
|
145
|
+
`[${entry.id}] (${scope})`,
|
|
146
|
+
`Symptom: ${entry.symptom}`,
|
|
147
|
+
`Cause: ${entry.cause}`,
|
|
148
|
+
`Fix: ${entry.fix}`,
|
|
149
|
+
`Verify: ${entry.verify}`,
|
|
150
|
+
];
|
|
151
|
+
if (entry.seeAlso !== undefined)
|
|
152
|
+
parts.push(`See: ${entry.seeAlso}`);
|
|
153
|
+
return parts.join("\n");
|
|
154
|
+
}
|
|
155
|
+
function renderIndex(entries, adapter) {
|
|
156
|
+
const scope = adapter === undefined ? "all adapters" : `adapter: ${adapter}`;
|
|
157
|
+
const lines = entries.map((e) => {
|
|
158
|
+
const s = e.adapters.includes("all") ? "all" : e.adapters.join("/");
|
|
159
|
+
return `- ${e.id} (${s}) - ${e.symptom}`;
|
|
160
|
+
});
|
|
161
|
+
return [
|
|
162
|
+
`nifra frontend guidance (${entries.length} of ${FRONTEND_GUIDANCE.length}, ${scope}). Pass a symptom to get the cause + fix + how to verify; pass an adapter to filter.`,
|
|
163
|
+
"",
|
|
164
|
+
...lines,
|
|
165
|
+
].join("\n");
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Render the catalog. Filters by `adapter` (when it names a known one), then by a `symptom` keyword
|
|
169
|
+
* query. With no query it returns the (filtered) index; with a query it returns the best-matching
|
|
170
|
+
* entries in full, or the index plus a "no match" note when nothing scores. Pure, so both heads agree.
|
|
171
|
+
*/
|
|
172
|
+
export function renderFrontendResult(args) {
|
|
173
|
+
const adapter = parseAdapter(args.adapter);
|
|
174
|
+
const scoped = adapter === undefined
|
|
175
|
+
? FRONTEND_GUIDANCE
|
|
176
|
+
: FRONTEND_GUIDANCE.filter((e) => appliesTo(e, adapter));
|
|
177
|
+
const query = args.symptom?.trim();
|
|
178
|
+
if (query === undefined || query.length === 0)
|
|
179
|
+
return renderIndex(scoped, adapter);
|
|
180
|
+
const words = query
|
|
181
|
+
.toLowerCase()
|
|
182
|
+
.split(/\s+/)
|
|
183
|
+
.filter((w) => w.length > 1);
|
|
184
|
+
const limit = Math.min(Math.max(args.limit ?? 3, 1), 5);
|
|
185
|
+
const ranked = scoped
|
|
186
|
+
.map((entry) => ({ entry, s: score(entry, words) }))
|
|
187
|
+
.filter((r) => r.s > 0)
|
|
188
|
+
.sort((a, b) => b.s - a.s)
|
|
189
|
+
.slice(0, limit);
|
|
190
|
+
if (ranked.length === 0) {
|
|
191
|
+
return `No frontend-guidance entry matched "${query}"${adapter ? ` for ${adapter}` : ""}.\n\n${renderIndex(scoped, adapter)}`;
|
|
192
|
+
}
|
|
193
|
+
return ranked.map((r) => renderEntry(r.entry)).join("\n\n");
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=frontend-guidance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontend-guidance.js","sourceRoot":"","sources":["../src/frontend-guidance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface CollisionSite {
|
|
2
|
+
readonly file: string;
|
|
3
|
+
/** 1-based, as `tsc` reports them. */
|
|
4
|
+
readonly line: number;
|
|
5
|
+
readonly column: number;
|
|
6
|
+
/** The colliding path segment, read out of the type the compiler named. */
|
|
7
|
+
readonly segment: string;
|
|
8
|
+
}
|
|
9
|
+
/** Every reserved-segment collision `tsc` reported, in the order it reported them. */
|
|
10
|
+
export declare function parseCollisionSites(tscOutput: string): readonly CollisionSite[];
|
|
11
|
+
/**
|
|
12
|
+
* The span to replace for one site: the `.segment` immediately preceding the failing property access,
|
|
13
|
+
* which becomes `("segment")`. Undefined when the text does not have that shape - the caller reports
|
|
14
|
+
* those rather than rewriting something it did not recognise.
|
|
15
|
+
*
|
|
16
|
+
* The scan runs backwards over the whole source rather than one line, so a chain broken across lines
|
|
17
|
+
* (`api.foo\n .delete\n .post()`) rewrites the same as a chain on one line.
|
|
18
|
+
*/
|
|
19
|
+
export declare function collisionSpan(source: string, site: CollisionSite): {
|
|
20
|
+
readonly start: number;
|
|
21
|
+
readonly end: number;
|
|
22
|
+
} | undefined;
|
|
23
|
+
export interface FileRewrite {
|
|
24
|
+
readonly source: string;
|
|
25
|
+
/** Sites in this file the scan declined to rewrite, so the caller can report them. */
|
|
26
|
+
readonly skipped: readonly CollisionSite[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Apply every site belonging to one file. Spans are applied back to front so an earlier rewrite never
|
|
30
|
+
* shifts the offsets of one not yet applied, and a duplicate span (two failing properties resolving
|
|
31
|
+
* through the same access) is applied once.
|
|
32
|
+
*/
|
|
33
|
+
export declare function rewriteFile(source: string, sites: readonly CollisionSite[]): FileRewrite;
|
|
34
|
+
/** The project's own `tsc` binary, found upward from `root`. Never installs one. */
|
|
35
|
+
export declare function resolveTscBin(root: string): string | undefined;
|
|
36
|
+
//# sourceMappingURL=reserved-segment-codemod.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reserved-segment-codemod.d.ts","sourceRoot":"","sources":["../../src/internal/reserved-segment-codemod.ts"],"names":[],"mappings":"AA2BA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAED,sFAAsF;AACtF,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,aAAa,EAAE,CAe/E;AAoBD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,aAAa,GAClB;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAwB9D;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,sFAAsF;IACtF,QAAQ,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,CAAA;CAC3C;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,aAAa,EAAE,GAAG,WAAW,CAiBxF;AAED,oFAAoF;AACpF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAS9D"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codemod for reserved-segment collisions in typed-client call sites.
|
|
3
|
+
*
|
|
4
|
+
* A route segment spelling a reserved proxy key (`get`…`options`, `subscribe`, `ws`, `index`, `then`)
|
|
5
|
+
* cannot be reached by property access - the proxy resolves the reserved behavior first - so the client
|
|
6
|
+
* type resolves that child to `ReservedSegmentCollision<"seg">` and every `api.foo.delete.post()` in the
|
|
7
|
+
* project stops compiling at once. The fix is mechanical and identical at every site: call the parent
|
|
8
|
+
* node with the segment instead, `api.foo("delete").post()`. Doing that by hand across a codebase is
|
|
9
|
+
* pure toil, and it is toil the framework caused.
|
|
10
|
+
*
|
|
11
|
+
* The compiler is the site index. `tsc` already reports every broken access, and its message names the
|
|
12
|
+
* segment (`Property 'post' does not exist on type 'ReservedSegmentCollision<"delete">'`), so the
|
|
13
|
+
* rewrite needs no type graph of its own and cannot invent a site the compiler did not reject. That
|
|
14
|
+
* also disambiguates the one case a textual search could not: `.delete` as a real DELETE verb call is
|
|
15
|
+
* valid and never appears in this list.
|
|
16
|
+
*
|
|
17
|
+
* A site the scan cannot rewrite CONFIDENTLY is left alone and reported, never guessed at. Bracket
|
|
18
|
+
* access, a collision node stored in a variable and used elsewhere, and any shape where the text before
|
|
19
|
+
* the failing property is not `.<segment>` all fall into that bucket.
|
|
20
|
+
*/
|
|
21
|
+
import { existsSync } from "node:fs";
|
|
22
|
+
import { dirname, join } from "node:path";
|
|
23
|
+
/** `path/to/file.ts(12,16): error TS2339: … 'ReservedSegmentCollision<"delete">'.` */
|
|
24
|
+
const TSC_POSITION = /^(.+?)\((\d+),(\d+)\):\s*(?:error|warning)\s+TS\d+:\s*(.+)$/;
|
|
25
|
+
const COLLISION_TYPE = /ReservedSegmentCollision<"([^"]+)">/;
|
|
26
|
+
/** Every reserved-segment collision `tsc` reported, in the order it reported them. */
|
|
27
|
+
export function parseCollisionSites(tscOutput) {
|
|
28
|
+
const sites = [];
|
|
29
|
+
for (const raw of tscOutput.split("\n")) {
|
|
30
|
+
const position = TSC_POSITION.exec(raw.trim());
|
|
31
|
+
if (position === null)
|
|
32
|
+
continue;
|
|
33
|
+
const segment = COLLISION_TYPE.exec(position[4]);
|
|
34
|
+
if (segment === null)
|
|
35
|
+
continue;
|
|
36
|
+
sites.push({
|
|
37
|
+
file: position[1],
|
|
38
|
+
line: Number(position[2]),
|
|
39
|
+
column: Number(position[3]),
|
|
40
|
+
segment: segment[1],
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return sites;
|
|
44
|
+
}
|
|
45
|
+
/** Byte offset of a 1-based (line, column) in `source`, or undefined when it is past the end. */
|
|
46
|
+
const offsetOf = (source, line, column) => {
|
|
47
|
+
let offset = 0;
|
|
48
|
+
for (let current = 1; current < line; current += 1) {
|
|
49
|
+
const next = source.indexOf("\n", offset);
|
|
50
|
+
if (next === -1)
|
|
51
|
+
return undefined;
|
|
52
|
+
offset = next + 1;
|
|
53
|
+
}
|
|
54
|
+
const target = offset + column - 1;
|
|
55
|
+
return target <= source.length ? target : undefined;
|
|
56
|
+
};
|
|
57
|
+
const skipSpaceBack = (source, from) => {
|
|
58
|
+
let index = from;
|
|
59
|
+
while (index >= 0 && /\s/.test(source[index]))
|
|
60
|
+
index -= 1;
|
|
61
|
+
return index;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* The span to replace for one site: the `.segment` immediately preceding the failing property access,
|
|
65
|
+
* which becomes `("segment")`. Undefined when the text does not have that shape - the caller reports
|
|
66
|
+
* those rather than rewriting something it did not recognise.
|
|
67
|
+
*
|
|
68
|
+
* The scan runs backwards over the whole source rather than one line, so a chain broken across lines
|
|
69
|
+
* (`api.foo\n .delete\n .post()`) rewrites the same as a chain on one line.
|
|
70
|
+
*/
|
|
71
|
+
export function collisionSpan(source, site) {
|
|
72
|
+
const offset = offsetOf(source, site.line, site.column);
|
|
73
|
+
if (offset === undefined)
|
|
74
|
+
return undefined;
|
|
75
|
+
// Back over the dot that introduces the FAILING property (`.post` in `…delete.post`).
|
|
76
|
+
let index = skipSpaceBack(source, offset - 1);
|
|
77
|
+
if (source[index] !== ".")
|
|
78
|
+
return undefined;
|
|
79
|
+
// Back over the colliding segment itself.
|
|
80
|
+
index = skipSpaceBack(source, index - 1);
|
|
81
|
+
const segmentEnd = index;
|
|
82
|
+
const segmentStart = segmentEnd - site.segment.length + 1;
|
|
83
|
+
if (segmentStart < 0)
|
|
84
|
+
return undefined;
|
|
85
|
+
if (source.slice(segmentStart, segmentEnd + 1) !== site.segment)
|
|
86
|
+
return undefined;
|
|
87
|
+
// Back over the dot that introduces the segment. Anything else (bracket access, a bare identifier
|
|
88
|
+
// holding the node) is a shape this rewrite does not understand.
|
|
89
|
+
const dot = skipSpaceBack(source, segmentStart - 1);
|
|
90
|
+
if (source[dot] !== ".")
|
|
91
|
+
return undefined;
|
|
92
|
+
// Take any whitespace before that dot into the span too, so a chain broken across lines closes up
|
|
93
|
+
// (`api.jobs\n .subscribe` becomes `api.jobs("subscribe")`) instead of leaving the call argument
|
|
94
|
+
// dangling on its own line.
|
|
95
|
+
return { start: skipSpaceBack(source, dot - 1) + 1, end: segmentEnd };
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Apply every site belonging to one file. Spans are applied back to front so an earlier rewrite never
|
|
99
|
+
* shifts the offsets of one not yet applied, and a duplicate span (two failing properties resolving
|
|
100
|
+
* through the same access) is applied once.
|
|
101
|
+
*/
|
|
102
|
+
export function rewriteFile(source, sites) {
|
|
103
|
+
const skipped = [];
|
|
104
|
+
const spans = [];
|
|
105
|
+
for (const site of sites) {
|
|
106
|
+
const span = collisionSpan(source, site);
|
|
107
|
+
if (span === undefined) {
|
|
108
|
+
skipped.push(site);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (spans.some((other) => other.start === span.start && other.end === span.end))
|
|
112
|
+
continue;
|
|
113
|
+
spans.push({ ...span, segment: site.segment });
|
|
114
|
+
}
|
|
115
|
+
let out = source;
|
|
116
|
+
for (const span of [...spans].sort((a, b) => b.start - a.start)) {
|
|
117
|
+
out = `${out.slice(0, span.start)}("${span.segment}")${out.slice(span.end + 1)}`;
|
|
118
|
+
}
|
|
119
|
+
return { source: out, skipped };
|
|
120
|
+
}
|
|
121
|
+
/** The project's own `tsc` binary, found upward from `root`. Never installs one. */
|
|
122
|
+
export function resolveTscBin(root) {
|
|
123
|
+
let dir = root;
|
|
124
|
+
while (true) {
|
|
125
|
+
const candidate = join(dir, "node_modules", "typescript", "bin", "tsc");
|
|
126
|
+
if (existsSync(candidate))
|
|
127
|
+
return candidate;
|
|
128
|
+
const parent = dirname(dir);
|
|
129
|
+
if (parent === dir)
|
|
130
|
+
return undefined;
|
|
131
|
+
dir = parent;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=reserved-segment-codemod.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reserved-segment-codemod.js","sourceRoot":"","sources":["../../src/internal/reserved-segment-codemod.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
/** The slice of the compiler the SQL scanner uses. Structural, so the peer stays optional. */
|
|
15
15
|
export type TypeScriptApi = typeof import("typescript");
|
|
16
16
|
/**
|
|
17
|
-
* Import the
|
|
17
|
+
* Import TypeScript from the CLI's own dependency tree, or `undefined` when it is not installed.
|
|
18
18
|
*
|
|
19
19
|
* Only a RESOLUTION failure is absence. A compiler that resolves and then fails while evaluating is a
|
|
20
20
|
* broken install, not a missing one, and telling that user to install what they already have sends
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typescript-import.d.ts","sourceRoot":"","sources":["../../src/internal/typescript-import.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"typescript-import.d.ts","sourceRoot":"","sources":["../../src/internal/typescript-import.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,8FAA8F;AAC9F,MAAM,MAAM,aAAa,GAAG,cAAc,YAAY,CAAC,CAAA;AAyBvD;;;;;;GAMG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAQ3E;AA6BD;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAgB9F"}
|
|
@@ -19,8 +19,33 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
19
19
|
}
|
|
20
20
|
return path;
|
|
21
21
|
};
|
|
22
|
+
import { existsSync } from "node:fs";
|
|
23
|
+
import { dirname, join } from "node:path";
|
|
22
24
|
/**
|
|
23
|
-
*
|
|
25
|
+
* Is this module actually the compiler, rather than something else published under the same name?
|
|
26
|
+
*
|
|
27
|
+
* The `typescript` package name also carries small non-compiler entry points (a version stub, for
|
|
28
|
+
* one), and a resolver that falls back to a global download cache can hand one of those back. They
|
|
29
|
+
* import cleanly and then explode on first use - `undefined is not an object (evaluating
|
|
30
|
+
* 'ts.ScriptKind.TSX')` deep inside a scan - which reads as a Nifra bug rather than as "the compiler
|
|
31
|
+
* we loaded is not a compiler". Checking the two members every caller needs turns that into a
|
|
32
|
+
* resolution miss, which the fallbacks below already know how to handle.
|
|
33
|
+
*/
|
|
34
|
+
function isCompiler(module) {
|
|
35
|
+
if (typeof module !== "object" || module === null)
|
|
36
|
+
return false;
|
|
37
|
+
const api = module;
|
|
38
|
+
return typeof api.createSourceFile === "function" && api.ScriptKind !== undefined;
|
|
39
|
+
}
|
|
40
|
+
/** Unwrap the CJS default interop wrapper, so both `import * as ts` shapes look the same. */
|
|
41
|
+
function compilerOf(module) {
|
|
42
|
+
if (isCompiler(module))
|
|
43
|
+
return module;
|
|
44
|
+
const wrapped = module?.default;
|
|
45
|
+
return isCompiler(wrapped) ? wrapped : undefined;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Import TypeScript from the CLI's own dependency tree, or `undefined` when it is not installed.
|
|
24
49
|
*
|
|
25
50
|
* Only a RESOLUTION failure is absence. A compiler that resolves and then fails while evaluating is a
|
|
26
51
|
* broken install, not a missing one, and telling that user to install what they already have sends
|
|
@@ -28,7 +53,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
28
53
|
*/
|
|
29
54
|
export async function importTypeScript() {
|
|
30
55
|
try {
|
|
31
|
-
return (await import("typescript"));
|
|
56
|
+
return compilerOf(await import("typescript"));
|
|
32
57
|
}
|
|
33
58
|
catch (cause) {
|
|
34
59
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
@@ -37,6 +62,35 @@ export async function importTypeScript() {
|
|
|
37
62
|
throw cause;
|
|
38
63
|
}
|
|
39
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Find the project's compiler the way module resolution would: `node_modules/typescript` in `root`,
|
|
67
|
+
* then each parent directory up to the filesystem root - the same walk the typecheck gate uses to
|
|
68
|
+
* find `tsc`, so both gates agree on which compiler the project has.
|
|
69
|
+
*
|
|
70
|
+
* The walk is deliberate, not a fallback for a missing resolver. `Bun.resolveSync` memoizes a
|
|
71
|
+
* specifier for the life of the process AND auto-installs from the global download cache when the
|
|
72
|
+
* project has none, so a long-lived process (the MCP server) that first resolved `typescript` before
|
|
73
|
+
* `bun install` kept handing back that cache entry afterwards - the typecheck went phantom until the
|
|
74
|
+
* server was restarted. A filesystem probe is re-answered every call, so an install that lands
|
|
75
|
+
* mid-session is picked up on the next scan.
|
|
76
|
+
*/
|
|
77
|
+
function resolveProjectTypeScript(root) {
|
|
78
|
+
let dir = root;
|
|
79
|
+
while (true) {
|
|
80
|
+
const pkg = join(dir, "node_modules", "typescript");
|
|
81
|
+
// `lib/typescript.js` is the compiler entry across every published major; probing it directly
|
|
82
|
+
// (rather than importing the directory) keeps a package whose main entry is a stub out of play.
|
|
83
|
+
const entry = join(pkg, "lib", "typescript.js");
|
|
84
|
+
if (existsSync(entry))
|
|
85
|
+
return entry;
|
|
86
|
+
if (existsSync(join(pkg, "package.json")))
|
|
87
|
+
return pkg;
|
|
88
|
+
const parent = dirname(dir);
|
|
89
|
+
if (parent === dir)
|
|
90
|
+
return undefined;
|
|
91
|
+
dir = parent;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
40
94
|
/**
|
|
41
95
|
* Import TypeScript resolved from the PROJECT root first, falling back to the CLI's own dependency
|
|
42
96
|
* tree. A bare `import("typescript")` resolves relative to this file - the CLI's install - so a
|
|
@@ -46,17 +100,14 @@ export async function importTypeScript() {
|
|
|
46
100
|
* the CLI was installed - the same cwd-invariance the typecheck gate's tsc resolution has.
|
|
47
101
|
*/
|
|
48
102
|
export async function importProjectTypeScript(root) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
catch {
|
|
54
|
-
// Unresolvable from the project (not installed there, or a non-Bun host without resolveSync):
|
|
55
|
-
// fall back to the CLI's own tree, which keeps the previous behavior as the floor.
|
|
103
|
+
const resolved = resolveProjectTypeScript(root);
|
|
104
|
+
// Not installed in the project (or installed without a compiler entry): fall back to the CLI's own
|
|
105
|
+
// tree, which keeps the previous behavior as the floor.
|
|
106
|
+
if (resolved === undefined)
|
|
56
107
|
return importTypeScript();
|
|
57
|
-
}
|
|
58
108
|
try {
|
|
59
|
-
|
|
109
|
+
const compiler = compilerOf(await import(__rewriteRelativeImportExtension(resolved)));
|
|
110
|
+
return compiler ?? (await importTypeScript());
|
|
60
111
|
}
|
|
61
112
|
catch (cause) {
|
|
62
113
|
// Same contract as importTypeScript: a compiler that RESOLVES and then fails evaluating is a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typescript-import.js","sourceRoot":"","sources":["../../src/internal/typescript-import.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"typescript-import.js","sourceRoot":"","sources":["../../src/internal/typescript-import.ts"],"names":[],"mappings":"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"}
|
package/dist/introspect.d.ts
CHANGED
|
@@ -26,6 +26,11 @@ export declare function tsTypeOf(schema: unknown, depth?: number): string;
|
|
|
26
26
|
* that appends the value (`({ id })`), the root path is `.index`, and the HTTP verb is the terminal call.
|
|
27
27
|
* Body verbs (POST/PUT/PATCH) take the body first then call-options; other verbs take call-options first -
|
|
28
28
|
* so the `{ query }` argument lands in the right slot for each.
|
|
29
|
+
*
|
|
30
|
+
* A segment spelling a reserved proxy key (`delete`, `subscribe`, `then`, ...) is emitted as a call on
|
|
31
|
+
* the parent node instead - and NOT as bracket access, which does not help: the proxy intercepts the
|
|
32
|
+
* key however it is spelled. Emitting `.delete` here would hand an agent a chain that resolves the
|
|
33
|
+
* DELETE verb and silently requests the wrong path.
|
|
29
34
|
*/
|
|
30
35
|
export declare function clientCall(method: string, path: string, schema: unknown): string;
|
|
31
36
|
/** Markdown section listing the backend's API routes with their request + response field shapes. */
|
|
@@ -109,6 +114,20 @@ export interface RouteTableInput {
|
|
|
109
114
|
* joined methods, for stable output.
|
|
110
115
|
*/
|
|
111
116
|
export declare function buildRouteTable(input: RouteTableInput): RouteTableEntry[];
|
|
117
|
+
/**
|
|
118
|
+
* The typed-client spelling for a route path, and the reserved key it collides with when one of its
|
|
119
|
+
* static segments cannot be reached by property access.
|
|
120
|
+
*
|
|
121
|
+
* This is where a dev meets the reserved set EARLIEST: `nifra routes` is what you run while the
|
|
122
|
+
* route is being written, long before a typed client call site exists to break. The reserved names
|
|
123
|
+
* are deliberately unguessable from the outside (`then` and `index` especially - they exist so the
|
|
124
|
+
* proxy stays await-safe and can address `/`), so the route table names the collision and shows the
|
|
125
|
+
* spelling that reaches the route, rather than leaving it to be discovered by a failing build.
|
|
126
|
+
*/
|
|
127
|
+
export declare function clientSpellingFor(path: string): {
|
|
128
|
+
readonly spelling: string;
|
|
129
|
+
readonly reserved: string;
|
|
130
|
+
} | undefined;
|
|
112
131
|
/** Render the route table as a terse aligned text table (the `nifra routes` default output). Pure. */
|
|
113
132
|
export declare function renderRouteTable(rows: readonly RouteTableEntry[]): string;
|
|
114
133
|
/** One JSON row for `nifra routes --json` (the agent-facing, stable shape). */
|
|
@@ -118,9 +137,15 @@ export interface RouteJsonRow {
|
|
|
118
137
|
readonly methods: readonly string[];
|
|
119
138
|
readonly file?: string;
|
|
120
139
|
readonly autoMounted?: boolean;
|
|
140
|
+
/** The reserved typed-client key a static segment of this path collides with, when one does. */
|
|
141
|
+
readonly reservedKey?: string;
|
|
142
|
+
/** The typed-client call spelling that reaches this route, present only alongside `reservedKey`. */
|
|
143
|
+
readonly clientSpelling?: string;
|
|
121
144
|
}
|
|
122
145
|
/** The route table as a JSON document for `--json` (agents). Stable keys; `file`/`autoMounted` present
|
|
123
|
-
* only where meaningful (file on pages, autoMounted on API routes).
|
|
146
|
+
* only where meaningful (file on pages, autoMounted on API routes). A colliding row additionally
|
|
147
|
+
* carries `reservedKey`/`clientSpelling` so an agent writing client code from this table never has to
|
|
148
|
+
* infer the closed set of reserved names - the one thing about it nobody can guess. */
|
|
124
149
|
export declare function routeTableToJson(rows: readonly RouteTableEntry[]): {
|
|
125
150
|
readonly routes: readonly RouteJsonRow[];
|
|
126
151
|
};
|