@zalify/cli 0.2.4 → 0.3.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/dist/cli.js +138 -34
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -11308,11 +11308,35 @@ import { homedir } from "node:os";
|
|
|
11308
11308
|
import { join } from "node:path";
|
|
11309
11309
|
var dir = join(homedir(), ".zalify");
|
|
11310
11310
|
var file = join(dir, "config.json");
|
|
11311
|
+
function migrate(raw) {
|
|
11312
|
+
if (!raw || typeof raw !== "object")
|
|
11313
|
+
return null;
|
|
11314
|
+
const obj = raw;
|
|
11315
|
+
if (obj.version === 2 && obj.workspaces && obj.appUrl) {
|
|
11316
|
+
return obj;
|
|
11317
|
+
}
|
|
11318
|
+
if (typeof obj.key === "string" && typeof obj.workspaceId === "string") {
|
|
11319
|
+
return {
|
|
11320
|
+
version: 2,
|
|
11321
|
+
appUrl: obj.appUrl ?? "https://app.zalify.com",
|
|
11322
|
+
activeWorkspaceId: obj.workspaceId,
|
|
11323
|
+
workspaces: {
|
|
11324
|
+
[obj.workspaceId]: {
|
|
11325
|
+
key: obj.key,
|
|
11326
|
+
name: obj.workspaceName ?? "",
|
|
11327
|
+
slug: obj.workspaceSlug ?? "",
|
|
11328
|
+
createdAt: obj.createdAt ?? new Date().toISOString()
|
|
11329
|
+
}
|
|
11330
|
+
}
|
|
11331
|
+
};
|
|
11332
|
+
}
|
|
11333
|
+
return null;
|
|
11334
|
+
}
|
|
11311
11335
|
function readConfig() {
|
|
11312
11336
|
if (!existsSync(file))
|
|
11313
11337
|
return null;
|
|
11314
11338
|
try {
|
|
11315
|
-
return JSON.parse(readFileSync(file, "utf8"));
|
|
11339
|
+
return migrate(JSON.parse(readFileSync(file, "utf8")));
|
|
11316
11340
|
} catch {
|
|
11317
11341
|
return null;
|
|
11318
11342
|
}
|
|
@@ -11325,12 +11349,13 @@ function writeConfig(config) {
|
|
|
11325
11349
|
function deleteConfig() {
|
|
11326
11350
|
rmSync(file, { force: true });
|
|
11327
11351
|
}
|
|
11328
|
-
function
|
|
11352
|
+
function requireActive() {
|
|
11329
11353
|
const config = readConfig();
|
|
11330
|
-
if (!config
|
|
11354
|
+
if (!config || Object.keys(config.workspaces).length === 0) {
|
|
11331
11355
|
throw new Error("Not logged in. Run `zalify login` first.");
|
|
11332
11356
|
}
|
|
11333
|
-
|
|
11357
|
+
const workspaceId = config.activeWorkspaceId && config.workspaces[config.activeWorkspaceId] ? config.activeWorkspaceId : Object.keys(config.workspaces)[0];
|
|
11358
|
+
return { appUrl: config.appUrl, workspaceId, entry: config.workspaces[workspaceId], config };
|
|
11334
11359
|
}
|
|
11335
11360
|
|
|
11336
11361
|
// src/auth.ts
|
|
@@ -11346,10 +11371,14 @@ function openBrowser(url) {
|
|
|
11346
11371
|
detached: true
|
|
11347
11372
|
}).unref();
|
|
11348
11373
|
}
|
|
11374
|
+
function decodePayload(encoded) {
|
|
11375
|
+
const b64 = encoded.replace(/-/g, "+").replace(/_/g, "/");
|
|
11376
|
+
return JSON.parse(Buffer.from(b64, "base64").toString("utf8"));
|
|
11377
|
+
}
|
|
11349
11378
|
async function login(options) {
|
|
11350
11379
|
const base = (options.appUrl || readConfig()?.appUrl || DEFAULT_APP_URL).replace(/\/$/, "");
|
|
11351
11380
|
const state = randomBytes(32).toString("hex");
|
|
11352
|
-
const
|
|
11381
|
+
const payload = await new Promise((resolvePromise, reject) => {
|
|
11353
11382
|
const timer = setTimeout(() => {
|
|
11354
11383
|
server.close();
|
|
11355
11384
|
server.closeAllConnections();
|
|
@@ -11387,22 +11416,38 @@ async function login(options) {
|
|
|
11387
11416
|
});
|
|
11388
11417
|
return;
|
|
11389
11418
|
}
|
|
11390
|
-
const
|
|
11391
|
-
|
|
11392
|
-
|
|
11419
|
+
const encoded = url.searchParams.get("payload");
|
|
11420
|
+
const legacyKey = url.searchParams.get("key");
|
|
11421
|
+
let parsed = null;
|
|
11422
|
+
if (encoded) {
|
|
11423
|
+
try {
|
|
11424
|
+
parsed = decodePayload(encoded);
|
|
11425
|
+
} catch {
|
|
11426
|
+
parsed = null;
|
|
11427
|
+
}
|
|
11428
|
+
} else if (legacyKey) {
|
|
11429
|
+
parsed = {
|
|
11430
|
+
keys: [
|
|
11431
|
+
{
|
|
11432
|
+
workspaceId: url.searchParams.get("workspace_id") ?? "",
|
|
11433
|
+
workspaceName: url.searchParams.get("workspace_name") ?? "",
|
|
11434
|
+
workspaceSlug: url.searchParams.get("workspace_slug") ?? "",
|
|
11435
|
+
key: legacyKey
|
|
11436
|
+
}
|
|
11437
|
+
]
|
|
11438
|
+
};
|
|
11439
|
+
}
|
|
11440
|
+
if (!parsed || parsed.keys.length === 0) {
|
|
11441
|
+
done("Login failed: no keys in callback.", () => {
|
|
11393
11442
|
teardown();
|
|
11394
|
-
reject(new Error("Callback did not include
|
|
11443
|
+
reject(new Error("Callback did not include any keys."));
|
|
11395
11444
|
});
|
|
11396
11445
|
return;
|
|
11397
11446
|
}
|
|
11398
|
-
|
|
11447
|
+
const result = parsed;
|
|
11448
|
+
done(`Logged in to ${result.keys.length} workspace${result.keys.length === 1 ? "" : "s"}!`, () => {
|
|
11399
11449
|
teardown();
|
|
11400
|
-
resolvePromise(
|
|
11401
|
-
key,
|
|
11402
|
-
workspaceId: url.searchParams.get("workspace_id") ?? "",
|
|
11403
|
-
workspaceName: url.searchParams.get("workspace_name") ?? "",
|
|
11404
|
-
workspaceSlug: url.searchParams.get("workspace_slug") ?? ""
|
|
11405
|
-
});
|
|
11450
|
+
resolvePromise(result);
|
|
11406
11451
|
});
|
|
11407
11452
|
});
|
|
11408
11453
|
server.listen(0, "127.0.0.1", () => {
|
|
@@ -11416,13 +11461,40 @@ async function login(options) {
|
|
|
11416
11461
|
});
|
|
11417
11462
|
server.on("error", reject);
|
|
11418
11463
|
});
|
|
11419
|
-
|
|
11420
|
-
|
|
11464
|
+
const existing = readConfig();
|
|
11465
|
+
const config = {
|
|
11466
|
+
version: 2,
|
|
11467
|
+
appUrl: base,
|
|
11468
|
+
activeWorkspaceId: existing?.activeWorkspaceId ?? null,
|
|
11469
|
+
workspaces: { ...existing?.appUrl === base ? existing?.workspaces : {} }
|
|
11470
|
+
};
|
|
11471
|
+
const now = new Date().toISOString();
|
|
11472
|
+
for (const k of payload.keys) {
|
|
11473
|
+
config.workspaces[k.workspaceId] = {
|
|
11474
|
+
key: k.key,
|
|
11475
|
+
name: k.workspaceName,
|
|
11476
|
+
slug: k.workspaceSlug,
|
|
11477
|
+
createdAt: now
|
|
11478
|
+
};
|
|
11479
|
+
}
|
|
11480
|
+
if (!config.activeWorkspaceId || !config.workspaces[config.activeWorkspaceId]) {
|
|
11481
|
+
config.activeWorkspaceId = payload.keys[0].workspaceId;
|
|
11482
|
+
}
|
|
11483
|
+
writeConfig(config);
|
|
11484
|
+
console.log(`✓ Authorized ${payload.keys.length} workspace(s):`);
|
|
11485
|
+
for (const k of payload.keys) {
|
|
11486
|
+
const active = k.workspaceId === config.activeWorkspaceId ? " (active)" : "";
|
|
11487
|
+
console.log(` - ${k.workspaceName} (${k.workspaceSlug})${active}`);
|
|
11488
|
+
}
|
|
11489
|
+
for (const f of payload.failed ?? []) {
|
|
11490
|
+
console.log(` ✗ ${f.workspaceName}: ${f.error}`);
|
|
11491
|
+
}
|
|
11492
|
+
console.log("Switch with `zalify workspace set <slug>`.");
|
|
11421
11493
|
}
|
|
11422
11494
|
function logout() {
|
|
11423
11495
|
deleteConfig();
|
|
11424
11496
|
console.log("✓ Logged out (local credentials deleted).");
|
|
11425
|
-
console.log(" To revoke
|
|
11497
|
+
console.log(" To revoke keys: each workspace's Settings → Developer on app.zalify.com.");
|
|
11426
11498
|
}
|
|
11427
11499
|
function ago(iso) {
|
|
11428
11500
|
if (!iso)
|
|
@@ -11439,12 +11511,12 @@ function ago(iso) {
|
|
|
11439
11511
|
return `${Math.round(hours / 24)}d ago`;
|
|
11440
11512
|
}
|
|
11441
11513
|
async function whoami() {
|
|
11442
|
-
const config =
|
|
11514
|
+
const { appUrl, workspaceId, entry, config } = requireActive();
|
|
11443
11515
|
let live = null;
|
|
11444
11516
|
let liveError = null;
|
|
11445
11517
|
try {
|
|
11446
|
-
const res = await fetch(`${
|
|
11447
|
-
headers: { Authorization: `Bearer ${
|
|
11518
|
+
const res = await fetch(`${appUrl}/api/cli/whoami`, {
|
|
11519
|
+
headers: { Authorization: `Bearer ${entry.key}` }
|
|
11448
11520
|
});
|
|
11449
11521
|
if (res.ok) {
|
|
11450
11522
|
live = await res.json();
|
|
@@ -11457,18 +11529,19 @@ async function whoami() {
|
|
|
11457
11529
|
liveError = "could not reach the server (offline?)";
|
|
11458
11530
|
}
|
|
11459
11531
|
const ws = live?.workspace;
|
|
11460
|
-
|
|
11532
|
+
const total = Object.keys(config.workspaces).length;
|
|
11533
|
+
console.log(`workspace ${ws?.name ?? entry.name ?? "(unknown)"} (${ws?.slug ?? entry.slug ?? "?"}) [${ws?.id ?? workspaceId}]` + (total > 1 ? ` — 1 of ${total}, see \`zalify workspace list\`` : ""));
|
|
11461
11534
|
if (live) {
|
|
11462
11535
|
console.log(`plan ${live.plan.name} (${live.plan.type})`);
|
|
11463
11536
|
if (live.key.createdByName) {
|
|
11464
11537
|
console.log(`user ${live.key.createdByName}`);
|
|
11465
11538
|
}
|
|
11466
|
-
console.log(`key ${live.key.name ??
|
|
11539
|
+
console.log(`key ${live.key.name ?? entry.key.slice(0, 16) + "…"} — created ${live.key.createdAt ? new Date(live.key.createdAt).toISOString().slice(0, 10) : "?"}, last used ${ago(live.key.lastRequest)}`);
|
|
11467
11540
|
} else {
|
|
11468
|
-
console.log(`key ${
|
|
11541
|
+
console.log(`key ${entry.key.slice(0, 16)}…`);
|
|
11469
11542
|
}
|
|
11470
|
-
const slug = ws?.slug ??
|
|
11471
|
-
console.log(`app ${slug ? `${
|
|
11543
|
+
const slug = ws?.slug ?? entry.slug;
|
|
11544
|
+
console.log(`app ${slug ? `${appUrl}/store/${slug}` : appUrl}`);
|
|
11472
11545
|
console.log(live ? "status ✓ key verified" : `status ! not verified — ${liveError}`);
|
|
11473
11546
|
}
|
|
11474
11547
|
|
|
@@ -11513,23 +11586,23 @@ async function assetsPull(storeDir) {
|
|
|
11513
11586
|
console.log(`Done: ${pulled} downloaded, ${Object.keys(index).length - pulled} already local.`);
|
|
11514
11587
|
}
|
|
11515
11588
|
async function assetsPush(storeDir) {
|
|
11516
|
-
const
|
|
11589
|
+
const { appUrl, workspaceId, entry } = requireActive();
|
|
11517
11590
|
const imagesDir = imagesDirFor(storeDir);
|
|
11518
11591
|
const indexPath = join2(imagesDir, "assets.json");
|
|
11519
11592
|
const index = readIndex(imagesDir);
|
|
11520
11593
|
async function api(path9, body) {
|
|
11521
|
-
const res = await fetch(`${
|
|
11594
|
+
const res = await fetch(`${appUrl}${path9}`, {
|
|
11522
11595
|
method: "POST",
|
|
11523
11596
|
headers: {
|
|
11524
11597
|
"Content-Type": "application/json",
|
|
11525
|
-
Authorization: `Bearer ${
|
|
11598
|
+
Authorization: `Bearer ${entry.key}`
|
|
11526
11599
|
},
|
|
11527
11600
|
body: JSON.stringify(body)
|
|
11528
11601
|
});
|
|
11529
11602
|
const json = await res.json().catch(() => ({}));
|
|
11530
11603
|
if (!res.ok) {
|
|
11531
11604
|
if (json.code === "UPGRADE_REQUIRED") {
|
|
11532
|
-
const billingUrl =
|
|
11605
|
+
const billingUrl = entry.slug ? `${appUrl}/store/${entry.slug}/settings/billing` : `${appUrl} (Settings → Billing)`;
|
|
11533
11606
|
throw new Error(`${json.error ?? "This feature requires a paid plan."}
|
|
11534
11607
|
` + ` Upgrade this workspace: ${billingUrl}`);
|
|
11535
11608
|
}
|
|
@@ -11545,11 +11618,11 @@ async function assetsPush(storeDir) {
|
|
|
11545
11618
|
console.log("Everything already pushed.");
|
|
11546
11619
|
return;
|
|
11547
11620
|
}
|
|
11548
|
-
console.log(`Pushing ${files.length} file(s) to "${
|
|
11621
|
+
console.log(`Pushing ${files.length} file(s) to "${entry.name || workspaceId}"`);
|
|
11549
11622
|
for (let i = 0;i < files.length; i += BATCH) {
|
|
11550
11623
|
const batch = files.slice(i, i + BATCH);
|
|
11551
11624
|
const { uploads } = await api("/api/assets/presign", {
|
|
11552
|
-
workspaceId
|
|
11625
|
+
workspaceId,
|
|
11553
11626
|
folderId: process.env.ZALIFY_FOLDER_ID ?? null,
|
|
11554
11627
|
files: batch.map((f) => ({
|
|
11555
11628
|
id: f.name,
|
|
@@ -11589,7 +11662,7 @@ async function assetsPush(storeDir) {
|
|
|
11589
11662
|
}
|
|
11590
11663
|
if (toFinalize.length) {
|
|
11591
11664
|
const { assets, pending } = await api("/api/assets/finalize", {
|
|
11592
|
-
workspaceId
|
|
11665
|
+
workspaceId,
|
|
11593
11666
|
items: toFinalize.map(({ file: file2, up }) => ({
|
|
11594
11667
|
assetId: up.assetId,
|
|
11595
11668
|
checksum: file2.checksum,
|
|
@@ -11648,6 +11721,34 @@ Try manually: ${cmd} ${args.join(" ")}`);
|
|
|
11648
11721
|
✓ Done${updated ? ` (version: ${updated})` : ""}`);
|
|
11649
11722
|
}
|
|
11650
11723
|
|
|
11724
|
+
// src/workspace.ts
|
|
11725
|
+
function workspaceList() {
|
|
11726
|
+
const config = readConfig();
|
|
11727
|
+
const entries = Object.entries(config?.workspaces ?? {});
|
|
11728
|
+
if (!config || entries.length === 0) {
|
|
11729
|
+
throw new Error("Not logged in. Run `zalify login` first.");
|
|
11730
|
+
}
|
|
11731
|
+
for (const [id, w] of entries) {
|
|
11732
|
+
const marker = id === config.activeWorkspaceId ? "* " : " ";
|
|
11733
|
+
console.log(`${marker}${w.slug.padEnd(36)} ${w.name} [${id}]`);
|
|
11734
|
+
}
|
|
11735
|
+
}
|
|
11736
|
+
function workspaceSet(slugOrId) {
|
|
11737
|
+
const config = readConfig();
|
|
11738
|
+
if (!config || Object.keys(config.workspaces).length === 0) {
|
|
11739
|
+
throw new Error("Not logged in. Run `zalify login` first.");
|
|
11740
|
+
}
|
|
11741
|
+
const match = Object.entries(config.workspaces).find(([id, w]) => id === slugOrId || w.slug === slugOrId || w.name === slugOrId);
|
|
11742
|
+
if (!match) {
|
|
11743
|
+
const known = Object.values(config.workspaces).map((w) => w.slug).join(", ");
|
|
11744
|
+
throw new Error(`No authorized workspace matches "${slugOrId}". Known: ${known}.
|
|
11745
|
+
` + "Run `zalify login` to authorize more workspaces.");
|
|
11746
|
+
}
|
|
11747
|
+
config.activeWorkspaceId = match[0];
|
|
11748
|
+
writeConfig(config);
|
|
11749
|
+
console.log(`✓ Active workspace: ${match[1].name} (${match[1].slug})`);
|
|
11750
|
+
}
|
|
11751
|
+
|
|
11651
11752
|
// src/cli.ts
|
|
11652
11753
|
var __dirname4 = dirname(fileURLToPath3(import.meta.url));
|
|
11653
11754
|
var require2 = createRequire2(import.meta.url);
|
|
@@ -11688,6 +11789,9 @@ program2.command("logout").description("Delete the stored CLI credentials").acti
|
|
|
11688
11789
|
program2.command("whoami").description("Verify the stored key and show workspace, plan, and key details").action(async () => {
|
|
11689
11790
|
await whoami();
|
|
11690
11791
|
});
|
|
11792
|
+
var workspace = program2.command("workspace").description("List authorized workspaces or switch the active one");
|
|
11793
|
+
workspace.command("list", { isDefault: true }).description("List authorized workspaces (* = active)").action(() => workspaceList());
|
|
11794
|
+
workspace.command("set <slug-or-id>").description("Switch the active workspace (local, no re-login)").action((slugOrId) => workspaceSet(slugOrId));
|
|
11691
11795
|
var assets = program2.command("assets").description("Sync images with the Zalify asset library");
|
|
11692
11796
|
assets.command("push <store-dir>").description("Upload <store-dir>/images/*.png (sha256 dedup, writes assets.json)").action(async (storeDir) => {
|
|
11693
11797
|
await assetsPush(storeDir);
|