create-lacspace-app 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -120,6 +120,41 @@ npx create-lacspace-app my-app --theme "#0bb9d9,#7c3aed" # an exact fr
120
120
 
121
121
  **Presets:** `lacspace` · `violet` · `indigo` · `blue` · `cyan` · `sky` · `teal` · `emerald` · `green` · `lime` · `amber` · `orange` · `red` · `rose` · `pink` · `fuchsia` · `purple` · `slate` · `sunset` · `ocean` · `forest` · `aurora` · `gold`. Give a single hex and the CLI derives a matching second stop for you.
122
122
 
123
+ ## 📦 Use it as a library
124
+
125
+ The same generator is exported as a programmatic API, so you can scaffold Lacspace projects from your own code — build tools, custom CLIs, CI jobs, tests, a playground, or a server-side **"download as ZIP"** button. It ships dual **ESM + CJS** with full TypeScript types.
126
+
127
+ ```ts
128
+ import {
129
+ generateProject, // pure: returns an in-memory file map — no disk I/O
130
+ scaffold, // Node: writes a project to disk
131
+ listTemplates, // the 9 templates + their metadata
132
+ listSections, // the 15 prebuilt sections
133
+ } from "create-lacspace-app";
134
+
135
+ // 1. Pure — get the whole project as { "path": "contents" }. Runs anywhere.
136
+ const files = generateProject({ name: "acme", template: "saas", theme: "#ff6a00" });
137
+ files["app/page.tsx"]; // → the generated home page source
138
+ Object.keys(files).length; // → ~70 files
139
+
140
+ // 2. Write it to disk (Node).
141
+ const { dir, files: written } = await scaffold({ name: "acme", template: "saas" });
142
+ console.log(`Scaffolded ${written.length} files → ${dir}`);
143
+ ```
144
+
145
+ `generateProject` is **pure** (no I/O), so it's perfect for previews, snapshot tests, zipping in a server route, or post-processing files before you write them. `scaffold` is the thin Node wrapper that writes the map to disk.
146
+
147
+ | Export | What it does |
148
+ | --- | --- |
149
+ | `generateProject(options)` | Returns the project as an in-memory `{ path: contents }` map. Pure, isomorphic. |
150
+ | `scaffold(options)` | Generates **and writes** the project to disk (`options.dir`, or `<cwd>/<name>`). Node only. |
151
+ | `listTemplates()` / `templates` | The built-in templates with metadata (`key`, `label`, `description`, `accent`, defaults). |
152
+ | `getTemplate(key)` | One template's metadata, or `undefined`. |
153
+ | `listSections()` / `getSection(name)` | The prebuilt section names, and one section's source. |
154
+ | `templateKeys` | Every valid `template` key. |
155
+
156
+ `options`: `{ name?, template?, theme? }` — the same choices as the CLI flags above.
157
+
123
158
  ## Licensing
124
159
 
125
160
  Free under the **[Lacspace Free Licence](https://lacspace.com/licenses/lacspace-free-1.0)** — permissive freedoms. Use it in personal and commercial projects at no cost; the apps you generate are entirely yours.
package/dist/index.js CHANGED
@@ -2,8 +2,9 @@
2
2
  import { existsSync, readdirSync, mkdirSync, writeFileSync } from 'fs';
3
3
  import { resolve, basename, join, dirname } from 'path';
4
4
  import { createInterface } from 'readline/promises';
5
- import { stdout, exit, argv, stdin, cwd } from 'process';
5
+ import { argv, stdout, exit, stdin, cwd } from 'process';
6
6
  import { spawnSync } from 'child_process';
7
+ import { pathToFileURL } from 'url';
7
8
 
8
9
  var C = {
9
10
  reset: "\x1B[0m",
@@ -27,6 +28,15 @@ var TEMPLATES = [
27
28
  { key: "restaurant", label: "Restaurant / cafe", description: "A warm restaurant home with menu highlights and reservations.", accent: ["#e11d48", "#f59e0b"], siteName: "LSResto", siteDescription: "Seasonal plates, natural wine and a warm room. Book a table." },
28
29
  { key: "marketplace", label: "Marketplace / commerce", description: "A real storefront wired to the Lacspace commerce packages \u2014 cart, checkout, tax, shipping, orders, invoices and Nepal payments.", accent: ["#0d9488", "#6366f1"], siteName: "LSBazaar", siteDescription: "A modern storefront \u2014 cart to checkout, wired end to end." }
29
30
  ];
31
+ function resolveContext(options = {}) {
32
+ const base = TEMPLATES.find((t) => t.key === options.template) ?? TEMPLATES[0];
33
+ const accent = resolveAccent(options.theme);
34
+ const template = accent ? { ...base, accent } : base;
35
+ const raw = options.name ?? "my-app";
36
+ const seg = raw.split(/[\\/]/).filter(Boolean).pop() ?? "my-app";
37
+ const name = seg.toLowerCase().replace(/[^a-z0-9-_]/g, "-").replace(/^-+|-+$/g, "") || "my-app";
38
+ return { name, template };
39
+ }
30
40
  var pkgJson = (ctx) => JSON.stringify({
31
41
  name: ctx.name,
32
42
  version: "0.1.0",
@@ -555,7 +565,8 @@ function homePage(ctx) {
555
565
  restaurant: { t: "Join us for dinner", s: "We fill up fast on weekends \u2014 book your table ahead.", l: "Reserve a table", h: "/reservations" }
556
566
  };
557
567
  const cc = ctaCopy[ctx.template.key] ?? ctaCopy.business;
558
- return `import { site } from "@/lib/site";
568
+ return `import Link from "next/link";
569
+ import { site } from "@/lib/site";
559
570
  import { LiveStats } from "@/components/live-stats";
560
571
  import { Aurora } from "@/components/aurora";
561
572
  import { HeroArt } from "@/components/hero-art";
@@ -588,13 +599,13 @@ export default function Home() {
588
599
  <p className="lead mx-auto mt-6 max-w-xl">${ctx.template.siteDescription}</p>
589
600
  <div className="mt-10 flex flex-wrap justify-center gap-3">
590
601
  <a href="#work" className="shimmer rounded-full gradient-bg px-7 py-3.5 font-semibold on-accent transition hover:-translate-y-0.5">View my work</a>
591
- <a href="/contact" className="rounded-full border border-hairline px-7 py-3.5 font-semibold transition hover:bg-surface">Get in touch</a>
602
+ <Link href="/contact" className="rounded-full border border-hairline px-7 py-3.5 font-semibold transition hover:bg-surface">Get in touch</Link>
592
603
  </div>
593
604
  </section>
594
605
  <section id="work" className="mx-auto max-w-6xl px-6 py-20">
595
606
  <div className="mb-12 flex items-end justify-between">
596
607
  <div><p className="text-sm font-semibold uppercase tracking-widest gradient-text">Selected work</p><h2 className="mt-3 text-3xl font-bold sm:text-4xl">Things I've shipped</h2></div>
597
- <a href="/work" className="hidden text-sm text-muted underline-offset-4 transition hover:text-fg hover:underline sm:block">All projects \u2192</a>
608
+ <Link href="/work" className="hidden text-sm text-muted underline-offset-4 transition hover:text-fg hover:underline sm:block">All projects \u2192</Link>
598
609
  </div>
599
610
  <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
600
611
  {[
@@ -605,12 +616,12 @@ export default function Home() {
605
616
  { t: "Cadence Marketing", d: "A brand and site refresh that doubled qualified inbound leads.", tag: "Brand", e: "\u2728" },
606
617
  { t: "Meadow Docs", d: "A fast, searchable docs platform teams actually enjoy reading.", tag: "Web", e: "\u{1F4DA}" },
607
618
  ].map((p) => (
608
- <a key={p.t} href="/work" className="card group flex flex-col p-6">
619
+ <Link key={p.t} href="/work" className="card group flex flex-col p-6">
609
620
  <div className="mb-5 flex aspect-[16/10] items-center justify-center overflow-hidden rounded-xl gradient-bg text-5xl on-accent">{p.e}</div>
610
621
  <span className="text-xs font-semibold uppercase tracking-widest gradient-text">{p.tag}</span>
611
622
  <h3 className="mt-1.5 font-semibold tracking-tight">{p.t}</h3>
612
623
  <p className="mt-1.5 text-sm leading-relaxed text-muted">{p.d}</p>
613
- </a>
624
+ </Link>
614
625
  ))}
615
626
  </div>
616
627
  </section>`);
@@ -621,8 +632,8 @@ export default function Home() {
621
632
  <h1 className="text-display mt-7">We build products <span className="gradient-text">that grow businesses</span></h1>
622
633
  <p className="lead mx-auto mt-6 max-w-2xl">${ctx.template.siteDescription}</p>
623
634
  <div className="mt-10 flex flex-wrap justify-center gap-3">
624
- <a href="/contact" className="shimmer rounded-full gradient-bg px-7 py-3.5 font-semibold on-accent transition hover:-translate-y-0.5">Start a project</a>
625
- <a href="/work" className="rounded-full border border-hairline px-7 py-3.5 font-semibold transition hover:bg-surface">See our work</a>
635
+ <Link href="/contact" className="shimmer rounded-full gradient-bg px-7 py-3.5 font-semibold on-accent transition hover:-translate-y-0.5">Start a project</Link>
636
+ <Link href="/work" className="rounded-full border border-hairline px-7 py-3.5 font-semibold transition hover:bg-surface">See our work</Link>
626
637
  </div>
627
638
  </section>
628
639
  <section id="services" className="mx-auto max-w-6xl px-6 py-20">
@@ -648,14 +659,14 @@ export default function Home() {
648
659
  <h1 className="text-display mt-7"><span className="gradient-text">${n}</span></h1>
649
660
  <p className="lead mx-auto mt-6 max-w-xl">${ctx.template.siteDescription}</p>
650
661
  <div className="mt-10 flex flex-wrap justify-center gap-3">
651
- <a href="/shop" className="shimmer rounded-full gradient-bg px-8 py-3.5 font-semibold on-accent transition hover:-translate-y-0.5">Shop the collection</a>
652
- <a href="/collections" className="rounded-full border border-hairline px-8 py-3.5 font-semibold transition hover:bg-surface">Browse collections</a>
662
+ <Link href="/shop" className="shimmer rounded-full gradient-bg px-8 py-3.5 font-semibold on-accent transition hover:-translate-y-0.5">Shop the collection</Link>
663
+ <Link href="/collections" className="rounded-full border border-hairline px-8 py-3.5 font-semibold transition hover:bg-surface">Browse collections</Link>
653
664
  </div>
654
665
  </section>
655
666
  <section id="shop" className="mx-auto max-w-6xl px-6 py-20">
656
667
  <div className="mb-12 flex items-end justify-between">
657
668
  <div><p className="text-sm font-semibold uppercase tracking-widest gradient-text">Featured</p><h2 className="mt-3 text-3xl font-bold sm:text-4xl">This week's edit</h2></div>
658
- <a href="/shop" className="hidden text-sm text-muted underline-offset-4 transition hover:text-fg hover:underline sm:block">Shop all \u2192</a>
669
+ <Link href="/shop" className="hidden text-sm text-muted underline-offset-4 transition hover:text-fg hover:underline sm:block">Shop all \u2192</Link>
659
670
  </div>
660
671
  <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
661
672
  {[
@@ -663,12 +674,12 @@ export default function Home() {
663
674
  { n: "Linen Waffle Throw", p: "$65", e: "\u{1F9FA}" }, { n: "Oak Monitor Stand", p: "$120", e: "\u{1FAB5}" },
664
675
  ].map((prod) => (
665
676
  <div key={prod.n} className="card group flex flex-col p-4">
666
- <a href="/shop" className="mb-4 flex aspect-square items-center justify-center overflow-hidden rounded-xl gradient-bg text-6xl on-accent transition group-hover:scale-[1.03]">{prod.e}</a>
677
+ <Link href="/shop" className="mb-4 flex aspect-square items-center justify-center overflow-hidden rounded-xl gradient-bg text-6xl on-accent transition group-hover:scale-[1.03]">{prod.e}</Link>
667
678
  <div className="flex items-start justify-between gap-2">
668
679
  <h3 className="font-semibold tracking-tight">{prod.n}</h3>
669
680
  <span className="shrink-0 font-semibold text-muted">{prod.p}</span>
670
681
  </div>
671
- <a href="/shop" className="mt-4 block w-full rounded-full gradient-bg py-2.5 text-center text-sm font-semibold on-accent transition hover:-translate-y-0.5">Add to cart</a>
682
+ <Link href="/shop" className="mt-4 block w-full rounded-full gradient-bg py-2.5 text-center text-sm font-semibold on-accent transition hover:-translate-y-0.5">Add to cart</Link>
672
683
  </div>
673
684
  ))}
674
685
  </div>
@@ -679,10 +690,10 @@ export default function Home() {
679
690
  <p className="text-sm font-semibold uppercase tracking-widest gradient-text">The Journal</p>
680
691
  <h1 className="text-display mt-4"><span className="gradient-text">${n}</span></h1>
681
692
  <p className="lead mt-6 max-w-2xl">${ctx.template.siteDescription}</p>
682
- <a href="/blog" className="mt-8 inline-flex items-center gap-2 text-sm font-semibold text-fg underline-offset-4 hover:underline">Read the archive \u2192</a>
693
+ <Link href="/blog" className="mt-8 inline-flex items-center gap-2 text-sm font-semibold text-fg underline-offset-4 hover:underline">Read the archive \u2192</Link>
683
694
  </section>
684
695
  <section id="latest" className="mx-auto max-w-5xl px-6 pb-8">
685
- <a href="/blog" className="card group grid gap-6 overflow-hidden p-6 sm:grid-cols-[1.2fr_1fr] sm:p-8">
696
+ <Link href="/blog" className="card group grid gap-6 overflow-hidden p-6 sm:grid-cols-[1.2fr_1fr] sm:p-8">
686
697
  <div className="flex aspect-[16/10] items-center justify-center overflow-hidden rounded-2xl gradient-bg text-6xl on-accent transition group-hover:scale-[1.02]">\u{1F4EE}</div>
687
698
  <div className="flex flex-col justify-center">
688
699
  <span className="text-xs font-semibold uppercase tracking-widest gradient-text">Featured</span>
@@ -690,7 +701,7 @@ export default function Home() {
690
701
  <p className="mt-3 leading-relaxed text-muted">Most teams polish the wrong things. Here's the single unglamorous check that separates a launch that lands from one that limps.</p>
691
702
  <span className="mt-5 text-sm text-faint">8 min read \xB7 Product</span>
692
703
  </div>
693
- </a>
704
+ </Link>
694
705
  </section>
695
706
  <section className="mx-auto max-w-5xl px-6 py-16">
696
707
  <div className="grid gap-8 sm:grid-cols-2">
@@ -700,12 +711,12 @@ export default function Home() {
700
711
  { t: "Building in the open, one year in", d: "What we learned shipping every week where anyone could watch.", tag: "Culture", e: "\u{1F331}" },
701
712
  { t: "Design systems for teams of one", d: "You don't need a committee to move fast and stay consistent.", tag: "Design", e: "\u{1F3A8}" },
702
713
  ].map((p) => (
703
- <a key={p.t} href="/blog" className="group">
714
+ <Link key={p.t} href="/blog" className="group">
704
715
  <div className="mb-4 flex aspect-[16/9] items-center justify-center overflow-hidden rounded-2xl gradient-bg text-5xl on-accent transition group-hover:scale-[1.02]">{p.e}</div>
705
716
  <span className="text-xs font-semibold uppercase tracking-widest gradient-text">{p.tag}</span>
706
717
  <h3 className="mt-1.5 text-xl font-semibold tracking-tight transition group-hover:text-[color:var(--accent-to)]">{p.t}</h3>
707
718
  <p className="mt-1.5 text-sm leading-relaxed text-muted">{p.d}</p>
708
- </a>
719
+ </Link>
709
720
  ))}
710
721
  </div>
711
722
  </section>`);
@@ -717,7 +728,7 @@ export default function Home() {
717
728
  <h1 className="text-display mt-7">Build with <span className="gradient-text">${n}</span></h1>
718
729
  <p className="lead mx-auto mt-6 max-w-2xl">Guides, API references and copy-paste examples \u2014 everything you need, in one fast, searchable place.</p>
719
730
  <div className="mt-9 flex flex-wrap items-center justify-center gap-3">
720
- <a href="/docs" className="shimmer rounded-full gradient-bg px-7 py-3.5 font-semibold on-accent transition hover:-translate-y-0.5">Read the docs</a>
731
+ <Link href="/docs" className="shimmer rounded-full gradient-bg px-7 py-3.5 font-semibold on-accent transition hover:-translate-y-0.5">Read the docs</Link>
721
732
  <div className="glass inline-flex items-center gap-3 rounded-full px-5 py-3 font-mono text-sm text-muted"><span className="gradient-text font-bold">$</span> npm i ${pkg}</div>
722
733
  </div>
723
734
  </section>
@@ -728,11 +739,11 @@ export default function Home() {
728
739
  { t: "Guides", d: "Task-focused walkthroughs for the paths you'll actually take.", e: "\u{1F9ED}", href: "/guides" },
729
740
  { t: "API reference", d: "Every endpoint, fully typed, with copy-paste examples in each language.", e: "\u{1F50C}", href: "/api-reference" },
730
741
  ].map((c) => (
731
- <a key={c.t} href={c.href} className="card group p-8">
742
+ <Link key={c.t} href={c.href} className="card group p-8">
732
743
  <div className="mb-5 inline-flex h-12 w-12 items-center justify-center rounded-2xl gradient-bg text-2xl accent-glow">{c.e}</div>
733
744
  <h3 className="text-xl font-bold tracking-tight">{c.t} <span className="inline-block transition group-hover:translate-x-1">\u2192</span></h3>
734
745
  <p className="mt-2 leading-relaxed text-muted">{c.d}</p>
735
- </a>
746
+ </Link>
736
747
  ))}
737
748
  </div>
738
749
  </section>`);
@@ -743,8 +754,8 @@ export default function Home() {
743
754
  <h1 className="text-display mt-5"><span className="gradient-text">${n}</span></h1>
744
755
  <p className="lead mx-auto mt-6 max-w-xl">${ctx.template.siteDescription}</p>
745
756
  <div className="mt-10 flex flex-wrap justify-center gap-3">
746
- <a href="/reservations" className="shimmer rounded-full gradient-bg px-8 py-3.5 font-semibold on-accent transition hover:-translate-y-0.5">Reserve a table</a>
747
- <a href="/menu" className="rounded-full border border-hairline px-8 py-3.5 font-semibold transition hover:bg-surface">View the menu</a>
757
+ <Link href="/reservations" className="shimmer rounded-full gradient-bg px-8 py-3.5 font-semibold on-accent transition hover:-translate-y-0.5">Reserve a table</Link>
758
+ <Link href="/menu" className="rounded-full border border-hairline px-8 py-3.5 font-semibold transition hover:bg-surface">View the menu</Link>
748
759
  </div>
749
760
  </section>
750
761
  <section id="menu" className="mx-auto max-w-4xl px-6 py-20">
@@ -770,8 +781,8 @@ export default function Home() {
770
781
  <h1 className="text-display mt-7">Ship faster with <span className="gradient-text">${n}</span></h1>
771
782
  <p className="lead mx-auto mt-6 max-w-2xl">${ctx.template.siteDescription}</p>
772
783
  <div className="mt-10 flex flex-wrap justify-center gap-3">
773
- <a href="/pricing" className="shimmer rounded-full gradient-bg px-8 py-3.5 font-semibold on-accent transition hover:-translate-y-0.5">Start free</a>
774
- <a href="/features" className="rounded-full border border-hairline px-8 py-3.5 font-semibold transition hover:bg-surface">See features</a>
784
+ <Link href="/pricing" className="shimmer rounded-full gradient-bg px-8 py-3.5 font-semibold on-accent transition hover:-translate-y-0.5">Start free</Link>
785
+ <Link href="/features" className="rounded-full border border-hairline px-8 py-3.5 font-semibold transition hover:bg-surface">See features</Link>
775
786
  </div>
776
787
  <p className="mt-5 text-sm text-faint">No credit card \xB7 Free forever plan \xB7 SOC 2-ready</p>
777
788
  </section>
@@ -1015,7 +1026,7 @@ var careersPage = (ctx) => {
1015
1026
  ].map((j) => (
1016
1027
  <div key={j.role} className="flex flex-wrap items-center justify-between gap-3 rounded-2xl border border-hairline bg-surface p-5">
1017
1028
  <div><h3 className="font-semibold">{j.role}</h3><p className="text-sm text-muted">{j.team}</p></div>
1018
- <div className="flex items-center gap-3"><Badge>{j.type}</Badge><a href="/contact" className="rounded-full gradient-bg px-4 py-2 text-sm font-semibold on-accent">Apply</a></div>
1029
+ <div className="flex items-center gap-3"><Badge>{j.type}</Badge><Link href="/contact" className="rounded-full gradient-bg px-4 py-2 text-sm font-semibold on-accent">Apply</Link></div>
1019
1030
  </div>
1020
1031
  ))}
1021
1032
  </div>
@@ -1112,7 +1123,7 @@ var reservationsPage = (ctx) => {
1112
1123
  <div className="mx-auto max-w-2xl rounded-3xl border border-hairline bg-surface p-8 text-center">
1113
1124
  <h2 className="text-2xl font-bold">Book by phone or online</h2>
1114
1125
  <p className="mt-3 text-muted">Call us on <span className="font-semibold text-fg">+1 (555) 012-3456</span> or reserve online in seconds.</p>
1115
- <a href="/contact" className="mt-6 inline-block rounded-full gradient-bg px-8 py-3 font-semibold on-accent">Reserve a table</a>
1126
+ <Link href="/contact" className="mt-6 inline-block rounded-full gradient-bg px-8 py-3 font-semibold on-accent">Reserve a table</Link>
1116
1127
  </div>
1117
1128
  </Section>`,
1118
1129
  cta: { title: "Planning something special?", subtitle: "Ask us about private dining and set menus.", label: "Enquire now", href: "/contact" }
@@ -5067,10 +5078,15 @@ ${c("bold", "Done! Next steps")}
5067
5078
 
5068
5079
  `);
5069
5080
  }
5070
- main().catch((err) => {
5071
- stdout.write(c("red", `
5081
+ var invokedAsCli = argv[1] ? import.meta.url === pathToFileURL(argv[1]).href : false;
5082
+ if (invokedAsCli) {
5083
+ main().catch((err) => {
5084
+ stdout.write(c("red", `
5072
5085
  \u2717 ${err instanceof Error ? err.message : String(err)}
5073
5086
 
5074
5087
  `));
5075
- exit(1);
5076
- });
5088
+ exit(1);
5089
+ });
5090
+ }
5091
+
5092
+ export { SECTIONS, TEMPLATES, buildFiles, resolveContext };