@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
package/src/scaffold.ts
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* `filePathToPatterns`: `:id`/`[id]` → `[id]`, `*rest`/`[...rest]` → `[...rest]`, `[[lang]]` optional,
|
|
5
5
|
* `/` → `index`. The framework (→ file extension) comes from the project's `clientModule`.
|
|
6
6
|
*
|
|
7
|
-
* Page stubs are emitted
|
|
8
|
-
* for
|
|
9
|
-
*
|
|
7
|
+
* Page stubs are emitted for the JSX family (react/preact/solid - one shared, verified shape) and
|
|
8
|
+
* for vanilla (a zero-runtime `html` page carrying the golden island pattern); for vue/svelte we
|
|
9
|
+
* return the correct PATH + the route-module contract and point at `nifra_example` for the body,
|
|
10
|
+
* rather than hand-writing an SFC we can't typecheck here.
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
import { lstat, mkdir, realpath, writeFile } from "node:fs/promises"
|
|
@@ -14,6 +15,12 @@ import { dirname, resolve, sep } from "node:path"
|
|
|
14
15
|
|
|
15
16
|
export type Framework = "react" | "preact" | "solid" | "vue" | "svelte" | "vanilla"
|
|
16
17
|
|
|
18
|
+
/** A scaffold flavour on top of the framework. `default` is the plain page stub; `stateful` only
|
|
19
|
+
* applies to vanilla and emits the golden **nano** pattern (explicit reactivity: `signal` +
|
|
20
|
+
* `computed(fn, [deps])` + keyed `bindList` + collected cleanups) instead of the static island stub -
|
|
21
|
+
* the AI-safe small-app lane whose three mistakes NF-C021/C022/C023 catch statically. */
|
|
22
|
+
export type ScaffoldVariant = "default" | "stateful"
|
|
23
|
+
|
|
17
24
|
const EXT: Record<Framework, string> = {
|
|
18
25
|
react: "tsx",
|
|
19
26
|
preact: "tsx",
|
|
@@ -75,6 +82,134 @@ export default function Page() {
|
|
|
75
82
|
`
|
|
76
83
|
}
|
|
77
84
|
|
|
85
|
+
/**
|
|
86
|
+
* The @nifrajs/web-vanilla golden stub: a zero-runtime `html` page (`hydrate = false`), plus the
|
|
87
|
+
* copy-paste island path as guidance. The uncommented body is a valid, typecheckable static page;
|
|
88
|
+
* the commented block is the AI-safe interactivity pattern - an imperative enhancer that ALWAYS
|
|
89
|
+
* returns its cleanup (the one thing NF-C020 checks), wired through `mountIslands`. Interactivity is
|
|
90
|
+
* added by uncommenting and writing the companion `<name>.client.ts`, never by turning on hydration.
|
|
91
|
+
*/
|
|
92
|
+
function vanillaStub(file: string, params: string[]): string {
|
|
93
|
+
const paramsNote =
|
|
94
|
+
params.length > 0 ? `params.${params.join(", params.")}` : "no path params on this route"
|
|
95
|
+
const loaderLine =
|
|
96
|
+
params.length > 0
|
|
97
|
+
? `// export async function loader({ params, api }: LoaderArgs<typeof backend>) { return { /* fetch by ${params[0]} */ } }`
|
|
98
|
+
: `// export async function loader({ api }: LoaderArgs<typeof backend>) { return {} }`
|
|
99
|
+
return `// ${file} - @nifrajs/web-vanilla route. Server-rendered HTML, ZERO framework runtime.
|
|
100
|
+
// Available here: ${paramsNote}. Fetch data in a loader via the typed \`api\`; see nifra_example("loader").
|
|
101
|
+
import { html } from "@nifrajs/web-vanilla"
|
|
102
|
+
|
|
103
|
+
// No client framework to hydrate with - vanilla routes are documents, not hydrated apps.
|
|
104
|
+
export const hydrate = false
|
|
105
|
+
|
|
106
|
+
${loaderLine}
|
|
107
|
+
|
|
108
|
+
export default function Page() {
|
|
109
|
+
return html\`<main><h1>TODO: ${file}</h1></main>\`
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// --- Add interactivity the AI-safe way (islands), NOT hydration ---------------------------------
|
|
113
|
+
// 1. Render a marker in the page above: html\`<nifra-island data-id="counter" data-props=\${JSON.stringify({ start: 0 })}></nifra-island>\`
|
|
114
|
+
// 2. Wire the route to its enhancer bundle: export const islandScripts = [/* built URL of ./counter.client.ts */]
|
|
115
|
+
// 3. Write ./counter.client.ts as an imperative enhancer that ALWAYS returns its cleanup:
|
|
116
|
+
//
|
|
117
|
+
// import { defineIsland, mountIslands } from "@nifrajs/web/islands"
|
|
118
|
+
// const counter = defineIsland<{ start: number }>((el, props) => {
|
|
119
|
+
// let n = props.start
|
|
120
|
+
// const out = el.querySelector("output")!
|
|
121
|
+
// const onClick = () => { out.textContent = String(++n) }
|
|
122
|
+
// el.querySelector("button")?.addEventListener("click", onClick)
|
|
123
|
+
// return () => el.querySelector("button")?.removeEventListener("click", onClick) // cleanup - NF-C020
|
|
124
|
+
// })
|
|
125
|
+
// mountIslands({ counter })
|
|
126
|
+
//
|
|
127
|
+
// Cross-island coordination: create ONE createIslandBus() and close over it in each enhancer.
|
|
128
|
+
`
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The nano golden stub (`nifra scaffold --variant stateful`, vanilla only): a zero-runtime `html`
|
|
133
|
+
* page that renders a `<nifra-island>` marker, plus the companion `<name>.client.ts` shown as the
|
|
134
|
+
* copy-paste block. The client is the AI-safe small-app lane - explicit reactivity, no VDOM, no
|
|
135
|
+
* auto-tracking - and every reactive edge is a visible call, which is what makes the three nano
|
|
136
|
+
* mistakes STATICALLY catchable:
|
|
137
|
+
* - a `bind`/`bindList` whose disposer is discarded -> NF-C021 (the block collects every disposer)
|
|
138
|
+
* - a `bindList` keyed by array index -> NF-C022 (the block keys by `item.id`)
|
|
139
|
+
* - a `computed` reading a signal its `[deps]` omits -> NF-C023 (the block declares `[todos]`)
|
|
140
|
+
* The page body is a valid, typecheckable static document; interactivity is added by writing the
|
|
141
|
+
* companion client shown, never by turning on hydration.
|
|
142
|
+
*/
|
|
143
|
+
function nanoStub(file: string, params: string[]): string {
|
|
144
|
+
const paramsNote =
|
|
145
|
+
params.length > 0 ? `params.${params.join(", params.")}` : "no path params on this route"
|
|
146
|
+
const loaderLine =
|
|
147
|
+
params.length > 0
|
|
148
|
+
? `// export async function loader({ params, api }: LoaderArgs<typeof backend>) { return { /* seed by ${params[0]} */ } }`
|
|
149
|
+
: `// export async function loader({ api }: LoaderArgs<typeof backend>) { return {} }`
|
|
150
|
+
return `// ${file} - @nifrajs/web-vanilla route + a nano island (explicit reactivity, zero VDOM).
|
|
151
|
+
// Available here: ${paramsNote}. Seed data in a loader via the typed \`api\`; see nifra_example("loader").
|
|
152
|
+
import { html } from "@nifrajs/web-vanilla"
|
|
153
|
+
|
|
154
|
+
// The document is static; the nano client below owns all interactivity. No hydration.
|
|
155
|
+
export const hydrate = false
|
|
156
|
+
|
|
157
|
+
${loaderLine}
|
|
158
|
+
|
|
159
|
+
export default function Page() {
|
|
160
|
+
// The island marker: data-props is the initial state the client reads on mount.
|
|
161
|
+
return html\`<main>
|
|
162
|
+
<h1>Todos</h1>
|
|
163
|
+
<nifra-island data-id="todos" data-props=\${JSON.stringify({ items: [] })}></nifra-island>
|
|
164
|
+
</main>\`
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Wire the route to the built URL of ./todos.client.ts (the nano enhancer below).
|
|
168
|
+
// export const islandScripts = [/* built URL of ./todos.client.ts */]
|
|
169
|
+
|
|
170
|
+
// --- ./todos.client.ts - the golden nano pattern (write this as a companion file) -----------------
|
|
171
|
+
//
|
|
172
|
+
// import { defineIsland, mountIslands } from "@nifrajs/web/islands"
|
|
173
|
+
// import { signal, computed, bind, bindList } from "@nifrajs/web/nano"
|
|
174
|
+
//
|
|
175
|
+
// interface Todo { id: string; text: string; done: boolean }
|
|
176
|
+
//
|
|
177
|
+
// const todos = defineIsland<{ items: Todo[] }>((el, props) => {
|
|
178
|
+
// // 1. State is a signal. Reads are \`.get()\`, writes are \`.set(...)\` - every edge is visible.
|
|
179
|
+
// const items = signal<Todo[]>(props.items)
|
|
180
|
+
//
|
|
181
|
+
// // 2. Derived state declares its deps EXPLICITLY. Omitting \`todos\`/\`items\` here is NF-C023.
|
|
182
|
+
// const remaining = computed(() => items.get().filter((t) => !t.done).length, [items])
|
|
183
|
+
//
|
|
184
|
+
// // 3. Collect EVERY disposer. A bare \`bind(...)\`/\`bindList(...)\` that drops it is NF-C021.
|
|
185
|
+
// const cleanups: Array<() => void> = []
|
|
186
|
+
// const count = el.querySelector("[data-count]")!
|
|
187
|
+
// cleanups.push(bind(count, remaining, (node, n) => { node.textContent = String(n) + " left" }))
|
|
188
|
+
//
|
|
189
|
+
// // 4. Keyed list. Key by a STABLE id on the item, never the array index (NF-C022) - or
|
|
190
|
+
// // add/remove/reorder reuses the wrong DOM node.
|
|
191
|
+
// const list = el.querySelector("[data-list]")!
|
|
192
|
+
// cleanups.push(bindList(items, list, {
|
|
193
|
+
// key: (t) => t.id,
|
|
194
|
+
// create: (t) => { const li = document.createElement("li"); li.dataset.id = t.id; return li },
|
|
195
|
+
// update: (li, t) => { li.textContent = t.text; li.classList.toggle("done", t.done) },
|
|
196
|
+
// }))
|
|
197
|
+
//
|
|
198
|
+
// // Mutations replace the value (Object.is-deduped); subscribers run synchronously.
|
|
199
|
+
// const add = (text: string) => items.set([...items.get(), { id: crypto.randomUUID(), text, done: false }])
|
|
200
|
+
// void add
|
|
201
|
+
//
|
|
202
|
+
// // 5. Return the teardown - islands call it on soft-nav. This is what NF-C021 protects.
|
|
203
|
+
// return () => { for (const off of cleanups) off() }
|
|
204
|
+
// })
|
|
205
|
+
//
|
|
206
|
+
// mountIslands({ todos })
|
|
207
|
+
//
|
|
208
|
+
// Cross-island coordination: one createIslandBus() closed over in each enhancer. nifra_docs("nano")
|
|
209
|
+
// has the full cookbook; \`nifra check\` runs NF-C021/C022/C023 over the client you write.
|
|
210
|
+
`
|
|
211
|
+
}
|
|
212
|
+
|
|
78
213
|
/** Param names a route file declares, for the stub's notes. */
|
|
79
214
|
function paramsOf(file: string): string[] {
|
|
80
215
|
const out: string[] = []
|
|
@@ -96,20 +231,40 @@ export interface ScaffoldWriteResult extends ScaffoldResult {
|
|
|
96
231
|
|
|
97
232
|
/** Scaffold a page route for `urlPath` under the project's `framework`. Returns the correct file path
|
|
98
233
|
* always; a ready-to-write stub for the JSX family, contract guidance otherwise. */
|
|
99
|
-
export function scaffoldRoute(
|
|
234
|
+
export function scaffoldRoute(
|
|
235
|
+
urlPath: string,
|
|
236
|
+
framework: Framework,
|
|
237
|
+
variant: ScaffoldVariant = "default",
|
|
238
|
+
): ScaffoldResult {
|
|
100
239
|
const ext = EXT[framework]
|
|
101
240
|
const file = routePathToFile(urlPath, ext)
|
|
102
241
|
const params = paramsOf(file)
|
|
242
|
+
// `stateful` is vanilla-only: it swaps the static island stub for the nano golden pattern. Asking
|
|
243
|
+
// for it on a JSX/SFC framework is a no-op on the flavour (those lanes have their own reactivity).
|
|
244
|
+
if (framework === "vanilla" && variant === "stateful") {
|
|
245
|
+
return {
|
|
246
|
+
file,
|
|
247
|
+
content: nanoStub(file, params),
|
|
248
|
+
note: `Create ${file} as a zero-runtime @nifrajs/web-vanilla route with a nano island. ${ROUTE_CONTRACT}\nThe stub embeds the golden nano client (signal + computed(fn,[deps]) + keyed bindList + collected cleanups); write it as the companion .client.ts. \`nifra check\` runs NF-C021/C022/C023 over it; nifra_docs("nano") is the full cookbook.`,
|
|
249
|
+
}
|
|
250
|
+
}
|
|
103
251
|
if (framework === "react" || framework === "preact" || framework === "solid") {
|
|
104
252
|
return {
|
|
105
253
|
file,
|
|
106
254
|
content: jsxStub(file, params),
|
|
107
|
-
note: `Create ${file}. ${ROUTE_CONTRACT}
|
|
255
|
+
note: `Create ${file}. ${ROUTE_CONTRACT}\nIf the rendered page misbehaves (hydration warning, a value that stops updating), call nifra_frontend { adapter: "${framework}" } for the cause + fix.`,
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (framework === "vanilla") {
|
|
259
|
+
return {
|
|
260
|
+
file,
|
|
261
|
+
content: vanillaStub(file, params),
|
|
262
|
+
note: `Create ${file} as a zero-runtime @nifrajs/web-vanilla route. ${ROUTE_CONTRACT}\nInteractivity comes from islands (imperative enhancers), never hydration - the stub embeds the golden pattern; nifra_example("islands") has the full cookbook. For explicit local state (a list a human edits), scaffold with variant "stateful" to get the nano pattern instead.`,
|
|
108
263
|
}
|
|
109
264
|
}
|
|
110
265
|
return {
|
|
111
266
|
file,
|
|
112
|
-
note: `Create ${file} as a ${framework} route module. ${ROUTE_CONTRACT}\nFor the ${framework} page body, call nifra_example (it ships verified ${framework} snippets) rather than guessing the SFC shape.`,
|
|
267
|
+
note: `Create ${file} as a ${framework} route module. ${ROUTE_CONTRACT}\nFor the ${framework} page body, call nifra_example (it ships verified ${framework} snippets) rather than guessing the SFC shape. If a value stops updating the template, call nifra_frontend { adapter: "${framework}" } for the ${framework} reactivity-loss fix.`,
|
|
113
268
|
}
|
|
114
269
|
}
|
|
115
270
|
|
|
@@ -160,8 +315,9 @@ export async function writeScaffoldRoute(
|
|
|
160
315
|
cwd: string,
|
|
161
316
|
urlPath: string,
|
|
162
317
|
framework: Framework,
|
|
318
|
+
variant: ScaffoldVariant = "default",
|
|
163
319
|
): Promise<ScaffoldWriteResult> {
|
|
164
|
-
const result = scaffoldRoute(urlPath, framework)
|
|
320
|
+
const result = scaffoldRoute(urlPath, framework, variant)
|
|
165
321
|
if (result.content === undefined) {
|
|
166
322
|
return {
|
|
167
323
|
...result,
|
|
@@ -185,10 +341,14 @@ export async function writeScaffoldRoute(
|
|
|
185
341
|
}
|
|
186
342
|
|
|
187
343
|
/** Render the tool result as markdown - the file path, the stub (if any), and the contract note. */
|
|
188
|
-
export function renderScaffold(
|
|
344
|
+
export function renderScaffold(
|
|
345
|
+
urlPath: string,
|
|
346
|
+
framework: Framework,
|
|
347
|
+
variant: ScaffoldVariant = "default",
|
|
348
|
+
): string {
|
|
189
349
|
let r: ScaffoldResult
|
|
190
350
|
try {
|
|
191
|
-
r = scaffoldRoute(urlPath, framework)
|
|
351
|
+
r = scaffoldRoute(urlPath, framework, variant)
|
|
192
352
|
} catch (err) {
|
|
193
353
|
return `Cannot scaffold ${JSON.stringify(urlPath)}: ${err instanceof Error ? err.message : String(err)}`
|
|
194
354
|
}
|
package/src/search-terms.ts
CHANGED
|
@@ -62,10 +62,14 @@ export function queryTermGroups(query: string): SearchTermGroup[] {
|
|
|
62
62
|
}))
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
function tokenMatches(term: string,
|
|
66
|
-
if (term ===
|
|
65
|
+
function tokenMatches(term: string, candidate: string): boolean {
|
|
66
|
+
if (term === candidate) return true
|
|
67
67
|
// Prefix matching catches plural/stem variants without letting very short terms get noisy.
|
|
68
|
-
return
|
|
68
|
+
return (
|
|
69
|
+
term.length >= 4 &&
|
|
70
|
+
candidate.length >= 4 &&
|
|
71
|
+
(term.startsWith(candidate) || candidate.startsWith(term))
|
|
72
|
+
)
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
export function tokenSetHas(tokens: ReadonlySet<string>, group: SearchTermGroup): boolean {
|