@flood-md/cli 0.2.3 → 0.2.4
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 +6 -0
- package/lib/index.mjs +41 -6
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -16,3 +16,9 @@ Read the skill's routing reference for the task/source/check matrix. Doctor chec
|
|
|
16
16
|
the complete guide set and package alignment; it does not validate application
|
|
17
17
|
behavior, CSS imports or agent compliance. Merge customized guide upgrades
|
|
18
18
|
deliberately; init has no overwrite mode.
|
|
19
|
+
|
|
20
|
+
Init and doctor reject linked project-directory ancestry. Init also requires a
|
|
21
|
+
regular nonlinked package.json (including no hardlinks), validates its project
|
|
22
|
+
object and React dependency, and rechecks paths after npm before owned writes.
|
|
23
|
+
These checks do not sandbox npm or stop an external process from racing filesystem
|
|
24
|
+
changes; run installs only in a trusted project and inspect npm failures.
|
package/lib/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { readFile, writeFile, mkdir, lstat } from "node:fs/promises";
|
|
2
|
-
import { resolve, join } from "node:path";
|
|
2
|
+
import { resolve, join, parse, relative, sep } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
|
|
6
|
-
export const VERSION = "0.2.
|
|
6
|
+
export const VERSION = "0.2.4";
|
|
7
7
|
const packages = ["@flood-md/tokens", "@flood-md/react", "@flood-md/agent"];
|
|
8
8
|
const json = async (path) => JSON.parse(await readFile(path, "utf8"));
|
|
9
9
|
const agentFile = (name) =>
|
|
@@ -19,6 +19,39 @@ if (agentManifest.schemaVersion !== 1 || agentManifest.version !== VERSION || !A
|
|
|
19
19
|
throw new Error("Invalid Flood agent file manifest.");
|
|
20
20
|
export const AGENT_FILES = Object.freeze(agentManifest.files.map(file => Object.freeze({ ...file })));
|
|
21
21
|
|
|
22
|
+
async function projectDirectory(root) {
|
|
23
|
+
const absolute = resolve(root);
|
|
24
|
+
const anchor = parse(absolute).root;
|
|
25
|
+
let current = anchor;
|
|
26
|
+
// Inspect prefixes before descendants so a linked ancestor cannot redirect
|
|
27
|
+
// either package.json reads or subsequent npm/configuration writes.
|
|
28
|
+
for (const part of ["", ...relative(anchor, absolute).split(sep).filter(Boolean)]) {
|
|
29
|
+
if (part) current = join(current, part);
|
|
30
|
+
const info = await lstat(current).catch(() => {
|
|
31
|
+
throw new Error("Run Flood inside an existing project directory.");
|
|
32
|
+
});
|
|
33
|
+
if (info.isSymbolicLink()) throw new Error(`Refusing linked project directory: ${current}`);
|
|
34
|
+
if (!info.isDirectory()) throw new Error("Run Flood inside a regular project directory.");
|
|
35
|
+
}
|
|
36
|
+
return absolute;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function projectManifest(root) {
|
|
40
|
+
const path = join(root, "package.json");
|
|
41
|
+
const info = await lstat(path).catch(() => {
|
|
42
|
+
throw new Error("Run init inside a React project with a package.json.");
|
|
43
|
+
});
|
|
44
|
+
if (info.isSymbolicLink()) throw new Error("Refusing linked path: package.json");
|
|
45
|
+
if (!info.isFile()) throw new Error("package.json must be a regular file.");
|
|
46
|
+
if (info.nlink > 1) throw new Error("Refusing linked path: package.json");
|
|
47
|
+
const manifest = await json(path).catch(() => {
|
|
48
|
+
throw new Error("package.json must contain a valid project object.");
|
|
49
|
+
});
|
|
50
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest))
|
|
51
|
+
throw new Error("package.json must contain a valid project object.");
|
|
52
|
+
return manifest;
|
|
53
|
+
}
|
|
54
|
+
|
|
22
55
|
async function inspectTarget(root, relative) {
|
|
23
56
|
let current = root;
|
|
24
57
|
let info;
|
|
@@ -64,10 +97,9 @@ export async function initProject(
|
|
|
64
97
|
root,
|
|
65
98
|
{ dryRun = false, installer = install, log = console.log } = {},
|
|
66
99
|
) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (!(manifest.dependencies?.react || manifest.devDependencies?.react))
|
|
100
|
+
root = await projectDirectory(root);
|
|
101
|
+
const manifest = await projectManifest(root);
|
|
102
|
+
if (![manifest.dependencies?.react, manifest.devDependencies?.react].some(value => typeof value === "string" && value.trim()))
|
|
71
103
|
throw new Error(
|
|
72
104
|
"React was not found. Create a React or Next.js project first.",
|
|
73
105
|
);
|
|
@@ -86,6 +118,8 @@ export async function initProject(
|
|
|
86
118
|
...file, content: await readFile(agentFile(file.source)),
|
|
87
119
|
})));
|
|
88
120
|
await installer(root);
|
|
121
|
+
await projectDirectory(root);
|
|
122
|
+
await projectManifest(root);
|
|
89
123
|
const config = {
|
|
90
124
|
$schema: "https://flood.md/schema/config.json",
|
|
91
125
|
version: VERSION,
|
|
@@ -113,6 +147,7 @@ export async function initProject(
|
|
|
113
147
|
}
|
|
114
148
|
|
|
115
149
|
export async function doctor(root, log = console.log) {
|
|
150
|
+
root = await projectDirectory(root);
|
|
116
151
|
const configInfo = await inspectTarget(root, "flood.json");
|
|
117
152
|
if (!configInfo?.isFile()) throw new Error("No regular flood.json. Run init first.");
|
|
118
153
|
const config = await json(join(root, "flood.json")).catch(() => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flood-md/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Connect Flood to React and Next.js projects.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"node": ">=22.12.0"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@flood-md/agent": "0.2.
|
|
24
|
+
"@flood-md/agent": "0.2.4"
|
|
25
25
|
},
|
|
26
26
|
"publishConfig": {
|
|
27
27
|
"access": "public"
|