@calo-design/cli 0.4.3 → 0.4.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/bin/cli.js CHANGED
@@ -408,6 +408,44 @@ export default function Index() {
408
408
  `);
409
409
  }
410
410
 
411
+ // The component gallery's routes: a root layout (fonts + providers, no local imports so it's
412
+ // portable) and the @calo/design-system showroom (bundled with the CLI) as the index route.
413
+ // Always (re)written so a `calo-design update` + relaunch also ships the latest showroom.
414
+ function writeGalleryApp(projectRoot) {
415
+ const srcDir = path.join(projectRoot, "src");
416
+ const appDir = path.join(srcDir, "app");
417
+ fs.mkdirSync(appDir, { recursive: true });
418
+ // Silences known non-actionable RNW-on-React-19 dev warnings (nested Pressables, deprecated
419
+ // style props) so the embedded gallery has a clean overlay. Imported first in _layout.
420
+ fs.cpSync(path.join(__dirname, "templates", "gallery-suppress.ts"), path.join(srcDir, "suppress-dev-warnings.ts"));
421
+ fs.writeFileSync(path.join(appDir, "_layout.tsx"), `import "../suppress-dev-warnings";
422
+ import { Stack } from "expo-router";
423
+ import { useFonts } from "expo-font";
424
+ import { CaloBottomSheetProvider, CaloScreen, caloFonts, caloTokens } from "@calo/design-system";
425
+ import { SafeAreaProvider } from "react-native-safe-area-context";
426
+
427
+ export default function Layout() {
428
+ const [loaded] = useFonts(caloFonts);
429
+ return (
430
+ <SafeAreaProvider>
431
+ {loaded ? (
432
+ <CaloBottomSheetProvider>
433
+ <Stack screenOptions={{ headerShown: false, contentStyle: { backgroundColor: caloTokens.color.canvas } }} />
434
+ </CaloBottomSheetProvider>
435
+ ) : (
436
+ <CaloScreen />
437
+ )}
438
+ </SafeAreaProvider>
439
+ );
440
+ }
441
+ `);
442
+ const showroom = fs.readFileSync(path.join(__dirname, "templates", "gallery-index.tsx"), "utf8");
443
+ fs.writeFileSync(path.join(appDir, "index.tsx"), showroom);
444
+ // The showroom require()s a few local images (../assets/... from src/app) — ship them too.
445
+ const assetsSrc = path.join(__dirname, "templates", "gallery-assets");
446
+ if (fs.existsSync(assetsSrc)) fs.cpSync(assetsSrc, path.join(projectRoot, "src", "assets"), { recursive: true });
447
+ }
448
+
411
449
  function writeGitignore(projectRoot) {
412
450
  const gi = path.join(projectRoot, ".gitignore");
413
451
  const needed = ["node_modules/", ".expo/", "dist/", "*.log"];
@@ -551,16 +589,35 @@ async function cmdUpdate() {
551
589
  // Re-pin when the runtime is fully present OR merely scaffolded (package.json on disk),
552
590
  // so a half-installed runtime self-heals via plain `update` instead of being stranded.
553
591
  const scaffolded = fs.existsSync(path.join(runtimeDir(), "package.json"));
592
+ // Snapshot the pinned @calo commit SHAs BEFORE re-pinning, so we can tell whether this update
593
+ // actually moved anything. The design system is git-HEAD-tracked (pinned by commit, not by an
594
+ // npm version bump — its package.json version can sit unchanged across many real commits), so
595
+ // the SHA is the true "did anything change" signal.
596
+ const before = installedCaloShas(runtimeDir()) || {};
597
+ let repinned = false;
554
598
  if (runtimeExists() || scaffolded || has("--build")) {
555
599
  await ensureRuntime({ force: true });
556
600
  ok("shared runtime re-pinned — every linked prototype now uses the latest Calo stack");
601
+ repinned = true;
557
602
  } else {
558
603
  log(c.dim(" No shared runtime yet — run `calo-design init` in a prototype folder to create one."));
559
604
  }
560
- // Report the refresh to the design-ops feed, tagged with the DS version we floated to.
605
+ // Only record an update event when the pinned code ACTUALLY changed (or we can't tell / it's a
606
+ // first install). A no-op re-pin — clicking Update when nothing new shipped — writes nothing,
607
+ // so the feed stops filling with identical "updated the design system to 0.2.0" rows.
608
+ const after = installedCaloShas(runtimeDir()) || {};
609
+ const changed = !Object.keys(before).length || JSON.stringify(before) !== JSON.stringify(after);
610
+ if (repinned && !changed) {
611
+ log(c.dim(" Already up to date — nothing new to record in the feed."));
612
+ return;
613
+ }
614
+ // Tag the feed entry with the DS version we floated to (read fresh from disk, not require()'s
615
+ // cache). Bump @calo/design-system's version on real releases (`npm run release` there) to make
616
+ // this number meaningful — the SHA gate above already guarantees it only logs on real change.
561
617
  let dsVersion = "";
562
618
  try {
563
- dsVersion = require(path.join(runtimeDir(), "node_modules", "@calo", "design-system", "package.json")).version || "";
619
+ const pj = JSON.parse(fs.readFileSync(path.join(runtimeDir(), "node_modules", "@calo", "design-system", "package.json"), "utf8"));
620
+ dsVersion = pj.version || "";
564
621
  } catch { /* runtime not present — report without a version */ }
565
622
  await reportEvent("update", dsVersion);
566
623
  }
@@ -602,6 +659,37 @@ async function cmdFeed() {
602
659
  for (const e of data.events) log(`${c.dim(e.ts)} ${e.email} ${c.b(e.action)} ${e.target || ""}`);
603
660
  }
604
661
 
662
+ // Serve the component gallery (the @calo/design-system showroom) locally, rendered LIVE from
663
+ // the shared runtime — so `calo-design update` keeps it current (no rebuild/redeploy). Design
664
+ // Kitchen auto-starts this; it also works standalone in a terminal. Runs Metro in the
665
+ // foreground so whoever launched it owns the process lifecycle.
666
+ async function cmdGallery() {
667
+ if (!runtimeExists()) {
668
+ throw new Error("The Calo runtime isn't set up on this machine yet — run `calo-design init` first, then retry.");
669
+ }
670
+ const dir = path.join(designchefHome(), "gallery");
671
+ fs.mkdirSync(dir, { recursive: true });
672
+ // Materialize / refresh a thin project linked to the shared runtime (same machinery as
673
+ // a linked prototype), then swap in the showroom routes.
674
+ writeThinConfigs(dir);
675
+ ensureBabelConfig(dir);
676
+ writeMetroConfig(dir);
677
+ writeGitignore(dir);
678
+ linkNodeModules(dir);
679
+ writeGalleryApp(dir);
680
+
681
+ let port = "8091";
682
+ const pj = args.find((a) => a.startsWith("--port="));
683
+ if (pj) port = pj.slice(7);
684
+ else { const pi = args.indexOf("--port"); if (pi >= 0) port = args[pi + 1] || port; }
685
+ if (!/^\d+$/.test(port)) port = "8091";
686
+
687
+ log(c.b(`\n[gallery] Serving the Calo component gallery on http://localhost:${port}`));
688
+ log(c.dim(` Live from the shared runtime (${tilde(runtimeDir())}) — always the installed @calo/design-system.`));
689
+ log(c.dim(" First launch compiles the design system (~30–60s). Ctrl-C to stop."));
690
+ run("npx", ["expo", "start", "--web", "--port", port], { cwd: dir });
691
+ }
692
+
605
693
  function help() {
606
694
  log(`${c.b("calo-design")} — one-line setup for Calo design tooling
607
695
 
@@ -617,6 +705,8 @@ function help() {
617
705
  ${c.dim(" push --slug x --title \"…\" --owner \"…\" --screenshot path --dry-run --direct")}
618
706
  ${c.dim("share")} publish THIS web prototype (React/Vite/CRA/Next) to Cloudflare Pages → share link
619
707
  ${c.dim(" share --slug x --dir dist --build --project calo-prototypes --dry-run")}
708
+ ${c.dim("gallery")} serve the @calo/design-system component gallery locally (live from the runtime)
709
+ ${c.dim(" gallery --port 8091")}
620
710
 
621
711
  Login is required once before init; the session refreshes automatically.
622
712
  init always installs the skill and ensures the shared ds/flows runtime on your machine;
@@ -636,6 +726,7 @@ function help() {
636
726
  else if (cmd === "share") await cmdShare(args.slice(1));
637
727
  else if (cmd === "feed") await cmdFeed();
638
728
  else if (cmd === "whoami") await cmdWhoami();
729
+ else if (cmd === "gallery") await cmdGallery();
639
730
  else help();
640
731
  } catch (err) {
641
732
  // Diagnostics go to stderr so stdout stays pure for machine callers (The Pass parses