@vxil/cli 0.30.0 → 0.32.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 +1 -1
- package/dist/_vxil-feature-configs.d.ts +10 -0
- package/dist/vxil-main.js +73 -22
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ Verbs: `init templates quickstart try login link keys account logout invites mem
|
|
|
52
52
|
- `vxil doctor` — preflight: secrets referenced-vs-stored (advisory, names only) plus a served / published / local hash verdict per function. Under `--json` stdout is one JSON document; notices go to stderr.
|
|
53
53
|
- `vxil link <slug>` — reuses the key already stored for the slug on this machine; `--mint` for a fresh one, `--key` to hand one in.
|
|
54
54
|
- `vxil keys mint --name <name> --scopes <a,b,c> [--env live|test] [--store]` — mint a **narrow** key for a server (scopes validated locally; `admin` refused); the key prints **once** to stdout (`--json` → `{ api_key, key_id, scopes }`) and is never stored unless `--store`. `vxil keys list` shows names, ids, scopes, created and last-used (never secrets); `vxil keys revoke <key_id> [--yes]` asks you to type the id, and refuses the key the CLI itself is using unless `--yes`. Needs a dashboard session (`vxil login`, or it prompts at a terminal; a piped / `--json` run fails with that remedy instead — pass `--email`/`--password` to script it, which never stores the session).
|
|
55
|
-
- `vxil cms drop <collection> [--yes]` — remove one collection definition through the same guarded ack `push --allow-destructive` uses (rows are retained).
|
|
55
|
+
- `vxil cms drop <collection> [--purge-rows] [--yes]` — remove one collection definition through the same guarded ack `push --allow-destructive` uses (rows are retained, and a re-create brings them back; `--purge-rows` hard-deletes them too, and on a collection dropped earlier deletes its retained rows).
|
|
56
56
|
- `vxil members list|invite|add|rm|uninvite` and `vxil invites accept <token|url>` — the bound project's team from the terminal (your `vxil login` session): invite by e-mail with a role, add an existing account (also how you change a role), remove (typed e-mail, refuses the last owner), revoke a pending invitation, accept one.
|
|
57
57
|
- `vxil projects list|create|rm` and `vxil billing status|upgrade --tier <t>` — your projects and the bound project's plan. `projects rm` deletes any project you own after you type its slug; `billing upgrade` prints the hosted checkout URL (never opens a browser) and also downgrades (`--tier free`).
|
|
58
58
|
- `vxil files put <path> --user <id>`, `vxil webhooks sources add|list|rm|test`, `vxil webhooks events replay|runs`, `vxil cms bulk-rm <collection> --filter '<json>' [--dry-run]`, `vxil search sync run|reset <key> [--purge]`, `vxil users import <file>` — the operator actions where the CLI adds something a raw call cannot (bytes from disk, a print-once receiver URL, a dry run before a typed confirm, a chunked bulk). Everything else the dashboard does per feature is one `vxil api` line: `vxil api --help`.
|
|
@@ -685,6 +685,11 @@ export declare const DeclaredAiTemplateSchema: import("@sinclair/typebox").TObje
|
|
|
685
685
|
user: import("@sinclair/typebox").TString;
|
|
686
686
|
system: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
687
687
|
schema: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TUnknown>>;
|
|
688
|
+
/** MX-1023-MEDIA-RESOLUTION: this template's Gemini media resolution (sent
|
|
689
|
+
* to ai-v1 as `media_resolution`). A request's media_resolution wins; absent
|
|
690
|
+
* falls back to defaults.mediaResolution. Part of the content hash only
|
|
691
|
+
* when set, so an existing declared template is not re-versioned. */
|
|
692
|
+
mediaResolution: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"low">, import("@sinclair/typebox").TLiteral<"medium">, import("@sinclair/typebox").TLiteral<"high">]>>;
|
|
688
693
|
}>;
|
|
689
694
|
export type DeclaredAiTemplate = Static<typeof DeclaredAiTemplateSchema>;
|
|
690
695
|
export declare const AiConfigSchema: import("@sinclair/typebox").TObject<{
|
|
@@ -723,6 +728,11 @@ export declare const AiConfigSchema: import("@sinclair/typebox").TObject<{
|
|
|
723
728
|
user: import("@sinclair/typebox").TString;
|
|
724
729
|
system: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
725
730
|
schema: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TUnknown>>;
|
|
731
|
+
/** MX-1023-MEDIA-RESOLUTION: this template's Gemini media resolution (sent
|
|
732
|
+
* to ai-v1 as `media_resolution`). A request's media_resolution wins; absent
|
|
733
|
+
* falls back to defaults.mediaResolution. Part of the content hash only
|
|
734
|
+
* when set, so an existing declared template is not re-versioned. */
|
|
735
|
+
mediaResolution: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"low">, import("@sinclair/typebox").TLiteral<"medium">, import("@sinclair/typebox").TLiteral<"high">]>>;
|
|
726
736
|
}>>>;
|
|
727
737
|
testMarkers: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
|
|
728
738
|
}>;
|
package/dist/vxil-main.js
CHANGED
|
@@ -4717,7 +4717,12 @@ var DeclaredAiTemplateSchema = Type.Object({
|
|
|
4717
4717
|
template: Type.String({ minLength: 1, maxLength: 128, pattern: AI_TEMPLATE_NAME_PATTERN }),
|
|
4718
4718
|
user: Type.String({ minLength: 1, maxLength: 64e3 }),
|
|
4719
4719
|
system: Type.Optional(Type.String({ maxLength: 32e3 })),
|
|
4720
|
-
schema: Type.Optional(Type.Record(Type.String(), Type.Unknown()))
|
|
4720
|
+
schema: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
4721
|
+
/** MX-1023-MEDIA-RESOLUTION: this template's Gemini media resolution (sent
|
|
4722
|
+
* to ai-v1 as `media_resolution`). A request's media_resolution wins; absent
|
|
4723
|
+
* falls back to defaults.mediaResolution. Part of the content hash only
|
|
4724
|
+
* when set, so an existing declared template is not re-versioned. */
|
|
4725
|
+
mediaResolution: Type.Optional(Type.Union([Type.Literal("low"), Type.Literal("medium"), Type.Literal("high")]))
|
|
4721
4726
|
});
|
|
4722
4727
|
var AiConfigSchema = Type.Object({
|
|
4723
4728
|
enabled: Type.Boolean({ default: true }),
|
|
@@ -4802,8 +4807,9 @@ var AiConfigSchema = Type.Object({
|
|
|
4802
4807
|
// aiTemplateContentSha256) against the `content_sha256` GET /v1/ai/templates
|
|
4803
4808
|
// stamps on the latest version, and POSTs a new version ONLY when the content
|
|
4804
4809
|
// differs — so a push is idempotent and versions stay monotonic per name.
|
|
4805
|
-
// Item shape mirrors ai-v1 core.ts TemplateBody
|
|
4806
|
-
//
|
|
4810
|
+
// Item shape mirrors ai-v1 core.ts TemplateBody (`template` is the name;
|
|
4811
|
+
// `mediaResolution` is sent as the body's `media_resolution`).
|
|
4812
|
+
// Stored templates the config does not declare are reported and left
|
|
4807
4813
|
// in place — RETIRED (soft: hidden from list + render, history kept) only
|
|
4808
4814
|
// under --allow-destructive (2026-10-01). Bounded to 50 entries: the
|
|
4809
4815
|
// manifest rides the 1 MiB config body cap. An Optional ARRAY is ONE leaf.
|
|
@@ -5666,9 +5672,9 @@ function lowerAllTriggerBindings(def, name) {
|
|
|
5666
5672
|
return bindings;
|
|
5667
5673
|
}
|
|
5668
5674
|
var CONFIG_FILENAMES = ["vxil.config.ts", "vxil.config.mjs", "vxil.config.js"];
|
|
5669
|
-
var VXIL_CONFIG_PKG_VERSION = "0.
|
|
5670
|
-
var VXIL_SDK_PKG_VERSION = "0.
|
|
5671
|
-
var VXIL_CLI_PKG_VERSION = "0.
|
|
5675
|
+
var VXIL_CONFIG_PKG_VERSION = "0.24.0";
|
|
5676
|
+
var VXIL_SDK_PKG_VERSION = "0.31.0";
|
|
5677
|
+
var VXIL_CLI_PKG_VERSION = "0.32.0";
|
|
5672
5678
|
function ensureScaffoldPackageJson(cwd, opts = {}) {
|
|
5673
5679
|
const file = resolve(cwd, "package.json");
|
|
5674
5680
|
const wanted = {
|
|
@@ -8569,6 +8575,7 @@ async function aiTemplateContentSha256(t) {
|
|
|
8569
8575
|
return Object.fromEntries(Object.keys(o).sort().map((k) => [k, canon(o[k], depth + 1)]));
|
|
8570
8576
|
};
|
|
8571
8577
|
return sha256Hex(JSON.stringify({
|
|
8578
|
+
...t.mediaResolution ? { media_resolution: t.mediaResolution } : {},
|
|
8572
8579
|
schema: canon(t.schema ?? null, 1),
|
|
8573
8580
|
system: t.system ?? null,
|
|
8574
8581
|
user: t.user
|
|
@@ -9014,7 +9021,7 @@ async function readOnlyPlan(api, block, allowDestructive) {
|
|
|
9014
9021
|
} else {
|
|
9015
9022
|
const hashed = await Promise.all(manifest.templates.map(async (t) => ({
|
|
9016
9023
|
template: t.template,
|
|
9017
|
-
content_sha256: await aiTemplateContentSha256({ system: t.system ?? null, user: t.user, schema: t.schema ?? null })
|
|
9024
|
+
content_sha256: await aiTemplateContentSha256({ system: t.system ?? null, user: t.user, schema: t.schema ?? null, mediaResolution: t.mediaResolution ?? null })
|
|
9018
9025
|
})));
|
|
9019
9026
|
const plan = planAiTemplates(hashed, listOf(data2.templates));
|
|
9020
9027
|
s.created.push(...plan.create);
|
|
@@ -10396,14 +10403,43 @@ async function dropCollection(api, collection) {
|
|
|
10396
10403
|
throw new Error(`cms: drop collection ${collection} failed: ${e.code ?? res.status} ${e.message ?? ""}`);
|
|
10397
10404
|
}
|
|
10398
10405
|
}
|
|
10399
|
-
function
|
|
10406
|
+
async function dropCollectionPurgingRows(api, collection) {
|
|
10407
|
+
let purgedRows = 0;
|
|
10408
|
+
for (let round = 0; ; round++) {
|
|
10409
|
+
if (round > 0) {
|
|
10410
|
+
const read = await readRemote2(api);
|
|
10411
|
+
if (read.unavailable) {
|
|
10412
|
+
throw new Error(`cms: purge of ${collection} stopped after ${purgedRows} purged row(s): cannot read ${read.unavailable.route}`);
|
|
10413
|
+
}
|
|
10414
|
+
if (read.map.has(collection)) {
|
|
10415
|
+
throw new Error(`cms: purge of ${collection} stopped after ${purgedRows} purged row(s): the collection was re-created meanwhile \u2014 its rows are kept`);
|
|
10416
|
+
}
|
|
10417
|
+
}
|
|
10418
|
+
const res = await api("POST", "/v1/cms/collections", { drop_collection: collection, allow_destructive: true, purge_rows: true });
|
|
10419
|
+
if (res.body.error?.code === "not_found") return { purgedRows };
|
|
10420
|
+
if (res.status < 200 || res.status >= 300) {
|
|
10421
|
+
const e = res.body.error ?? {};
|
|
10422
|
+
throw new Error(`cms: drop collection ${collection} failed after ${purgedRows} purged row(s): ${e.code ?? res.status} ${e.message ?? ""}`);
|
|
10423
|
+
}
|
|
10424
|
+
const d = res.body.data ?? {};
|
|
10425
|
+
const n = typeof d.purged_rows === "number" ? d.purged_rows : 0;
|
|
10426
|
+
purgedRows += n;
|
|
10427
|
+
if (d.purge_complete === true) return { purgedRows };
|
|
10428
|
+
if (n === 0) throw new Error(`cms: purge of ${collection} made no progress after ${purgedRows} purged row(s) \u2014 re-run \`vxil cms drop ${collection} --purge-rows\``);
|
|
10429
|
+
}
|
|
10430
|
+
}
|
|
10431
|
+
function formatCollectionDrop(collection, plan, declaredLocally, purgeRows = false) {
|
|
10400
10432
|
const rows = plan.rows === null ? "an unknown number of live rows" : `${plan.rows} live row(s)`;
|
|
10401
|
-
const lines = [
|
|
10402
|
-
` ! drop collection ${collection} (${plan.fields} field(s), ${rows}) \u2014 the definition is removed; stored item rows are NOT deleted (they become unreachable through the API until a collection of that name exists again)
|
|
10433
|
+
const lines = !plan.exists && purgeRows ? [` ! collection ${collection} has no definition \u2014 its retained rows from an earlier drop (live and deleted) are HARD-DELETED; they cannot be restored`] : [
|
|
10434
|
+
` ! drop collection ${collection} (${plan.fields} field(s), ${rows}) \u2014 the definition is removed; ` + (purgeRows ? "its stored item rows (live and deleted) are HARD-DELETED with it; they cannot be restored" : "stored item rows are NOT deleted (they become unreachable through the API until a collection of that name exists again)")
|
|
10403
10435
|
];
|
|
10404
10436
|
if (declaredLocally) {
|
|
10405
|
-
|
|
10406
|
-
|
|
10437
|
+
if (purgeRows) {
|
|
10438
|
+
lines.push(` \u26A0 '${collection}' is still declared in vxil.config.ts \u2014 the next \`vxil push\` re-creates it empty; remove it from the config too`);
|
|
10439
|
+
} else {
|
|
10440
|
+
const back = plan.rows === null ? "the retained rows" : `the ${plan.rows} retained row(s)`;
|
|
10441
|
+
lines.push(` \u26A0 '${collection}' is still declared in vxil.config.ts \u2014 the next \`vxil push\` re-creates it and ${back} come back with it; remove it from the config too`);
|
|
10442
|
+
}
|
|
10407
10443
|
}
|
|
10408
10444
|
return lines.join("\n");
|
|
10409
10445
|
}
|
|
@@ -13276,7 +13312,7 @@ var TOOLS = [
|
|
|
13276
13312
|
{
|
|
13277
13313
|
name: "notifications_send",
|
|
13278
13314
|
feature: "notifications",
|
|
13279
|
-
description: 'Send a transactional email to an end-user by ID. Returns a delivery_id immediately (202); delivery is asynchronous. Templates: magic-link (data: url, expires_minutes), otp-code (data: code, expires_minutes), welcome (data: app_name, first_name?), transactional (data: subject, paragraph, cta_label?, cta_url?, unsubscribe_url?). unsubscribe_url is a link value only (http/https/mailto/tel, anything else becomes #): write the labelled <a href="{{unsubscribe_url}}"> in your own template override; the built-in body adds nothing for it.',
|
|
13315
|
+
description: 'Send a transactional email to an end-user by ID. Returns a delivery_id immediately (202); delivery is asynchronous. Templates: magic-link (data: url, expires_minutes), otp-code (data: code, expires_minutes, url? \u2014 an optional sign-in link appended under the code), welcome (data: app_name, first_name?), transactional (data: subject, paragraph, cta_label?, cta_url?, unsubscribe_url?). unsubscribe_url is a link value only (http/https/mailto/tel, anything else becomes #): write the labelled <a href="{{unsubscribe_url}}"> in your own template override; the built-in body adds nothing for it.',
|
|
13280
13316
|
inputSchema: {
|
|
13281
13317
|
type: "object",
|
|
13282
13318
|
properties: {
|
|
@@ -14914,7 +14950,7 @@ var TOOLS = [
|
|
|
14914
14950
|
media_resolution: {
|
|
14915
14951
|
type: "string",
|
|
14916
14952
|
enum: ["low", "medium", "high"],
|
|
14917
|
-
description: "Gemini token budget per image or PDF page (ignored by other providers): lower is faster and bills fewer input tokens. Omitted: the tenant's defaults.mediaResolution, then the model default."
|
|
14953
|
+
description: "Gemini token budget per image or PDF page (ignored by other providers): lower is faster and bills fewer input tokens. Omitted: the stored template's own level (when template is set), then the tenant's defaults.mediaResolution, then the model default."
|
|
14918
14954
|
},
|
|
14919
14955
|
mode: {
|
|
14920
14956
|
type: "string",
|
|
@@ -16892,6 +16928,9 @@ function quickstartConfigSrc(features, slug) {
|
|
|
16892
16928
|
function quickstartTemplateSrc(configSrc, slug) {
|
|
16893
16929
|
return configSrc.replaceAll("fromEmail: 'noreply@example.com'", `fromEmail: '${quickstartFromEmail(slug)}'`);
|
|
16894
16930
|
}
|
|
16931
|
+
function scaffoldsStarterFunction(configSrc) {
|
|
16932
|
+
return configSrc.includes("./functions/hello.ts");
|
|
16933
|
+
}
|
|
16895
16934
|
|
|
16896
16935
|
// src/verbHelp.ts
|
|
16897
16936
|
var SELF_HELP_VERBS = /* @__PURE__ */ new Set(["keys", "account", "members", "api", "migrate"]);
|
|
@@ -16932,7 +16971,7 @@ var VERB_USAGE = {
|
|
|
16932
16971
|
import: "usage: vxil import <feature> --file <bundle.json|export.ndjson> [--mode merge|replace] [--allow-split-links]",
|
|
16933
16972
|
migrate: "usage: vxil migrate plan|schema|data|verify|payments \u2026 (run `vxil migrate --help`)",
|
|
16934
16973
|
functions: "usage: vxil functions new <name> [--force]|deploy [<name>] [--force]|list|delete|enable|disable [--yes]|invoke ['<json>' | --data '<json>'] [--async]|logs [--tail]|dev <name> [--port <n>] [--trigger <lane>] [--event <file.json>]",
|
|
16935
|
-
cms: "usage: vxil cms status|pull|reindex <collection> [--field <field>]|drop <collection> [--yes]|bulk-rm <collection> --filter '<json>' [--limit <1..100>] [--dry-run] [--yes]",
|
|
16974
|
+
cms: "usage: vxil cms status|pull|reindex <collection> [--field <field>]|drop <collection> [--purge-rows] [--yes]|bulk-rm <collection> --filter '<json>' [--limit <1..100>] [--dry-run] [--yes]",
|
|
16936
16975
|
payments: "usage: vxil payments simulate --scenario <refund-pair|cross-platform-unlock|renewal|expiry|past-due-grace|transfer> --user <id> [--tier <t>] [--dev] \u2014 mock/dev projects only",
|
|
16937
16976
|
dev: "usage: vxil dev up [--ttl <h>] [--new] [--name <n>] [--reuse-slug <slug>] [--email <e> --password <p>] [--invite <code>] | down [--yes] | seed | reset | branch [<name>] [--ttl <h=24>] [--no-env] [--print] | --list | --rm <name> [--yes] | --watch [--target <name>] \u2014 dev up signs in with --email/--password (or VXIL_DEV_EMAIL/VXIL_DEV_PASSWORD, + VXIL_DEV_INVITE), else your `vxil login` session, else a prompt",
|
|
16938
16977
|
config: "usage: vxil config get <feature> \xB7 vxil config draft show|save [--file <f>]|publish|discard <feature> \xB7 vxil config draft duplicate <feature> --from <version>",
|
|
@@ -17052,6 +17091,7 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
17052
17091
|
"check",
|
|
17053
17092
|
"all-projects",
|
|
17054
17093
|
"allow-destructive",
|
|
17094
|
+
"purge-rows",
|
|
17055
17095
|
"exit-code",
|
|
17056
17096
|
"show-runs",
|
|
17057
17097
|
"skip-functions",
|
|
@@ -24714,11 +24754,12 @@ export default defineConfig({
|
|
|
24714
24754
|
function scaffoldProject(configSrc, fns) {
|
|
24715
24755
|
const cwd = process.cwd();
|
|
24716
24756
|
writeFileSync6(resolve7(cwd, "vxil.config.ts"), configSrc);
|
|
24717
|
-
mkdirSync5(resolve7(cwd, "functions"), { recursive: true });
|
|
24718
24757
|
const entries = Object.entries(fns ?? {});
|
|
24719
24758
|
if (entries.length > 0) {
|
|
24759
|
+
mkdirSync5(resolve7(cwd, "functions"), { recursive: true });
|
|
24720
24760
|
for (const [file, src] of entries) writeFileSync6(resolve7(cwd, "functions", file), src);
|
|
24721
|
-
} else if (!existsSync8(resolve7(cwd, "functions/hello.ts"))) {
|
|
24761
|
+
} else if (scaffoldsStarterFunction(configSrc) && !existsSync8(resolve7(cwd, "functions/hello.ts"))) {
|
|
24762
|
+
mkdirSync5(resolve7(cwd, "functions"), { recursive: true });
|
|
24722
24763
|
writeFileSync6(resolve7(cwd, "functions/hello.ts"), STARTER_FN);
|
|
24723
24764
|
}
|
|
24724
24765
|
mkdirSync5(resolve7(cwd, ".vxil"), { recursive: true });
|
|
@@ -24754,7 +24795,7 @@ function initProject(opts = {}) {
|
|
|
24754
24795
|
console.log(` # collections: ${tpl.collections.join(", ")} \xB7 features: ${tpl.features.join(", ")}`);
|
|
24755
24796
|
const fnFiles = Object.keys(tpl.functions ?? {});
|
|
24756
24797
|
if (fnFiles.length > 0) for (const f of fnFiles) console.log(` functions/${f.padEnd(8)} # the template's function`);
|
|
24757
|
-
else console.log(" functions/hello.ts # a starter function");
|
|
24798
|
+
else if (scaffoldsStarterFunction(tpl.configSrc)) console.log(" functions/hello.ts # a starter function");
|
|
24758
24799
|
console.log(" .vxil/ # project binding (gitignored)");
|
|
24759
24800
|
if (pkgRes.action === "created" || pkgRes.action === "added") {
|
|
24760
24801
|
const sdk = Object.values(tpl.functions ?? {}).some((src) => src.includes("from '@vxil/sdk'"));
|
|
@@ -26134,25 +26175,35 @@ export default defineConfig(${JSON.stringify({ features: featBlocks, cms: { coll
|
|
|
26134
26175
|
const [sub] = positional(0);
|
|
26135
26176
|
if (sub === "drop") {
|
|
26136
26177
|
const [collection] = positional(1);
|
|
26137
|
-
if (!collection) fail("usage: vxil cms drop <collection> [--yes]");
|
|
26178
|
+
if (!collection) fail("usage: vxil cms drop <collection> [--purge-rows] [--yes]");
|
|
26179
|
+
const purgeRows = hasFlag("purge-rows");
|
|
26138
26180
|
const { api: api2, target } = requireApi(selector(), { write: `cms drop ${collection}` });
|
|
26139
26181
|
const plan = await planCollectionDrop(api2, collection);
|
|
26140
26182
|
if (plan.remoteUnavailable) {
|
|
26141
26183
|
fail(`cannot read ${plan.remoteUnavailable.route} (${plan.remoteUnavailable.code ?? plan.remoteUnavailable.status}) \u2014 refusing to drop against an unread target`);
|
|
26142
26184
|
}
|
|
26143
|
-
if (!plan.exists) fail(`collection '${collection}' does not exist on tenant '${target.tenantSlug ?? "(env key)"}' \u2014 nothing to drop`);
|
|
26185
|
+
if (!plan.exists && !purgeRows) fail(`collection '${collection}' does not exist on tenant '${target.tenantSlug ?? "(env key)"}' \u2014 nothing to drop (\`--purge-rows\` deletes the rows an earlier drop retained)`);
|
|
26144
26186
|
let declaredLocally = false;
|
|
26145
26187
|
try {
|
|
26146
26188
|
const cfg = await loadVxilConfig();
|
|
26147
26189
|
declaredLocally = Object.prototype.hasOwnProperty.call(cfg.cms?.collections ?? {}, collection);
|
|
26148
26190
|
} catch {
|
|
26149
26191
|
}
|
|
26150
|
-
console.error(formatCollectionDrop(collection, plan, declaredLocally));
|
|
26192
|
+
console.error(formatCollectionDrop(collection, plan, declaredLocally, purgeRows));
|
|
26151
26193
|
if (!hasFlag("yes")) {
|
|
26152
26194
|
if (!process.stdin.isTTY) fail("cms drop is destructive \u2014 pass --yes to confirm in a non-interactive run");
|
|
26153
|
-
const ans = await prompt(
|
|
26195
|
+
const ans = await prompt(`${purgeRows ? "drop collection and hard-delete the rows of" : "drop collection"} '${collection}'? type the collection name to confirm: `);
|
|
26154
26196
|
if (ans !== collection) fail("confirmation did not match \u2014 aborted.");
|
|
26155
26197
|
}
|
|
26198
|
+
if (purgeRows) {
|
|
26199
|
+
const { purgedRows } = await dropCollectionPurgingRows(api2, collection);
|
|
26200
|
+
if (jsonOut) {
|
|
26201
|
+
console.log(JSON.stringify({ ok: true, collection, dropped: plan.exists, fields: plan.fields, purged_rows: purgedRows, declared_locally: declaredLocally }));
|
|
26202
|
+
} else {
|
|
26203
|
+
console.log(`\u2713 ${plan.exists ? `dropped collection ${collection} (${plan.fields} field(s)) and ` : `${collection}: `}hard-deleted ${purgedRows} stored row(s)`);
|
|
26204
|
+
}
|
|
26205
|
+
break;
|
|
26206
|
+
}
|
|
26156
26207
|
await dropCollection(api2, collection);
|
|
26157
26208
|
if (jsonOut) {
|
|
26158
26209
|
console.log(JSON.stringify({ ok: true, collection, fields: plan.fields, rows: plan.rows, declared_locally: declaredLocally }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vxil/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The vxil CLI — init, quickstart, push, gen, secrets, keys, migrate, doctor, diff, functions, payments; installs the `vxil` command (npm i -g @vxil/cli).",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@vxil/config": "0.
|
|
49
|
-
"@vxil/feature-configs": "0.
|
|
48
|
+
"@vxil/config": "0.24.0",
|
|
49
|
+
"@vxil/feature-configs": "0.22.0",
|
|
50
50
|
"@vxil/runtime": "0.0.1",
|
|
51
51
|
"@vxil/types": "0.0.1",
|
|
52
52
|
"miniflare": "^4.20260730.0"
|