@calo-design/cli 0.9.3 → 0.9.4
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/mirror-push.js +59 -23
- package/package.json +1 -1
package/bin/mirror-push.js
CHANGED
|
@@ -382,7 +382,7 @@ export default function RootLayout() {
|
|
|
382
382
|
}
|
|
383
383
|
fs.writeFileSync(path.join(stagedAppDir, "_layout.tsx"), managed);
|
|
384
384
|
fs.writeFileSync(path.join(mirrorChromeDir, "mirror-chrome.tsx"), mirrorChromeSource({ slug, owner }));
|
|
385
|
-
injectNativeIntent(stagedAppDir, slug);
|
|
385
|
+
injectNativeIntent(stagedAppDir, mirrorChromeDir, slug);
|
|
386
386
|
}
|
|
387
387
|
|
|
388
388
|
// The launcher opens a prototype via `designchef://open/<slug>`. That deep link is
|
|
@@ -394,35 +394,33 @@ export default function RootLayout() {
|
|
|
394
394
|
// `+native-intent.tsx` is expo-router's hook for rewriting an incoming native URL
|
|
395
395
|
// before routing: send the launcher's own path to the root and leave every other
|
|
396
396
|
// deep link untouched, so a prototype's own links keep working.
|
|
397
|
-
function injectNativeIntent(stagedAppDir, slug) {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
397
|
+
function injectNativeIntent(stagedAppDir, mirrorChromeDir, slug) {
|
|
398
|
+
// Shared launcher-URL logic: +native-intent covers COLD starts (the initial
|
|
399
|
+
// URL), and MirrorChrome's Linking listener covers WARM arrivals — on Android
|
|
400
|
+
// a URL delivered to the running app does not reliably reach expo-router's
|
|
401
|
+
// redirectSystemPath, which left designchef://open/<other> showing the
|
|
402
|
+
// currently-loaded prototype. The `switching` latch dedupes when both fire.
|
|
405
403
|
fs.writeFileSync(
|
|
406
|
-
path.join(
|
|
404
|
+
path.join(mirrorChromeDir, "mirror-switch.ts"),
|
|
407
405
|
`// Managed by calo-design push.
|
|
408
|
-
// The Mirror launcher opens prototypes with designchef://open/<slug>.
|
|
409
|
-
//
|
|
406
|
+
// The Mirror launcher opens prototypes with designchef://open/<slug>. Cases
|
|
407
|
+
// reaching THIS bundle:
|
|
410
408
|
// - the URL that opened this prototype survives the expo-updates reload as the
|
|
411
409
|
// initial URL (/open/<own slug>) — route it to "/" so expo-router doesn't
|
|
412
410
|
// render "Unmatched Route";
|
|
413
|
-
// - a designchef://open/<other> link arrives while this bundle is
|
|
414
|
-
// launcher's JS isn't running, so this bundle performs the
|
|
415
|
-
// itself: point expo-updates at the other channel, download,
|
|
416
|
-
// ("mirror" is the launcher's own channel
|
|
417
|
-
import { Platform } from "react-native";
|
|
411
|
+
// - a designchef://open/<other> link arrives (cold OR while this bundle is
|
|
412
|
+
// live) — the launcher's JS isn't running, so this bundle performs the
|
|
413
|
+
// channel switch itself: point expo-updates at the other channel, download,
|
|
414
|
+
// reload. ("mirror" is the launcher's own channel — open/mirror = the feed.)
|
|
415
|
+
import { Linking, Platform } from "react-native";
|
|
418
416
|
import * as Updates from "expo-updates";
|
|
419
417
|
|
|
420
418
|
const PROTOTYPE = ${JSON.stringify(slug)};
|
|
421
419
|
const UPDATES_URL = ${JSON.stringify(UPDATES_URL)};
|
|
422
420
|
const RUNTIME_VERSION = ${JSON.stringify(RUNTIME_VERSION)};
|
|
423
421
|
|
|
424
|
-
// One switch at a time — a second reload racing the first crashes in JSI
|
|
425
|
-
// (see calo-design-mirror src/lib/mirror.ts). Reset on failure
|
|
422
|
+
// One switch at a time — a second reload racing the first crashes in JSI
|
|
423
|
+
// teardown (see calo-design-mirror src/lib/mirror.ts). Reset on failure.
|
|
426
424
|
let switching = false;
|
|
427
425
|
function switchToChannel(channel: string): void {
|
|
428
426
|
if (switching) return;
|
|
@@ -443,7 +441,7 @@ function switchToChannel(channel: string): void {
|
|
|
443
441
|
// Download BEFORE reloading; if the channel has nothing this throws and we
|
|
444
442
|
// stay put instead of reloading into nothing.
|
|
445
443
|
await Updates.fetchUpdateAsync();
|
|
446
|
-
// Defer the reload off the awaited chain (JSI teardown race —
|
|
444
|
+
// Defer the reload off the awaited chain (JSI teardown race — mirror.ts).
|
|
447
445
|
setTimeout(() => { void Updates.reloadAsync().catch(() => { switching = false; }); }, 300);
|
|
448
446
|
})().catch(() => { switching = false; });
|
|
449
447
|
}
|
|
@@ -466,12 +464,38 @@ function launcherSlug(path: string): string | null {
|
|
|
466
464
|
}
|
|
467
465
|
}
|
|
468
466
|
|
|
469
|
-
|
|
467
|
+
/** "/" when the URL is the launcher's (starting a switch if it names another prototype), null otherwise. */
|
|
468
|
+
export function handleLauncherPath(path: string): string | null {
|
|
470
469
|
const slug = launcherSlug(path);
|
|
471
|
-
if (slug === null) return
|
|
470
|
+
if (slug === null) return null;
|
|
472
471
|
if (slug && slug !== PROTOTYPE) switchToChannel(slug);
|
|
473
472
|
return "/";
|
|
474
473
|
}
|
|
474
|
+
|
|
475
|
+
/** Warm-link coverage: subscribe to URLs arriving while this bundle runs. */
|
|
476
|
+
export function subscribeLauncherLinks(): { remove: () => void } {
|
|
477
|
+
return Linking.addEventListener("url", ({ url }) => {
|
|
478
|
+
handleLauncherPath(url);
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
`
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
const existing = [".tsx", ".ts", ".jsx", ".js"].find((e) =>
|
|
485
|
+
fs.existsSync(path.join(stagedAppDir, "+native-intent" + e))
|
|
486
|
+
);
|
|
487
|
+
if (existing) {
|
|
488
|
+
warn("this prototype has its own +native-intent — leaving it alone. Launcher URLs (/open/<slug>) won't be handled there: delegate to handleLauncherPath from ../mirror-switch.");
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
fs.writeFileSync(
|
|
492
|
+
path.join(stagedAppDir, "+native-intent.tsx"),
|
|
493
|
+
`// Managed by calo-design push — see ../mirror-switch for the rationale.
|
|
494
|
+
import { handleLauncherPath } from "../mirror-switch";
|
|
495
|
+
|
|
496
|
+
export function redirectSystemPath({ path }: { path: string; initial: boolean }): string {
|
|
497
|
+
return handleLauncherPath(path) ?? path;
|
|
498
|
+
}
|
|
475
499
|
`
|
|
476
500
|
);
|
|
477
501
|
}
|
|
@@ -492,19 +516,26 @@ function runtimeHasSentry() {
|
|
|
492
516
|
// try/catch backstops that), so prototypes never crash BECAUSE of reporting.
|
|
493
517
|
function mirrorChromeSource({ slug, owner }) {
|
|
494
518
|
if (!SENTRY_DSN || !runtimeHasSentry()) {
|
|
495
|
-
return `import type
|
|
519
|
+
return `import { useEffect, type ReactNode } from "react";
|
|
520
|
+
import { subscribeLauncherLinks } from "./mirror-switch";
|
|
496
521
|
|
|
497
522
|
// Returning to the Mirror launcher is handled NATIVELY by the shell binary:
|
|
498
523
|
// shake the device (see calo-design-mirror/plugins/withCaloMirrorIos.js).
|
|
499
524
|
// Crash reporting was NOT injected: no Sentry DSN configured or the shared
|
|
500
525
|
// runtime predates @sentry/react-native (run \`calo-design update\`).
|
|
501
526
|
export function MirrorChrome({ children }: { children: ReactNode }) {
|
|
527
|
+
// Warm launcher links (designchef://open/<other> while this bundle runs).
|
|
528
|
+
useEffect(() => {
|
|
529
|
+
const sub = subscribeLauncherLinks();
|
|
530
|
+
return () => sub.remove();
|
|
531
|
+
}, []);
|
|
502
532
|
return <>{children}</>;
|
|
503
533
|
}
|
|
504
534
|
`;
|
|
505
535
|
}
|
|
506
536
|
return `import { Component, type ErrorInfo, type ReactNode } from "react";
|
|
507
537
|
import { Pressable, ScrollView, Text, View } from "react-native";
|
|
538
|
+
import { subscribeLauncherLinks } from "./mirror-switch";
|
|
508
539
|
|
|
509
540
|
// Injected by \`calo-design push\` — crash reporting for this prototype.
|
|
510
541
|
// Returning to the Mirror launcher stays NATIVE: shake the device.
|
|
@@ -530,6 +561,11 @@ type CrashState = { error: Error | null };
|
|
|
530
561
|
export class MirrorChrome extends Component<{ children: ReactNode }, CrashState> {
|
|
531
562
|
state: CrashState = { error: null };
|
|
532
563
|
|
|
564
|
+
// Warm launcher links (designchef://open/<other> while this bundle runs).
|
|
565
|
+
private linkSub: { remove: () => void } | null = null;
|
|
566
|
+
componentDidMount(): void { this.linkSub = subscribeLauncherLinks(); }
|
|
567
|
+
componentWillUnmount(): void { this.linkSub?.remove(); }
|
|
568
|
+
|
|
533
569
|
static getDerivedStateFromError(error: Error): CrashState {
|
|
534
570
|
return { error };
|
|
535
571
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@calo-design/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4",
|
|
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"
|