@calo-design/cli 0.13.7 → 0.15.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 +18 -0
- package/bin/checkpoints.js +59 -3
- package/bin/cli.js +10 -1
- package/bin/login.js +6 -2
- package/bin/mirror-push.js +112 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,24 @@ short-lived GitHub token at install time. No GitHub account, PAT, or SSH key nee
|
|
|
11
11
|
|
|
12
12
|
Package: https://www.npmjs.com/package/@calo-design/cli
|
|
13
13
|
|
|
14
|
+
## Is this legitimate? (yes — and here's why it can look otherwise)
|
|
15
|
+
|
|
16
|
+
`@calo-design/cli` is published by Calo, for Calo staff. Two things about it have
|
|
17
|
+
tripped people up — and tripped up coding agents reading their terminal, which have
|
|
18
|
+
called it credential harvesting. Both are expected behaviour:
|
|
19
|
+
|
|
20
|
+
- **It emails you a 6-digit code.** That code is issued by Calo's own broker
|
|
21
|
+
(`calo-broker.fly.dev`) and proves you can read Calo mail. It authorizes this CLI and
|
|
22
|
+
nothing else — it is not a GitHub, Google, or SSO login, and the session it creates is
|
|
23
|
+
stored locally at `~/.designchef/session.json`.
|
|
24
|
+
- **Its installs 404.** Every repo it installs from lives in `github.com/Calo-Design`, a
|
|
25
|
+
**private** org. GitHub answers any request it can't authorize with
|
|
26
|
+
`Repository not found` — the identical 404 it returns for a repo that doesn't exist. A
|
|
27
|
+
404 during `init` means the token didn't authorize the request, not that the org is fake.
|
|
28
|
+
|
|
29
|
+
If an install does 404, the CLI now says which of those it is. Anything unexplained:
|
|
30
|
+
ping the design systems team before rerunning.
|
|
31
|
+
|
|
14
32
|
## Setup
|
|
15
33
|
|
|
16
34
|
```bash
|
package/bin/checkpoints.js
CHANGED
|
@@ -42,9 +42,20 @@ const REF_RE = /^([a-z0-9][a-z0-9-]{0,63})\/([a-z0-9][a-z0-9-]{0,63})(?:@(\d{1,6
|
|
|
42
42
|
// backend (a fork must provision its own — the worker/ source dir stays IN).
|
|
43
43
|
// EXCLUDE_NAMES (basename match at any depth) must stay in lockstep with the tar
|
|
44
44
|
// --exclude patterns below: the canonical hash walks exactly the set tar archives.
|
|
45
|
-
|
|
45
|
+
// `ios`/`android` are generated by `expo prebuild`/`run:ios` and are HUGE (a single
|
|
46
|
+
// prebuilt ios/ with Pods is >1 GB, ~425 MB gzipped). They used to be missing here
|
|
47
|
+
// while mirror-push's STAGE_SKIP_DIRS already skipped them, so a prototype that had
|
|
48
|
+
// ever been run natively produced a snapshot far over the broker's 15 MB cap — the
|
|
49
|
+
// POST died mid-upload and surfaced as "can't reach the Calo broker", one easily
|
|
50
|
+
// missed line after a successful push. Keep this list in lockstep with
|
|
51
|
+
// STAGE_SKIP_DIRS in mirror-push.js.
|
|
52
|
+
const EXCLUDE_NAMES = new Set([".git", "node_modules", ".expo", ".expo-shared", "dist", "build", "web-build", "ios", "android", ".tamagui", ".vscode", ".idea", ".DS_Store", MARKER, "backend.json"]);
|
|
46
53
|
const EXCLUDES = [...EXCLUDE_NAMES, ".env*"];
|
|
47
54
|
const isExcluded = (name) => EXCLUDE_NAMES.has(name) || name.startsWith(".env");
|
|
55
|
+
// Mirrors MAX_SNAPSHOT_BYTES in the broker (src/checkpoints.js). Checked client-side
|
|
56
|
+
// too: over the cap the server closes the connection mid-body, so the client never
|
|
57
|
+
// sees the 413 that would have explained itself.
|
|
58
|
+
const MAX_SNAPSHOT_BYTES = 15e6;
|
|
48
59
|
|
|
49
60
|
// Keep byte-identical with the broker's handleOf (src/checkpoints.js) — it's only used
|
|
50
61
|
// here to *predict* fork-vs-continue; the broker's derivation is authoritative.
|
|
@@ -128,6 +139,36 @@ function canonicalHash(root) {
|
|
|
128
139
|
return h.digest("hex");
|
|
129
140
|
}
|
|
130
141
|
|
|
142
|
+
// The top-level folders carrying the most bytes, for the over-the-limit message —
|
|
143
|
+
// "which folder is this?" is the only question that matters at that moment.
|
|
144
|
+
function biggestEntries(root, take = 3) {
|
|
145
|
+
const sizeOf = (p) => {
|
|
146
|
+
let n = 0;
|
|
147
|
+
const rec = (dir) => {
|
|
148
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
149
|
+
if (isExcluded(e.name) || e.isSymbolicLink()) continue;
|
|
150
|
+
const child = path.join(dir, e.name);
|
|
151
|
+
if (e.isDirectory()) rec(child);
|
|
152
|
+
else if (e.isFile()) n += fs.statSync(child).size;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
try {
|
|
156
|
+
if (fs.statSync(p).isDirectory()) rec(p);
|
|
157
|
+
else n = fs.statSync(p).size;
|
|
158
|
+
} catch {
|
|
159
|
+
/* vanished mid-walk — it just doesn't count toward the total */
|
|
160
|
+
}
|
|
161
|
+
return n;
|
|
162
|
+
};
|
|
163
|
+
return fs
|
|
164
|
+
.readdirSync(root, { withFileTypes: true })
|
|
165
|
+
.filter((e) => !isExcluded(e.name) && !e.isSymbolicLink())
|
|
166
|
+
.map((e) => ({ name: e.name, bytes: sizeOf(path.join(root, e.name)) }))
|
|
167
|
+
.sort((a, b) => b.bytes - a.bytes)
|
|
168
|
+
.slice(0, take)
|
|
169
|
+
.map((e) => `${e.name} (${(e.bytes / 1e6).toFixed(1)} MB)`);
|
|
170
|
+
}
|
|
171
|
+
|
|
131
172
|
// tar the prototype source with the exclusion list; returns { work, tarPath, hash }
|
|
132
173
|
// in a tmpdir the caller must clean up.
|
|
133
174
|
function makeSnapshot(root) {
|
|
@@ -164,7 +205,14 @@ async function autoCheckpoint({ root, note, publishedUrl, artifact }) {
|
|
|
164
205
|
try {
|
|
165
206
|
await saveFlow({ root, note, publishedUrl, artifact, force: true, quiet: true });
|
|
166
207
|
} catch (e) {
|
|
167
|
-
|
|
208
|
+
// Loud on purpose: the deploy is live but UNRECOVERABLE — no `calo-design open`,
|
|
209
|
+
// no teammate handoff, no way to get back to the exact source behind it. The old
|
|
210
|
+
// one-liner scrolled past under the QR code and we only noticed weeks later, when
|
|
211
|
+
// a crashing prototype had no source to inspect.
|
|
212
|
+
elog(`\n${c.y("!")} DEPLOY IS LIVE, BUT ITS SOURCE WAS NOT CHECKPOINTED.`);
|
|
213
|
+
elog(c.dim(` Nobody can \`calo-design open\` this version or hand it to a teammate.`));
|
|
214
|
+
elog(` ${e.message}`);
|
|
215
|
+
elog(c.dim(` Fix the cause, then run \`calo-design save\` — the deploy itself is fine.\n`));
|
|
168
216
|
}
|
|
169
217
|
}
|
|
170
218
|
|
|
@@ -198,6 +246,14 @@ async function saveFlow({ root, slugOverride, note = "", force = false, json = f
|
|
|
198
246
|
return;
|
|
199
247
|
}
|
|
200
248
|
const tgz = zlib.gzipSync(fs.readFileSync(snap.tarPath));
|
|
249
|
+
if (tgz.length > MAX_SNAPSHOT_BYTES) {
|
|
250
|
+
throw new Error(
|
|
251
|
+
`this prototype's source is ${(tgz.length / 1e6).toFixed(0)} MB compressed — over the ${MAX_SNAPSHOT_BYTES / 1e6} MB checkpoint limit.\n` +
|
|
252
|
+
` Biggest folders: ${biggestEntries(root).join(", ")}\n` +
|
|
253
|
+
` Checkpoints hold SOURCE only. Move bulk images to the CDN with \`calo-design art push\`,\n` +
|
|
254
|
+
` or delete generated folders — node_modules, .git, ios, android and dist are already skipped.`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
201
257
|
|
|
202
258
|
let res;
|
|
203
259
|
try {
|
|
@@ -357,4 +413,4 @@ async function cmdProjects(args = []) {
|
|
|
357
413
|
log("");
|
|
358
414
|
}
|
|
359
415
|
|
|
360
|
-
module.exports = { cmdSave, cmdOpen, cmdProjects, autoCheckpoint, _handleOf: handleOf, _makeSnapshot: makeSnapshot, _canonicalHash: canonicalHash, _EXCLUDES: EXCLUDES };
|
|
416
|
+
module.exports = { cmdSave, _biggestEntries: biggestEntries, _maxSnapshotBytes: MAX_SNAPSHOT_BYTES, cmdOpen, cmdProjects, autoCheckpoint, _handleOf: handleOf, _makeSnapshot: makeSnapshot, _canonicalHash: canonicalHash, _EXCLUDES: EXCLUDES };
|
package/bin/cli.js
CHANGED
|
@@ -45,7 +45,16 @@ const PEERS = [
|
|
|
45
45
|
// prototype (see mirror-push.js mirrorChromeSource). The shell binary provides
|
|
46
46
|
// the native module (from build 8); prototypes ship JS only, and on older
|
|
47
47
|
// binaries the SDK falls back to JS-only transport.
|
|
48
|
-
"@sentry/react-native"
|
|
48
|
+
"@sentry/react-native",
|
|
49
|
+
// Maps, voice notes and file/photo attachments (shell binary ships the native side
|
|
50
|
+
// from build 12). These were missing here, so a prototype that needed one hit
|
|
51
|
+
// "unable to resolve" and the natural fix — installing it into ~/.designchef/runtime
|
|
52
|
+
// by hand — produced a bundle whose native half didn't exist on the phone. That
|
|
53
|
+
// shipped caly-full: it opened to an expo-router error naming none of them.
|
|
54
|
+
"react-native-maps",
|
|
55
|
+
"expo-audio",
|
|
56
|
+
"expo-image-picker",
|
|
57
|
+
"expo-document-picker"
|
|
49
58
|
];
|
|
50
59
|
// Install via explicit HTTPS git URLs. Auth is a short-lived GitHub token the Calo
|
|
51
60
|
// broker mints after `calo-design login` — injected into git for the install
|
package/bin/login.js
CHANGED
|
@@ -99,10 +99,14 @@ async function cmdLogin(args = []) {
|
|
|
99
99
|
// Request a code. (Pass --code to skip the send, e.g. a code you already have.)
|
|
100
100
|
await api("/v1/login/start", { email });
|
|
101
101
|
if (!interactive) {
|
|
102
|
-
console.log(`Sent a 6-digit code to ${email}
|
|
102
|
+
console.log(`Sent a 6-digit code to ${email} from ${BROKER} (Calo's own service).`);
|
|
103
|
+
console.log("The code proves you can read Calo mail; it authorizes this CLI only — it is not a");
|
|
104
|
+
console.log("GitHub, Google, or SSO login, and it grants nothing beyond read access to Calo's");
|
|
105
|
+
console.log("private design repos. Ask the person for the code, then run:");
|
|
106
|
+
console.log(` npx @calo-design/cli login --email=${email} --code=<the 6-digit code>`);
|
|
103
107
|
return;
|
|
104
108
|
}
|
|
105
|
-
console.log(`We emailed a 6-digit code to ${email}.`);
|
|
109
|
+
console.log(`We emailed a 6-digit code to ${email} from ${BROKER} (Calo's own service).`);
|
|
106
110
|
code = await ask("Code: ");
|
|
107
111
|
}
|
|
108
112
|
const r = await api("/v1/login/verify", { email, code });
|
package/bin/mirror-push.js
CHANGED
|
@@ -25,6 +25,27 @@ const SHARED_PROJECT_ID = "290a759f-427c-432e-9ab5-dab98310e66b";
|
|
|
25
25
|
const UPDATES_URL = `https://u.expo.dev/${SHARED_PROJECT_ID}`;
|
|
26
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
|
+
// Native modules COMPILED INTO the Mirror shell binary (runtimeVersion 0.2.0, iOS
|
|
29
|
+
// build 11+). This — not the shared runtime — is the authority on what a pushed
|
|
30
|
+
// prototype may import: a push ships JS only, so a package whose native side isn't
|
|
31
|
+
// in the binary throws the moment its module scope runs `requireNativeModule`.
|
|
32
|
+
// Metro then SWALLOWS that throw (guardedLoadModule → ErrorUtils.reportFatalError,
|
|
33
|
+
// returning undefined), so expo-router destructures undefined and the phone shows
|
|
34
|
+
// "Cannot read property 'ErrorBoundary' of undefined" — an error that points
|
|
35
|
+
// nowhere near the missing module. That's why this list exists and why the
|
|
36
|
+
// preflight below is an error, not a warning. Keep it in lockstep with
|
|
37
|
+
// calo-design-mirror/package.json whenever a new shell binary ships.
|
|
38
|
+
const SHELL_MODULES = new Set([
|
|
39
|
+
"@expo/ui", "@gorhom/bottom-sheet", "@microsoft/react-native-clarity", "@sentry/react-native",
|
|
40
|
+
"expo", "expo-asset", "expo-camera", "expo-clipboard", "expo-constants", "expo-device",
|
|
41
|
+
"expo-font", "expo-glass-effect", "expo-image", "expo-linking", "expo-router",
|
|
42
|
+
"expo-splash-screen", "expo-status-bar", "expo-symbols", "expo-system-ui", "expo-updates",
|
|
43
|
+
"expo-video", "expo-web-browser", "expo-audio", "expo-document-picker", "expo-image-picker",
|
|
44
|
+
"expo-file-system", "react-native-maps", "phosphor-react-native", "react", "react-dom",
|
|
45
|
+
"react-native", "react-native-gesture-handler", "react-native-qrcode-svg",
|
|
46
|
+
"react-native-reanimated", "react-native-safe-area-context", "react-native-screens",
|
|
47
|
+
"react-native-svg", "react-native-web", "react-native-worklets",
|
|
48
|
+
]);
|
|
28
49
|
// Crash reporting: every pushed prototype gets a Sentry init + error boundary
|
|
29
50
|
// injected (see injectBackChrome). Same project as the shell's own init
|
|
30
51
|
// (calo-design-mirror expo.extra.sentryDsn) — keep the two DSNs in sync. A DSN
|
|
@@ -140,8 +161,10 @@ function findScreenshot(root, explicit) {
|
|
|
140
161
|
|
|
141
162
|
// ---- staging ----------------------------------------------------------------
|
|
142
163
|
|
|
143
|
-
// The prototype's deps must be a subset of the shared runtime — the
|
|
144
|
-
//
|
|
164
|
+
// The prototype's deps must be a subset of the shared runtime — it's what the staged
|
|
165
|
+
// node_modules symlink points at, so anything extra simply isn't there to bundle.
|
|
166
|
+
// This is about BUILDING the update; whether the phone can RUN it is a separate,
|
|
167
|
+
// stricter question answered by preflightNativeModules (SHELL_MODULES).
|
|
145
168
|
function validateDeps(pkg) {
|
|
146
169
|
const runtimePkgPath = path.join(runtimeDir(), "package.json");
|
|
147
170
|
if (!fs.existsSync(runtimePkgPath)) {
|
|
@@ -150,10 +173,27 @@ function validateDeps(pkg) {
|
|
|
150
173
|
}
|
|
151
174
|
const runtimeDeps = new Set(Object.keys(JSON.parse(fs.readFileSync(runtimePkgPath, "utf8")).dependencies || {}));
|
|
152
175
|
const extras = Object.keys(pkg.dependencies || {}).filter((d) => !d.startsWith("@calo/") && !runtimeDeps.has(d));
|
|
153
|
-
if (extras.length)
|
|
154
|
-
|
|
155
|
-
|
|
176
|
+
if (!extras.length) return;
|
|
177
|
+
|
|
178
|
+
// A dep the runtime lacks normally can't even bundle — but "unable to resolve X" is
|
|
179
|
+
// reasonably fixed by installing X into ~/.designchef/runtime by hand, and then the
|
|
180
|
+
// push succeeds and the PHONE has no native side for it. That is how caly-full went
|
|
181
|
+
// out: expo-audio, expo-image-picker, expo-document-picker and react-native-maps were
|
|
182
|
+
// all declared here, all absent from the runtime, and the prototype died on open with
|
|
183
|
+
// an expo-router error naming none of them. So: anything the shell binary can't serve
|
|
184
|
+
// is a hard stop, not a warning nobody reads.
|
|
185
|
+
const unusable = extras.filter((d) => !SHELL_MODULES.has(d));
|
|
186
|
+
if (unusable.length) {
|
|
187
|
+
throw new Error(
|
|
188
|
+
`these dependencies aren't in the shared runtime AND aren't in the Mirror shell binary:\n` +
|
|
189
|
+
` ${unusable.join(", ")}\n` +
|
|
190
|
+
` Even if the bundle builds (e.g. they were installed into the runtime by hand), the phone\n` +
|
|
191
|
+
` has no native code for them: the import throws on open and the prototype shows an\n` +
|
|
192
|
+
` unrelated expo-router error. Drop them, or ask for a runtime + shell bump that carries them.`
|
|
193
|
+
);
|
|
156
194
|
}
|
|
195
|
+
warn(`these deps aren't in the shared runtime, so the bundle can only build if they're installed there:\n ${extras.join(", ")}`);
|
|
196
|
+
warn("the Mirror binary does carry their native side — but keep prototypes to the shared-runtime deps where you can.");
|
|
157
197
|
}
|
|
158
198
|
|
|
159
199
|
function writeJSON(p, obj) {
|
|
@@ -288,6 +328,71 @@ function preflightAssetCount(stage) {
|
|
|
288
328
|
}
|
|
289
329
|
}
|
|
290
330
|
|
|
331
|
+
// ---- native-module preflight ------------------------------------------------
|
|
332
|
+
// A push ships JS; native code only ever arrives with a new shell BINARY. The
|
|
333
|
+
// shared runtime is deliberately wider than the binary at times (a package can
|
|
334
|
+
// land in the runtime before the next TestFlight build), and importing one of
|
|
335
|
+
// those crashes the prototype at startup with an error that names expo-router,
|
|
336
|
+
// not the module — see SHELL_MODULES. Catch it here, where the fix is obvious.
|
|
337
|
+
//
|
|
338
|
+
// "Native" is derived from the runtime itself rather than hard-coded: a package
|
|
339
|
+
// is native when it carries an Expo module config or a podspec.
|
|
340
|
+
const SOURCE_EXTS = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
341
|
+
// Package name from a specifier: "expo-image/build/x" → "expo-image", "@expo/ui/swift-ui" → "@expo/ui".
|
|
342
|
+
const packageOf = (spec) => {
|
|
343
|
+
if (spec.startsWith(".") || spec.startsWith("/")) return null;
|
|
344
|
+
const parts = spec.split("/");
|
|
345
|
+
return spec.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
function isNativePackage(name) {
|
|
349
|
+
const dir = path.join(runtimeDir(), "node_modules", name);
|
|
350
|
+
if (fs.existsSync(path.join(dir, "expo-module.config.json"))) return true;
|
|
351
|
+
try {
|
|
352
|
+
return fs.readdirSync(dir).some((f) => f.endsWith(".podspec"));
|
|
353
|
+
} catch {
|
|
354
|
+
return false; // not in the runtime — validateDeps owns that case
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function stagedImports(stage) {
|
|
359
|
+
const found = new Set();
|
|
360
|
+
const re = /(?:\bfrom\s*|\bimport\s*\(?\s*|\brequire\s*\(\s*)(["'])([^"']+)\1/g;
|
|
361
|
+
const walk = (dir) => {
|
|
362
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
363
|
+
if (e.name === "node_modules" || e.name.startsWith(".")) continue;
|
|
364
|
+
const p = path.join(dir, e.name);
|
|
365
|
+
if (e.isSymbolicLink()) continue; // never follow into the runtime symlink
|
|
366
|
+
if (e.isDirectory()) walk(p);
|
|
367
|
+
else if (SOURCE_EXTS.has(path.extname(e.name))) {
|
|
368
|
+
const src = fs.readFileSync(p, "utf8");
|
|
369
|
+
for (const m of src.matchAll(re)) {
|
|
370
|
+
const pkg = packageOf(m[2]);
|
|
371
|
+
if (pkg) found.add(pkg);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
walk(stage);
|
|
377
|
+
return found;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function preflightNativeModules(stage) {
|
|
381
|
+
const missing = [...stagedImports(stage)]
|
|
382
|
+
.filter((pkg) => !SHELL_MODULES.has(pkg) && isNativePackage(pkg))
|
|
383
|
+
.sort();
|
|
384
|
+
if (!missing.length) return;
|
|
385
|
+
throw new Error(
|
|
386
|
+
`this prototype imports native modules the Mirror shell binary doesn't contain:\n` +
|
|
387
|
+
` ${missing.join(", ")}\n` +
|
|
388
|
+
` A push ships JavaScript only — native code arrives with a new Mirror build, so on the phone\n` +
|
|
389
|
+
` these throw at startup and the prototype dies with a misleading expo-router error\n` +
|
|
390
|
+
` ("Cannot read property 'ErrorBoundary' of undefined").\n` +
|
|
391
|
+
` Swap them for something in the shared runtime the shell provides, or ask for a shell build\n` +
|
|
392
|
+
` that includes them (calo-design-mirror → TestFlight, then update SHELL_MODULES here).`
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
291
396
|
function writeMetroConfig(stage) {
|
|
292
397
|
const runtimeReal = fs.realpathSync(runtimeDir());
|
|
293
398
|
fs.writeFileSync(
|
|
@@ -883,6 +988,7 @@ async function cmdPush(args) {
|
|
|
883
988
|
try {
|
|
884
989
|
stageProject({ root, stage, slug, title, ad, owner });
|
|
885
990
|
preflightAssetCount(stage);
|
|
991
|
+
preflightNativeModules(stage);
|
|
886
992
|
|
|
887
993
|
if (dry) {
|
|
888
994
|
keepStage = true;
|
|
@@ -1007,4 +1113,4 @@ async function cmdPush(args) {
|
|
|
1007
1113
|
}
|
|
1008
1114
|
}
|
|
1009
1115
|
|
|
1010
|
-
module.exports = { cmdPush, _s3Put: s3Put, _upsertRegistry: upsertRegistry, _publicBase: PUBLIC_BASE, _extractEasGroup: extractEasGroup, _versionBranchOf: versionBranchOf };
|
|
1116
|
+
module.exports = { cmdPush, _validateDeps: validateDeps, _stageSkipDirs: STAGE_SKIP_DIRS, _s3Put: s3Put, _upsertRegistry: upsertRegistry, _publicBase: PUBLIC_BASE, _extractEasGroup: extractEasGroup, _versionBranchOf: versionBranchOf, _stagedImports: stagedImports, _preflightNativeModules: preflightNativeModules, _shellModules: SHELL_MODULES };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@calo-design/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
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"
|