@calo-design/cli 0.3.0 → 0.4.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.
- package/README.md +10 -0
- package/bin/cli.js +33 -7
- package/bin/login.js +1 -1
- package/bin/mirror-push.js +16 -1
- package/package.json +13 -5
package/README.md
CHANGED
|
@@ -18,6 +18,16 @@ npx @calo-design/cli login # one-time: your Calo email + the emailed code
|
|
|
18
18
|
npx @calo-design/cli init # skill + the shared ds/flows runtime, and a prototype here
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
> **`@calo-design/cli` is a public npm package.** If npx reports it can't be found — "not in
|
|
22
|
+
> this registry", "404", or "not a public package" — your npm is pointed at a private or
|
|
23
|
+
> corporate registry that doesn't carry it (common on managed laptops). Check with
|
|
24
|
+
> `npm config get registry` (it should be `https://registry.npmjs.org/`). To run regardless
|
|
25
|
+
> of your `.npmrc`, pin just this scope to public npm:
|
|
26
|
+
>
|
|
27
|
+
> ```bash
|
|
28
|
+
> npx --@calo-design:registry=https://registry.npmjs.org/ @calo-design/cli login
|
|
29
|
+
> ```
|
|
30
|
+
|
|
21
31
|
`init` does two machine-level things every time: it installs the skill into
|
|
22
32
|
`~/.claude/skills/`, and it ensures the shared runtime at `~/.designchef/runtime/` — one
|
|
23
33
|
copy of `@calo/design-system`, `@calo/flows`, Expo and the peers, shared by every
|
package/bin/cli.js
CHANGED
|
@@ -131,12 +131,31 @@ function runtimeExists() {
|
|
|
131
131
|
);
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
// expo-router needs babel-preset-expo (its Babel plugin injects the route require.context);
|
|
135
|
+
// without a babel.config.js, `expo export` produces an empty, routeless app. The shared
|
|
136
|
+
// runtime isn't always scaffolded with one, so guarantee it. Linked prototypes inherit it
|
|
137
|
+
// via writeThinConfigs; push staging backstops it again (see mirror-push.js).
|
|
138
|
+
function ensureBabelConfig(dir) {
|
|
139
|
+
const p = path.join(dir, "babel.config.js");
|
|
140
|
+
if (fs.existsSync(p)) return;
|
|
141
|
+
fs.writeFileSync(p, 'module.exports = (api) => {\n api.cache(true);\n return { presets: ["babel-preset-expo"] };\n};\n');
|
|
142
|
+
}
|
|
143
|
+
|
|
134
144
|
async function ensureRuntime({ force } = {}) {
|
|
135
145
|
const rt = runtimeDir();
|
|
136
|
-
if (!force && runtimeExists()) {
|
|
137
|
-
|
|
146
|
+
if (!force && runtimeExists()) {
|
|
147
|
+
ensureBabelConfig(rt); // backfill: runtimes built before this shipped no babel.config.js
|
|
148
|
+
ok(`shared runtime ready (${tilde(rt)})`);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const scaffolded = fs.existsSync(path.join(rt, "package.json"));
|
|
152
|
+
const repin = force && scaffolded && !has("--clean");
|
|
153
|
+
log(c.b(`\n[runtime] ${repin ? "Re-pinning" : "Building"} the shared Calo runtime${repin ? "" : " (one-time, ~a couple of minutes)"}`));
|
|
138
154
|
fs.mkdirSync(rt, { recursive: true });
|
|
139
|
-
|
|
155
|
+
// Re-pin is NON-destructive: `npm install` updates node_modules in place and aborts
|
|
156
|
+
// cleanly on failure (e.g. a peer conflict), so a working shared runtime is never left
|
|
157
|
+
// wiped. `--clean` opts into a from-scratch rebuild for a genuinely corrupted tree.
|
|
158
|
+
if (force && has("--clean")) {
|
|
140
159
|
fs.rmSync(path.join(rt, "node_modules"), { recursive: true, force: true });
|
|
141
160
|
try { fs.rmSync(runtimeManifestPath()); } catch {}
|
|
142
161
|
}
|
|
@@ -144,11 +163,12 @@ async function ensureRuntime({ force } = {}) {
|
|
|
144
163
|
if (!scaffoldExpoApp(rt)) throw new Error("runtime scaffold failed");
|
|
145
164
|
}
|
|
146
165
|
await installCaloDeps(rt);
|
|
166
|
+
ensureBabelConfig(rt); // a freshly scaffolded runtime may also lack one
|
|
147
167
|
fs.writeFileSync(
|
|
148
168
|
runtimeManifestPath(),
|
|
149
169
|
JSON.stringify({ builtAt: new Date().toISOString(), pkgSpecs: PKG_SPECS, peers: PEERS }, null, 2) + "\n"
|
|
150
170
|
);
|
|
151
|
-
ok(`shared runtime built -> ${tilde(rt)}`);
|
|
171
|
+
ok(`shared runtime ${repin ? "re-pinned" : "built"} -> ${tilde(rt)}`);
|
|
152
172
|
}
|
|
153
173
|
|
|
154
174
|
// ---------------------------------------------------------------- shared helpers
|
|
@@ -199,12 +219,14 @@ function scaffoldExpoApp(destDir = process.cwd()) {
|
|
|
199
219
|
async function installCaloDeps(cwd) {
|
|
200
220
|
// Private @calo/* installs authenticate with a short-lived broker-minted token,
|
|
201
221
|
// injected into git for the npm subprocess only (no gh / PAT / SSH key).
|
|
222
|
+
// --legacy-peer-deps: the Expo/RN dep graph routinely has benign peerOptional conflicts
|
|
223
|
+
// (e.g. overlapping typescript ranges); without it a single conflict ERESOLVEs the whole install.
|
|
202
224
|
const env = gitTokenEnv(await githubToken());
|
|
203
|
-
runWithRetry("npm", ["install", ...PKG_SPECS], 3, { cwd, env });
|
|
225
|
+
runWithRetry("npm", ["install", ...PKG_SPECS, "--legacy-peer-deps"], 3, { cwd, env });
|
|
204
226
|
if (isExpoProject(cwd)) run("npx", ["expo", "install", ...PEERS, "expo-font"], { cwd });
|
|
205
227
|
else {
|
|
206
228
|
warn("non-Expo project — installing latest peers; pin them to match your React Native version if needed.");
|
|
207
|
-
run("npm", ["install", ...PEERS], { cwd });
|
|
229
|
+
run("npm", ["install", ...PEERS, "--legacy-peer-deps"], { cwd });
|
|
208
230
|
}
|
|
209
231
|
}
|
|
210
232
|
|
|
@@ -455,7 +477,10 @@ async function cmdUpdate() {
|
|
|
455
477
|
await ensureLoggedIn();
|
|
456
478
|
await installSkill();
|
|
457
479
|
ok("skill updated to latest");
|
|
458
|
-
|
|
480
|
+
// Re-pin when the runtime is fully present OR merely scaffolded (package.json on disk),
|
|
481
|
+
// so a half-installed runtime self-heals via plain `update` instead of being stranded.
|
|
482
|
+
const scaffolded = fs.existsSync(path.join(runtimeDir(), "package.json"));
|
|
483
|
+
if (runtimeExists() || scaffolded || has("--build")) {
|
|
459
484
|
await ensureRuntime({ force: true });
|
|
460
485
|
ok("shared runtime re-pinned — every linked prototype now uses the latest Calo stack");
|
|
461
486
|
} else {
|
|
@@ -472,6 +497,7 @@ function help() {
|
|
|
472
497
|
${c.dim("init --isolated")} install the Calo stack into THIS folder (no shared runtime)
|
|
473
498
|
${c.dim("init --skip-packages")} skill only (no runtime)
|
|
474
499
|
${c.dim("update")} refresh the skill + re-pin the shared runtime (all linked prototypes float to latest)
|
|
500
|
+
${c.dim(" update --clean")} re-pin with a from-scratch runtime rebuild (only if the tree is corrupted)
|
|
475
501
|
${c.dim("logout")} forget the saved Calo session
|
|
476
502
|
${c.dim("push")} publish THIS prototype to the Calo Mirror (login only — no EAS/Tigris creds needed)
|
|
477
503
|
${c.dim(" push --slug x --title \"…\" --owner \"…\" --screenshot path --dry-run --direct")}
|
package/bin/login.js
CHANGED
|
@@ -179,7 +179,7 @@ async function publishWeb({ slug, project, tarball }) {
|
|
|
179
179
|
const text = await res.text();
|
|
180
180
|
let json;
|
|
181
181
|
try { json = text ? JSON.parse(text) : {}; } catch { json = { raw: text }; }
|
|
182
|
-
if (!res.ok) throw new Error(json.error || `publish-web → HTTP ${res.status}
|
|
182
|
+
if (!res.ok) throw new Error([json.error || `publish-web → HTTP ${res.status}`, json.detail].filter(Boolean).join("\n "));
|
|
183
183
|
return json;
|
|
184
184
|
}
|
|
185
185
|
|
package/bin/mirror-push.js
CHANGED
|
@@ -122,6 +122,18 @@ function writeJSON(p, obj) {
|
|
|
122
122
|
fs.writeFileSync(p, JSON.stringify(obj, null, 2) + "\n");
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
// A babel.config.js with babel-preset-expo MUST exist wherever `expo export` runs. Without
|
|
126
|
+
// it the expo-router Babel plugin never injects the route require.context, so the export is
|
|
127
|
+
// a routeless, empty shell (no screens, no design system, no assets). The shared runtime
|
|
128
|
+
// doesn't always ship one, so guarantee it: prefer the prototype's own config, else default.
|
|
129
|
+
function ensureBabelConfig(dir, fallbackFrom) {
|
|
130
|
+
const dst = path.join(dir, "babel.config.js");
|
|
131
|
+
if (fs.existsSync(dst)) return;
|
|
132
|
+
const fb = fallbackFrom && path.join(fallbackFrom, "babel.config.js");
|
|
133
|
+
if (fb && fs.existsSync(fb)) { fs.cpSync(fb, dst); return; }
|
|
134
|
+
fs.writeFileSync(dst, 'module.exports = (api) => {\n api.cache(true);\n return { presets: ["babel-preset-expo"] };\n};\n');
|
|
135
|
+
}
|
|
136
|
+
|
|
125
137
|
function stageProject({ root, stage, slug, title, ad }) {
|
|
126
138
|
// Copy the author's routes + assets verbatim.
|
|
127
139
|
const stageSrcParent = path.join(stage, path.relative(root, path.dirname(ad)));
|
|
@@ -164,6 +176,9 @@ function stageProject({ root, stage, slug, title, ad }) {
|
|
|
164
176
|
const src = path.join(runtimeDir(), f);
|
|
165
177
|
if (fs.existsSync(src)) fs.cpSync(src, path.join(stage, f));
|
|
166
178
|
}
|
|
179
|
+
// The runtime may not have a babel.config.js; without one the staged export is an empty,
|
|
180
|
+
// routeless app. Backstop it from the prototype's own config, else a default.
|
|
181
|
+
ensureBabelConfig(stage, root);
|
|
167
182
|
|
|
168
183
|
writeMetroConfig(stage);
|
|
169
184
|
linkNodeModules(stage);
|
|
@@ -213,7 +228,7 @@ function injectBackChrome({ stagedAppDir, mirrorChromeDir, libRel }) {
|
|
|
213
228
|
if (fs.existsSync(layout)) {
|
|
214
229
|
const cur = fs.readFileSync(layout, "utf8");
|
|
215
230
|
const standard = /useFonts\(caloFonts\)/.test(cur) && /<Stack/.test(cur);
|
|
216
|
-
if (!standard) warn("custom root _layout detected — Mirror wraps it with a Stack;
|
|
231
|
+
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.");
|
|
217
232
|
}
|
|
218
233
|
fs.writeFileSync(
|
|
219
234
|
layout,
|
package/package.json
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@calo-design/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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
|
-
"bin": {
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
"bin": {
|
|
6
|
+
"calo-design": "bin/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin"
|
|
10
|
+
],
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
8
14
|
"license": "UNLICENSED",
|
|
9
|
-
"engines": {
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
17
|
+
}
|
|
10
18
|
}
|