create-fornix 0.0.3 → 0.0.5
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/index.js +607 -236
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -8,9 +8,9 @@ import { defineCommand as defineCommand8 } from "citty";
|
|
|
8
8
|
|
|
9
9
|
// src/cli/commands/create.ts
|
|
10
10
|
import { defineCommand } from "citty";
|
|
11
|
-
import { resolve, basename as basename2 } from "path";
|
|
12
|
-
import { mkdirSync as
|
|
13
|
-
import { join as
|
|
11
|
+
import { resolve as resolve2, basename as basename2 } from "path";
|
|
12
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
13
|
+
import { join as join6 } from "path";
|
|
14
14
|
import * as p2 from "@clack/prompts";
|
|
15
15
|
import pc3 from "picocolors";
|
|
16
16
|
|
|
@@ -213,7 +213,7 @@ function topologicalSort(blocks, manifests) {
|
|
|
213
213
|
}
|
|
214
214
|
|
|
215
215
|
// src/scaffold/structure-generator.ts
|
|
216
|
-
function generateStructure(config) {
|
|
216
|
+
function generateStructure(config, manifests = []) {
|
|
217
217
|
const files = {};
|
|
218
218
|
const adapterDeps = getAdapterDependencies(config);
|
|
219
219
|
const pkg = {
|
|
@@ -278,16 +278,35 @@ pnpm-debug.log*
|
|
|
278
278
|
.DS_Store
|
|
279
279
|
Thumbs.db
|
|
280
280
|
`.trim() + "\n";
|
|
281
|
-
|
|
281
|
+
const blockImports = [];
|
|
282
|
+
const blockComponents = [];
|
|
283
|
+
if (manifests.length > 0) {
|
|
284
|
+
for (const manifest2 of manifests) {
|
|
285
|
+
if (manifest2.type !== "section") continue;
|
|
286
|
+
const mainFile = manifest2.files.find((f) => f.destination.endsWith(".astro") || f.destination.endsWith(".tsx"));
|
|
287
|
+
if (mainFile) {
|
|
288
|
+
const componentName = manifest2.name.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
289
|
+
let importPath = mainFile.destination;
|
|
290
|
+
if (importPath.startsWith("src/")) {
|
|
291
|
+
importPath = "../" + importPath.substring(4);
|
|
292
|
+
}
|
|
293
|
+
blockImports.push(`import ${componentName} from '${importPath}';`);
|
|
294
|
+
blockComponents.push(` <${componentName} />`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const indexAstroContent = `
|
|
282
299
|
---
|
|
283
300
|
import Layout from '../layouts/Layout.astro';
|
|
301
|
+
${blockImports.join("\n")}
|
|
284
302
|
---
|
|
285
|
-
<Layout title="Welcome to
|
|
303
|
+
<Layout title="Welcome to ${config.projectName}.">
|
|
286
304
|
<main>
|
|
287
|
-
<h1>Welcome to <span class="text-gradient">${config.projectName}</span></h1
|
|
305
|
+
${blockComponents.length > 0 ? blockComponents.join("\n") : ` <h1>Welcome to <span class="text-gradient">${config.projectName}</span></h1>`}
|
|
288
306
|
</main>
|
|
289
307
|
</Layout>
|
|
290
|
-
`.trim() + "
|
|
308
|
+
`.trim() + "\\n";
|
|
309
|
+
files["src/pages/index.astro"] = indexAstroContent;
|
|
291
310
|
files["src/layouts/Layout.astro"] = `
|
|
292
311
|
---
|
|
293
312
|
interface Props {
|
|
@@ -584,11 +603,12 @@ function generateContentConfig(blocks) {
|
|
|
584
603
|
'import { defineCollection, z } from "astro:content";'
|
|
585
604
|
];
|
|
586
605
|
const collections = [];
|
|
606
|
+
const dataCollections = /* @__PURE__ */ new Map();
|
|
587
607
|
for (const block of blocks) {
|
|
588
608
|
if (block.collections && block.collections.length > 0) {
|
|
589
609
|
for (const col of block.collections) {
|
|
590
610
|
const importName = `${block.name.replace(/-/g, "")}${col.name}Schema`;
|
|
591
|
-
const importPath = col.schemaSource.replace(
|
|
611
|
+
const importPath = col.schemaSource.replace(/\\.ts$/, "");
|
|
592
612
|
imports.push(`import { schema as ${importName} } from "${importPath}";`);
|
|
593
613
|
collections.push(
|
|
594
614
|
` "${col.name}": defineCollection({
|
|
@@ -600,17 +620,30 @@ function generateContentConfig(blocks) {
|
|
|
600
620
|
}
|
|
601
621
|
const slots = block.ai?.contentSlots;
|
|
602
622
|
if (slots && Object.keys(slots).length > 0) {
|
|
603
|
-
const schemaFields = Object.entries(slots).map(([name, slot]) => `
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
623
|
+
const schemaFields = Object.entries(slots).map(([name, slot]) => ` ${name}: ${zodTypeForSlot(slot)}.optional(),`).join("\n");
|
|
624
|
+
const subdirectory = TYPE_DIRECTORY[block.type] ?? block.type;
|
|
625
|
+
if (!dataCollections.has(subdirectory)) {
|
|
626
|
+
dataCollections.set(subdirectory, []);
|
|
627
|
+
}
|
|
628
|
+
dataCollections.get(subdirectory).push(
|
|
629
|
+
` // ${block.name}
|
|
630
|
+
z.object({
|
|
608
631
|
${schemaFields}
|
|
609
|
-
})
|
|
610
|
-
})`
|
|
632
|
+
})`
|
|
611
633
|
);
|
|
612
634
|
}
|
|
613
635
|
}
|
|
636
|
+
for (const [colName, schemas] of dataCollections.entries()) {
|
|
637
|
+
const schemaStr = schemas.length === 1 ? schemas[0] : `z.union([
|
|
638
|
+
${schemas.join(",\n")}
|
|
639
|
+
])`;
|
|
640
|
+
collections.push(
|
|
641
|
+
` "${colName}": defineCollection({
|
|
642
|
+
type: "data",
|
|
643
|
+
schema: ${schemaStr},
|
|
644
|
+
})`
|
|
645
|
+
);
|
|
646
|
+
}
|
|
614
647
|
const lines = [
|
|
615
648
|
...imports,
|
|
616
649
|
"",
|
|
@@ -789,13 +822,13 @@ function scaffold(input) {
|
|
|
789
822
|
const selectedBlockNames = config.blocks.map((block) => block.name);
|
|
790
823
|
const dependencyResult = resolveDependencies(selectedBlockNames, manifests);
|
|
791
824
|
if (!isOk(dependencyResult)) {
|
|
792
|
-
return err(new Error(
|
|
825
|
+
return err(Object.assign(new Error(dependencyResult.error.message), dependencyResult.error));
|
|
793
826
|
}
|
|
794
827
|
const resolvedBlockNames = dependencyResult.value;
|
|
795
828
|
const files = {};
|
|
796
|
-
const structureFiles = generateStructure(config);
|
|
797
|
-
Object.assign(files, structureFiles);
|
|
798
829
|
const resolvedManifests = resolvedBlockNames.filter((name) => manifests[name] !== void 0).map((name) => manifests[name]);
|
|
830
|
+
const structureFiles = generateStructure(config, resolvedManifests);
|
|
831
|
+
Object.assign(files, structureFiles);
|
|
799
832
|
const astroConfigResult = generateAstroConfig(config, resolvedManifests);
|
|
800
833
|
if (!isOk(astroConfigResult)) {
|
|
801
834
|
return err(astroConfigResult.error);
|
|
@@ -1735,7 +1768,6 @@ export function getStaticPaths() { return [{ params: { slug: '1' } }]; }
|
|
|
1735
1768
|
"cta-newsletter": { "cta-newsletter.astro": "<section>CTA</section>\n" },
|
|
1736
1769
|
"header-sticky": { "header-sticky.astro": "<header>Sticky</header>\n" }
|
|
1737
1770
|
};
|
|
1738
|
-
var FIXTURE_DEFAULT_CONTENT = {};
|
|
1739
1771
|
function loadAllPalettes() {
|
|
1740
1772
|
if (BUILTIN_PALETTES.length > 0) {
|
|
1741
1773
|
return [...BUILTIN_PALETTES];
|
|
@@ -1769,6 +1801,202 @@ function findRegistryPalettesDir() {
|
|
|
1769
1801
|
}
|
|
1770
1802
|
}
|
|
1771
1803
|
|
|
1804
|
+
// src/registry/registry-fetcher.ts
|
|
1805
|
+
import { existsSync, readFileSync as readFileSync2, mkdirSync, writeFileSync, statSync } from "fs";
|
|
1806
|
+
import { join as join2 } from "path";
|
|
1807
|
+
import { homedir } from "os";
|
|
1808
|
+
var DEFAULT_CONFIG = {
|
|
1809
|
+
registryUrl: process.env.FORNIX_REGISTRY_URL || "https://raw.githubusercontent.com/kamsqe/fornix/main/packages/fornix-registry/registry.json",
|
|
1810
|
+
cacheDir: join2(homedir(), ".cache", "fornix"),
|
|
1811
|
+
force: process.env.FORNIX_NO_CACHE === "true",
|
|
1812
|
+
maxCacheAge: 24 * 60 * 60 * 1e3
|
|
1813
|
+
// 24 hours
|
|
1814
|
+
};
|
|
1815
|
+
async function fetchRegistryIndex(config = {}) {
|
|
1816
|
+
if (process.env.FORNIX_E2E_MOCK === "true") {
|
|
1817
|
+
return ok({ blocks: FIXTURE_MANIFESTS, palettes: BUILTIN_PALETTES });
|
|
1818
|
+
}
|
|
1819
|
+
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
1820
|
+
const cacheFile = join2(cfg.cacheDir, "registry.json");
|
|
1821
|
+
const mapDataToIndex = (data) => {
|
|
1822
|
+
const blocksArray = Array.isArray(data.blocks) ? data.blocks : [];
|
|
1823
|
+
const blocksRecord = {};
|
|
1824
|
+
for (const block of blocksArray) {
|
|
1825
|
+
if (block.name) {
|
|
1826
|
+
blocksRecord[block.name] = block;
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
return {
|
|
1830
|
+
blocks: blocksRecord,
|
|
1831
|
+
palettes: Array.isArray(data.palettes) ? data.palettes : []
|
|
1832
|
+
};
|
|
1833
|
+
};
|
|
1834
|
+
if (!cfg.force && isCacheValid(cacheFile, cfg.maxCacheAge)) {
|
|
1835
|
+
const cached = loadFromCache(cacheFile);
|
|
1836
|
+
if (cached) return ok(mapDataToIndex(cached));
|
|
1837
|
+
}
|
|
1838
|
+
try {
|
|
1839
|
+
const response = await fetch(cfg.registryUrl);
|
|
1840
|
+
if (!response.ok) {
|
|
1841
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
1842
|
+
}
|
|
1843
|
+
const data = await response.json();
|
|
1844
|
+
mkdirSync(cfg.cacheDir, { recursive: true });
|
|
1845
|
+
writeFileSync(cacheFile, JSON.stringify(data, null, 2));
|
|
1846
|
+
return ok(mapDataToIndex(data));
|
|
1847
|
+
} catch (error) {
|
|
1848
|
+
const cached = loadFromCache(cacheFile);
|
|
1849
|
+
if (cached) {
|
|
1850
|
+
return ok(mapDataToIndex(cached));
|
|
1851
|
+
}
|
|
1852
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1853
|
+
return err({
|
|
1854
|
+
kind: "RegistryFetchError",
|
|
1855
|
+
message: `Failed to fetch registry from ${cfg.registryUrl}: ${message}`
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
function isCacheValid(cachePath, maxAge) {
|
|
1860
|
+
if (!existsSync(cachePath)) {
|
|
1861
|
+
return false;
|
|
1862
|
+
}
|
|
1863
|
+
try {
|
|
1864
|
+
const stat = statSync(cachePath);
|
|
1865
|
+
const age = Date.now() - stat.mtimeMs;
|
|
1866
|
+
return age < maxAge;
|
|
1867
|
+
} catch {
|
|
1868
|
+
return false;
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
function loadFromCache(cachePath) {
|
|
1872
|
+
try {
|
|
1873
|
+
if (!existsSync(cachePath)) return null;
|
|
1874
|
+
const raw = readFileSync2(cachePath, "utf-8");
|
|
1875
|
+
return JSON.parse(raw);
|
|
1876
|
+
} catch {
|
|
1877
|
+
return null;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
// src/registry/block-fetcher.ts
|
|
1882
|
+
import { downloadTemplate } from "giget";
|
|
1883
|
+
import {
|
|
1884
|
+
existsSync as existsSync2,
|
|
1885
|
+
readFileSync as readFileSync3,
|
|
1886
|
+
readdirSync as readdirSync2,
|
|
1887
|
+
mkdirSync as mkdirSync2,
|
|
1888
|
+
statSync as statSync2
|
|
1889
|
+
} from "fs";
|
|
1890
|
+
import { join as join3 } from "path";
|
|
1891
|
+
import { homedir as homedir2 } from "os";
|
|
1892
|
+
var DEFAULT_CONFIG2 = {
|
|
1893
|
+
repo: "kamsqe/fornix",
|
|
1894
|
+
ref: "main",
|
|
1895
|
+
cacheDir: join3(homedir2(), ".cache", "fornix", "blocks"),
|
|
1896
|
+
blocksPrefix: "packages/fornix-blocks/blocks",
|
|
1897
|
+
force: process.env.FORNIX_NO_CACHE === "true",
|
|
1898
|
+
maxCacheAge: 24 * 60 * 60 * 1e3
|
|
1899
|
+
// 24 hours
|
|
1900
|
+
};
|
|
1901
|
+
async function fetchBlock(blockName, config = {}) {
|
|
1902
|
+
if (process.env.FORNIX_E2E_MOCK === "true") {
|
|
1903
|
+
const manifest2 = FIXTURE_MANIFESTS[blockName];
|
|
1904
|
+
const files = FIXTURE_BLOCK_SOURCES[blockName];
|
|
1905
|
+
if (!manifest2 || !files) {
|
|
1906
|
+
return err({
|
|
1907
|
+
kind: "FetchError",
|
|
1908
|
+
blockName,
|
|
1909
|
+
message: `Mock block not found: ${blockName}`
|
|
1910
|
+
});
|
|
1911
|
+
}
|
|
1912
|
+
return ok({ manifest: manifest2, files, fromCache: true });
|
|
1913
|
+
}
|
|
1914
|
+
const cfg = { ...DEFAULT_CONFIG2, ...config };
|
|
1915
|
+
const blockCacheDir = join3(cfg.cacheDir, blockName);
|
|
1916
|
+
if (!cfg.force && isCacheValid2(blockCacheDir, cfg.maxCacheAge)) {
|
|
1917
|
+
return loadFromCache2(blockName, blockCacheDir);
|
|
1918
|
+
}
|
|
1919
|
+
try {
|
|
1920
|
+
const source = `gh:${cfg.repo}/${cfg.blocksPrefix}/${blockName}#${cfg.ref}`;
|
|
1921
|
+
mkdirSync2(cfg.cacheDir, { recursive: true });
|
|
1922
|
+
await downloadTemplate(source, {
|
|
1923
|
+
dir: blockCacheDir,
|
|
1924
|
+
force: true
|
|
1925
|
+
// overwrite cache
|
|
1926
|
+
});
|
|
1927
|
+
return loadFromCache2(blockName, blockCacheDir);
|
|
1928
|
+
} catch (error) {
|
|
1929
|
+
if (existsSync2(join3(blockCacheDir, "block.json"))) {
|
|
1930
|
+
return loadFromCache2(blockName, blockCacheDir);
|
|
1931
|
+
}
|
|
1932
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1933
|
+
return err({
|
|
1934
|
+
kind: "FetchError",
|
|
1935
|
+
blockName,
|
|
1936
|
+
message: `Failed to fetch block '${blockName}': ${message}`
|
|
1937
|
+
});
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
async function fetchBlocks(blockNames, config = {}) {
|
|
1941
|
+
return Promise.all(blockNames.map((name) => fetchBlock(name, config)));
|
|
1942
|
+
}
|
|
1943
|
+
function isCacheValid2(blockCacheDir, maxAge) {
|
|
1944
|
+
const manifestPath = join3(blockCacheDir, "block.json");
|
|
1945
|
+
if (!existsSync2(manifestPath)) {
|
|
1946
|
+
return false;
|
|
1947
|
+
}
|
|
1948
|
+
try {
|
|
1949
|
+
const stat = statSync2(manifestPath);
|
|
1950
|
+
const age = Date.now() - stat.mtimeMs;
|
|
1951
|
+
return age < maxAge;
|
|
1952
|
+
} catch {
|
|
1953
|
+
return false;
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
function loadFromCache2(blockName, blockCacheDir) {
|
|
1957
|
+
try {
|
|
1958
|
+
const manifestPath = join3(blockCacheDir, "block.json");
|
|
1959
|
+
if (!existsSync2(manifestPath)) {
|
|
1960
|
+
return err({
|
|
1961
|
+
kind: "FetchError",
|
|
1962
|
+
blockName,
|
|
1963
|
+
message: `Cache miss: no block.json found in ${blockCacheDir}`
|
|
1964
|
+
});
|
|
1965
|
+
}
|
|
1966
|
+
const raw = readFileSync3(manifestPath, "utf-8");
|
|
1967
|
+
const parsed = JSON.parse(raw);
|
|
1968
|
+
const result = BlockManifestSchema.safeParse(parsed);
|
|
1969
|
+
if (!result.success) {
|
|
1970
|
+
const issues = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
|
|
1971
|
+
return err({
|
|
1972
|
+
kind: "FetchError",
|
|
1973
|
+
blockName,
|
|
1974
|
+
message: `Invalid manifest in cache: ${issues}`
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
const files = {};
|
|
1978
|
+
const entries = readdirSync2(blockCacheDir);
|
|
1979
|
+
for (const entry of entries) {
|
|
1980
|
+
const fullPath = join3(blockCacheDir, entry);
|
|
1981
|
+
if (statSync2(fullPath).isFile() && entry !== "block.json") {
|
|
1982
|
+
files[entry] = readFileSync3(fullPath, "utf-8");
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
return ok({
|
|
1986
|
+
manifest: result.data,
|
|
1987
|
+
files,
|
|
1988
|
+
fromCache: true
|
|
1989
|
+
});
|
|
1990
|
+
} catch (error) {
|
|
1991
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1992
|
+
return err({
|
|
1993
|
+
kind: "FetchError",
|
|
1994
|
+
blockName,
|
|
1995
|
+
message: `Failed to load block from cache: ${message}`
|
|
1996
|
+
});
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
|
|
1772
2000
|
// src/prompts/manual-flow.ts
|
|
1773
2001
|
import * as p from "@clack/prompts";
|
|
1774
2002
|
import pc from "picocolors";
|
|
@@ -1959,8 +2187,8 @@ function buildSummary(config, blockNames, palette) {
|
|
|
1959
2187
|
|
|
1960
2188
|
// src/scaffold/post-scaffold.ts
|
|
1961
2189
|
import { execSync } from "child_process";
|
|
1962
|
-
import { writeFileSync } from "fs";
|
|
1963
|
-
import { join as
|
|
2190
|
+
import { writeFileSync as writeFileSync2 } from "fs";
|
|
2191
|
+
import { join as join4, basename } from "path";
|
|
1964
2192
|
import pc2 from "picocolors";
|
|
1965
2193
|
function runPostScaffold(input, callbacks) {
|
|
1966
2194
|
const log = callbacks?.onLog ?? ((msg) => console.log(msg));
|
|
@@ -2126,7 +2354,7 @@ function generateClaudeMd(projectDir, config, blockNames) {
|
|
|
2126
2354
|
lines.push("```");
|
|
2127
2355
|
lines.push("");
|
|
2128
2356
|
const content = lines.join("\n");
|
|
2129
|
-
|
|
2357
|
+
writeFileSync2(join4(projectDir, "CLAUDE.md"), content, "utf-8");
|
|
2130
2358
|
}
|
|
2131
2359
|
|
|
2132
2360
|
// src/ai/prompt-builder.ts
|
|
@@ -2154,7 +2382,8 @@ Your job:
|
|
|
2154
2382
|
|
|
2155
2383
|
You MUST only select blocks that exist in the catalog below. Never invent block names.`;
|
|
2156
2384
|
}
|
|
2157
|
-
function buildBlocksCatalog(
|
|
2385
|
+
function buildBlocksCatalog(blocksRecord) {
|
|
2386
|
+
const blocks = Object.values(blocksRecord);
|
|
2158
2387
|
const lines = ["# Available Blocks\n"];
|
|
2159
2388
|
const grouped = /* @__PURE__ */ new Map();
|
|
2160
2389
|
for (const block of blocks) {
|
|
@@ -2234,7 +2463,8 @@ function buildPalettesCatalog(palettes) {
|
|
|
2234
2463
|
}
|
|
2235
2464
|
return lines.join("\n");
|
|
2236
2465
|
}
|
|
2237
|
-
function buildConstraintRules(
|
|
2466
|
+
function buildConstraintRules(blocksRecord) {
|
|
2467
|
+
const blocks = Object.values(blocksRecord);
|
|
2238
2468
|
const lines = ["# Constraint Rules\n"];
|
|
2239
2469
|
lines.push("## Render Modes");
|
|
2240
2470
|
lines.push("- `static` \u2014 Pre-rendered HTML, no server. Best for blogs, docs, landing pages.");
|
|
@@ -2960,8 +3190,8 @@ function createCloudflareProvider(opts = {}) {
|
|
|
2960
3190
|
}
|
|
2961
3191
|
|
|
2962
3192
|
// src/ai/providers/mock-provider.ts
|
|
2963
|
-
import { readFileSync as
|
|
2964
|
-
import { join as
|
|
3193
|
+
import { readFileSync as readFileSync4, existsSync as existsSync3 } from "fs";
|
|
3194
|
+
import { join as join5, dirname as dirname2 } from "path";
|
|
2965
3195
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2966
3196
|
var FIXTURE_ENTRIES = [
|
|
2967
3197
|
{
|
|
@@ -3001,9 +3231,9 @@ function createMockProvider() {
|
|
|
3001
3231
|
});
|
|
3002
3232
|
}
|
|
3003
3233
|
const fixtureDir = resolveFixtureDir();
|
|
3004
|
-
const filePath =
|
|
3234
|
+
const filePath = join5(fixtureDir, match.filename);
|
|
3005
3235
|
try {
|
|
3006
|
-
const raw =
|
|
3236
|
+
const raw = readFileSync4(filePath, "utf-8");
|
|
3007
3237
|
const parsed = JSON.parse(raw);
|
|
3008
3238
|
const validated = IntentSchema.parse(parsed);
|
|
3009
3239
|
return ok(validated);
|
|
@@ -3020,10 +3250,10 @@ function createMockProvider() {
|
|
|
3020
3250
|
}
|
|
3021
3251
|
function resolveFixtureDir() {
|
|
3022
3252
|
const thisDir = dirname2(fileURLToPath2(import.meta.url));
|
|
3023
|
-
const fromSource =
|
|
3024
|
-
if (
|
|
3025
|
-
const fromDist =
|
|
3026
|
-
if (
|
|
3253
|
+
const fromSource = join5(thisDir, "..", "..", "..", "tests", "fixtures", "ai-responses");
|
|
3254
|
+
if (existsSync3(fromSource)) return fromSource;
|
|
3255
|
+
const fromDist = join5(thisDir, "..", "tests", "fixtures", "ai-responses");
|
|
3256
|
+
if (existsSync3(fromDist)) return fromDist;
|
|
3027
3257
|
return fromSource;
|
|
3028
3258
|
}
|
|
3029
3259
|
|
|
@@ -3330,30 +3560,37 @@ var createCommand = defineCommand({
|
|
|
3330
3560
|
}
|
|
3331
3561
|
},
|
|
3332
3562
|
async run({ args: args2 }) {
|
|
3333
|
-
const
|
|
3563
|
+
const registryResult = await fetchRegistryIndex();
|
|
3564
|
+
if (!isOk(registryResult)) {
|
|
3565
|
+
console.error(pc3.red(`\u2716 Failed to fetch block registry: ${registryResult.error.message}`));
|
|
3566
|
+
process.exitCode = 1;
|
|
3567
|
+
return;
|
|
3568
|
+
}
|
|
3569
|
+
const manifests = registryResult.value.blocks;
|
|
3570
|
+
const allPalettes = registryResult.value.palettes.length > 0 ? registryResult.value.palettes : loadAllPalettes();
|
|
3334
3571
|
const hasExplicitFlags = !!(args2.render || args2.deploy || args2.blocks || args2.database || args2.css || args2.locales || args2.palette || args2.recipe);
|
|
3335
3572
|
if (args2.manual && !args2.yes) {
|
|
3336
|
-
const defaultProjectName = args2.dir ? basename2(
|
|
3573
|
+
const defaultProjectName = args2.dir ? basename2(resolve2(args2.dir)) : "my-project";
|
|
3337
3574
|
const config = await runManualFlow({
|
|
3338
3575
|
defaultProjectName,
|
|
3339
|
-
manifests
|
|
3576
|
+
manifests,
|
|
3340
3577
|
allPalettes
|
|
3341
3578
|
});
|
|
3342
3579
|
if (!config) {
|
|
3343
3580
|
process.exitCode = 0;
|
|
3344
3581
|
return;
|
|
3345
3582
|
}
|
|
3346
|
-
const projectDir = args2.dir ?
|
|
3583
|
+
const projectDir = args2.dir ? resolve2(args2.dir) : resolve2(config.projectDir);
|
|
3347
3584
|
const finalConfig = { ...config, projectDir };
|
|
3348
|
-
return runScaffold(finalConfig, allPalettes, args2["dry-run"] ?? false, args2.verbose ?? false, !(args2.install ?? true), !(args2.git ?? true));
|
|
3585
|
+
return runScaffold(finalConfig, manifests, allPalettes, args2["dry-run"] ?? false, args2.verbose ?? false, !(args2.install ?? true), !(args2.git ?? true));
|
|
3349
3586
|
}
|
|
3350
3587
|
if (args2.manual || hasExplicitFlags) {
|
|
3351
|
-
return runFlagDrivenMode(args2, allPalettes);
|
|
3588
|
+
return runFlagDrivenMode(args2, manifests, allPalettes);
|
|
3352
3589
|
}
|
|
3353
|
-
return runAIMode(args2, allPalettes);
|
|
3590
|
+
return runAIMode(args2, manifests, allPalettes);
|
|
3354
3591
|
}
|
|
3355
3592
|
});
|
|
3356
|
-
async function runAIMode(args2, allPalettes) {
|
|
3593
|
+
async function runAIMode(args2, manifests, allPalettes) {
|
|
3357
3594
|
const providerName = parseProviderName(args2.provider);
|
|
3358
3595
|
if (args2.provider && !providerName) {
|
|
3359
3596
|
console.error(pc3.red(`\u2716 Unknown provider: ${String(args2.provider)}`));
|
|
@@ -3377,10 +3614,10 @@ async function runAIMode(args2, allPalettes) {
|
|
|
3377
3614
|
return;
|
|
3378
3615
|
}
|
|
3379
3616
|
const registry = {
|
|
3380
|
-
blocks: Object.values(
|
|
3617
|
+
blocks: Object.values(manifests),
|
|
3381
3618
|
palettes: [...allPalettes]
|
|
3382
3619
|
};
|
|
3383
|
-
const projectDir =
|
|
3620
|
+
const projectDir = resolve2(String(args2.dir ?? "."));
|
|
3384
3621
|
const projectName = basename2(projectDir);
|
|
3385
3622
|
const nameResult = validateProjectName(projectName);
|
|
3386
3623
|
if (!nameResult.ok) {
|
|
@@ -3415,6 +3652,7 @@ async function runAIMode(args2, allPalettes) {
|
|
|
3415
3652
|
}
|
|
3416
3653
|
return runScaffold(
|
|
3417
3654
|
config,
|
|
3655
|
+
manifests,
|
|
3418
3656
|
allPalettes,
|
|
3419
3657
|
(args2["dry-run"] ?? false) === true,
|
|
3420
3658
|
(args2.verbose ?? false) === true,
|
|
@@ -3422,8 +3660,8 @@ async function runAIMode(args2, allPalettes) {
|
|
|
3422
3660
|
!((args2.git ?? true) === true)
|
|
3423
3661
|
);
|
|
3424
3662
|
}
|
|
3425
|
-
function runFlagDrivenMode(args2, allPalettes) {
|
|
3426
|
-
const projectDir =
|
|
3663
|
+
function runFlagDrivenMode(args2, manifests, allPalettes) {
|
|
3664
|
+
const projectDir = resolve2(String(args2.dir ?? "."));
|
|
3427
3665
|
const projectName = basename2(projectDir);
|
|
3428
3666
|
const nameResult = validateProjectName(projectName);
|
|
3429
3667
|
if (!nameResult.ok) {
|
|
@@ -3506,6 +3744,7 @@ function runFlagDrivenMode(args2, allPalettes) {
|
|
|
3506
3744
|
};
|
|
3507
3745
|
return runScaffold(
|
|
3508
3746
|
config,
|
|
3747
|
+
manifests,
|
|
3509
3748
|
allPalettes,
|
|
3510
3749
|
(args2["dry-run"] ?? false) === true,
|
|
3511
3750
|
(args2.verbose ?? false) === true,
|
|
@@ -3513,12 +3752,36 @@ function runFlagDrivenMode(args2, allPalettes) {
|
|
|
3513
3752
|
!((args2.git ?? true) === true)
|
|
3514
3753
|
);
|
|
3515
3754
|
}
|
|
3516
|
-
function runScaffold(config, allPalettes, dryRun, verbose, skipInstall, skipGit) {
|
|
3755
|
+
async function runScaffold(config, manifests, allPalettes, dryRun, verbose, skipInstall, skipGit) {
|
|
3756
|
+
const spinner2 = p2.spinner();
|
|
3757
|
+
spinner2.start("Fetching blocks from registry...");
|
|
3758
|
+
const blockNames = config.blocks.map((b) => b.name);
|
|
3759
|
+
const blockResults = await fetchBlocks(blockNames);
|
|
3760
|
+
const blockSources = {};
|
|
3761
|
+
const blockDefaultContent = {};
|
|
3762
|
+
for (const result2 of blockResults) {
|
|
3763
|
+
if (!isOk(result2)) {
|
|
3764
|
+
spinner2.stop(`Failed to fetch block '${result2.error.blockName}'`, 1);
|
|
3765
|
+
console.error(pc3.red(`\u2716 ${result2.error.message}`));
|
|
3766
|
+
process.exitCode = 1;
|
|
3767
|
+
return;
|
|
3768
|
+
}
|
|
3769
|
+
const { manifest: manifest2, files: files2 } = result2.value;
|
|
3770
|
+
blockSources[manifest2.name] = files2;
|
|
3771
|
+
try {
|
|
3772
|
+
if (files2["default-content.json"]) {
|
|
3773
|
+
blockDefaultContent[manifest2.name] = JSON.parse(files2["default-content.json"]);
|
|
3774
|
+
}
|
|
3775
|
+
} catch (e) {
|
|
3776
|
+
console.error(pc3.yellow(`\u26A0 Warning: Failed to parse default-content.json for block '${manifest2.name}'`));
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3779
|
+
spinner2.stop("Blocks fetched successfully.");
|
|
3517
3780
|
const input = {
|
|
3518
3781
|
config,
|
|
3519
|
-
manifests
|
|
3520
|
-
blockSources:
|
|
3521
|
-
blockDefaultContent:
|
|
3782
|
+
manifests,
|
|
3783
|
+
blockSources: Object.freeze(blockSources),
|
|
3784
|
+
blockDefaultContent: Object.freeze(blockDefaultContent),
|
|
3522
3785
|
allPalettes
|
|
3523
3786
|
};
|
|
3524
3787
|
const result = scaffold(input);
|
|
@@ -3552,10 +3815,10 @@ function runScaffold(config, allPalettes, dryRun, verbose, skipInstall, skipGit)
|
|
|
3552
3815
|
const files = result.value.files;
|
|
3553
3816
|
let filesWritten = 0;
|
|
3554
3817
|
for (const [relativePath, content] of Object.entries(files)) {
|
|
3555
|
-
const fullPath =
|
|
3556
|
-
const parentDir =
|
|
3557
|
-
|
|
3558
|
-
|
|
3818
|
+
const fullPath = join6(config.projectDir, relativePath);
|
|
3819
|
+
const parentDir = join6(fullPath, "..");
|
|
3820
|
+
mkdirSync4(parentDir, { recursive: true });
|
|
3821
|
+
writeFileSync3(fullPath, content, "utf-8");
|
|
3559
3822
|
filesWritten++;
|
|
3560
3823
|
if (verbose) {
|
|
3561
3824
|
console.log(pc3.dim(` created ${relativePath}`));
|
|
@@ -3644,8 +3907,8 @@ function showNoProviderGuide() {
|
|
|
3644
3907
|
// src/cli/commands/add.ts
|
|
3645
3908
|
import { defineCommand as defineCommand2 } from "citty";
|
|
3646
3909
|
import pc4 from "picocolors";
|
|
3647
|
-
import { readFileSync as
|
|
3648
|
-
import { join as
|
|
3910
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync5 } from "fs";
|
|
3911
|
+
import { join as join7, dirname as dirname3 } from "path";
|
|
3649
3912
|
var addCommand = defineCommand2({
|
|
3650
3913
|
meta: {
|
|
3651
3914
|
name: "add",
|
|
@@ -3673,28 +3936,36 @@ var addCommand = defineCommand2({
|
|
|
3673
3936
|
default: false
|
|
3674
3937
|
}
|
|
3675
3938
|
},
|
|
3676
|
-
run({ args: args2 }) {
|
|
3939
|
+
async run({ args: args2 }) {
|
|
3677
3940
|
const typedArgs = args2;
|
|
3678
3941
|
const cwd = process.cwd();
|
|
3679
|
-
const manifestPath =
|
|
3680
|
-
if (!
|
|
3942
|
+
const manifestPath = join7(cwd, "fornix.json");
|
|
3943
|
+
if (!existsSync4(manifestPath)) {
|
|
3681
3944
|
console.error(
|
|
3682
3945
|
pc4.red("\u2717 No fornix.json found. Are you in a Fornix project?")
|
|
3683
3946
|
);
|
|
3684
3947
|
process.exit(1);
|
|
3685
3948
|
}
|
|
3686
|
-
const manifestRaw =
|
|
3949
|
+
const manifestRaw = readFileSync5(manifestPath, "utf-8");
|
|
3687
3950
|
const manifest2 = JSON.parse(manifestRaw);
|
|
3951
|
+
const registryResult = await fetchRegistryIndex();
|
|
3952
|
+
if (!isOk(registryResult)) {
|
|
3953
|
+
console.error(pc4.red(`\u2716 Failed to fetch block registry: ${registryResult.error.message}`));
|
|
3954
|
+
process.exitCode = 1;
|
|
3955
|
+
return;
|
|
3956
|
+
}
|
|
3957
|
+
const manifests = registryResult.value.blocks;
|
|
3688
3958
|
const blockName = typedArgs.block;
|
|
3689
|
-
const blockManifest =
|
|
3959
|
+
const blockManifest = manifests[blockName];
|
|
3690
3960
|
if (!blockManifest) {
|
|
3691
3961
|
console.error(pc4.red(`\u2717 Block '${blockName}' not found in registry.`));
|
|
3692
3962
|
console.error(
|
|
3693
3963
|
pc4.dim(
|
|
3694
|
-
` Available: ${Object.keys(
|
|
3964
|
+
` Available: ${Object.keys(manifests).join(", ")}`
|
|
3695
3965
|
)
|
|
3696
3966
|
);
|
|
3697
|
-
process.
|
|
3967
|
+
process.exitCode = 1;
|
|
3968
|
+
return;
|
|
3698
3969
|
}
|
|
3699
3970
|
const installedNames = new Set(manifest2.blocks.map((b) => b.name));
|
|
3700
3971
|
if (installedNames.has(blockName)) {
|
|
@@ -3703,26 +3974,32 @@ var addCommand = defineCommand2({
|
|
|
3703
3974
|
);
|
|
3704
3975
|
return;
|
|
3705
3976
|
}
|
|
3706
|
-
const blocksToAdd = resolveDependencies2(blockName, installedNames);
|
|
3977
|
+
const blocksToAdd = resolveDependencies2(blockName, installedNames, manifests);
|
|
3707
3978
|
for (const name of blocksToAdd) {
|
|
3708
|
-
const m =
|
|
3979
|
+
const m = manifests[name];
|
|
3709
3980
|
if (m?.requiredMode && manifest2.renderMode !== m.requiredMode) {
|
|
3710
3981
|
console.error(
|
|
3711
3982
|
pc4.red(
|
|
3712
3983
|
`\u2717 Block '${name}' requires '${m.requiredMode}' mode, but project uses '${manifest2.renderMode}'.`
|
|
3713
3984
|
)
|
|
3714
3985
|
);
|
|
3715
|
-
process.
|
|
3986
|
+
process.exitCode = 1;
|
|
3987
|
+
return;
|
|
3716
3988
|
}
|
|
3717
3989
|
}
|
|
3990
|
+
console.log(pc4.dim("Fetching blocks..."));
|
|
3991
|
+
const blockResults = await fetchBlocks(blocksToAdd);
|
|
3718
3992
|
const filesToWrite = [];
|
|
3719
|
-
for (
|
|
3720
|
-
const
|
|
3721
|
-
const
|
|
3722
|
-
if (!
|
|
3723
|
-
console.error(pc4.red(`\u2717
|
|
3724
|
-
process.
|
|
3993
|
+
for (let i = 0; i < blocksToAdd.length; i++) {
|
|
3994
|
+
const name = blocksToAdd[i];
|
|
3995
|
+
const result = blockResults[i];
|
|
3996
|
+
if (!result || !isOk(result)) {
|
|
3997
|
+
console.error(pc4.red(`\u2717 Failed to fetch files for block '${name}'.`));
|
|
3998
|
+
process.exitCode = 1;
|
|
3999
|
+
return;
|
|
3725
4000
|
}
|
|
4001
|
+
const bManifest = result.value.manifest;
|
|
4002
|
+
const sources = result.value.files;
|
|
3726
4003
|
for (const file of bManifest.files) {
|
|
3727
4004
|
const content = sources[file.source];
|
|
3728
4005
|
if (content === void 0) {
|
|
@@ -3731,10 +4008,11 @@ var addCommand = defineCommand2({
|
|
|
3731
4008
|
`\u2717 Source file '${file.source}' not found for block '${name}'.`
|
|
3732
4009
|
)
|
|
3733
4010
|
);
|
|
3734
|
-
process.
|
|
4011
|
+
process.exitCode = 1;
|
|
4012
|
+
return;
|
|
3735
4013
|
}
|
|
3736
4014
|
filesToWrite.push({
|
|
3737
|
-
path:
|
|
4015
|
+
path: join7(cwd, file.destination),
|
|
3738
4016
|
content
|
|
3739
4017
|
});
|
|
3740
4018
|
}
|
|
@@ -3755,15 +4033,15 @@ var addCommand = defineCommand2({
|
|
|
3755
4033
|
return;
|
|
3756
4034
|
}
|
|
3757
4035
|
for (const file of filesToWrite) {
|
|
3758
|
-
|
|
3759
|
-
|
|
4036
|
+
mkdirSync5(dirname3(file.path), { recursive: true });
|
|
4037
|
+
writeFileSync4(file.path, file.content);
|
|
3760
4038
|
if (typedArgs.verbose) {
|
|
3761
4039
|
console.log(` ${pc4.dim("\u2192")} ${file.path}`);
|
|
3762
4040
|
}
|
|
3763
4041
|
}
|
|
3764
4042
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3765
4043
|
for (const name of blocksToAdd) {
|
|
3766
|
-
const bManifest =
|
|
4044
|
+
const bManifest = manifests[name];
|
|
3767
4045
|
if (!bManifest) continue;
|
|
3768
4046
|
manifest2.blocks.push({
|
|
3769
4047
|
name,
|
|
@@ -3772,7 +4050,7 @@ var addCommand = defineCommand2({
|
|
|
3772
4050
|
installedAt: now
|
|
3773
4051
|
});
|
|
3774
4052
|
}
|
|
3775
|
-
|
|
4053
|
+
writeFileSync4(manifestPath, JSON.stringify(manifest2, null, 2) + "\n");
|
|
3776
4054
|
console.log();
|
|
3777
4055
|
for (const name of blocksToAdd) {
|
|
3778
4056
|
const isDep = name !== blockName;
|
|
@@ -3793,13 +4071,13 @@ var addCommand = defineCommand2({
|
|
|
3793
4071
|
console.log();
|
|
3794
4072
|
}
|
|
3795
4073
|
});
|
|
3796
|
-
function resolveDependencies2(blockName, installedNames) {
|
|
4074
|
+
function resolveDependencies2(blockName, installedNames, manifests) {
|
|
3797
4075
|
const result = [];
|
|
3798
4076
|
const visited = /* @__PURE__ */ new Set();
|
|
3799
4077
|
function walk(name) {
|
|
3800
4078
|
if (visited.has(name) || installedNames.has(name)) return;
|
|
3801
4079
|
visited.add(name);
|
|
3802
|
-
const manifest2 =
|
|
4080
|
+
const manifest2 = manifests[name];
|
|
3803
4081
|
if (!manifest2) return;
|
|
3804
4082
|
for (const dep of manifest2.requires) {
|
|
3805
4083
|
walk(dep);
|
|
@@ -3813,8 +4091,8 @@ function resolveDependencies2(blockName, installedNames) {
|
|
|
3813
4091
|
// src/cli/commands/remove.ts
|
|
3814
4092
|
import { defineCommand as defineCommand3 } from "citty";
|
|
3815
4093
|
import pc5 from "picocolors";
|
|
3816
|
-
import { readFileSync as
|
|
3817
|
-
import { join as
|
|
4094
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync5, unlinkSync, readdirSync as readdirSync3, rmdirSync } from "fs";
|
|
4095
|
+
import { join as join8, dirname as dirname4 } from "path";
|
|
3818
4096
|
var removeCommand = defineCommand3({
|
|
3819
4097
|
meta: {
|
|
3820
4098
|
name: "remove",
|
|
@@ -3842,18 +4120,25 @@ var removeCommand = defineCommand3({
|
|
|
3842
4120
|
default: false
|
|
3843
4121
|
}
|
|
3844
4122
|
},
|
|
3845
|
-
run({ args: args2 }) {
|
|
4123
|
+
async run({ args: args2 }) {
|
|
3846
4124
|
const typedArgs = args2;
|
|
3847
4125
|
const cwd = process.cwd();
|
|
3848
|
-
const manifestPath =
|
|
3849
|
-
if (!
|
|
4126
|
+
const manifestPath = join8(cwd, "fornix.json");
|
|
4127
|
+
if (!existsSync5(manifestPath)) {
|
|
3850
4128
|
console.error(
|
|
3851
4129
|
pc5.red("\u2717 No fornix.json found. Are you in a Fornix project?")
|
|
3852
4130
|
);
|
|
3853
4131
|
process.exit(1);
|
|
3854
4132
|
}
|
|
3855
|
-
const manifestRaw =
|
|
4133
|
+
const manifestRaw = readFileSync6(manifestPath, "utf-8");
|
|
3856
4134
|
const manifest2 = JSON.parse(manifestRaw);
|
|
4135
|
+
const registryResult = await fetchRegistryIndex();
|
|
4136
|
+
if (!isOk(registryResult)) {
|
|
4137
|
+
console.error(pc5.red(`\u2716 Failed to fetch block registry: ${registryResult.error.message}`));
|
|
4138
|
+
process.exitCode = 1;
|
|
4139
|
+
return;
|
|
4140
|
+
}
|
|
4141
|
+
const manifests = registryResult.value.blocks;
|
|
3857
4142
|
const blockName = typedArgs.block;
|
|
3858
4143
|
const installedNames = new Set(manifest2.blocks.map((b) => b.name));
|
|
3859
4144
|
if (!installedNames.has(blockName)) {
|
|
@@ -3862,7 +4147,7 @@ var removeCommand = defineCommand3({
|
|
|
3862
4147
|
);
|
|
3863
4148
|
return;
|
|
3864
4149
|
}
|
|
3865
|
-
const dependents = findDependents(blockName, installedNames);
|
|
4150
|
+
const dependents = findDependents(blockName, installedNames, manifests);
|
|
3866
4151
|
if (dependents.length > 0 && !typedArgs.force) {
|
|
3867
4152
|
console.log(
|
|
3868
4153
|
pc5.yellow(
|
|
@@ -3874,12 +4159,12 @@ var removeCommand = defineCommand3({
|
|
|
3874
4159
|
);
|
|
3875
4160
|
return;
|
|
3876
4161
|
}
|
|
3877
|
-
const blockManifest =
|
|
4162
|
+
const blockManifest = manifests[blockName];
|
|
3878
4163
|
const filesToRemove = [];
|
|
3879
4164
|
if (blockManifest) {
|
|
3880
4165
|
for (const file of blockManifest.files) {
|
|
3881
|
-
const filePath =
|
|
3882
|
-
if (
|
|
4166
|
+
const filePath = join8(cwd, file.destination);
|
|
4167
|
+
if (existsSync5(filePath)) {
|
|
3883
4168
|
filesToRemove.push(filePath);
|
|
3884
4169
|
}
|
|
3885
4170
|
}
|
|
@@ -3901,7 +4186,7 @@ var removeCommand = defineCommand3({
|
|
|
3901
4186
|
tryRemoveEmptyDir(dirname4(filePath), cwd);
|
|
3902
4187
|
}
|
|
3903
4188
|
manifest2.blocks = manifest2.blocks.filter((b) => b.name !== blockName);
|
|
3904
|
-
|
|
4189
|
+
writeFileSync5(manifestPath, JSON.stringify(manifest2, null, 2) + "\n");
|
|
3905
4190
|
console.log();
|
|
3906
4191
|
console.log(` ${pc5.red("-")} ${pc5.bold(blockName)} removed`);
|
|
3907
4192
|
if (dependents.length > 0) {
|
|
@@ -3920,10 +4205,10 @@ var removeCommand = defineCommand3({
|
|
|
3920
4205
|
console.log();
|
|
3921
4206
|
}
|
|
3922
4207
|
});
|
|
3923
|
-
function findDependents(blockName, installedNames) {
|
|
4208
|
+
function findDependents(blockName, installedNames, manifests) {
|
|
3924
4209
|
const dependents = [];
|
|
3925
4210
|
for (const name of installedNames) {
|
|
3926
|
-
const manifest2 =
|
|
4211
|
+
const manifest2 = manifests[name];
|
|
3927
4212
|
if (manifest2 && manifest2.requires.includes(blockName)) {
|
|
3928
4213
|
dependents.push(name);
|
|
3929
4214
|
}
|
|
@@ -3933,7 +4218,7 @@ function findDependents(blockName, installedNames) {
|
|
|
3933
4218
|
function tryRemoveEmptyDir(dirPath, rootPath) {
|
|
3934
4219
|
if (dirPath === rootPath || !dirPath.startsWith(rootPath)) return;
|
|
3935
4220
|
try {
|
|
3936
|
-
const entries =
|
|
4221
|
+
const entries = readdirSync3(dirPath);
|
|
3937
4222
|
if (entries.length === 0) {
|
|
3938
4223
|
rmdirSync(dirPath);
|
|
3939
4224
|
tryRemoveEmptyDir(dirname4(dirPath), rootPath);
|
|
@@ -3970,9 +4255,16 @@ var listCommand = defineCommand4({
|
|
|
3970
4255
|
default: false
|
|
3971
4256
|
}
|
|
3972
4257
|
},
|
|
3973
|
-
run({ args: args2 }) {
|
|
4258
|
+
async run({ args: args2 }) {
|
|
3974
4259
|
const typedArgs = args2;
|
|
3975
|
-
const
|
|
4260
|
+
const registryResult = await fetchRegistryIndex();
|
|
4261
|
+
if (!isOk(registryResult)) {
|
|
4262
|
+
console.error(pc6.red(`\u2716 Failed to fetch block registry: ${registryResult.error.message}`));
|
|
4263
|
+
process.exitCode = 1;
|
|
4264
|
+
return;
|
|
4265
|
+
}
|
|
4266
|
+
const manifests = registryResult.value.blocks;
|
|
4267
|
+
const blocks = getFilteredBlocks(typedArgs, manifests);
|
|
3976
4268
|
if (blocks.length === 0) {
|
|
3977
4269
|
console.log(pc6.yellow("No blocks found matching your filters."));
|
|
3978
4270
|
return;
|
|
@@ -3984,8 +4276,8 @@ var listCommand = defineCommand4({
|
|
|
3984
4276
|
}
|
|
3985
4277
|
}
|
|
3986
4278
|
});
|
|
3987
|
-
function getFilteredBlocks(args2) {
|
|
3988
|
-
let blocks = Object.values(
|
|
4279
|
+
function getFilteredBlocks(args2, manifests) {
|
|
4280
|
+
let blocks = Object.values(manifests);
|
|
3989
4281
|
if (args2.type) {
|
|
3990
4282
|
const filterType = args2.type.toLowerCase();
|
|
3991
4283
|
blocks = blocks.filter((b) => b.type === filterType);
|
|
@@ -4066,8 +4358,8 @@ function printFormatted(blocks, verbose) {
|
|
|
4066
4358
|
// src/cli/commands/status.ts
|
|
4067
4359
|
import { defineCommand as defineCommand5 } from "citty";
|
|
4068
4360
|
import pc7 from "picocolors";
|
|
4069
|
-
import { readFileSync as
|
|
4070
|
-
import { join as
|
|
4361
|
+
import { readFileSync as readFileSync7, existsSync as existsSync6 } from "fs";
|
|
4362
|
+
import { join as join9 } from "path";
|
|
4071
4363
|
var statusCommand = defineCommand5({
|
|
4072
4364
|
meta: {
|
|
4073
4365
|
name: "status",
|
|
@@ -4088,8 +4380,8 @@ var statusCommand = defineCommand5({
|
|
|
4088
4380
|
run({ args: args2 }) {
|
|
4089
4381
|
const typedArgs = args2;
|
|
4090
4382
|
const cwd = process.cwd();
|
|
4091
|
-
const manifestPath =
|
|
4092
|
-
if (!
|
|
4383
|
+
const manifestPath = join9(cwd, "fornix.json");
|
|
4384
|
+
if (!existsSync6(manifestPath)) {
|
|
4093
4385
|
console.error(
|
|
4094
4386
|
pc7.red("\u2717 No fornix.json found in the current directory.")
|
|
4095
4387
|
);
|
|
@@ -4102,7 +4394,7 @@ var statusCommand = defineCommand5({
|
|
|
4102
4394
|
}
|
|
4103
4395
|
let manifest2;
|
|
4104
4396
|
try {
|
|
4105
|
-
const raw =
|
|
4397
|
+
const raw = readFileSync7(manifestPath, "utf-8");
|
|
4106
4398
|
manifest2 = JSON.parse(raw);
|
|
4107
4399
|
} catch {
|
|
4108
4400
|
console.error(pc7.red("\u2717 Failed to parse fornix.json."));
|
|
@@ -4183,8 +4475,8 @@ function printStatus(manifest2, verbose) {
|
|
|
4183
4475
|
// src/cli/commands/doctor.ts
|
|
4184
4476
|
import { defineCommand as defineCommand6 } from "citty";
|
|
4185
4477
|
import pc8 from "picocolors";
|
|
4186
|
-
import { readFileSync as
|
|
4187
|
-
import { join as
|
|
4478
|
+
import { readFileSync as readFileSync8, existsSync as existsSync7 } from "fs";
|
|
4479
|
+
import { join as join10 } from "path";
|
|
4188
4480
|
var doctorCommand = defineCommand6({
|
|
4189
4481
|
meta: {
|
|
4190
4482
|
name: "doctor",
|
|
@@ -4197,9 +4489,9 @@ var doctorCommand = defineCommand6({
|
|
|
4197
4489
|
default: false
|
|
4198
4490
|
}
|
|
4199
4491
|
},
|
|
4200
|
-
run({ args: args2 }) {
|
|
4492
|
+
async run({ args: args2 }) {
|
|
4201
4493
|
const cwd = process.cwd();
|
|
4202
|
-
const manifestPath =
|
|
4494
|
+
const manifestPath = join10(cwd, "fornix.json");
|
|
4203
4495
|
let hasErrors = false;
|
|
4204
4496
|
const errors = [];
|
|
4205
4497
|
function reportError(msg) {
|
|
@@ -4209,7 +4501,7 @@ var doctorCommand = defineCommand6({
|
|
|
4209
4501
|
console.error(pc8.red(`\u2717 ${msg}`));
|
|
4210
4502
|
}
|
|
4211
4503
|
}
|
|
4212
|
-
if (!
|
|
4504
|
+
if (!existsSync7(manifestPath)) {
|
|
4213
4505
|
reportError("No fornix.json found in the current directory.");
|
|
4214
4506
|
if (args2.json) {
|
|
4215
4507
|
console.log(JSON.stringify({ healthy: false, errors }));
|
|
@@ -4218,7 +4510,7 @@ var doctorCommand = defineCommand6({
|
|
|
4218
4510
|
}
|
|
4219
4511
|
let manifest2;
|
|
4220
4512
|
try {
|
|
4221
|
-
const raw =
|
|
4513
|
+
const raw = readFileSync8(manifestPath, "utf-8");
|
|
4222
4514
|
manifest2 = JSON.parse(raw);
|
|
4223
4515
|
} catch {
|
|
4224
4516
|
reportError("Failed to parse fornix.json.");
|
|
@@ -4227,24 +4519,34 @@ var doctorCommand = defineCommand6({
|
|
|
4227
4519
|
}
|
|
4228
4520
|
process.exit(1);
|
|
4229
4521
|
}
|
|
4522
|
+
const registryResult = await fetchRegistryIndex();
|
|
4523
|
+
if (!isOk(registryResult)) {
|
|
4524
|
+
reportError(`Failed to fetch block registry: ${registryResult.error.message}`);
|
|
4525
|
+
if (args2.json) {
|
|
4526
|
+
console.log(JSON.stringify({ healthy: false, errors }));
|
|
4527
|
+
}
|
|
4528
|
+
process.exitCode = 1;
|
|
4529
|
+
return;
|
|
4530
|
+
}
|
|
4531
|
+
const manifests = registryResult.value.blocks;
|
|
4230
4532
|
const isMultiLocale = manifest2.locales && manifest2.locales.length >= 2;
|
|
4231
4533
|
const locales = isMultiLocale ? manifest2.locales : [""];
|
|
4232
4534
|
const installedBlocks = new Set(manifest2.blocks.map((b) => b.name));
|
|
4233
4535
|
for (const block of manifest2.blocks) {
|
|
4234
|
-
const bManifest =
|
|
4536
|
+
const bManifest = manifests[block.name];
|
|
4235
4537
|
if (bManifest) {
|
|
4236
4538
|
for (const file of bManifest.files) {
|
|
4237
|
-
const filePath =
|
|
4238
|
-
if (!
|
|
4539
|
+
const filePath = join10(cwd, file.destination);
|
|
4540
|
+
if (!existsSync7(filePath)) {
|
|
4239
4541
|
reportError(`Missing expected file for installed block '${block.name}': ${file.destination}`);
|
|
4240
4542
|
}
|
|
4241
4543
|
}
|
|
4242
4544
|
}
|
|
4243
4545
|
}
|
|
4244
|
-
for (const [name, bManifest] of Object.entries(
|
|
4546
|
+
for (const [name, bManifest] of Object.entries(manifests)) {
|
|
4245
4547
|
if (!installedBlocks.has(name)) {
|
|
4246
4548
|
const foundOrphaned = bManifest.files.some((file) => {
|
|
4247
|
-
return
|
|
4549
|
+
return existsSync7(join10(cwd, file.destination));
|
|
4248
4550
|
});
|
|
4249
4551
|
if (foundOrphaned) {
|
|
4250
4552
|
reportError(`Orphaned block files detected for '${name}'. The block is not in fornix.json.`);
|
|
@@ -4254,7 +4556,7 @@ var doctorCommand = defineCommand6({
|
|
|
4254
4556
|
let requiresContentConfig = false;
|
|
4255
4557
|
const missingContentFiles = [];
|
|
4256
4558
|
for (const block of manifest2.blocks) {
|
|
4257
|
-
const bManifest =
|
|
4559
|
+
const bManifest = manifests[block.name];
|
|
4258
4560
|
if (!bManifest) continue;
|
|
4259
4561
|
const hasContentSlots = bManifest.ai?.contentSlots && Object.keys(bManifest.ai.contentSlots).length > 0;
|
|
4260
4562
|
const hasCollections = bManifest.collections && bManifest.collections.length > 0;
|
|
@@ -4268,14 +4570,14 @@ var doctorCommand = defineCommand6({
|
|
|
4268
4570
|
if (locale !== "") {
|
|
4269
4571
|
pathFragment = `src/content/${locale}/${subdirectory}/${bManifest.name}.json`;
|
|
4270
4572
|
}
|
|
4271
|
-
if (!
|
|
4573
|
+
if (!existsSync7(join10(cwd, pathFragment))) {
|
|
4272
4574
|
missingContentFiles.push(pathFragment);
|
|
4273
4575
|
}
|
|
4274
4576
|
}
|
|
4275
4577
|
}
|
|
4276
4578
|
}
|
|
4277
4579
|
if (requiresContentConfig) {
|
|
4278
|
-
if (!
|
|
4580
|
+
if (!existsSync7(join10(cwd, "src/content/config.ts"))) {
|
|
4279
4581
|
reportError("Missing expected file: src/content/config.ts");
|
|
4280
4582
|
}
|
|
4281
4583
|
}
|
|
@@ -4328,57 +4630,71 @@ import {
|
|
|
4328
4630
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
4329
4631
|
|
|
4330
4632
|
// src/mcp/tools/list-blocks.ts
|
|
4331
|
-
function
|
|
4332
|
-
|
|
4333
|
-
if (
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
if (input.search) {
|
|
4342
|
-
const searchTerm = input.search.toLowerCase();
|
|
4343
|
-
blocks = blocks.filter(
|
|
4344
|
-
(block) => block.name.includes(searchTerm) || block.description.toLowerCase().includes(searchTerm) || block.tags.some((tag) => tag.includes(searchTerm))
|
|
4345
|
-
);
|
|
4633
|
+
async function listBlocksHandler(args2) {
|
|
4634
|
+
const result = await fetchRegistryIndex();
|
|
4635
|
+
if (!isOk(result)) {
|
|
4636
|
+
throw new Error(`Failed to fetch block registry: ${result.error.message}`);
|
|
4637
|
+
}
|
|
4638
|
+
const manifests = result.value.blocks;
|
|
4639
|
+
const typedArgs = args2;
|
|
4640
|
+
let blocks = Object.values(manifests);
|
|
4641
|
+
if (typedArgs?.type) {
|
|
4642
|
+
blocks = blocks.filter((b) => b.type === typedArgs.type);
|
|
4346
4643
|
}
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4644
|
+
if (typedArgs?.category) {
|
|
4645
|
+
blocks = blocks.filter((b) => b.category === typedArgs.category);
|
|
4646
|
+
}
|
|
4647
|
+
return {
|
|
4648
|
+
content: [
|
|
4649
|
+
{
|
|
4650
|
+
type: "text",
|
|
4651
|
+
text: JSON.stringify(
|
|
4652
|
+
blocks.map((b) => ({
|
|
4653
|
+
name: b.name,
|
|
4654
|
+
description: b.description,
|
|
4655
|
+
type: b.type,
|
|
4656
|
+
category: b.category,
|
|
4657
|
+
requiredMode: b.requiredMode,
|
|
4658
|
+
requires: b.requires,
|
|
4659
|
+
conflicts: b.conflicts,
|
|
4660
|
+
envVars: b.envVars
|
|
4661
|
+
})),
|
|
4662
|
+
null,
|
|
4663
|
+
2
|
|
4664
|
+
)
|
|
4665
|
+
}
|
|
4666
|
+
]
|
|
4667
|
+
};
|
|
4357
4668
|
}
|
|
4358
4669
|
|
|
4359
4670
|
// src/mcp/tools/add-block.ts
|
|
4360
|
-
import { readFileSync as
|
|
4361
|
-
import { join as
|
|
4362
|
-
function addBlock2(input) {
|
|
4671
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync6 } from "fs";
|
|
4672
|
+
import { join as join11, dirname as dirname5 } from "path";
|
|
4673
|
+
async function addBlock2(input) {
|
|
4363
4674
|
const { name, variant = "default", projectDirectory } = input;
|
|
4364
|
-
const manifestPath =
|
|
4365
|
-
if (!
|
|
4675
|
+
const manifestPath = join11(projectDirectory, "fornix.json");
|
|
4676
|
+
if (!existsSync8(manifestPath)) {
|
|
4366
4677
|
return err(
|
|
4367
4678
|
new Error("No fornix.json found. Not a Fornix project directory.")
|
|
4368
4679
|
);
|
|
4369
4680
|
}
|
|
4370
4681
|
let manifest2;
|
|
4371
4682
|
try {
|
|
4372
|
-
const raw =
|
|
4683
|
+
const raw = readFileSync9(manifestPath, "utf-8");
|
|
4373
4684
|
manifest2 = JSON.parse(raw);
|
|
4374
4685
|
} catch {
|
|
4375
4686
|
return err(new Error("Failed to parse fornix.json."));
|
|
4376
4687
|
}
|
|
4377
|
-
const
|
|
4688
|
+
const registryResult = await fetchRegistryIndex();
|
|
4689
|
+
if (!isOk(registryResult)) {
|
|
4690
|
+
return err(new Error(`Failed to fetch block registry: ${registryResult.error.message}`));
|
|
4691
|
+
}
|
|
4692
|
+
const manifests = registryResult.value.blocks;
|
|
4693
|
+
const blockManifest = manifests[name];
|
|
4378
4694
|
if (!blockManifest) {
|
|
4379
4695
|
return err(
|
|
4380
4696
|
new Error(
|
|
4381
|
-
`Block '${name}' not found in registry. Available: ${Object.keys(
|
|
4697
|
+
`Block '${name}' not found in registry. Available: ${Object.keys(manifests).join(", ")}`
|
|
4382
4698
|
)
|
|
4383
4699
|
);
|
|
4384
4700
|
}
|
|
@@ -4386,9 +4702,9 @@ function addBlock2(input) {
|
|
|
4386
4702
|
if (installedNames.has(name)) {
|
|
4387
4703
|
return ok({ addedBlocks: [], filesCreated: 0 });
|
|
4388
4704
|
}
|
|
4389
|
-
const blocksToAdd = resolveDependencies3(name, installedNames);
|
|
4705
|
+
const blocksToAdd = resolveDependencies3(name, installedNames, manifests);
|
|
4390
4706
|
for (const blockName of blocksToAdd) {
|
|
4391
|
-
const dependencyManifest =
|
|
4707
|
+
const dependencyManifest = manifests[blockName];
|
|
4392
4708
|
if (dependencyManifest?.requiredMode && manifest2.renderMode !== dependencyManifest.requiredMode) {
|
|
4393
4709
|
return err(
|
|
4394
4710
|
new Error(
|
|
@@ -4398,14 +4714,15 @@ function addBlock2(input) {
|
|
|
4398
4714
|
}
|
|
4399
4715
|
}
|
|
4400
4716
|
let filesCreated = 0;
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
const
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4717
|
+
const blockResults = await fetchBlocks(blocksToAdd);
|
|
4718
|
+
for (let i = 0; i < blocksToAdd.length; i++) {
|
|
4719
|
+
const blockName = blocksToAdd[i];
|
|
4720
|
+
const result = blockResults[i];
|
|
4721
|
+
if (!result || !isOk(result)) {
|
|
4722
|
+
return err(new Error(`Failed to fetch files for block '${blockName}'.`));
|
|
4723
|
+
}
|
|
4724
|
+
const blockDef = result.value.manifest;
|
|
4725
|
+
const sources = result.value.files;
|
|
4409
4726
|
for (const file of blockDef.files) {
|
|
4410
4727
|
const content = sources[file.source];
|
|
4411
4728
|
if (content === void 0) {
|
|
@@ -4415,15 +4732,15 @@ function addBlock2(input) {
|
|
|
4415
4732
|
)
|
|
4416
4733
|
);
|
|
4417
4734
|
}
|
|
4418
|
-
const filePath =
|
|
4419
|
-
|
|
4420
|
-
|
|
4735
|
+
const filePath = join11(projectDirectory, file.destination);
|
|
4736
|
+
mkdirSync6(dirname5(filePath), { recursive: true });
|
|
4737
|
+
writeFileSync6(filePath, content);
|
|
4421
4738
|
filesCreated++;
|
|
4422
4739
|
}
|
|
4423
4740
|
}
|
|
4424
4741
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4425
4742
|
for (const blockName of blocksToAdd) {
|
|
4426
|
-
const blockDef =
|
|
4743
|
+
const blockDef = manifests[blockName];
|
|
4427
4744
|
if (!blockDef) continue;
|
|
4428
4745
|
manifest2.blocks.push({
|
|
4429
4746
|
name: blockName,
|
|
@@ -4432,16 +4749,16 @@ function addBlock2(input) {
|
|
|
4432
4749
|
installedAt: now
|
|
4433
4750
|
});
|
|
4434
4751
|
}
|
|
4435
|
-
|
|
4752
|
+
writeFileSync6(manifestPath, JSON.stringify(manifest2, null, 2) + "\n");
|
|
4436
4753
|
return ok({ addedBlocks: blocksToAdd, filesCreated });
|
|
4437
4754
|
}
|
|
4438
|
-
function resolveDependencies3(blockName, installedNames) {
|
|
4755
|
+
function resolveDependencies3(blockName, installedNames, manifests) {
|
|
4439
4756
|
const result = [];
|
|
4440
4757
|
const visited = /* @__PURE__ */ new Set();
|
|
4441
4758
|
function walk(currentName) {
|
|
4442
4759
|
if (visited.has(currentName) || installedNames.has(currentName)) return;
|
|
4443
4760
|
visited.add(currentName);
|
|
4444
|
-
const manifest2 =
|
|
4761
|
+
const manifest2 = manifests[currentName];
|
|
4445
4762
|
if (!manifest2) return;
|
|
4446
4763
|
for (const dependency of manifest2.requires) {
|
|
4447
4764
|
walk(dependency);
|
|
@@ -4454,34 +4771,39 @@ function resolveDependencies3(blockName, installedNames) {
|
|
|
4454
4771
|
|
|
4455
4772
|
// src/mcp/tools/remove-block.ts
|
|
4456
4773
|
import {
|
|
4457
|
-
readFileSync as
|
|
4458
|
-
writeFileSync as
|
|
4459
|
-
existsSync as
|
|
4774
|
+
readFileSync as readFileSync10,
|
|
4775
|
+
writeFileSync as writeFileSync7,
|
|
4776
|
+
existsSync as existsSync9,
|
|
4460
4777
|
unlinkSync as unlinkSync2,
|
|
4461
|
-
readdirSync as
|
|
4778
|
+
readdirSync as readdirSync4,
|
|
4462
4779
|
rmdirSync as rmdirSync2
|
|
4463
4780
|
} from "fs";
|
|
4464
|
-
import { join as
|
|
4465
|
-
function removeBlock(input) {
|
|
4781
|
+
import { join as join12, dirname as dirname6 } from "path";
|
|
4782
|
+
async function removeBlock(input) {
|
|
4466
4783
|
const { name, force = false, projectDirectory } = input;
|
|
4467
|
-
const manifestPath =
|
|
4468
|
-
if (!
|
|
4784
|
+
const manifestPath = join12(projectDirectory, "fornix.json");
|
|
4785
|
+
if (!existsSync9(manifestPath)) {
|
|
4469
4786
|
return err(
|
|
4470
4787
|
new Error("No fornix.json found. Not a Fornix project directory.")
|
|
4471
4788
|
);
|
|
4472
4789
|
}
|
|
4473
4790
|
let manifest2;
|
|
4474
4791
|
try {
|
|
4475
|
-
const raw =
|
|
4792
|
+
const raw = readFileSync10(manifestPath, "utf-8");
|
|
4476
4793
|
manifest2 = JSON.parse(raw);
|
|
4477
4794
|
} catch {
|
|
4478
4795
|
return err(new Error("Failed to parse fornix.json."));
|
|
4479
4796
|
}
|
|
4797
|
+
const registryResult = await fetchRegistryIndex();
|
|
4798
|
+
if (!isOk(registryResult)) {
|
|
4799
|
+
return err(new Error(`Failed to fetch block registry: ${registryResult.error.message}`));
|
|
4800
|
+
}
|
|
4801
|
+
const manifests = registryResult.value.blocks;
|
|
4480
4802
|
const installedNames = new Set(manifest2.blocks.map((block) => block.name));
|
|
4481
4803
|
if (!installedNames.has(name)) {
|
|
4482
4804
|
return err(new Error(`Block '${name}' is not installed.`));
|
|
4483
4805
|
}
|
|
4484
|
-
const dependents = findDependents2(name, installedNames);
|
|
4806
|
+
const dependents = findDependents2(name, installedNames, manifests);
|
|
4485
4807
|
if (dependents.length > 0 && !force) {
|
|
4486
4808
|
return err(
|
|
4487
4809
|
new Error(
|
|
@@ -4489,12 +4811,12 @@ function removeBlock(input) {
|
|
|
4489
4811
|
)
|
|
4490
4812
|
);
|
|
4491
4813
|
}
|
|
4492
|
-
const blockManifest =
|
|
4814
|
+
const blockManifest = manifests[name];
|
|
4493
4815
|
const filesToRemove = [];
|
|
4494
4816
|
if (blockManifest) {
|
|
4495
4817
|
for (const file of blockManifest.files) {
|
|
4496
|
-
const filePath =
|
|
4497
|
-
if (
|
|
4818
|
+
const filePath = join12(projectDirectory, file.destination);
|
|
4819
|
+
if (existsSync9(filePath)) {
|
|
4498
4820
|
filesToRemove.push(filePath);
|
|
4499
4821
|
}
|
|
4500
4822
|
}
|
|
@@ -4504,17 +4826,17 @@ function removeBlock(input) {
|
|
|
4504
4826
|
tryRemoveEmptyDirectory(dirname6(filePath), projectDirectory);
|
|
4505
4827
|
}
|
|
4506
4828
|
manifest2.blocks = manifest2.blocks.filter((block) => block.name !== name);
|
|
4507
|
-
|
|
4829
|
+
writeFileSync7(manifestPath, JSON.stringify(manifest2, null, 2) + "\n");
|
|
4508
4830
|
return ok({
|
|
4509
4831
|
removedBlock: name,
|
|
4510
4832
|
filesRemoved: filesToRemove.length,
|
|
4511
4833
|
dependentsWarning: dependents
|
|
4512
4834
|
});
|
|
4513
4835
|
}
|
|
4514
|
-
function findDependents2(blockName, installedNames) {
|
|
4836
|
+
function findDependents2(blockName, installedNames, manifests) {
|
|
4515
4837
|
const dependents = [];
|
|
4516
4838
|
for (const installedName of installedNames) {
|
|
4517
|
-
const manifest2 =
|
|
4839
|
+
const manifest2 = manifests[installedName];
|
|
4518
4840
|
if (manifest2 && manifest2.requires.includes(blockName)) {
|
|
4519
4841
|
dependents.push(installedName);
|
|
4520
4842
|
}
|
|
@@ -4524,7 +4846,7 @@ function findDependents2(blockName, installedNames) {
|
|
|
4524
4846
|
function tryRemoveEmptyDirectory(directoryPath, rootPath) {
|
|
4525
4847
|
if (directoryPath === rootPath || !directoryPath.startsWith(rootPath)) return;
|
|
4526
4848
|
try {
|
|
4527
|
-
const entries =
|
|
4849
|
+
const entries = readdirSync4(directoryPath);
|
|
4528
4850
|
if (entries.length === 0) {
|
|
4529
4851
|
rmdirSync2(directoryPath);
|
|
4530
4852
|
tryRemoveEmptyDirectory(dirname6(directoryPath), rootPath);
|
|
@@ -4534,33 +4856,51 @@ function tryRemoveEmptyDirectory(directoryPath, rootPath) {
|
|
|
4534
4856
|
}
|
|
4535
4857
|
|
|
4536
4858
|
// src/mcp/tools/get-content-schema.ts
|
|
4537
|
-
function
|
|
4538
|
-
const
|
|
4539
|
-
|
|
4859
|
+
async function getContentSchemaHandler(args2) {
|
|
4860
|
+
const typedArgs = args2;
|
|
4861
|
+
if (!typedArgs?.block) {
|
|
4862
|
+
throw new Error("Missing required argument: block");
|
|
4863
|
+
}
|
|
4864
|
+
const result = await fetchRegistryIndex();
|
|
4865
|
+
if (!isOk(result)) {
|
|
4866
|
+
throw new Error(`Failed to fetch block registry: ${result.error.message}`);
|
|
4867
|
+
}
|
|
4868
|
+
const manifests = result.value.blocks;
|
|
4869
|
+
const manifest2 = manifests[typedArgs.block];
|
|
4540
4870
|
if (!manifest2) {
|
|
4541
|
-
|
|
4542
|
-
new Error(`Collection '${collection}' not found in registry.`)
|
|
4543
|
-
);
|
|
4871
|
+
throw new Error(`Block '${typedArgs.block}' not found in registry`);
|
|
4544
4872
|
}
|
|
4545
|
-
const
|
|
4546
|
-
if (
|
|
4547
|
-
return
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4873
|
+
const slots = manifest2.ai?.contentSlots ?? {};
|
|
4874
|
+
if (Object.keys(slots).length === 0) {
|
|
4875
|
+
return {
|
|
4876
|
+
content: [
|
|
4877
|
+
{
|
|
4878
|
+
type: "text",
|
|
4879
|
+
text: `Block '${typedArgs.block}' does not have any AI-configurable content slots.`
|
|
4880
|
+
}
|
|
4881
|
+
]
|
|
4882
|
+
};
|
|
4552
4883
|
}
|
|
4553
|
-
return
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4884
|
+
return {
|
|
4885
|
+
content: [
|
|
4886
|
+
{
|
|
4887
|
+
type: "text",
|
|
4888
|
+
text: JSON.stringify(slots, null, 2)
|
|
4889
|
+
}
|
|
4890
|
+
]
|
|
4891
|
+
};
|
|
4557
4892
|
}
|
|
4558
4893
|
|
|
4559
4894
|
// src/mcp/tools/validate-content.ts
|
|
4560
4895
|
import { z as z5 } from "zod";
|
|
4561
|
-
function validateContent(input) {
|
|
4896
|
+
async function validateContent(input) {
|
|
4562
4897
|
const { collection, data } = input;
|
|
4563
|
-
const
|
|
4898
|
+
const registryResult = await fetchRegistryIndex();
|
|
4899
|
+
if (!isOk(registryResult)) {
|
|
4900
|
+
return err(new Error(`Failed to fetch block registry: ${registryResult.error.message}`));
|
|
4901
|
+
}
|
|
4902
|
+
const manifests = registryResult.value.blocks;
|
|
4903
|
+
const manifest2 = manifests[collection];
|
|
4564
4904
|
if (!manifest2) {
|
|
4565
4905
|
return err(
|
|
4566
4906
|
new Error(
|
|
@@ -4608,17 +4948,17 @@ function zodTypeForSlot2(slotType) {
|
|
|
4608
4948
|
}
|
|
4609
4949
|
|
|
4610
4950
|
// src/mcp/tools/get-project-status.ts
|
|
4611
|
-
import { readFileSync as
|
|
4612
|
-
import { join as
|
|
4951
|
+
import { readFileSync as readFileSync11, existsSync as existsSync10 } from "fs";
|
|
4952
|
+
import { join as join13 } from "path";
|
|
4613
4953
|
function getProjectStatus(input) {
|
|
4614
|
-
const manifestPath =
|
|
4615
|
-
if (!
|
|
4954
|
+
const manifestPath = join13(input.projectDirectory, "fornix.json");
|
|
4955
|
+
if (!existsSync10(manifestPath)) {
|
|
4616
4956
|
return err(
|
|
4617
4957
|
new Error("No fornix.json found. Not a Fornix project directory.")
|
|
4618
4958
|
);
|
|
4619
4959
|
}
|
|
4620
4960
|
try {
|
|
4621
|
-
const raw =
|
|
4961
|
+
const raw = readFileSync11(manifestPath, "utf-8");
|
|
4622
4962
|
const manifest2 = JSON.parse(raw);
|
|
4623
4963
|
const blocks = manifest2.blocks;
|
|
4624
4964
|
return ok({
|
|
@@ -4640,8 +4980,8 @@ function getProjectStatus(input) {
|
|
|
4640
4980
|
}
|
|
4641
4981
|
|
|
4642
4982
|
// src/mcp/tools/scaffold-project.ts
|
|
4643
|
-
import { mkdirSync as
|
|
4644
|
-
import { join as
|
|
4983
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
4984
|
+
import { join as join14, basename as basename3 } from "path";
|
|
4645
4985
|
var DEFAULT_COLORS2 = {
|
|
4646
4986
|
primary: "#6366f1",
|
|
4647
4987
|
secondary: "#818cf8",
|
|
@@ -4649,7 +4989,7 @@ var DEFAULT_COLORS2 = {
|
|
|
4649
4989
|
background: "#0f172a",
|
|
4650
4990
|
foreground: "#f8fafc"
|
|
4651
4991
|
};
|
|
4652
|
-
function scaffoldProject(input) {
|
|
4992
|
+
async function scaffoldProject(input) {
|
|
4653
4993
|
const {
|
|
4654
4994
|
projectDirectory,
|
|
4655
4995
|
renderMode = "static",
|
|
@@ -4657,9 +4997,14 @@ function scaffoldProject(input) {
|
|
|
4657
4997
|
blocks = [],
|
|
4658
4998
|
locales = ["en"]
|
|
4659
4999
|
} = input;
|
|
5000
|
+
const registryResult = await fetchRegistryIndex();
|
|
5001
|
+
if (!isOk(registryResult)) {
|
|
5002
|
+
return err(new Error(`Failed to fetch block registry: ${registryResult.error.message}`));
|
|
5003
|
+
}
|
|
5004
|
+
const manifests = registryResult.value.blocks;
|
|
5005
|
+
const allPalettes = registryResult.value.palettes.length > 0 ? registryResult.value.palettes : loadAllPalettes();
|
|
4660
5006
|
const projectName = basename3(projectDirectory);
|
|
4661
5007
|
const blockSelections = blocks.map((name) => ({ name, variant: "default" }));
|
|
4662
|
-
const allPalettes = loadAllPalettes();
|
|
4663
5008
|
const config = {
|
|
4664
5009
|
projectName,
|
|
4665
5010
|
projectDir: projectDirectory,
|
|
@@ -4677,11 +5022,27 @@ function scaffoldProject(input) {
|
|
|
4677
5022
|
themeSwitcher: false,
|
|
4678
5023
|
createdWith: "mcp"
|
|
4679
5024
|
};
|
|
5025
|
+
const blockResults = await fetchBlocks([...blocks]);
|
|
5026
|
+
const blockSources = {};
|
|
5027
|
+
const blockDefaultContent = {};
|
|
5028
|
+
for (const result2 of blockResults) {
|
|
5029
|
+
if (!isOk(result2)) {
|
|
5030
|
+
return err(new Error(`Failed to fetch block '${result2.error.blockName}': ${result2.error.message}`));
|
|
5031
|
+
}
|
|
5032
|
+
const { manifest: manifest2, files: files2 } = result2.value;
|
|
5033
|
+
blockSources[manifest2.name] = files2;
|
|
5034
|
+
try {
|
|
5035
|
+
if (files2["default-content.json"]) {
|
|
5036
|
+
blockDefaultContent[manifest2.name] = JSON.parse(files2["default-content.json"]);
|
|
5037
|
+
}
|
|
5038
|
+
} catch {
|
|
5039
|
+
}
|
|
5040
|
+
}
|
|
4680
5041
|
const scaffoldInput = {
|
|
4681
5042
|
config,
|
|
4682
|
-
manifests
|
|
4683
|
-
blockSources
|
|
4684
|
-
blockDefaultContent
|
|
5043
|
+
manifests,
|
|
5044
|
+
blockSources,
|
|
5045
|
+
blockDefaultContent,
|
|
4685
5046
|
allPalettes
|
|
4686
5047
|
};
|
|
4687
5048
|
const result = scaffold(scaffoldInput);
|
|
@@ -4691,10 +5052,10 @@ function scaffoldProject(input) {
|
|
|
4691
5052
|
const files = result.value.files;
|
|
4692
5053
|
let filesCreated = 0;
|
|
4693
5054
|
for (const [relativePath, content] of Object.entries(files)) {
|
|
4694
|
-
const fullPath =
|
|
4695
|
-
const parentDirectory =
|
|
4696
|
-
|
|
4697
|
-
|
|
5055
|
+
const fullPath = join14(projectDirectory, relativePath);
|
|
5056
|
+
const parentDirectory = join14(fullPath, "..");
|
|
5057
|
+
mkdirSync7(parentDirectory, { recursive: true });
|
|
5058
|
+
writeFileSync8(fullPath, content, "utf-8");
|
|
4698
5059
|
filesCreated++;
|
|
4699
5060
|
}
|
|
4700
5061
|
return ok({
|
|
@@ -4891,7 +5252,11 @@ var FornixMCPServer = class {
|
|
|
4891
5252
|
async (request) => {
|
|
4892
5253
|
const uri = request.params.uri;
|
|
4893
5254
|
if (uri === "fornix://registry") {
|
|
4894
|
-
const
|
|
5255
|
+
const registryResult = await fetchRegistryIndex();
|
|
5256
|
+
if (!isOk(registryResult)) {
|
|
5257
|
+
throw new Error(`Failed to fetch block registry: ${registryResult.error.message}`);
|
|
5258
|
+
}
|
|
5259
|
+
const blocks = Object.values(registryResult.value.blocks).map((manifest2) => ({
|
|
4895
5260
|
name: manifest2.name,
|
|
4896
5261
|
type: manifest2.type,
|
|
4897
5262
|
category: manifest2.category,
|
|
@@ -4934,16 +5299,19 @@ var FornixMCPServer = class {
|
|
|
4934
5299
|
async executeTool(toolName, args2) {
|
|
4935
5300
|
switch (toolName) {
|
|
4936
5301
|
case "list_blocks": {
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
5302
|
+
try {
|
|
5303
|
+
const response = await listBlocksHandler({
|
|
5304
|
+
type: args2.type,
|
|
5305
|
+
category: args2.category,
|
|
5306
|
+
search: args2.search
|
|
5307
|
+
});
|
|
5308
|
+
return ok(response.content[0].text);
|
|
5309
|
+
} catch (e) {
|
|
5310
|
+
return err(e instanceof Error ? e : new Error(String(e)));
|
|
5311
|
+
}
|
|
4944
5312
|
}
|
|
4945
5313
|
case "add_block": {
|
|
4946
|
-
const result = addBlock2({
|
|
5314
|
+
const result = await addBlock2({
|
|
4947
5315
|
name: args2.name,
|
|
4948
5316
|
variant: args2.variant,
|
|
4949
5317
|
projectDirectory: args2.projectDirectory
|
|
@@ -4952,7 +5320,7 @@ var FornixMCPServer = class {
|
|
|
4952
5320
|
return ok(JSON.stringify(result.value, null, 2));
|
|
4953
5321
|
}
|
|
4954
5322
|
case "remove_block": {
|
|
4955
|
-
const result = removeBlock({
|
|
5323
|
+
const result = await removeBlock({
|
|
4956
5324
|
name: args2.name,
|
|
4957
5325
|
force: args2.force,
|
|
4958
5326
|
projectDirectory: args2.projectDirectory
|
|
@@ -4961,14 +5329,17 @@ var FornixMCPServer = class {
|
|
|
4961
5329
|
return ok(JSON.stringify(result.value, null, 2));
|
|
4962
5330
|
}
|
|
4963
5331
|
case "get_content_schema": {
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
5332
|
+
try {
|
|
5333
|
+
const response = await getContentSchemaHandler({
|
|
5334
|
+
block: args2.collection
|
|
5335
|
+
});
|
|
5336
|
+
return ok(response.content[0].text);
|
|
5337
|
+
} catch (e) {
|
|
5338
|
+
return err(e instanceof Error ? e : new Error(String(e)));
|
|
5339
|
+
}
|
|
4969
5340
|
}
|
|
4970
5341
|
case "update_content": {
|
|
4971
|
-
const validationResult = validateContent({
|
|
5342
|
+
const validationResult = await validateContent({
|
|
4972
5343
|
collection: args2.collection,
|
|
4973
5344
|
data: args2.data
|
|
4974
5345
|
});
|
|
@@ -4990,7 +5361,7 @@ var FornixMCPServer = class {
|
|
|
4990
5361
|
);
|
|
4991
5362
|
}
|
|
4992
5363
|
case "validate_content": {
|
|
4993
|
-
const result = validateContent({
|
|
5364
|
+
const result = await validateContent({
|
|
4994
5365
|
collection: args2.collection,
|
|
4995
5366
|
data: args2.data
|
|
4996
5367
|
});
|
|
@@ -5005,7 +5376,7 @@ var FornixMCPServer = class {
|
|
|
5005
5376
|
return ok(JSON.stringify(result.value, null, 2));
|
|
5006
5377
|
}
|
|
5007
5378
|
case "scaffold_project": {
|
|
5008
|
-
const result = scaffoldProject({
|
|
5379
|
+
const result = await scaffoldProject({
|
|
5009
5380
|
description: args2.description,
|
|
5010
5381
|
projectDirectory: args2.projectDirectory,
|
|
5011
5382
|
renderMode: args2.renderMode,
|