@calo-design/cli 0.8.0 → 0.9.1

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.
Files changed (2) hide show
  1. package/bin/mirror-push.js +75 -17
  2. package/package.json +1 -1
@@ -23,7 +23,7 @@ const { ensureLoggedIn, easToken, publishToMirror } = require("./login");
23
23
  // ---- shared contract with the Mirror shell (calo-design-mirror) -------------
24
24
  const SHARED_PROJECT_ID = "290a759f-427c-432e-9ab5-dab98310e66b";
25
25
  const UPDATES_URL = `https://u.expo.dev/${SHARED_PROJECT_ID}`;
26
- const RUNTIME_VERSION = "0.1.0"; // must equal the shell binary's runtimeVersion (appVersion policy, version 0.1.0)
26
+ const RUNTIME_VERSION = "0.2.0"; // must equal the shell binary's runtimeVersion (SDK 57 shell, version 0.2.0)
27
27
  const LAUNCHER_CHANNEL = "mirror";
28
28
  // Crash reporting: every pushed prototype gets a Sentry init + error boundary
29
29
  // injected (see injectBackChrome). Same project as the shell's own init
@@ -293,24 +293,81 @@ function linkNodeModules(stage) {
293
293
  fs.symlinkSync(target, link, process.platform === "win32" ? "junction" : "dir");
294
294
  }
295
295
 
296
- // Overwrite the root _layout with a managed one. Returning to the launcher is now
296
+ // Wrap the root _layout with Mirror chrome. Returning to the launcher is
297
297
  // handled natively by the Mirror shell (shake the device — see withCaloMirrorIos),
298
298
  // so no visible "back" control is injected; MirrorChrome carries the injected
299
- // crash reporting (Sentry init + error boundary). NOTE (v1): a custom root _layout
300
- // (custom providers/Tabs) is not preserved — we warn when it looks non-standard.
299
+ // crash reporting (Sentry init + error boundary).
300
+ //
301
+ // The author's own root _layout is PRESERVED: it's relocated one directory up
302
+ // (next to mirror-chrome, where expo-router won't treat it as a route — inside
303
+ // the app dir even underscore-prefixed files become routes), its relative
304
+ // imports are shifted for the move, and the managed layout renders it inside
305
+ // MirrorChrome. Providers, fonts, and custom UI in the root layout all carry
306
+ // over — the old behavior silently dropped them, and every context hook in the
307
+ // prototype then threw "must be used inside <Provider>" at runtime.
308
+
309
+ // Moving a module one directory up (src/app/_layout → src/mirror-original-layout)
310
+ // shifts what its relative imports resolve to; rewrite the specifiers so they
311
+ // point at the same files from the new location. Covers `from "…"`, bare
312
+ // `import "…"`, dynamic `import("…")` and `require("…")`. Alias (@/…) and
313
+ // package imports are untouched.
314
+ function shiftRelativeImportsUp(source, appDirName) {
315
+ return source.replace(
316
+ /(\bfrom\s*|\bimport\s*\(?\s*|\brequire\s*\(\s*)(["'])(\.{1,2}\/[^"']+)\2/g,
317
+ (whole, lead, quote, spec) => {
318
+ let next;
319
+ if (spec.startsWith("../")) {
320
+ next = spec.slice(3);
321
+ if (!next.startsWith("../") && !next.startsWith("./")) next = "./" + next;
322
+ } else {
323
+ next = "./" + appDirName + "/" + spec.slice(2);
324
+ }
325
+ return `${lead}${quote}${next}${quote}`;
326
+ }
327
+ );
328
+ }
329
+
301
330
  function injectBackChrome({ stagedAppDir, mirrorChromeDir, libRel, slug, owner }) {
302
- const layout = path.join(stagedAppDir, "_layout.tsx");
303
- if (fs.existsSync(layout)) {
304
- const cur = fs.readFileSync(layout, "utf8");
305
- const standard = /useFonts\(caloFonts\)/.test(cur) && /<Stack/.test(cur);
306
- if (!standard) warn("custom root _layout detected — Mirror wraps it with a Stack; providers in the ROOT layout won't carry over. Move them to a nested layout (e.g. src/app/(group)/_layout.tsx), which the Mirror preserves.");
307
- }
308
- fs.writeFileSync(
309
- layout,
310
- `import { Stack } from "expo-router";
331
+ const exts = [".tsx", ".jsx", ".ts", ".js"];
332
+ const ext = exts.find((e) => fs.existsSync(path.join(stagedAppDir, "_layout" + e)));
333
+ const authorLayout = ext ? fs.readFileSync(path.join(stagedAppDir, "_layout" + ext), "utf8") : null;
334
+ // Root layouts must default-export a component; without one we can't wrap it.
335
+ const wrappable = authorLayout && /export\s+default\b|export\s*\{[^}]*\bdefault\b[^}]*\}/.test(authorLayout);
336
+
337
+ let managed;
338
+ if (wrappable) {
339
+ fs.writeFileSync(
340
+ path.join(mirrorChromeDir, "mirror-original-layout" + ext),
341
+ shiftRelativeImportsUp(authorLayout, path.basename(stagedAppDir))
342
+ );
343
+ fs.rmSync(path.join(stagedAppDir, "_layout" + ext));
344
+ log(c.dim(" root _layout preserved — wrapped with Mirror crash reporting (providers carry over)"));
345
+ managed = `// Managed by calo-design push. Your root _layout is preserved verbatim as
346
+ // ../mirror-original-layout (imports shifted for the move) and rendered inside
347
+ // Mirror's crash reporting — providers, fonts, and custom UI all carry over.
348
+ // MirrorChrome MUST stay the first import: its module scope boots Sentry, so a
349
+ // crash while any later module evaluates (your layout, the design system — the
350
+ // SDK-drift class) is captured instead of dying unreported before init.
351
+ import { MirrorChrome } from "${libRel}";
352
+ import OriginalLayout from "../mirror-original-layout";
353
+
354
+ export default function RootLayout() {
355
+ return (
356
+ <MirrorChrome>
357
+ <OriginalLayout />
358
+ </MirrorChrome>
359
+ );
360
+ }
361
+ `;
362
+ } else {
363
+ if (authorLayout) warn("root _layout has no default export — replacing it with the standard Mirror layout.");
364
+ managed = `// MirrorChrome MUST be the first import: its module scope boots Sentry, so a
365
+ // crash while any later module evaluates (expo-router, the design system — the
366
+ // SDK-drift class) is captured instead of dying unreported before init.
367
+ import { MirrorChrome } from "${libRel}";
368
+ import { Stack } from "expo-router";
311
369
  import { useFonts } from "expo-font";
312
370
  import { caloFonts } from "@calo/design-system";
313
- import { MirrorChrome } from "${libRel}";
314
371
 
315
372
  export default function RootLayout() {
316
373
  const [loaded] = useFonts(caloFonts);
@@ -321,8 +378,9 @@ export default function RootLayout() {
321
378
  </MirrorChrome>
322
379
  );
323
380
  }
324
- `
325
- );
381
+ `;
382
+ }
383
+ fs.writeFileSync(path.join(stagedAppDir, "_layout.tsx"), managed);
326
384
  fs.writeFileSync(path.join(mirrorChromeDir, "mirror-chrome.tsx"), mirrorChromeSource({ slug, owner }));
327
385
  }
328
386
 
@@ -370,7 +428,7 @@ try {
370
428
  enableLogs: true,
371
429
  enableAutoSessionTracking: false,
372
430
  });
373
- Sentry!.setTags({ surface: "prototype", prototype: PROTOTYPE, owner: OWNER });
431
+ Sentry!.setTags({ surface: "prototype", prototype: PROTOTYPE, owner: OWNER, runtime: ${JSON.stringify(RUNTIME_VERSION)} });
374
432
  } catch {
375
433
  Sentry = null; // reporting must never be the thing that crashes a prototype
376
434
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@calo-design/cli",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "description": "One-line setup for Calo design tooling: logs in with your Calo email and installs the calo-design skill + design-system packages. No GitHub account needed.",
5
5
  "bin": {
6
6
  "calo-design": "bin/cli.js"