@erdoai/cli 0.4.0 → 0.5.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/dist/index.js +88 -28
- package/package.json +1 -1
- package/scripts/postinstall.mjs +22 -2
package/dist/index.js
CHANGED
|
@@ -293,26 +293,32 @@ var ErdoClient = class {
|
|
|
293
293
|
runHeartbeat(id) {
|
|
294
294
|
return this.request("POST", `/v1/heartbeats/${encodeURIComponent(id)}/run`, {});
|
|
295
295
|
}
|
|
296
|
-
// --- collections ---
|
|
297
|
-
|
|
298
|
-
return this.request("GET", "/v1/
|
|
296
|
+
// --- kv (named key/value stores, a.k.a. collections) ---
|
|
297
|
+
listKVStores() {
|
|
298
|
+
return this.request("GET", "/v1/kv");
|
|
299
299
|
}
|
|
300
|
-
|
|
301
|
-
return this.request("POST", "/v1/
|
|
300
|
+
createKVStore(slug) {
|
|
301
|
+
return this.request("POST", "/v1/kv", { slug });
|
|
302
302
|
}
|
|
303
|
-
|
|
303
|
+
getKVItem(slug, key) {
|
|
304
304
|
return this.request(
|
|
305
305
|
"GET",
|
|
306
|
-
`/v1/
|
|
306
|
+
`/v1/kv/${encodeURIComponent(slug)}/items/${encodeURIComponent(key)}`
|
|
307
307
|
);
|
|
308
308
|
}
|
|
309
|
-
|
|
309
|
+
setKVItem(slug, key, value) {
|
|
310
310
|
return this.request(
|
|
311
311
|
"PUT",
|
|
312
|
-
`/v1/
|
|
312
|
+
`/v1/kv/${encodeURIComponent(slug)}/items/${encodeURIComponent(key)}`,
|
|
313
313
|
{ value }
|
|
314
314
|
);
|
|
315
315
|
}
|
|
316
|
+
deleteKVItem(slug, key) {
|
|
317
|
+
return this.request(
|
|
318
|
+
"DELETE",
|
|
319
|
+
`/v1/kv/${encodeURIComponent(slug)}/items/${encodeURIComponent(key)}`
|
|
320
|
+
);
|
|
321
|
+
}
|
|
316
322
|
// --- agent runs ---
|
|
317
323
|
listAgentRuns(opts) {
|
|
318
324
|
const params = new URLSearchParams();
|
|
@@ -438,19 +444,30 @@ async function browserLogin() {
|
|
|
438
444
|
import { spawnSync } from "child_process";
|
|
439
445
|
var PKG = "@erdoai/cli";
|
|
440
446
|
var MANUAL_HINT = `npm install -g ${PKG}@latest`;
|
|
447
|
+
function parseVersion(v) {
|
|
448
|
+
const [core, pre = ""] = v.split("-", 2);
|
|
449
|
+
const nums = core.split(".").map((s) => Number.parseInt(s, 10) || 0);
|
|
450
|
+
return { nums, pre };
|
|
451
|
+
}
|
|
441
452
|
function isNewer(latest, current) {
|
|
442
|
-
const a = latest
|
|
443
|
-
const b = current
|
|
453
|
+
const a = parseVersion(latest);
|
|
454
|
+
const b = parseVersion(current);
|
|
444
455
|
for (let i = 0; i < 3; i++) {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
456
|
+
const x = a.nums[i] ?? 0;
|
|
457
|
+
const y = b.nums[i] ?? 0;
|
|
458
|
+
if (x > y) return true;
|
|
459
|
+
if (x < y) return false;
|
|
460
|
+
}
|
|
461
|
+
if (!a.pre && b.pre) return true;
|
|
462
|
+
if (a.pre && !b.pre) return false;
|
|
463
|
+
return a.pre > b.pre;
|
|
449
464
|
}
|
|
450
465
|
async function update(current) {
|
|
451
466
|
let latest;
|
|
452
467
|
try {
|
|
453
|
-
const res = await fetch(`https://registry.npmjs.org/${PKG}/latest
|
|
468
|
+
const res = await fetch(`https://registry.npmjs.org/${PKG}/latest`, {
|
|
469
|
+
signal: AbortSignal.timeout(1e4)
|
|
470
|
+
});
|
|
454
471
|
if (res.ok) latest = (await res.json()).version;
|
|
455
472
|
} catch {
|
|
456
473
|
}
|
|
@@ -468,6 +485,10 @@ async function update(current) {
|
|
|
468
485
|
// npm is npm.cmd on Windows; needs a shell to resolve.
|
|
469
486
|
shell: process.platform === "win32"
|
|
470
487
|
});
|
|
488
|
+
if (result.error) {
|
|
489
|
+
console.error(`Update failed: ${result.error.message}. Try manually: ${MANUAL_HINT}`);
|
|
490
|
+
process.exit(1);
|
|
491
|
+
}
|
|
471
492
|
if (result.status !== 0) {
|
|
472
493
|
console.error(`Update failed. Try manually: ${MANUAL_HINT}`);
|
|
473
494
|
process.exit(result.status ?? 1);
|
|
@@ -945,7 +966,16 @@ function readMaybeFile(v) {
|
|
|
945
966
|
if (!v) return void 0;
|
|
946
967
|
return v.startsWith("@") ? readFileSync2(v.slice(1), "utf8") : v;
|
|
947
968
|
}
|
|
948
|
-
|
|
969
|
+
function grantList(v) {
|
|
970
|
+
if (v === void 0) return void 0;
|
|
971
|
+
if (v.trim() === "[]") return [];
|
|
972
|
+
const slugs = v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
973
|
+
if (slugs.length === 0) {
|
|
974
|
+
throw new Error(`expected comma-separated slugs, or "[]" to clear \u2014 got ${JSON.stringify(v)}`);
|
|
975
|
+
}
|
|
976
|
+
return slugs;
|
|
977
|
+
}
|
|
978
|
+
pagesCmd.command("deploy").description("Deploy a page (HTML/React); --html/--js/--css accept @file").requiredOption("--title <title>", "page title").requiredOption("--html <htmlOr@file>", "HTML (or @path to a file)").option("--js <jsOr@file>", "JS/JSX (or @path)").option("--css <cssOr@file>", "CSS (or @path)").option("--runtime <runtime>", "react-tailwind (default) or none").option("--datasets <csv>", "dataset slugs the page reads").option("--writable-datasets <csv>", "dataset slugs the page may write via erdo.insertRows").option("--kv <csv>", "named KV-store slugs the page reads").option("--writable-kv <csv>", "named KV-store slugs the page may write").option("--public", "make the page publicly viewable").action(
|
|
949
979
|
async (opts) => {
|
|
950
980
|
try {
|
|
951
981
|
const csv = (v) => v ? v.split(",").map((s) => s.trim()) : void 0;
|
|
@@ -957,8 +987,8 @@ pagesCmd.command("deploy").description("Deploy a page (HTML/React); --html/--js/
|
|
|
957
987
|
runtime: opts.runtime,
|
|
958
988
|
dataset_slugs: csv(opts.datasets),
|
|
959
989
|
writable_dataset_slugs: csv(opts.writableDatasets),
|
|
960
|
-
|
|
961
|
-
|
|
990
|
+
kv_slugs: csv(opts.kv),
|
|
991
|
+
writable_kv_slugs: csv(opts.writableKv),
|
|
962
992
|
public: !!opts.public
|
|
963
993
|
});
|
|
964
994
|
console.log(`${res.id} ${res.public_url || res.url}`);
|
|
@@ -968,6 +998,29 @@ pagesCmd.command("deploy").description("Deploy a page (HTML/React); --html/--js/
|
|
|
968
998
|
}
|
|
969
999
|
}
|
|
970
1000
|
);
|
|
1001
|
+
pagesCmd.command("update <id>").description(
|
|
1002
|
+
"Update an existing page by id; provided fields are merged (update just --js to keep html/css). --html/--js/--css accept @file"
|
|
1003
|
+
).option("--title <title>", "new title").option("--html <htmlOr@file>", "HTML (or @path to a file)").option("--js <jsOr@file>", "JS/JSX (or @path)").option("--css <cssOr@file>", "CSS (or @path)").option("--datasets <csv>", 'replacement read-dataset slugs ("[]" clears)').option("--writable-datasets <csv>", 'replacement writable-dataset slugs ("[]" clears)').option("--kv <csv>", 'replacement read KV-store slugs ("[]" clears)').option("--writable-kv <csv>", 'replacement writable KV-store slugs ("[]" clears)').option("--public", "make the page publicly viewable").option("--private", "make the page private").action(
|
|
1004
|
+
async (id, opts) => {
|
|
1005
|
+
try {
|
|
1006
|
+
const res = await new ErdoClient().updatePage(id, {
|
|
1007
|
+
title: opts.title,
|
|
1008
|
+
html: readMaybeFile(opts.html),
|
|
1009
|
+
js: readMaybeFile(opts.js),
|
|
1010
|
+
css: readMaybeFile(opts.css),
|
|
1011
|
+
dataset_slugs: grantList(opts.datasets),
|
|
1012
|
+
writable_dataset_slugs: grantList(opts.writableDatasets),
|
|
1013
|
+
kv_slugs: grantList(opts.kv),
|
|
1014
|
+
writable_kv_slugs: grantList(opts.writableKv),
|
|
1015
|
+
public: opts.public ? true : opts.private ? false : void 0
|
|
1016
|
+
});
|
|
1017
|
+
console.log(`${res.id} ${res.public_url || res.url}`);
|
|
1018
|
+
if (res.warning) console.error(`warning: ${res.warning}`);
|
|
1019
|
+
} catch (e) {
|
|
1020
|
+
fail(e);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
);
|
|
971
1024
|
pagesCmd.command("validate").description("Validate page content without deploying").requiredOption("--html <htmlOr@file>", "HTML (or @path)").option("--js <jsOr@file>", "JS/JSX (or @path)").option("--css <cssOr@file>", "CSS (or @path)").option("--runtime <runtime>", "react-tailwind (default) or none").action(async (opts) => {
|
|
972
1025
|
try {
|
|
973
1026
|
print(
|
|
@@ -1044,29 +1097,29 @@ autoCmd.command("run <id>").description("Trigger an automation now").action(asyn
|
|
|
1044
1097
|
fail(e);
|
|
1045
1098
|
}
|
|
1046
1099
|
});
|
|
1047
|
-
var
|
|
1048
|
-
|
|
1100
|
+
var kvCmd = program.command("kv").description("Named key/value stores");
|
|
1101
|
+
kvCmd.command("list").description("List KV stores").action(async () => {
|
|
1049
1102
|
try {
|
|
1050
|
-
print(await new ErdoClient().
|
|
1103
|
+
print(await new ErdoClient().listKVStores());
|
|
1051
1104
|
} catch (e) {
|
|
1052
1105
|
fail(e);
|
|
1053
1106
|
}
|
|
1054
1107
|
});
|
|
1055
|
-
|
|
1108
|
+
kvCmd.command("create <slug>").description("Create a named KV store (slug: lowercase letters, digits, hyphens)").action(async (slug) => {
|
|
1056
1109
|
try {
|
|
1057
|
-
print(await new ErdoClient().
|
|
1110
|
+
print(await new ErdoClient().createKVStore(slug));
|
|
1058
1111
|
} catch (e) {
|
|
1059
1112
|
fail(e);
|
|
1060
1113
|
}
|
|
1061
1114
|
});
|
|
1062
|
-
|
|
1115
|
+
kvCmd.command("get <slug> <key>").description("Get a KV item").action(async (slug, key) => {
|
|
1063
1116
|
try {
|
|
1064
|
-
print(await new ErdoClient().
|
|
1117
|
+
print(await new ErdoClient().getKVItem(slug, key));
|
|
1065
1118
|
} catch (e) {
|
|
1066
1119
|
fail(e);
|
|
1067
1120
|
}
|
|
1068
1121
|
});
|
|
1069
|
-
|
|
1122
|
+
kvCmd.command("set <slug> <key> <jsonValue>").description("Set a KV item (value is parsed as JSON, else stored as string)").action(async (slug, key, jsonValue) => {
|
|
1070
1123
|
try {
|
|
1071
1124
|
let value;
|
|
1072
1125
|
try {
|
|
@@ -1074,7 +1127,14 @@ collCmd.command("set <slug> <key> <jsonValue>").description("Set a collection it
|
|
|
1074
1127
|
} catch {
|
|
1075
1128
|
value = jsonValue;
|
|
1076
1129
|
}
|
|
1077
|
-
print(await new ErdoClient().
|
|
1130
|
+
print(await new ErdoClient().setKVItem(slug, key, value));
|
|
1131
|
+
} catch (e) {
|
|
1132
|
+
fail(e);
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
1135
|
+
kvCmd.command("delete <slug> <key>").description("Delete a KV item").action(async (slug, key) => {
|
|
1136
|
+
try {
|
|
1137
|
+
print(await new ErdoClient().deleteKVItem(slug, key));
|
|
1078
1138
|
} catch (e) {
|
|
1079
1139
|
fail(e);
|
|
1080
1140
|
}
|
package/package.json
CHANGED
package/scripts/postinstall.mjs
CHANGED
|
@@ -1,19 +1,39 @@
|
|
|
1
1
|
// Friendly getting-started hint, printed once after `npm install -g @erdoai/cli`.
|
|
2
2
|
// Stays quiet in CI and non-interactive installs (and never fails the install).
|
|
3
|
-
import { existsSync } from "node:fs";
|
|
3
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
4
6
|
|
|
5
7
|
try {
|
|
6
8
|
// In our own source checkout the package ships `dist` + this script but never
|
|
7
9
|
// `src`, so a sibling `src/` means this is a dev `npm install` — stay quiet.
|
|
8
10
|
const devCheckout = existsSync(new URL("../src", import.meta.url));
|
|
9
11
|
|
|
12
|
+
// Print once per machine: a sentinel under the CLI's config dir (same dir as
|
|
13
|
+
// config.json) suppresses the banner on later installs — notably the global
|
|
14
|
+
// reinstall `erdo update` performs, which would otherwise re-trigger it.
|
|
15
|
+
const configDir = join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "erdo");
|
|
16
|
+
const sentinel = join(configDir, ".welcomed");
|
|
17
|
+
|
|
10
18
|
const quiet =
|
|
11
19
|
devCheckout ||
|
|
20
|
+
// Only greet on a global install — a local/devDependency install must not
|
|
21
|
+
// print the banner or write the sentinel (which would silence the real
|
|
22
|
+
// `npm install -g` welcome later). npm sets this to "true" for `-g`.
|
|
23
|
+
process.env.npm_config_global !== "true" ||
|
|
12
24
|
process.env.CI ||
|
|
13
25
|
process.env.ERDO_NO_POSTINSTALL ||
|
|
14
|
-
!process.stdout.isTTY
|
|
26
|
+
!process.stdout.isTTY ||
|
|
27
|
+
existsSync(sentinel);
|
|
15
28
|
|
|
16
29
|
if (!quiet) {
|
|
30
|
+
try {
|
|
31
|
+
mkdirSync(configDir, { recursive: true });
|
|
32
|
+
writeFileSync(sentinel, "");
|
|
33
|
+
} catch {
|
|
34
|
+
// a read-only home shouldn't suppress the hint or fail the install
|
|
35
|
+
}
|
|
36
|
+
|
|
17
37
|
const c = (code, s) => `\x1b[${code}m${s}\x1b[0m`;
|
|
18
38
|
const b = (s) => c("1", s);
|
|
19
39
|
const dim = (s) => c("2", s);
|