create-fornix 0.0.7 → 0.0.9

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 CHANGED
@@ -9,7 +9,7 @@ import { defineCommand as defineCommand8 } from "citty";
9
9
  // src/cli/commands/create.ts
10
10
  import { defineCommand } from "citty";
11
11
  import { resolve as resolve2, basename as basename2 } from "path";
12
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
12
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
13
13
  import { join as join6 } from "path";
14
14
  import * as p2 from "@clack/prompts";
15
15
  import pc3 from "picocolors";
@@ -329,6 +329,39 @@ const { title } = Astro.props;${tailwindImport}
329
329
  <slot />
330
330
  </body>
331
331
  </html>
332
+
333
+ <style is:global>
334
+ *, *::before, *::after {
335
+ box-sizing: border-box;
336
+ margin: 0;
337
+ padding: 0;
338
+ }
339
+
340
+ html {
341
+ scroll-behavior: smooth;
342
+ -webkit-font-smoothing: antialiased;
343
+ -moz-osx-font-smoothing: grayscale;
344
+ }
345
+
346
+ body {
347
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
348
+ background: var(--color-background, #0f172a);
349
+ color: var(--color-foreground, #f8fafc);
350
+ line-height: 1.6;
351
+ min-height: 100vh;
352
+ }
353
+
354
+ img, video {
355
+ max-width: 100%;
356
+ height: auto;
357
+ display: block;
358
+ }
359
+
360
+ a {
361
+ color: inherit;
362
+ text-decoration: none;
363
+ }
364
+ </style>
332
365
  `.trim() + "\n";
333
366
  if (config.deployTarget === "cloudflare") {
334
367
  const wrangler = {
@@ -373,7 +406,7 @@ function generateAstroConfig(config, blocks = []) {
373
406
  local: "defineConfig"
374
407
  });
375
408
  const configObject = module.exports.default.$args[0];
376
- if (config.renderMode === "server") {
409
+ if (config.renderMode === "server" || config.renderMode === "hybrid") {
377
410
  configObject.output = "server";
378
411
  }
379
412
  const adapter = ADAPTER_MAP[config.deployTarget];
@@ -427,22 +460,11 @@ function generateTailwindConfig(config) {
427
460
  if (config.cssEngine !== "tailwind") {
428
461
  return ok(null);
429
462
  }
430
- const themeBlock = [
431
- "@theme {",
432
- " --color-primary: var(--color-primary);",
433
- " --color-secondary: var(--color-secondary);",
434
- " --color-accent: var(--color-accent);",
435
- " --color-background: var(--color-background);",
436
- " --color-foreground: var(--color-foreground);",
437
- "}"
438
- ].join("\n");
439
463
  const lines = [
440
464
  '@import "tailwindcss";',
441
465
  '@import "./src/styles/palettes/_current.css";',
442
466
  "",
443
467
  `@source "./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}";`,
444
- "",
445
- themeBlock,
446
468
  ""
447
469
  ];
448
470
  return ok(lines.join("\n"));
@@ -615,7 +637,7 @@ function generateContentConfig(blocks) {
615
637
  'import { defineCollection, z } from "astro:content";'
616
638
  ];
617
639
  const collections = [];
618
- const dataCollections = /* @__PURE__ */ new Map();
640
+ const dataCollectionNames = /* @__PURE__ */ new Set();
619
641
  for (const block of blocks) {
620
642
  if (block.collections && block.collections.length > 0) {
621
643
  for (const col of block.collections) {
@@ -632,27 +654,15 @@ function generateContentConfig(blocks) {
632
654
  }
633
655
  const slots = block.ai?.contentSlots;
634
656
  if (slots && Object.keys(slots).length > 0) {
635
- const schemaFields = Object.entries(slots).map(([name, slot]) => ` ${name}: ${zodTypeForSlot(slot)}.optional(),`).join("\n");
636
657
  const subdirectory = TYPE_DIRECTORY[block.type] ?? block.type;
637
- if (!dataCollections.has(subdirectory)) {
638
- dataCollections.set(subdirectory, []);
639
- }
640
- dataCollections.get(subdirectory).push(
641
- ` // ${block.name}
642
- z.object({
643
- ${schemaFields}
644
- })`
645
- );
658
+ dataCollectionNames.add(subdirectory);
646
659
  }
647
660
  }
648
- for (const [colName, schemas] of dataCollections.entries()) {
649
- const schemaStr = schemas.length === 1 ? schemas[0] : `z.union([
650
- ${schemas.join(",\n")}
651
- ])`;
661
+ for (const colName of dataCollectionNames) {
652
662
  collections.push(
653
663
  ` "${colName}": defineCollection({
654
664
  type: "data",
655
- schema: ${schemaStr},
665
+ schema: z.record(z.unknown()),
656
666
  })`
657
667
  );
658
668
  }
@@ -666,20 +676,6 @@ ${schemas.join(",\n")}
666
676
  ];
667
677
  return lines.join("\n");
668
678
  }
669
- function zodTypeForSlot(slot) {
670
- switch (slot.type) {
671
- case "string":
672
- return "z.string()";
673
- case "number":
674
- return "z.number()";
675
- case "boolean":
676
- return "z.boolean()";
677
- case "array":
678
- return "z.array(z.unknown())";
679
- case "object":
680
- return "z.record(z.unknown())";
681
- }
682
- }
683
679
  function buildDefaultFromSlots(slots) {
684
680
  const content = {};
685
681
  for (const [name, slot] of Object.entries(slots)) {
@@ -703,13 +699,13 @@ function defaultValueForType(type) {
703
699
  }
704
700
 
705
701
  // src/scaffold/i18n-wiring.ts
706
- function wireI18n(config) {
702
+ function wireI18n(config, manifests) {
707
703
  const files = {};
708
704
  if (config.locales.length < 2) {
709
705
  return ok(files);
710
706
  }
711
707
  files["src/i18n/utils.ts"] = generateI18nUtils(config);
712
- files["src/pages/[locale]/index.astro"] = generateLocaleIndexPage(config);
708
+ files["src/pages/[locale]/index.astro"] = generateLocaleIndexPage(config, manifests ?? []);
713
709
  return ok(files);
714
710
  }
715
711
  function generateI18nUtils(config) {
@@ -744,11 +740,21 @@ export function t<T>(
744
740
  }
745
741
  `;
746
742
  }
747
- function generateLocaleIndexPage(config) {
743
+ function generateLocaleIndexPage(config, manifests) {
744
+ const sectionBlocks = manifests.filter((m) => m.type === "section");
745
+ const imports = [];
746
+ const tags = [];
747
+ for (const block of sectionBlocks) {
748
+ const componentName = block.name.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
749
+ imports.push(`import ${componentName} from '../../../components/sections/${block.name}.astro';`);
750
+ tags.push(` <${componentName} />`);
751
+ }
752
+ const importSection = imports.length > 0 ? imports.join("\n") + "\n" : "";
753
+ const blockSection = tags.length > 0 ? "\n" + tags.join("\n") + "\n " : "\n <h1>" + config.projectName + "</h1>\n <p>Locale: {locale}</p>\n ";
748
754
  return `---
749
- import { locales } from "../../i18n/utils";
750
- import Layout from "../../layouts/Layout.astro";
751
-
755
+ import { locales } from "../../../i18n/utils";
756
+ import Layout from "../../../layouts/Layout.astro";
757
+ ${importSection}
752
758
  export function getStaticPaths() {
753
759
  return locales.map((locale) => ({ params: { locale } }));
754
760
  }
@@ -756,10 +762,7 @@ export function getStaticPaths() {
756
762
  const { locale } = Astro.params;
757
763
  ---
758
764
  <Layout title="${config.projectName}">
759
- <main>
760
- <h1>${config.projectName}</h1>
761
- <p>Locale: {locale}</p>
762
- </main>
765
+ <main>${blockSection}</main>
763
766
  </Layout>
764
767
  `;
765
768
  }
@@ -875,7 +878,7 @@ function scaffold(input) {
875
878
  return err(contentResult.error);
876
879
  }
877
880
  Object.assign(files, contentResult.value);
878
- const i18nResult = wireI18n(config);
881
+ const i18nResult = wireI18n(config, resolvedManifests);
879
882
  if (!isOk(i18nResult)) {
880
883
  return err(i18nResult.error);
881
884
  }
@@ -1503,7 +1506,7 @@ function manifest(name, overrides = {}) {
1503
1506
  var FIXTURE_MANIFESTS = {
1504
1507
  "hero-gradient": manifest("hero-gradient", {
1505
1508
  category: "hero",
1506
- conflicts: ["hero-video"],
1509
+ conflicts: ["hero-video", "hero-split"],
1507
1510
  ai: {
1508
1511
  whenToUse: "Landing page hero with gradient background",
1509
1512
  whenNotToUse: "Internal pages",
@@ -1515,7 +1518,8 @@ var FIXTURE_MANIFESTS = {
1515
1518
  }
1516
1519
  }),
1517
1520
  "footer-minimal": manifest("footer-minimal", {
1518
- category: "footer"
1521
+ category: "footer",
1522
+ conflicts: ["footer-rich"]
1519
1523
  }),
1520
1524
  "cta-simple": manifest("cta-simple", {
1521
1525
  category: "cta"
@@ -1626,17 +1630,96 @@ var FIXTURE_MANIFESTS = {
1626
1630
  }
1627
1631
  }
1628
1632
  }),
1629
- "hero-video": manifest("hero-video"),
1630
- "features-bento": manifest("features-bento"),
1631
- "pricing-table": manifest("pricing-table"),
1632
- "faq-accordion": manifest("faq-accordion"),
1633
- "footer-rich": manifest("footer-rich"),
1633
+ "hero-video": manifest("hero-video", {
1634
+ category: "hero",
1635
+ conflicts: ["hero-gradient", "hero-split"],
1636
+ files: [
1637
+ { source: "hero-video.astro", destination: "src/components/sections/hero-video.astro" },
1638
+ { source: "hero-video.css", destination: "src/styles/sections/hero-video.css" }
1639
+ ],
1640
+ ai: {
1641
+ whenToUse: "Hero with background video",
1642
+ whenNotToUse: "Static sites",
1643
+ pairsWith: [],
1644
+ contentSlots: {
1645
+ headline: { type: "string" },
1646
+ subheadline: { type: "string" },
1647
+ ctaText: { type: "string" },
1648
+ ctaHref: { type: "string" },
1649
+ videoUrl: { type: "string" },
1650
+ posterUrl: { type: "string" }
1651
+ }
1652
+ }
1653
+ }),
1654
+ "features-bento": manifest("features-bento", {
1655
+ category: "features",
1656
+ ai: {
1657
+ whenToUse: "Feature showcase in bento grid",
1658
+ whenNotToUse: "Simple pages",
1659
+ pairsWith: [],
1660
+ contentSlots: {
1661
+ headline: { type: "string" },
1662
+ subheadline: { type: "string" },
1663
+ items: { type: "array" }
1664
+ }
1665
+ }
1666
+ }),
1667
+ "pricing-table": manifest("pricing-table", {
1668
+ category: "pricing",
1669
+ conflicts: ["pricing-comparison"],
1670
+ ai: {
1671
+ whenToUse: "Pricing plans display",
1672
+ whenNotToUse: "Free products",
1673
+ pairsWith: [],
1674
+ contentSlots: {
1675
+ headline: { type: "string" },
1676
+ subheadline: { type: "string" },
1677
+ plans: { type: "array" }
1678
+ }
1679
+ }
1680
+ }),
1681
+ "faq-accordion": manifest("faq-accordion", {
1682
+ category: "faq",
1683
+ ai: {
1684
+ whenToUse: "Frequently asked questions",
1685
+ whenNotToUse: "Simple pages",
1686
+ pairsWith: [],
1687
+ contentSlots: {
1688
+ headline: { type: "string" },
1689
+ items: { type: "array" }
1690
+ }
1691
+ }
1692
+ }),
1693
+ "footer-rich": manifest("footer-rich", {
1694
+ category: "footer",
1695
+ conflicts: ["footer-minimal"],
1696
+ ai: {
1697
+ whenToUse: "Multi-column footer with links",
1698
+ whenNotToUse: "Minimal sites",
1699
+ pairsWith: [],
1700
+ contentSlots: {
1701
+ brand: { type: "string" },
1702
+ description: { type: "string" },
1703
+ columns: { type: "array" },
1704
+ copyright: { type: "string" }
1705
+ }
1706
+ }
1707
+ }),
1634
1708
  "testimonials-carousel": manifest("testimonials-carousel"),
1635
1709
  "contact-form": manifest("contact-form"),
1636
- "hero-split": manifest("hero-split"),
1637
- "header-transparent": manifest("header-transparent"),
1710
+ "hero-split": manifest("hero-split", {
1711
+ category: "hero",
1712
+ conflicts: ["hero-gradient", "hero-video"]
1713
+ }),
1714
+ "header-transparent": manifest("header-transparent", {
1715
+ category: "header",
1716
+ conflicts: ["header-sticky"]
1717
+ }),
1638
1718
  "cta-newsletter": manifest("cta-newsletter"),
1639
- "header-sticky": manifest("header-sticky")
1719
+ "header-sticky": manifest("header-sticky", {
1720
+ category: "header",
1721
+ conflicts: ["header-transparent"]
1722
+ })
1640
1723
  };
1641
1724
  var FIXTURE_BLOCK_SOURCES = {
1642
1725
  "hero-gradient": {
@@ -1769,11 +1852,31 @@ export function getStaticPaths() { return [{ params: { slug: '1' } }]; }
1769
1852
  <slot />`,
1770
1853
  "default-content.json": `{ "sidebarLinks": [], "logoutText": "" }`
1771
1854
  },
1772
- "hero-video": { "hero-video.astro": "<section>Hero Video</section>\n" },
1773
- "features-bento": { "features-bento.astro": "<section>Features Bento</section>\n" },
1774
- "pricing-table": { "pricing-table.astro": "<section>Pricing</section>\n" },
1775
- "faq-accordion": { "faq-accordion.astro": "<section>FAQ</section>\n" },
1776
- "footer-rich": { "footer-rich.astro": "<section>Footer</section>\n" },
1855
+ "hero-video": {
1856
+ "hero-video.astro": "<section>Hero Video</section>\n",
1857
+ "hero-video.css": ".hero-video { min-height: 80vh; }\n",
1858
+ "default-content.json": '{"headline":"Experience the Future","subheadline":"Immersive experiences.","ctaText":"Get Started","ctaHref":"#","videoUrl":"","posterUrl":""}'
1859
+ },
1860
+ "features-bento": {
1861
+ "features-bento.astro": "<section>Features Bento</section>\n",
1862
+ "features-bento.css": ".features-bento { padding: 4rem 2rem; }\n",
1863
+ "default-content.json": '{"headline":"Features","subheadline":"Everything you need.","items":[{"title":"Fast","description":"Lightning speed."}]}'
1864
+ },
1865
+ "pricing-table": {
1866
+ "pricing-table.astro": "<section>Pricing</section>\n",
1867
+ "pricing-table.css": ".pricing-table { padding: 4rem 2rem; }\n",
1868
+ "default-content.json": '{"headline":"Pricing","subheadline":"Simple pricing.","plans":[{"name":"Free","price":"$0","features":["Basic"]}]}'
1869
+ },
1870
+ "faq-accordion": {
1871
+ "faq-accordion.astro": "<section>FAQ</section>\n",
1872
+ "faq-accordion.css": ".faq-accordion { padding: 4rem 2rem; }\n",
1873
+ "default-content.json": '{"headline":"FAQ","items":[{"question":"How does it work?","answer":"It just works."}]}'
1874
+ },
1875
+ "footer-rich": {
1876
+ "footer-rich.astro": "<section>Footer</section>\n",
1877
+ "footer-rich.css": ".footer-rich { padding: 3rem 2rem; }\n",
1878
+ "default-content.json": '{"brand":"Acme","description":"Building the future.","columns":[],"copyright":"\xA9 2025"}'
1879
+ },
1777
1880
  "testimonials-carousel": { "testimonials-carousel.astro": "<section>Testimonials</section>\n" },
1778
1881
  "contact-form": { "contact-form.astro": "<section>Contact</section>\n" },
1779
1882
  "hero-split": { "hero-split.astro": "<section>Hero Split</section>\n" },
@@ -1898,10 +2001,13 @@ import {
1898
2001
  readFileSync as readFileSync3,
1899
2002
  readdirSync as readdirSync2,
1900
2003
  mkdirSync as mkdirSync2,
1901
- statSync as statSync2
2004
+ statSync as statSync2,
2005
+ writeFileSync as writeFileSync2,
2006
+ rmSync
1902
2007
  } from "fs";
1903
2008
  import { join as join3 } from "path";
1904
2009
  import { homedir as homedir2 } from "os";
2010
+ import { createRequire } from "module";
1905
2011
  var DEFAULT_CONFIG2 = {
1906
2012
  repo: "kamsqe/fornix",
1907
2013
  ref: "main",
@@ -1911,6 +2017,31 @@ var DEFAULT_CONFIG2 = {
1911
2017
  maxCacheAge: 24 * 60 * 60 * 1e3
1912
2018
  // 24 hours
1913
2019
  };
2020
+ var cacheVersionChecked = false;
2021
+ function ensureCacheVersion(cacheDir) {
2022
+ if (cacheVersionChecked) return;
2023
+ cacheVersionChecked = true;
2024
+ let cliVersion = "unknown";
2025
+ try {
2026
+ const require2 = createRequire(import.meta.url);
2027
+ const pkg = require2("../../package.json");
2028
+ cliVersion = pkg.version ?? "unknown";
2029
+ } catch {
2030
+ }
2031
+ const versionFile = join3(cacheDir, ".version");
2032
+ try {
2033
+ if (existsSync2(versionFile)) {
2034
+ const cached = readFileSync3(versionFile, "utf-8").trim();
2035
+ if (cached === cliVersion) return;
2036
+ }
2037
+ if (existsSync2(cacheDir)) {
2038
+ rmSync(cacheDir, { recursive: true, force: true });
2039
+ }
2040
+ mkdirSync2(cacheDir, { recursive: true });
2041
+ writeFileSync2(versionFile, cliVersion);
2042
+ } catch {
2043
+ }
2044
+ }
1914
2045
  async function fetchBlock(blockName, config = {}) {
1915
2046
  if (process.env.FORNIX_E2E_MOCK === "true") {
1916
2047
  const manifest2 = FIXTURE_MANIFESTS[blockName];
@@ -1925,6 +2056,7 @@ async function fetchBlock(blockName, config = {}) {
1925
2056
  return ok({ manifest: manifest2, files, fromCache: true });
1926
2057
  }
1927
2058
  const cfg = { ...DEFAULT_CONFIG2, ...config };
2059
+ ensureCacheVersion(cfg.cacheDir);
1928
2060
  const blockCacheDir = join3(cfg.cacheDir, blockName);
1929
2061
  if (!cfg.force && isCacheValid2(blockCacheDir, cfg.maxCacheAge)) {
1930
2062
  return loadFromCache2(blockName, blockCacheDir);
@@ -2038,7 +2170,7 @@ async function runManualFlow(input) {
2038
2170
  message: "Choose a render mode",
2039
2171
  options: [
2040
2172
  { value: "static", label: "Static (SSG)", hint: "Pre-built HTML, fastest" },
2041
- { value: "hybrid", label: "Hybrid", hint: "Static by default, opt into SSR per page" },
2173
+ { value: "hybrid", label: "Hybrid", hint: "Static + per-page SSR opt-in" },
2042
2174
  { value: "server", label: "Server (SSR)", hint: "Server-rendered on every request" }
2043
2175
  ]
2044
2176
  });
@@ -2206,7 +2338,7 @@ function buildSummary(config, blockNames, palette) {
2206
2338
 
2207
2339
  // src/scaffold/post-scaffold.ts
2208
2340
  import { execSync } from "child_process";
2209
- import { writeFileSync as writeFileSync2 } from "fs";
2341
+ import { writeFileSync as writeFileSync3 } from "fs";
2210
2342
  import { join as join4, basename } from "path";
2211
2343
  import pc2 from "picocolors";
2212
2344
  function runPostScaffold(input, callbacks) {
@@ -2256,7 +2388,7 @@ function runPostScaffold(input, callbacks) {
2256
2388
  log(pc2.dim(` ${config.packageManager} install`));
2257
2389
  }
2258
2390
  log(pc2.dim(` ${config.packageManager} dev`));
2259
- log(pc2.dim(` fornix add <block>`));
2391
+ log(pc2.dim(` npx create-fornix add <block>`));
2260
2392
  log("");
2261
2393
  }
2262
2394
  function installDependencies(projectDir, packageManager, verbose, log, warn) {
@@ -2349,9 +2481,9 @@ function generateClaudeMd(projectDir, config, blockNames) {
2349
2481
  lines.push("");
2350
2482
  lines.push(`- \`${config.packageManager} dev\` \u2014 start development server`);
2351
2483
  lines.push(`- \`${config.packageManager} build\` \u2014 build for production`);
2352
- lines.push("- `fornix add <block>` \u2014 add a new block");
2353
- lines.push("- `fornix remove <block>` \u2014 remove a block");
2354
- lines.push("- `fornix status` \u2014 show project configuration");
2484
+ lines.push("- `npx create-fornix add <block>` \u2014 add a new block");
2485
+ lines.push("- `npx create-fornix remove <block>` \u2014 remove a block");
2486
+ lines.push("- `npx create-fornix status` \u2014 show project configuration");
2355
2487
  lines.push("");
2356
2488
  lines.push("## File Structure");
2357
2489
  lines.push("");
@@ -2373,7 +2505,7 @@ function generateClaudeMd(projectDir, config, blockNames) {
2373
2505
  lines.push("```");
2374
2506
  lines.push("");
2375
2507
  const content = lines.join("\n");
2376
- writeFileSync2(join4(projectDir, "CLAUDE.md"), content, "utf-8");
2508
+ writeFileSync3(join4(projectDir, "CLAUDE.md"), content, "utf-8");
2377
2509
  }
2378
2510
 
2379
2511
  // src/ai/prompt-builder.ts
@@ -3800,8 +3932,9 @@ async function runFlagDrivenMode(args2, manifests, allPalettes) {
3800
3932
  async function runScaffold(config, manifests, allPalettes, dryRun, verbose, skipInstall, skipGit) {
3801
3933
  const spinner2 = p2.spinner();
3802
3934
  spinner2.start("Fetching blocks from registry...");
3803
- const blockNames = config.blocks.map((b) => b.name);
3804
- const blockResults = await fetchBlocks(blockNames);
3935
+ const selectedBlockNames = config.blocks.map((b) => b.name);
3936
+ const allBlockNames = preResolveDependencies(selectedBlockNames, manifests);
3937
+ const blockResults = await fetchBlocks(allBlockNames);
3805
3938
  const blockSources = {};
3806
3939
  const blockDefaultContent = {};
3807
3940
  for (const result2 of blockResults) {
@@ -3863,7 +3996,7 @@ async function runScaffold(config, manifests, allPalettes, dryRun, verbose, skip
3863
3996
  const fullPath = join6(config.projectDir, relativePath);
3864
3997
  const parentDir = join6(fullPath, "..");
3865
3998
  mkdirSync4(parentDir, { recursive: true });
3866
- writeFileSync3(fullPath, content, "utf-8");
3999
+ writeFileSync4(fullPath, content, "utf-8");
3867
4000
  filesWritten++;
3868
4001
  if (verbose) {
3869
4002
  console.log(pc3.dim(` created ${relativePath}`));
@@ -3948,12 +4081,79 @@ function showNoProviderGuide() {
3948
4081
  console.error(" export CLOUDFLARE_ACCOUNT_ID=... CLOUDFLARE_API_TOKEN=...\n");
3949
4082
  console.error(pc3.dim(" Or use manual mode: npx create-fornix --manual\n"));
3950
4083
  }
4084
+ function preResolveDependencies(selected, manifests) {
4085
+ const result = /* @__PURE__ */ new Set();
4086
+ function walk(name) {
4087
+ if (result.has(name)) return;
4088
+ const manifest2 = manifests[name];
4089
+ if (!manifest2) {
4090
+ result.add(name);
4091
+ return;
4092
+ }
4093
+ for (const dep of manifest2.requires) {
4094
+ walk(dep);
4095
+ }
4096
+ result.add(name);
4097
+ }
4098
+ for (const name of selected) {
4099
+ walk(name);
4100
+ }
4101
+ return [...result];
4102
+ }
3951
4103
 
3952
4104
  // src/cli/commands/add.ts
3953
4105
  import { defineCommand as defineCommand2 } from "citty";
3954
4106
  import pc4 from "picocolors";
3955
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync5 } from "fs";
4107
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync4, mkdirSync as mkdirSync5 } from "fs";
3956
4108
  import { join as join7, dirname as dirname3 } from "path";
4109
+
4110
+ // src/scaffold/page-updater.ts
4111
+ function blockNameToComponentName(blockName) {
4112
+ return blockName.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
4113
+ }
4114
+ function blockNameToImportPath(blockName) {
4115
+ return `../components/sections/${blockName}.astro`;
4116
+ }
4117
+ function addBlockToPage(pageContent, blockName) {
4118
+ const componentName = blockNameToComponentName(blockName);
4119
+ const importPath = blockNameToImportPath(blockName);
4120
+ if (pageContent.includes(importPath) || pageContent.includes(`import ${componentName}`)) {
4121
+ return pageContent;
4122
+ }
4123
+ const importLine = `import ${componentName} from '${importPath}';`;
4124
+ const componentTag = ` <${componentName} />`;
4125
+ const frontmatterEnd = pageContent.indexOf("---", pageContent.indexOf("---") + 3);
4126
+ if (frontmatterEnd === -1) {
4127
+ return pageContent;
4128
+ }
4129
+ let updated = pageContent.slice(0, frontmatterEnd) + importLine + "\n" + pageContent.slice(frontmatterEnd);
4130
+ const mainCloseIndex = updated.lastIndexOf("</main>");
4131
+ if (mainCloseIndex !== -1) {
4132
+ updated = updated.slice(0, mainCloseIndex) + componentTag + "\n " + updated.slice(mainCloseIndex);
4133
+ }
4134
+ return updated;
4135
+ }
4136
+ function removeBlockFromPage(pageContent, blockName) {
4137
+ const componentName = blockNameToComponentName(blockName);
4138
+ const importPath = blockNameToImportPath(blockName);
4139
+ let updated = pageContent;
4140
+ const importRegex = new RegExp(
4141
+ `^\\s*import\\s+${componentName}\\s+from\\s+['"]${escapeRegex(importPath)}['"];?\\s*\\n?`,
4142
+ "m"
4143
+ );
4144
+ updated = updated.replace(importRegex, "");
4145
+ const tagRegex = new RegExp(
4146
+ `^\\s*<${componentName}\\s*/?>\\s*\\n?`,
4147
+ "m"
4148
+ );
4149
+ updated = updated.replace(tagRegex, "");
4150
+ return updated;
4151
+ }
4152
+ function escapeRegex(str) {
4153
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4154
+ }
4155
+
4156
+ // src/cli/commands/add.ts
3957
4157
  var addCommand = defineCommand2({
3958
4158
  meta: {
3959
4159
  name: "add",
@@ -4079,7 +4279,7 @@ var addCommand = defineCommand2({
4079
4279
  }
4080
4280
  for (const file of filesToWrite) {
4081
4281
  mkdirSync5(dirname3(file.path), { recursive: true });
4082
- writeFileSync4(file.path, file.content);
4282
+ writeFileSync5(file.path, file.content);
4083
4283
  if (typedArgs.verbose) {
4084
4284
  console.log(` ${pc4.dim("\u2192")} ${file.path}`);
4085
4285
  }
@@ -4095,7 +4295,21 @@ var addCommand = defineCommand2({
4095
4295
  installedAt: now
4096
4296
  });
4097
4297
  }
4098
- writeFileSync4(manifestPath, JSON.stringify(manifest2, null, 2) + "\n");
4298
+ writeFileSync5(manifestPath, JSON.stringify(manifest2, null, 2) + "\n");
4299
+ const indexPath = join7(cwd, "src/pages/index.astro");
4300
+ if (existsSync4(indexPath)) {
4301
+ let pageContent = readFileSync5(indexPath, "utf-8");
4302
+ for (const name of blocksToAdd) {
4303
+ const bManifest = manifests[name];
4304
+ if (bManifest && bManifest.type === "section") {
4305
+ pageContent = addBlockToPage(pageContent, name);
4306
+ }
4307
+ }
4308
+ writeFileSync5(indexPath, pageContent);
4309
+ if (typedArgs.verbose) {
4310
+ console.log(` ${pc4.dim("\u270E")} updated index.astro`);
4311
+ }
4312
+ }
4099
4313
  console.log();
4100
4314
  for (const name of blocksToAdd) {
4101
4315
  const isDep = name !== blockName;
@@ -4136,7 +4350,7 @@ function resolveDependencies2(blockName, installedNames, manifests) {
4136
4350
  // src/cli/commands/remove.ts
4137
4351
  import { defineCommand as defineCommand3 } from "citty";
4138
4352
  import pc5 from "picocolors";
4139
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync5, unlinkSync, readdirSync as readdirSync3, rmdirSync } from "fs";
4353
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync5, unlinkSync, readdirSync as readdirSync3, rmdirSync } from "fs";
4140
4354
  import { join as join8, dirname as dirname4 } from "path";
4141
4355
  var removeCommand = defineCommand3({
4142
4356
  meta: {
@@ -4231,7 +4445,18 @@ var removeCommand = defineCommand3({
4231
4445
  tryRemoveEmptyDir(dirname4(filePath), cwd);
4232
4446
  }
4233
4447
  manifest2.blocks = manifest2.blocks.filter((b) => b.name !== blockName);
4234
- writeFileSync5(manifestPath, JSON.stringify(manifest2, null, 2) + "\n");
4448
+ writeFileSync6(manifestPath, JSON.stringify(manifest2, null, 2) + "\n");
4449
+ const indexPath = join8(cwd, "src/pages/index.astro");
4450
+ if (existsSync5(indexPath)) {
4451
+ const original = readFileSync6(indexPath, "utf-8");
4452
+ const updated = removeBlockFromPage(original, blockName);
4453
+ if (updated !== original) {
4454
+ writeFileSync6(indexPath, updated);
4455
+ if (typedArgs.verbose) {
4456
+ console.log(` ${pc5.dim("\u270E")} updated index.astro`);
4457
+ }
4458
+ }
4459
+ }
4235
4460
  console.log();
4236
4461
  console.log(` ${pc5.red("-")} ${pc5.bold(blockName)} removed`);
4237
4462
  if (dependents.length > 0) {
@@ -4713,7 +4938,7 @@ async function listBlocksHandler(args2) {
4713
4938
  }
4714
4939
 
4715
4940
  // src/mcp/tools/add-block.ts
4716
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync6 } from "fs";
4941
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, existsSync as existsSync8, mkdirSync as mkdirSync6 } from "fs";
4717
4942
  import { join as join11, dirname as dirname5 } from "path";
4718
4943
  async function addBlock2(input) {
4719
4944
  const { name, variant = "default", projectDirectory } = input;
@@ -4779,7 +5004,7 @@ async function addBlock2(input) {
4779
5004
  }
4780
5005
  const filePath = join11(projectDirectory, file.destination);
4781
5006
  mkdirSync6(dirname5(filePath), { recursive: true });
4782
- writeFileSync6(filePath, content);
5007
+ writeFileSync7(filePath, content);
4783
5008
  filesCreated++;
4784
5009
  }
4785
5010
  }
@@ -4794,7 +5019,7 @@ async function addBlock2(input) {
4794
5019
  installedAt: now
4795
5020
  });
4796
5021
  }
4797
- writeFileSync6(manifestPath, JSON.stringify(manifest2, null, 2) + "\n");
5022
+ writeFileSync7(manifestPath, JSON.stringify(manifest2, null, 2) + "\n");
4798
5023
  return ok({ addedBlocks: blocksToAdd, filesCreated });
4799
5024
  }
4800
5025
  function resolveDependencies3(blockName, installedNames, manifests) {
@@ -4817,7 +5042,7 @@ function resolveDependencies3(blockName, installedNames, manifests) {
4817
5042
  // src/mcp/tools/remove-block.ts
4818
5043
  import {
4819
5044
  readFileSync as readFileSync10,
4820
- writeFileSync as writeFileSync7,
5045
+ writeFileSync as writeFileSync8,
4821
5046
  existsSync as existsSync9,
4822
5047
  unlinkSync as unlinkSync2,
4823
5048
  readdirSync as readdirSync4,
@@ -4871,7 +5096,7 @@ async function removeBlock(input) {
4871
5096
  tryRemoveEmptyDirectory(dirname6(filePath), projectDirectory);
4872
5097
  }
4873
5098
  manifest2.blocks = manifest2.blocks.filter((block) => block.name !== name);
4874
- writeFileSync7(manifestPath, JSON.stringify(manifest2, null, 2) + "\n");
5099
+ writeFileSync8(manifestPath, JSON.stringify(manifest2, null, 2) + "\n");
4875
5100
  return ok({
4876
5101
  removedBlock: name,
4877
5102
  filesRemoved: filesToRemove.length,
@@ -4963,7 +5188,7 @@ async function validateContent(input) {
4963
5188
  }
4964
5189
  const schemaShape = {};
4965
5190
  for (const [slotName, slot] of Object.entries(contentSlots)) {
4966
- schemaShape[slotName] = zodTypeForSlot2(slot.type);
5191
+ schemaShape[slotName] = zodTypeForSlot(slot.type);
4967
5192
  }
4968
5193
  const schema = z5.object(schemaShape);
4969
5194
  const parseResult = schema.safeParse(data);
@@ -4975,7 +5200,7 @@ async function validateContent(input) {
4975
5200
  );
4976
5201
  return ok({ valid: false, errors });
4977
5202
  }
4978
- function zodTypeForSlot2(slotType) {
5203
+ function zodTypeForSlot(slotType) {
4979
5204
  switch (slotType) {
4980
5205
  case "string":
4981
5206
  return z5.string();
@@ -5025,7 +5250,7 @@ function getProjectStatus(input) {
5025
5250
  }
5026
5251
 
5027
5252
  // src/mcp/tools/scaffold-project.ts
5028
- import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
5253
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync9 } from "fs";
5029
5254
  import { join as join14, basename as basename3 } from "path";
5030
5255
  var DEFAULT_COLORS2 = {
5031
5256
  primary: "#6366f1",
@@ -5100,7 +5325,7 @@ async function scaffoldProject(input) {
5100
5325
  const fullPath = join14(projectDirectory, relativePath);
5101
5326
  const parentDirectory = join14(fullPath, "..");
5102
5327
  mkdirSync7(parentDirectory, { recursive: true });
5103
- writeFileSync8(fullPath, content, "utf-8");
5328
+ writeFileSync9(fullPath, content, "utf-8");
5104
5329
  filesCreated++;
5105
5330
  }
5106
5331
  return ok({