@calo-design/cli 0.10.0 → 0.12.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/bin/checkpoints.js +4 -3
- package/bin/mirror-push.js +58 -4
- package/bin/share.js +11 -4
- package/package.json +1 -1
package/bin/checkpoints.js
CHANGED
|
@@ -160,15 +160,15 @@ async function cmdSave(args = []) {
|
|
|
160
160
|
* user doing anything. Best-effort by contract — a checkpoint hiccup must never turn a
|
|
161
161
|
* successful deploy into a failed command.
|
|
162
162
|
*/
|
|
163
|
-
async function autoCheckpoint({ root, note, publishedUrl }) {
|
|
163
|
+
async function autoCheckpoint({ root, note, publishedUrl, artifact }) {
|
|
164
164
|
try {
|
|
165
|
-
await saveFlow({ root, note, publishedUrl, force: true, quiet: true });
|
|
165
|
+
await saveFlow({ root, note, publishedUrl, artifact, force: true, quiet: true });
|
|
166
166
|
} catch (e) {
|
|
167
167
|
elog(`${c.y("!")} deploy succeeded, but the source checkpoint didn't: ${e.message}`);
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
async function saveFlow({ root, slugOverride, note = "", force = false, json = false, quiet = false, publishedUrl }) {
|
|
171
|
+
async function saveFlow({ root, slugOverride, note = "", force = false, json = false, quiet = false, publishedUrl, artifact }) {
|
|
172
172
|
if (!fs.existsSync(path.join(root, "package.json"))) {
|
|
173
173
|
throw new Error("no package.json here — run `calo-design save` from a prototype folder");
|
|
174
174
|
}
|
|
@@ -212,6 +212,7 @@ async function saveFlow({ root, slugOverride, note = "", force = false, json = f
|
|
|
212
212
|
...(basedOn != null ? { "x-calo-based-on": String(basedOn) } : {}),
|
|
213
213
|
...(isFork && forkedFrom ? { "x-calo-forked-from": forkedFrom } : {}),
|
|
214
214
|
...(publishedUrl ? { "x-calo-published-url": publishedUrl } : {}),
|
|
215
|
+
...(artifact ? { "x-calo-artifact": encodeURIComponent(JSON.stringify(artifact)) } : {}),
|
|
215
216
|
...(force ? { "x-calo-force": "1" } : {}),
|
|
216
217
|
},
|
|
217
218
|
body: tgz,
|
package/bin/mirror-push.js
CHANGED
|
@@ -93,11 +93,28 @@ let easEnv = process.env;
|
|
|
93
93
|
const easRun = (argv, opts = {}) => { const [b, ...p] = easPrefix(); return run(b, [...p, ...argv], { env: easEnv, ...opts }); };
|
|
94
94
|
const easCapture = (argv, opts = {}) => { const [b, ...p] = easPrefix(); return capture(b, [...p, ...argv], { env: easEnv, ...opts }); };
|
|
95
95
|
|
|
96
|
+
// Pull the EAS update GROUP id (the immutable per-version ref checkpoints record) out
|
|
97
|
+
// of a `capture()` result from `eas update:list --json`. Regex, not JSON.parse: eas
|
|
98
|
+
// output shapes and banner noise vary by version, and this must stay best-effort.
|
|
99
|
+
function extractEasGroup(captured) {
|
|
100
|
+
if (!captured || captured.status !== 0 || typeof captured.out !== "string") return undefined;
|
|
101
|
+
const found = captured.out.match(/"group":\s*"([0-9a-fA-F-]{16,64})"/);
|
|
102
|
+
return found ? found[1] : undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// The per-version EAS branch/channel name. Deterministic from (slug, group) so any
|
|
106
|
+
// party — CLI, broker page, Mirror launcher — can derive it, but it's also stored in
|
|
107
|
+
// artifact.easBranch so the convention can evolve without re-deriving. EAS channel
|
|
108
|
+
// names share the slug charset; slug ≤63 + "--g" + 8 = ≤74, under EAS's limits.
|
|
109
|
+
const versionBranchOf = (slug, group) => `${slug}--g${String(group).slice(0, 8).toLowerCase()}`;
|
|
110
|
+
|
|
96
111
|
function runtimeDir() {
|
|
97
112
|
const home = process.env.DESIGNCHEF_HOME || path.join(os.homedir(), ".designchef");
|
|
98
113
|
return path.join(home, "runtime");
|
|
99
114
|
}
|
|
100
|
-
|
|
115
|
+
// Capped at 63 (SLUG_RE's bound everywhere else) — an uncapped slug would silently
|
|
116
|
+
// break the checkpoint stamp and overflow EAS channel-name limits on version pins.
|
|
117
|
+
const slugify = (s) => String(s).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 63).replace(/-+$/, "") || "prototype";
|
|
101
118
|
const titleize = (s) => String(s).replace(/[-_]+/g, " ").replace(/\b\w/g, (m) => m.toUpperCase());
|
|
102
119
|
|
|
103
120
|
function appDir(root) {
|
|
@@ -839,12 +856,49 @@ async function cmdPush(args) {
|
|
|
839
856
|
log(c.b("\n✨ Live in the Mirror.") + " Open Calo Mirror and tap Refresh — your prototype is at the top.");
|
|
840
857
|
|
|
841
858
|
// Deploy-stamped versioning: every successful push leaves a source checkpoint, so
|
|
842
|
-
// the thing on the phone and its exact source are permanently paired.
|
|
843
|
-
//
|
|
859
|
+
// the thing on the phone and its exact source are permanently paired. The artifact
|
|
860
|
+
// records the EAS update GROUP id — the immutable per-version bundle — so a future
|
|
861
|
+
// Mirror shell can load exactly this version (channel head keeps moving). Group id
|
|
862
|
+
// capture is best-effort (a quick update:list after the publish); so is the whole
|
|
863
|
+
// stamp — never fails the push. (Lazy require: mirror-push is also loaded by art-push.)
|
|
864
|
+
let easGroup;
|
|
865
|
+
let easBranch;
|
|
866
|
+
try {
|
|
867
|
+
// easCapture → { status, out }. Tolerant extraction: eas --json shapes (and
|
|
868
|
+
// banner noise) vary by version, so regex the output for the first uuid-shaped
|
|
869
|
+
// `group` value instead of parsing. Known race, accepted as best-effort: this
|
|
870
|
+
// lists the branch head, so a concurrent push to the same slug in this window
|
|
871
|
+
// could stamp the other push's group — rare, and a stamp is advisory metadata.
|
|
872
|
+
easGroup = extractEasGroup(easCapture(["update:list", "--branch", slug, "--limit", "1", "--json", "--non-interactive"], { cwd: stage }));
|
|
873
|
+
if (easGroup) {
|
|
874
|
+
// Pin this exact version behind its own channel: `slug--g<group[:8]>`.
|
|
875
|
+
// republish is a metadata copy (no rebundle); ensureChannel links channel →
|
|
876
|
+
// branch of the same name, which is what the Mirror's channel-name header
|
|
877
|
+
// switch loads. This is what makes "open v3 on your phone" possible after
|
|
878
|
+
// the channel head (slug) has moved on. Best-effort like the whole stamp.
|
|
879
|
+
const vb = versionBranchOf(slug, easGroup);
|
|
880
|
+
ensureChannel(stage, vb);
|
|
881
|
+
const rp = easCapture(
|
|
882
|
+
["update:republish", "--group", easGroup, "--destination-branch", vb, "-m", `pin ${slug} @ ${easGroup.slice(0, 8)}`, "--non-interactive"],
|
|
883
|
+
{ cwd: stage }
|
|
884
|
+
);
|
|
885
|
+
if (rp.status === 0) easBranch = vb;
|
|
886
|
+
else warn(`version pin skipped (republish → ${rp.out.trim().split("\n").slice(-1)[0] || rp.status})`);
|
|
887
|
+
}
|
|
888
|
+
} catch {
|
|
889
|
+
/* no artifact ref — the stamp still records source + deep link */
|
|
890
|
+
}
|
|
844
891
|
await require("./checkpoints").autoCheckpoint({
|
|
845
892
|
root: process.cwd(),
|
|
846
893
|
note: flag(args, "note") || `pushed to the Mirror (${slug})`,
|
|
847
894
|
publishedUrl: `designchef://open/${slug}`,
|
|
895
|
+
artifact: {
|
|
896
|
+
kind: "mirror",
|
|
897
|
+
channel: slug,
|
|
898
|
+
runtimeVersion: RUNTIME_VERSION,
|
|
899
|
+
...(easGroup ? { easGroup } : {}),
|
|
900
|
+
...(easBranch ? { easBranch } : {}),
|
|
901
|
+
},
|
|
848
902
|
});
|
|
849
903
|
|
|
850
904
|
// Share target: the deep link the Mirror scanner (and iOS camera) opens.
|
|
@@ -865,4 +919,4 @@ async function cmdPush(args) {
|
|
|
865
919
|
}
|
|
866
920
|
}
|
|
867
921
|
|
|
868
|
-
module.exports = { cmdPush, _s3Put: s3Put, _upsertRegistry: upsertRegistry, _publicBase: PUBLIC_BASE };
|
|
922
|
+
module.exports = { cmdPush, _s3Put: s3Put, _upsertRegistry: upsertRegistry, _publicBase: PUBLIC_BASE, _extractEasGroup: extractEasGroup, _versionBranchOf: versionBranchOf };
|
package/bin/share.js
CHANGED
|
@@ -168,10 +168,17 @@ async function cmdShare(args = []) {
|
|
|
168
168
|
log(c.dim(` Re-run \`calo-design share\` to update “${slug}” in place. Share the link above.`));
|
|
169
169
|
}
|
|
170
170
|
if (r.version) log(c.dim(` this version: ${r.version}`));
|
|
171
|
-
// Deploy-stamped versioning: every successful share leaves a source checkpoint
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
|
|
171
|
+
// Deploy-stamped versioning: every successful share leaves a source checkpoint, so
|
|
172
|
+
// the deployed thing and its exact source are permanently paired. publishedUrl
|
|
173
|
+
// prefers the IMMUTABLE per-deploy URL (Cloudflare Pages keeps every deployment
|
|
174
|
+
// forever) so the version page opens exactly this version, not whatever the alias
|
|
175
|
+
// points at by then; the artifact keeps both. Best-effort — never fails the share.
|
|
176
|
+
await autoCheckpoint({
|
|
177
|
+
root,
|
|
178
|
+
note: flag(args, "note") || `shared to the web (${slug})`,
|
|
179
|
+
publishedUrl: r.version || url,
|
|
180
|
+
artifact: { kind: "web", aliasUrl: url, ...(r.version ? { deployUrl: r.version } : {}) },
|
|
181
|
+
});
|
|
175
182
|
} finally {
|
|
176
183
|
try { fs.rmSync(tgz); } catch {}
|
|
177
184
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@calo-design/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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"
|