@nifrajs/cli 2.8.1 → 2.9.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/dist/cli.js +1 -1
- package/dist/mcp-http.js +1 -1
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +34 -1
- package/dist/scaffold.js.map +1 -1
- package/docs/examples.json +9 -3
- package/docs/llms-full.txt +219 -45
- package/docs/types.json +177 -15
- package/package.json +9 -9
- package/src/cli.ts +1 -1
- package/src/mcp-http.ts +1 -1
- package/src/scaffold.ts +34 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nifrajs/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"description": "The nifra CLI - zero-config dev/build/start for a nifra app (`nifra dev`, `nifra build`, `nifra start`).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -41,14 +41,14 @@
|
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@nifrajs/client": "2.
|
|
45
|
-
"@nifrajs/core": "2.
|
|
46
|
-
"@nifrajs/mcp": "2.
|
|
47
|
-
"@nifrajs/runner": "2.
|
|
48
|
-
"@nifrajs/schema": "2.
|
|
49
|
-
"@nifrajs/testing": "2.
|
|
50
|
-
"@nifrajs/web": "2.
|
|
51
|
-
"create-nifra": "2.
|
|
44
|
+
"@nifrajs/client": "2.9.0",
|
|
45
|
+
"@nifrajs/core": "2.9.0",
|
|
46
|
+
"@nifrajs/mcp": "2.9.0",
|
|
47
|
+
"@nifrajs/runner": "2.9.0",
|
|
48
|
+
"@nifrajs/schema": "2.9.0",
|
|
49
|
+
"@nifrajs/testing": "2.9.0",
|
|
50
|
+
"@nifrajs/web": "2.9.0",
|
|
51
|
+
"create-nifra": "2.9.0"
|
|
52
52
|
},
|
|
53
53
|
"repository": {
|
|
54
54
|
"type": "git",
|
package/src/cli.ts
CHANGED
|
@@ -148,7 +148,7 @@ Port: \`dev\` and \`start\` share the default ${DEFAULT_DEV_PORT}. Override with
|
|
|
148
148
|
\`PORT\` env var (\`--port\` wins over \`PORT\`, which wins over the default).`
|
|
149
149
|
|
|
150
150
|
// Kept in lockstep with packages/cli/package.json by check:publish's version-consistency gate.
|
|
151
|
-
const CLI_VERSION = "2.
|
|
151
|
+
const CLI_VERSION = "2.9.0"
|
|
152
152
|
|
|
153
153
|
// A render adapter + nifra server are opaque to the CLI (it just forwards them); cast at the seam.
|
|
154
154
|
const asAdapter = (v: unknown): RenderAdapter => v as RenderAdapter
|
package/src/mcp-http.ts
CHANGED
|
@@ -29,7 +29,7 @@ export { docsTools } from "./mcp-docs-tools.ts"
|
|
|
29
29
|
export type { TypeEntry } from "./types-search.ts"
|
|
30
30
|
|
|
31
31
|
// Kept in lockstep with packages/cli/package.json by check:publish's version-consistency gate.
|
|
32
|
-
const VERSION = "2.
|
|
32
|
+
const VERSION = "2.9.0"
|
|
33
33
|
const SERVER_INFO = { name: "nifra-docs", version: VERSION }
|
|
34
34
|
// Derive the GET/health tool list from the tools actually served, so the line can never drift from them.
|
|
35
35
|
const docsHealth = (tools: McpTool[]): string =>
|
package/src/scaffold.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* `nifra_example` for the body, rather than hand-writing an SFC we can't typecheck here.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { mkdir, writeFile } from "node:fs/promises"
|
|
12
|
+
import { lstat, mkdir, realpath, writeFile } from "node:fs/promises"
|
|
13
13
|
import { dirname, resolve, sep } from "node:path"
|
|
14
14
|
|
|
15
15
|
export type Framework = "react" | "preact" | "solid" | "vue" | "svelte" | "vanilla"
|
|
@@ -122,6 +122,37 @@ function resolveInsideCwd(cwd: string, relativeFile: string): string {
|
|
|
122
122
|
return target
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/** Lexical containment is not enough when an existing route directory is a symlink. Check every
|
|
126
|
+
* existing ancestor and its real path before scaffolding so an agent cannot redirect a write outside
|
|
127
|
+
* the selected project. The target itself is created with `wx`, so an existing target symlink is also
|
|
128
|
+
* never followed/overwritten. */
|
|
129
|
+
async function assertNoSymlinkedAncestors(root: string, target: string): Promise<void> {
|
|
130
|
+
const rootPath = resolve(root)
|
|
131
|
+
const realRoot = await realpath(rootPath)
|
|
132
|
+
let current = dirname(target)
|
|
133
|
+
while (current !== rootPath) {
|
|
134
|
+
if (!current.startsWith(`${rootPath}${sep}`)) {
|
|
135
|
+
throw new Error(`refusing to write outside project root: ${target}`)
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
if ((await lstat(current)).isSymbolicLink()) {
|
|
139
|
+
throw new Error(`refusing to write through symlinked directory: ${current}`)
|
|
140
|
+
}
|
|
141
|
+
const actual = await realpath(current)
|
|
142
|
+
if (actual !== realRoot && !actual.startsWith(`${realRoot}${sep}`)) {
|
|
143
|
+
throw new Error(`refusing to write outside project root through: ${current}`)
|
|
144
|
+
}
|
|
145
|
+
} catch (err) {
|
|
146
|
+
if (err && typeof err === "object" && (err as { code?: string }).code === "ENOENT") {
|
|
147
|
+
current = dirname(current)
|
|
148
|
+
continue
|
|
149
|
+
}
|
|
150
|
+
throw err
|
|
151
|
+
}
|
|
152
|
+
current = dirname(current)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
125
156
|
/** Write a scaffolded route stub when the framework has a verified ready-to-write body. The write is
|
|
126
157
|
* intentionally conservative: it refuses non-JSX stubs (where we only return contract guidance) and
|
|
127
158
|
* uses `wx`, so an agent cannot overwrite user work by accident. */
|
|
@@ -139,7 +170,9 @@ export async function writeScaffoldRoute(
|
|
|
139
170
|
}
|
|
140
171
|
}
|
|
141
172
|
const target = resolveInsideCwd(cwd, result.file)
|
|
173
|
+
await assertNoSymlinkedAncestors(cwd, target)
|
|
142
174
|
await mkdir(dirname(target), { recursive: true })
|
|
175
|
+
await assertNoSymlinkedAncestors(cwd, target)
|
|
143
176
|
try {
|
|
144
177
|
await writeFile(target, result.content, { flag: "wx" })
|
|
145
178
|
} catch (err) {
|