@zalify/cli 0.1.4 → 0.2.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 +44 -12
- package/dist/cli.js +306 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -12,14 +12,42 @@ npm i -g @zalify/cli@latest
|
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
15
|
+
### Authentication
|
|
16
|
+
|
|
15
17
|
```bash
|
|
16
|
-
zalify
|
|
17
|
-
zalify -
|
|
18
|
-
zalify
|
|
18
|
+
zalify login # opens the browser: pick a workspace, authorize
|
|
19
|
+
zalify login --app-url http://localhost:3000 # against a local app.zalify.com
|
|
20
|
+
zalify whoami # show the authorized workspace
|
|
21
|
+
zalify logout # delete local credentials
|
|
19
22
|
```
|
|
20
23
|
|
|
24
|
+
`login` runs a loopback flow (like `gh auth login`): the CLI listens on a
|
|
25
|
+
random localhost port, the browser opens `app.zalify.com/cli/auth`, and on
|
|
26
|
+
approval a **workspace-scoped API key** is handed back to the CLI and stored
|
|
27
|
+
in `~/.zalify/config.json` (0600). Keys are ordinary Zalify Public API keys —
|
|
28
|
+
list and revoke them in **Settings → Developer**.
|
|
29
|
+
|
|
30
|
+
### Assets
|
|
31
|
+
|
|
32
|
+
Sync a directory's `images/*.png` with the workspace asset library
|
|
33
|
+
(the same storage that backs Assets in the Zalify app):
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
zalify assets push <dir> # upload new/changed files (sha256 dedup)
|
|
37
|
+
zalify assets pull <dir> # download files listed in images/assets.json
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`push` writes `<dir>/images/assets.json` — a committed index mapping each
|
|
41
|
+
file to its asset id, public URL, and checksum. `pull` restores the binaries
|
|
42
|
+
on a fresh clone, so image masters stay out of git.
|
|
43
|
+
|
|
21
44
|
## Commands
|
|
22
45
|
|
|
46
|
+
- **login** `[--app-url <url>]` – Browser-based authorization; stores a workspace API key locally.
|
|
47
|
+
- **logout** – Deletes `~/.zalify/config.json` (revoke the key itself in Settings → Developer).
|
|
48
|
+
- **whoami** – Shows the authorized workspace, app origin, and key prefix.
|
|
49
|
+
- **assets push `<dir>`** – Uploads `<dir>/images/*.png` via presigned R2 PUTs; sha256 dedup; updates `assets.json`.
|
|
50
|
+
- **assets pull `<dir>`** – Downloads files listed in `<dir>/images/assets.json` that are missing locally.
|
|
23
51
|
- **version** – Prints `Zalify CLI version: x.x.x` and, if a newer version exists on npm, suggests running `npm i -g @zalify/cli@latest` to update.
|
|
24
52
|
|
|
25
53
|
## Development
|
|
@@ -35,17 +63,21 @@ bun run typecheck # Type-check only
|
|
|
35
63
|
|
|
36
64
|
## Publishing
|
|
37
65
|
|
|
38
|
-
|
|
66
|
+
CI publishes automatically (`.github/workflows/publish.yml`): pushing to
|
|
67
|
+
`main` with a `package.json` version that isn't on npm yet typechecks,
|
|
68
|
+
builds, publishes `@zalify/cli`, and tags the commit `v<version>`. The
|
|
69
|
+
`zalify/` wrapper (for `npm i -g zalify`) is published in the same run
|
|
70
|
+
whenever its own version is new. Auth is npm **Trusted Publishing** (OIDC,
|
|
71
|
+
no token secret): one-time setup per package on npmjs.com — package →
|
|
72
|
+
Settings → Publishing access → Add trusted publisher → GitHub Actions →
|
|
73
|
+
repo `zalify/zalify-cli`, workflow `publish.yml`.
|
|
39
74
|
|
|
40
|
-
Release
|
|
75
|
+
Release flow:
|
|
41
76
|
|
|
42
77
|
```bash
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
bun run release -- -mi # Minor (x.X.0)
|
|
46
|
-
bun run release -- -ma # Major (X.0.0)
|
|
78
|
+
npm version minor --no-git-tag-version # or patch/major
|
|
79
|
+
git commit -am "chore: release v0.x.0" && git push
|
|
47
80
|
```
|
|
48
81
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
To allow `npm i -g zalify`, publish the wrapper package after releasing `@zalify/cli`: from the repo root run `npm publish --access public` in the `zalify/` folder (it depends on `@zalify/cli` and exposes the same binary).
|
|
82
|
+
`bun run release` (bump + local `npm publish`, needs `NODE_AUTH_TOKEN` in
|
|
83
|
+
`.env`) remains as a manual fallback if CI is unavailable.
|
package/dist/cli.js
CHANGED
|
@@ -5,15 +5,29 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
function __accessProp(key) {
|
|
9
|
+
return this[key];
|
|
10
|
+
}
|
|
11
|
+
var __toESMCache_node;
|
|
12
|
+
var __toESMCache_esm;
|
|
8
13
|
var __toESM = (mod, isNodeMode, target) => {
|
|
14
|
+
var canCache = mod != null && typeof mod === "object";
|
|
15
|
+
if (canCache) {
|
|
16
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
17
|
+
var cached = cache.get(mod);
|
|
18
|
+
if (cached)
|
|
19
|
+
return cached;
|
|
20
|
+
}
|
|
9
21
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
22
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
23
|
for (let key of __getOwnPropNames(mod))
|
|
12
24
|
if (!__hasOwnProp.call(to, key))
|
|
13
25
|
__defProp(to, key, {
|
|
14
|
-
get: (
|
|
26
|
+
get: __accessProp.bind(mod, key),
|
|
15
27
|
enumerable: true
|
|
16
28
|
});
|
|
29
|
+
if (canCache)
|
|
30
|
+
cache.set(mod, to);
|
|
17
31
|
return to;
|
|
18
32
|
};
|
|
19
33
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
@@ -991,7 +1005,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
991
1005
|
this._exitCallback = (err) => {
|
|
992
1006
|
if (err.code !== "commander.executeSubCommandAsync") {
|
|
993
1007
|
throw err;
|
|
994
|
-
}
|
|
1008
|
+
}
|
|
995
1009
|
};
|
|
996
1010
|
}
|
|
997
1011
|
return this;
|
|
@@ -5154,7 +5168,7 @@ var require_ini2 = __commonJS((exports) => {
|
|
|
5154
5168
|
|
|
5155
5169
|
// node_modules/config-chain/index.js
|
|
5156
5170
|
var require_config_chain = __commonJS((exports, module) => {
|
|
5157
|
-
var __dirname = "/
|
|
5171
|
+
var __dirname = "/home/runner/work/zalify-cli/zalify-cli/node_modules/config-chain";
|
|
5158
5172
|
var ProtoList = require_proto_list();
|
|
5159
5173
|
var path5 = __require("path");
|
|
5160
5174
|
var fs3 = __require("fs");
|
|
@@ -11279,22 +11293,307 @@ function updateNotifier(options) {
|
|
|
11279
11293
|
|
|
11280
11294
|
// src/cli.ts
|
|
11281
11295
|
import { createRequire as createRequire2 } from "node:module";
|
|
11282
|
-
import { dirname, join } from "node:path";
|
|
11296
|
+
import { dirname, join as join3 } from "node:path";
|
|
11283
11297
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
11298
|
+
|
|
11299
|
+
// src/auth.ts
|
|
11300
|
+
import { randomBytes } from "node:crypto";
|
|
11301
|
+
import { createServer } from "node:http";
|
|
11302
|
+
import { hostname } from "node:os";
|
|
11303
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
11304
|
+
|
|
11305
|
+
// src/config.ts
|
|
11306
|
+
import { readFileSync, writeFileSync as writeFileSync2, mkdirSync, rmSync, existsSync } from "node:fs";
|
|
11307
|
+
import { homedir } from "node:os";
|
|
11308
|
+
import { join } from "node:path";
|
|
11309
|
+
var dir = join(homedir(), ".zalify");
|
|
11310
|
+
var file = join(dir, "config.json");
|
|
11311
|
+
function readConfig() {
|
|
11312
|
+
if (!existsSync(file))
|
|
11313
|
+
return null;
|
|
11314
|
+
try {
|
|
11315
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
11316
|
+
} catch {
|
|
11317
|
+
return null;
|
|
11318
|
+
}
|
|
11319
|
+
}
|
|
11320
|
+
function writeConfig(config) {
|
|
11321
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
11322
|
+
writeFileSync2(file, JSON.stringify(config, null, 2) + `
|
|
11323
|
+
`, { mode: 384 });
|
|
11324
|
+
}
|
|
11325
|
+
function deleteConfig() {
|
|
11326
|
+
rmSync(file, { force: true });
|
|
11327
|
+
}
|
|
11328
|
+
function requireConfig() {
|
|
11329
|
+
const config = readConfig();
|
|
11330
|
+
if (!config?.key) {
|
|
11331
|
+
throw new Error("Not logged in. Run `zalify login` first.");
|
|
11332
|
+
}
|
|
11333
|
+
return config;
|
|
11334
|
+
}
|
|
11335
|
+
|
|
11336
|
+
// src/auth.ts
|
|
11337
|
+
var DEFAULT_APP_URL = "https://app.zalify.com";
|
|
11338
|
+
var LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
|
|
11339
|
+
function openBrowser(url) {
|
|
11340
|
+
if (process.env.ZALIFY_NO_BROWSER)
|
|
11341
|
+
return;
|
|
11342
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
11343
|
+
spawn2(cmd, [url], {
|
|
11344
|
+
shell: process.platform === "win32",
|
|
11345
|
+
stdio: "ignore",
|
|
11346
|
+
detached: true
|
|
11347
|
+
}).unref();
|
|
11348
|
+
}
|
|
11349
|
+
async function login(options) {
|
|
11350
|
+
const base = (options.appUrl || readConfig()?.appUrl || DEFAULT_APP_URL).replace(/\/$/, "");
|
|
11351
|
+
const state = randomBytes(32).toString("hex");
|
|
11352
|
+
const result = await new Promise((resolvePromise, reject) => {
|
|
11353
|
+
const timer = setTimeout(() => {
|
|
11354
|
+
server.close();
|
|
11355
|
+
reject(new Error("Login timed out after 5 minutes."));
|
|
11356
|
+
}, LOGIN_TIMEOUT_MS);
|
|
11357
|
+
const server = createServer((req, res) => {
|
|
11358
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
11359
|
+
if (url.pathname !== "/callback") {
|
|
11360
|
+
res.writeHead(404).end();
|
|
11361
|
+
return;
|
|
11362
|
+
}
|
|
11363
|
+
const done = (message) => {
|
|
11364
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
11365
|
+
res.end(`<!doctype html><meta charset="utf-8"><title>Zalify CLI</title>
|
|
11366
|
+
<body style="font-family:system-ui;display:grid;place-items:center;height:100vh;margin:0">
|
|
11367
|
+
<p>${message} You can close this tab.</p></body>`);
|
|
11368
|
+
};
|
|
11369
|
+
if (url.searchParams.get("state") !== state) {
|
|
11370
|
+
done("Login failed: state mismatch.");
|
|
11371
|
+
return;
|
|
11372
|
+
}
|
|
11373
|
+
clearTimeout(timer);
|
|
11374
|
+
const error = url.searchParams.get("error");
|
|
11375
|
+
if (error) {
|
|
11376
|
+
done("Authorization was denied.");
|
|
11377
|
+
server.close();
|
|
11378
|
+
reject(new Error(`Authorization denied (${error}).`));
|
|
11379
|
+
return;
|
|
11380
|
+
}
|
|
11381
|
+
const key = url.searchParams.get("key");
|
|
11382
|
+
if (!key) {
|
|
11383
|
+
done("Login failed: no key in callback.");
|
|
11384
|
+
server.close();
|
|
11385
|
+
reject(new Error("Callback did not include a key."));
|
|
11386
|
+
return;
|
|
11387
|
+
}
|
|
11388
|
+
done("Logged in!");
|
|
11389
|
+
server.close();
|
|
11390
|
+
resolvePromise({
|
|
11391
|
+
key,
|
|
11392
|
+
workspaceId: url.searchParams.get("workspace_id") ?? "",
|
|
11393
|
+
workspaceName: url.searchParams.get("workspace_name") ?? "",
|
|
11394
|
+
workspaceSlug: url.searchParams.get("workspace_slug") ?? ""
|
|
11395
|
+
});
|
|
11396
|
+
});
|
|
11397
|
+
server.listen(0, "127.0.0.1", () => {
|
|
11398
|
+
const { port } = server.address();
|
|
11399
|
+
const authUrl = `${base}/cli/auth?port=${port}&state=${state}&host=${encodeURIComponent(hostname())}`;
|
|
11400
|
+
console.log(`Opening browser to authorize the Zalify CLI…
|
|
11401
|
+
${authUrl}
|
|
11402
|
+
`);
|
|
11403
|
+
console.log("If the browser doesn't open, visit the URL above manually.");
|
|
11404
|
+
openBrowser(authUrl);
|
|
11405
|
+
});
|
|
11406
|
+
server.on("error", reject);
|
|
11407
|
+
});
|
|
11408
|
+
writeConfig({ appUrl: base, ...result, createdAt: new Date().toISOString() });
|
|
11409
|
+
console.log(`✓ Logged in to workspace "${result.workspaceName || result.workspaceId}" (key stored in ~/.zalify/config.json)`);
|
|
11410
|
+
}
|
|
11411
|
+
function logout() {
|
|
11412
|
+
deleteConfig();
|
|
11413
|
+
console.log("✓ Logged out (local credentials deleted).");
|
|
11414
|
+
console.log(" To revoke the key itself: app.zalify.com → Settings → Developer.");
|
|
11415
|
+
}
|
|
11416
|
+
function whoami() {
|
|
11417
|
+
const config = requireConfig();
|
|
11418
|
+
console.log(`workspace ${config.workspaceName || "(unknown)"} [${config.workspaceId}]`);
|
|
11419
|
+
console.log(`app ${config.appUrl}`);
|
|
11420
|
+
console.log(`key ${config.key.slice(0, 16)}…`);
|
|
11421
|
+
}
|
|
11422
|
+
|
|
11423
|
+
// src/assets.ts
|
|
11424
|
+
import { createHash } from "node:crypto";
|
|
11425
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, existsSync as existsSync2, readdirSync } from "node:fs";
|
|
11426
|
+
import { join as join2, resolve } from "node:path";
|
|
11427
|
+
var BATCH = 10;
|
|
11428
|
+
function imagesDirFor(storeDir) {
|
|
11429
|
+
const dir2 = join2(resolve(storeDir), "images");
|
|
11430
|
+
if (!existsSync2(dir2))
|
|
11431
|
+
throw new Error(`No images/ directory in ${storeDir}`);
|
|
11432
|
+
return dir2;
|
|
11433
|
+
}
|
|
11434
|
+
function readIndex(imagesDir) {
|
|
11435
|
+
const path9 = join2(imagesDir, "assets.json");
|
|
11436
|
+
return existsSync2(path9) ? JSON.parse(readFileSync2(path9, "utf8")) : {};
|
|
11437
|
+
}
|
|
11438
|
+
function pngDims(buf) {
|
|
11439
|
+
if (buf.length > 24 && buf.readUInt32BE(12) === 1229472850) {
|
|
11440
|
+
return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) };
|
|
11441
|
+
}
|
|
11442
|
+
return {};
|
|
11443
|
+
}
|
|
11444
|
+
async function assetsPull(storeDir) {
|
|
11445
|
+
const imagesDir = imagesDirFor(storeDir);
|
|
11446
|
+
const index = readIndex(imagesDir);
|
|
11447
|
+
let pulled = 0;
|
|
11448
|
+
for (const [file2, meta] of Object.entries(index)) {
|
|
11449
|
+
const path9 = join2(imagesDir, file2);
|
|
11450
|
+
if (existsSync2(path9))
|
|
11451
|
+
continue;
|
|
11452
|
+
const res = await fetch(meta.url);
|
|
11453
|
+
if (!res.ok) {
|
|
11454
|
+
console.error(` ✗ ${file2}: ${res.status} from ${meta.url}`);
|
|
11455
|
+
continue;
|
|
11456
|
+
}
|
|
11457
|
+
writeFileSync3(path9, Buffer.from(await res.arrayBuffer()));
|
|
11458
|
+
pulled++;
|
|
11459
|
+
console.log(` ✓ pulled ${file2}`);
|
|
11460
|
+
}
|
|
11461
|
+
console.log(`Done: ${pulled} downloaded, ${Object.keys(index).length - pulled} already local.`);
|
|
11462
|
+
}
|
|
11463
|
+
async function assetsPush(storeDir) {
|
|
11464
|
+
const config = requireConfig();
|
|
11465
|
+
const imagesDir = imagesDirFor(storeDir);
|
|
11466
|
+
const indexPath = join2(imagesDir, "assets.json");
|
|
11467
|
+
const index = readIndex(imagesDir);
|
|
11468
|
+
async function api(path9, body) {
|
|
11469
|
+
const res = await fetch(`${config.appUrl}${path9}`, {
|
|
11470
|
+
method: "POST",
|
|
11471
|
+
headers: {
|
|
11472
|
+
"Content-Type": "application/json",
|
|
11473
|
+
Authorization: `Bearer ${config.key}`
|
|
11474
|
+
},
|
|
11475
|
+
body: JSON.stringify(body)
|
|
11476
|
+
});
|
|
11477
|
+
const json = await res.json().catch(() => ({}));
|
|
11478
|
+
if (!res.ok)
|
|
11479
|
+
throw new Error(`${path9} ${res.status}: ${JSON.stringify(json)}`);
|
|
11480
|
+
return json;
|
|
11481
|
+
}
|
|
11482
|
+
const files = readdirSync(imagesDir).filter((f) => f.endsWith(".png")).map((name) => {
|
|
11483
|
+
const bytes = readFileSync2(join2(imagesDir, name));
|
|
11484
|
+
return { name, bytes, checksum: createHash("sha256").update(bytes).digest("hex") };
|
|
11485
|
+
}).filter((f) => index[f.name]?.checksum !== f.checksum);
|
|
11486
|
+
if (!files.length) {
|
|
11487
|
+
console.log("Everything already pushed.");
|
|
11488
|
+
return;
|
|
11489
|
+
}
|
|
11490
|
+
console.log(`Pushing ${files.length} file(s) to "${config.workspaceName || config.workspaceId}"`);
|
|
11491
|
+
for (let i = 0;i < files.length; i += BATCH) {
|
|
11492
|
+
const batch = files.slice(i, i + BATCH);
|
|
11493
|
+
const { uploads } = await api("/api/assets/presign", {
|
|
11494
|
+
workspaceId: config.workspaceId,
|
|
11495
|
+
folderId: process.env.ZALIFY_FOLDER_ID ?? null,
|
|
11496
|
+
files: batch.map((f) => ({
|
|
11497
|
+
id: f.name,
|
|
11498
|
+
name: f.name,
|
|
11499
|
+
contentType: "image/png",
|
|
11500
|
+
size: f.bytes.length,
|
|
11501
|
+
checksum: f.checksum
|
|
11502
|
+
}))
|
|
11503
|
+
});
|
|
11504
|
+
const toFinalize = [];
|
|
11505
|
+
for (const up of uploads) {
|
|
11506
|
+
const file2 = batch.find((f) => f.name === up.id);
|
|
11507
|
+
if (!file2)
|
|
11508
|
+
continue;
|
|
11509
|
+
if (up.reused) {
|
|
11510
|
+
index[file2.name] = { assetId: up.assetId, url: up.publicUrl, checksum: file2.checksum };
|
|
11511
|
+
console.log(` - ${file2.name}: deduped (already in library)`);
|
|
11512
|
+
continue;
|
|
11513
|
+
}
|
|
11514
|
+
if (!up.uploadUrl) {
|
|
11515
|
+
console.error(` ✗ ${file2.name}: presign returned no uploadUrl`);
|
|
11516
|
+
continue;
|
|
11517
|
+
}
|
|
11518
|
+
const put = await fetch(up.uploadUrl, {
|
|
11519
|
+
method: "PUT",
|
|
11520
|
+
headers: {
|
|
11521
|
+
"Content-Type": "image/png",
|
|
11522
|
+
"Cache-Control": "public, max-age=31536000, immutable"
|
|
11523
|
+
},
|
|
11524
|
+
body: new Uint8Array(file2.bytes)
|
|
11525
|
+
});
|
|
11526
|
+
if (!put.ok) {
|
|
11527
|
+
console.error(` ✗ ${file2.name}: R2 PUT ${put.status}`);
|
|
11528
|
+
continue;
|
|
11529
|
+
}
|
|
11530
|
+
toFinalize.push({ file: file2, up });
|
|
11531
|
+
}
|
|
11532
|
+
if (toFinalize.length) {
|
|
11533
|
+
const { assets, pending } = await api("/api/assets/finalize", {
|
|
11534
|
+
workspaceId: config.workspaceId,
|
|
11535
|
+
items: toFinalize.map(({ file: file2, up }) => ({
|
|
11536
|
+
assetId: up.assetId,
|
|
11537
|
+
checksum: file2.checksum,
|
|
11538
|
+
...pngDims(file2.bytes)
|
|
11539
|
+
}))
|
|
11540
|
+
});
|
|
11541
|
+
for (const asset of assets) {
|
|
11542
|
+
const hit = toFinalize.find(({ up }) => up.assetId === asset.id);
|
|
11543
|
+
if (!hit)
|
|
11544
|
+
continue;
|
|
11545
|
+
index[hit.file.name] = { assetId: asset.id, url: asset.url, checksum: hit.file.checksum };
|
|
11546
|
+
console.log(` ✓ ${hit.file.name}`);
|
|
11547
|
+
}
|
|
11548
|
+
for (const id of pending ?? []) {
|
|
11549
|
+
console.error(` ✗ finalize pending (PUT missing?) assetId=${id}`);
|
|
11550
|
+
}
|
|
11551
|
+
}
|
|
11552
|
+
writeFileSync3(indexPath, JSON.stringify(index, null, 2) + `
|
|
11553
|
+
`);
|
|
11554
|
+
}
|
|
11555
|
+
console.log("Done.");
|
|
11556
|
+
}
|
|
11557
|
+
|
|
11558
|
+
// src/cli.ts
|
|
11284
11559
|
var __dirname4 = dirname(fileURLToPath3(import.meta.url));
|
|
11285
11560
|
var require2 = createRequire2(import.meta.url);
|
|
11286
|
-
var pkg = require2(
|
|
11561
|
+
var pkg = require2(join3(__dirname4, "..", "package.json"));
|
|
11287
11562
|
var program2 = new Command;
|
|
11288
11563
|
program2.name("zalify").description("Zalify CLI - command-line interface for Zalify").version(pkg.version, "-v, --version", "output the current version");
|
|
11289
11564
|
program2.command("version").description("Print Zalify CLI version and check for updates").action(async () => {
|
|
11290
11565
|
console.log(`Zalify CLI version: ${pkg.version}`);
|
|
11566
|
+
const isNewer = (a, b) => {
|
|
11567
|
+
const pa = a.split("-")[0].split(".").map(Number);
|
|
11568
|
+
const pb = b.split("-")[0].split(".").map(Number);
|
|
11569
|
+
for (let i = 0;i < 3; i++) {
|
|
11570
|
+
if ((pa[i] ?? 0) !== (pb[i] ?? 0))
|
|
11571
|
+
return (pa[i] ?? 0) > (pb[i] ?? 0);
|
|
11572
|
+
}
|
|
11573
|
+
return false;
|
|
11574
|
+
};
|
|
11291
11575
|
try {
|
|
11292
11576
|
const notifier = updateNotifier({ pkg });
|
|
11293
11577
|
const update = await notifier.fetchInfo();
|
|
11294
|
-
if (update && update.
|
|
11578
|
+
if (update && isNewer(update.latest, update.current)) {
|
|
11295
11579
|
console.log(`
|
|
11296
11580
|
A new version (${update.latest}) is available. Run "npm i -g @zalify/cli@latest" to update.`);
|
|
11297
11581
|
}
|
|
11298
11582
|
} catch {}
|
|
11299
11583
|
});
|
|
11300
|
-
program2.
|
|
11584
|
+
program2.command("login").description("Authenticate via the browser and store a workspace API key").option("--app-url <url>", "Zalify app origin (default: https://app.zalify.com)").action(async (options) => {
|
|
11585
|
+
await login(options);
|
|
11586
|
+
});
|
|
11587
|
+
program2.command("logout").description("Delete the stored CLI credentials").action(() => logout());
|
|
11588
|
+
program2.command("whoami").description("Show the authorized workspace and key prefix").action(() => whoami());
|
|
11589
|
+
var assets = program2.command("assets").description("Sync images with the Zalify asset library");
|
|
11590
|
+
assets.command("push <store-dir>").description("Upload <store-dir>/images/*.png (sha256 dedup, writes assets.json)").action(async (storeDir) => {
|
|
11591
|
+
await assetsPush(storeDir);
|
|
11592
|
+
});
|
|
11593
|
+
assets.command("pull <store-dir>").description("Download files listed in <store-dir>/images/assets.json").action(async (storeDir) => {
|
|
11594
|
+
await assetsPull(storeDir);
|
|
11595
|
+
});
|
|
11596
|
+
program2.parseAsync().catch((err) => {
|
|
11597
|
+
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
11598
|
+
process.exit(1);
|
|
11599
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zalify/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Zalify CLI - command-line interface for Zalify",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/cli.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"engines": {
|
|
18
18
|
"node": ">=20",
|
|
19
|
-
"bun": ">=1.
|
|
19
|
+
"bun": ">=1.3"
|
|
20
20
|
},
|
|
21
21
|
"keywords": [
|
|
22
22
|
"zalify",
|