@flood-md/cli 0.2.2 → 0.2.3
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 +9 -2
- package/lib/index.mjs +49 -34
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,8 +4,15 @@
|
|
|
4
4
|
`npx @flood-md/cli doctor` checks package versions and agent file presence.
|
|
5
5
|
|
|
6
6
|
Requires Node 22.12+. Installs matching tokens, React and agent packages; creates
|
|
7
|
-
flood.json
|
|
8
|
-
|
|
7
|
+
flood.json and every owned guide listed in the matching agent manifest: DESIGN.md,
|
|
8
|
+
rules.json and manifest.json under flood/, plus SKILL.md and focused references
|
|
9
|
+
under .agents/skills/flood-ui/. Version 0.2.3 includes the expanded bundle and
|
|
10
|
+
spacing reference. Refuses existing owned files or linked destinations.
|
|
11
|
+
Does not rewrite source code, client settings or AGENTS.md.
|
|
9
12
|
After init, import @flood-md/react/styles.css once at the application root.
|
|
10
13
|
Wrap new interfaces in FloodRoot from @flood-md/react to select the dark
|
|
11
14
|
foundations profile. Read the installed flood/DESIGN.md for the matching API.
|
|
15
|
+
Read the skill's routing reference for the task/source/check matrix. Doctor checks
|
|
16
|
+
the complete guide set and package alignment; it does not validate application
|
|
17
|
+
behavior, CSS imports or agent compliance. Merge customized guide upgrades
|
|
18
|
+
deliberately; init has no overwrite mode.
|
package/lib/index.mjs
CHANGED
|
@@ -1,35 +1,43 @@
|
|
|
1
|
-
import { readFile, writeFile, mkdir, lstat
|
|
1
|
+
import { readFile, writeFile, mkdir, lstat } from "node:fs/promises";
|
|
2
2
|
import { resolve, join } 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.3";
|
|
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
|
-
const exists = async (path) => {
|
|
10
|
-
try {
|
|
11
|
-
await access(path);
|
|
12
|
-
return true;
|
|
13
|
-
} catch {
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
9
|
const agentFile = (name) =>
|
|
18
10
|
fileURLToPath(import.meta.resolve(`@flood-md/agent/${name}`));
|
|
11
|
+
const agentManifest = await json(agentFile("manifest.json"));
|
|
12
|
+
const safeRelative = (path) => typeof path === "string" && /^[a-zA-Z0-9._/-]+$/.test(path) &&
|
|
13
|
+
!path.startsWith("/") && !path.split("/").some(part => !part || part === "." || part === "..");
|
|
14
|
+
if (agentManifest.schemaVersion !== 1 || agentManifest.version !== VERSION || !Array.isArray(agentManifest.files) ||
|
|
15
|
+
!agentManifest.files.length || agentManifest.files.some(file =>
|
|
16
|
+
!safeRelative(file.source) || !safeRelative(file.target) ||
|
|
17
|
+
!(file.target.startsWith("flood/") || file.target.startsWith(".agents/skills/flood-ui/"))) ||
|
|
18
|
+
new Set(agentManifest.files.map(file => file.target)).size !== agentManifest.files.length)
|
|
19
|
+
throw new Error("Invalid Flood agent file manifest.");
|
|
20
|
+
export const AGENT_FILES = Object.freeze(agentManifest.files.map(file => Object.freeze({ ...file })));
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
export async function checkTarget(root, relative) {
|
|
22
|
+
async function inspectTarget(root, relative) {
|
|
22
23
|
let current = root;
|
|
24
|
+
let info;
|
|
23
25
|
for (const part of relative.split("/")) {
|
|
24
26
|
current = join(current, part);
|
|
25
27
|
try {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
info = await lstat(current);
|
|
29
|
+
if (info.isSymbolicLink()) throw new Error(`Refusing linked path: ${relative}`);
|
|
28
30
|
} catch (error) {
|
|
29
|
-
if (error.code
|
|
31
|
+
if (error.code === "ENOENT") return undefined;
|
|
32
|
+
throw error;
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
|
-
|
|
35
|
+
return info;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Refuse links and existing instruction files before changing any project data.
|
|
39
|
+
export async function checkTarget(root, relative) {
|
|
40
|
+
if (await inspectTarget(root, relative))
|
|
33
41
|
throw new Error(`${relative} already exists. No files were overwritten.`);
|
|
34
42
|
}
|
|
35
43
|
|
|
@@ -67,16 +75,16 @@ export async function initProject(
|
|
|
67
75
|
manifest.dependencies?.next || manifest.devDependencies?.next
|
|
68
76
|
? "next"
|
|
69
77
|
: "react";
|
|
70
|
-
const files = [
|
|
71
|
-
"flood.json",
|
|
72
|
-
"flood/DESIGN.md",
|
|
73
|
-
".agents/skills/flood-ui/SKILL.md",
|
|
74
|
-
];
|
|
78
|
+
const files = ["flood.json", ...AGENT_FILES.map(file => file.target)];
|
|
75
79
|
for (const file of files) await checkTarget(root, file);
|
|
76
80
|
log(
|
|
77
81
|
`Flood ${VERSION} · ${framework} · npm\nInstall: ${packages.map((p) => `${p}@${VERSION}`).join(", ")}\nCreate: ${files.join(", ")}`,
|
|
78
82
|
);
|
|
79
83
|
if (dryRun) return;
|
|
84
|
+
// Read the complete bundled guide set before npm or owned writes can change anything.
|
|
85
|
+
const guides = await Promise.all(AGENT_FILES.map(async file => ({
|
|
86
|
+
...file, content: await readFile(agentFile(file.source)),
|
|
87
|
+
})));
|
|
80
88
|
await installer(root);
|
|
81
89
|
const config = {
|
|
82
90
|
$schema: "https://flood.md/schema/config.json",
|
|
@@ -89,27 +97,24 @@ export async function initProject(
|
|
|
89
97
|
};
|
|
90
98
|
// Recheck after npm has run, before creating owned files.
|
|
91
99
|
for (const file of files) await checkTarget(root, file);
|
|
92
|
-
await mkdir(
|
|
93
|
-
await mkdir(join(root, ".agents/skills/flood-ui"), { recursive: true });
|
|
100
|
+
for (const file of files) await mkdir(resolve(root, file, ".."), { recursive: true });
|
|
94
101
|
await writeFile(
|
|
95
102
|
join(root, files[0]),
|
|
96
103
|
JSON.stringify(config, null, 2) + "\n",
|
|
97
104
|
{ flag: "wx" },
|
|
98
105
|
);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
await
|
|
102
|
-
|
|
103
|
-
);
|
|
104
|
-
await writeFile(join(root, files[2]), await readFile(agentFile("SKILL.md")), {
|
|
105
|
-
flag: "wx",
|
|
106
|
-
});
|
|
106
|
+
for (const file of guides) {
|
|
107
|
+
await checkTarget(root, file.target);
|
|
108
|
+
await writeFile(join(root, file.target), file.content, { flag: "wx" });
|
|
109
|
+
}
|
|
107
110
|
log(
|
|
108
111
|
`\nConnected. Import '@flood-md/react/styles.css' in ${framework === "next" ? "app/layout.tsx" : "your app entry"}.\nWrap your interface in <FloodRoot> from '@flood-md/react' to use the dark foundations.\nRead flood/DESIGN.md for component APIs. Run npx @flood-md/cli doctor to verify.`,
|
|
109
112
|
);
|
|
110
113
|
}
|
|
111
114
|
|
|
112
115
|
export async function doctor(root, log = console.log) {
|
|
116
|
+
const configInfo = await inspectTarget(root, "flood.json");
|
|
117
|
+
if (!configInfo?.isFile()) throw new Error("No regular flood.json. Run init first.");
|
|
113
118
|
const config = await json(join(root, "flood.json")).catch(() => {
|
|
114
119
|
throw new Error("No flood.json. Run init first.");
|
|
115
120
|
});
|
|
@@ -131,11 +136,21 @@ export async function doctor(root, log = console.log) {
|
|
|
131
136
|
`${name}: expected ${config.version}, found ${found?.version ?? "missing"}`,
|
|
132
137
|
);
|
|
133
138
|
}
|
|
134
|
-
for (const
|
|
135
|
-
|
|
139
|
+
for (const { target } of AGENT_FILES) {
|
|
140
|
+
try {
|
|
141
|
+
const info = await inspectTarget(root, target);
|
|
142
|
+
if (!info?.isFile()) failures.push(`${target}: missing or not a regular file`);
|
|
143
|
+
} catch (error) { failures.push(error.message); }
|
|
144
|
+
}
|
|
145
|
+
if (!failures.length) {
|
|
146
|
+
const installed = await json(join(root, "flood/manifest.json")).catch(() => undefined);
|
|
147
|
+
if (installed?.version !== config.version) failures.push("Agent manifest version differs; compare and merge the matching guide bundle.");
|
|
148
|
+
const header = (await readFile(join(root, "flood/DESIGN.md"), "utf8")).split(/\r?\n/, 1)[0];
|
|
149
|
+
if (!header.endsWith("\u00b7 " + config.version)) failures.push("DESIGN.md version differs; preserve custom edits while updating the contract.");
|
|
150
|
+
}
|
|
136
151
|
if (failures.length) throw new Error(failures.join("\n"));
|
|
137
152
|
log(
|
|
138
|
-
`Flood ${config.version}: package versions and agent files are aligned.\nCheck the stylesheet import in your application entry; doctor does not parse application code.`,
|
|
153
|
+
`Flood ${config.version}: package versions and ${AGENT_FILES.length} agent files are aligned.\nCheck the stylesheet import in your application entry; doctor does not parse application code or validate UI.`,
|
|
139
154
|
);
|
|
140
155
|
}
|
|
141
156
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flood-md/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
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.3"
|
|
25
25
|
},
|
|
26
26
|
"publishConfig": {
|
|
27
27
|
"access": "public"
|