@vxil/cli 0.11.1 → 0.13.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/config.d.ts +15 -0
- package/dist/config.js +21 -0
- package/dist/vxil.js +253 -53
- package/package.json +3 -3
package/dist/config.d.ts
CHANGED
|
@@ -40,7 +40,22 @@ export interface FieldDef {
|
|
|
40
40
|
* `orgs` role alphabet). The gate
|
|
41
41
|
* never affects WRITES. Carried by BOTH push paths and alterable in place. */
|
|
42
42
|
readRoles?: string[];
|
|
43
|
+
/** EQUALITY INDEX for an UNSLOTTED field (cms.md §3 "Indexed equality"):
|
|
44
|
+
* `=` / `$eq` / `$in` filters on it are index-served instead of a bounded
|
|
45
|
+
* scan — results are identical either way. At most 4 per collection (the
|
|
46
|
+
* equality-key budget, separate from the 8 index slots); not together with
|
|
47
|
+
* `indexSlot` (a slot already indexes equality) and not on a computed field
|
|
48
|
+
* (both checked here at define time and again by the server). Carried by
|
|
49
|
+
* BOTH push paths and alterable in place; on a collection over 2,000 live
|
|
50
|
+
* items `vxil push` also runs the re-index that arms it. */
|
|
51
|
+
indexed?: boolean;
|
|
43
52
|
}
|
|
53
|
+
/** The per-collection budget of `indexed: true` fields (cms/0158: e1…e4). */
|
|
54
|
+
export declare const CMS_INDEXED_FIELD_BUDGET = 4;
|
|
55
|
+
/** Define-time checks of a collection's `indexed` fields — the same refusals
|
|
56
|
+
* the server makes (422), so a config fails at `defineConfig` / `collection()`
|
|
57
|
+
* instead of mid-push. Throws on the first violation. */
|
|
58
|
+
export declare function assertIndexedFields(name: string, def: CollectionDef): void;
|
|
44
59
|
/** One config-declared per-record ACTION button (cms.md §17): exactly ONE
|
|
45
60
|
* human-initiated step — pressing it invokes the deployed tenant function `fn`
|
|
46
61
|
* with `{ collection, item_id, action, actor, item }` and returns its result.
|
package/dist/config.js
CHANGED
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
// ../config/src/index.ts
|
|
2
|
+
var CMS_INDEXED_FIELD_BUDGET = 4;
|
|
3
|
+
function assertIndexedFields(name, def) {
|
|
4
|
+
let n = 0;
|
|
5
|
+
for (const [fname, f] of Object.entries(def.fields ?? {})) {
|
|
6
|
+
if (f.indexed !== true) continue;
|
|
7
|
+
n += 1;
|
|
8
|
+
if (f.indexSlot) {
|
|
9
|
+
throw new Error(`vxil.config: cms.${name}.${fname} sets both indexed and indexSlot '${f.indexSlot}' \u2014 a slot already indexes equality; use one`);
|
|
10
|
+
}
|
|
11
|
+
if (f.computed) {
|
|
12
|
+
throw new Error(`vxil.config: cms.${name}.${fname} is computed \u2014 a computed field cannot be indexed (bind it to an indexSlot instead)`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
if (n > CMS_INDEXED_FIELD_BUDGET) {
|
|
16
|
+
throw new Error(`vxil.config: cms.${name} has ${n} indexed fields \u2014 at most ${CMS_INDEXED_FIELD_BUDGET} per collection`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
2
19
|
var RELEASED_API_VERSIONS = ["v1"];
|
|
3
20
|
function defineConfig(c) {
|
|
4
21
|
if (c.apiVersion !== void 0 && !RELEASED_API_VERSIONS.includes(c.apiVersion)) {
|
|
@@ -6,16 +23,20 @@ function defineConfig(c) {
|
|
|
6
23
|
`vxil.config: apiVersion '${String(c.apiVersion)}' is not a released API major (released: ${RELEASED_API_VERSIONS.map((v) => `'${v}'`).join(", ")}). A new major ships as a new path with its own deprecation window \u2014 see https://vxil.com/docs/guide/12-going-to-production.`
|
|
7
24
|
);
|
|
8
25
|
}
|
|
26
|
+
for (const [name, def] of Object.entries(c.cms?.collections ?? {})) assertIndexedFields(name, def);
|
|
9
27
|
return c;
|
|
10
28
|
}
|
|
11
29
|
function field(type, opts = {}) {
|
|
12
30
|
return { type, ...opts };
|
|
13
31
|
}
|
|
14
32
|
function collection(def) {
|
|
33
|
+
assertIndexedFields(def.singular ?? "collection", def);
|
|
15
34
|
return def;
|
|
16
35
|
}
|
|
17
36
|
export {
|
|
37
|
+
CMS_INDEXED_FIELD_BUDGET,
|
|
18
38
|
RELEASED_API_VERSIONS,
|
|
39
|
+
assertIndexedFields,
|
|
19
40
|
collection,
|
|
20
41
|
defineConfig,
|
|
21
42
|
field
|
package/dist/vxil.js
CHANGED
|
@@ -4461,9 +4461,9 @@ function lowerAllTriggerBindings(def, name) {
|
|
|
4461
4461
|
return bindings;
|
|
4462
4462
|
}
|
|
4463
4463
|
var CONFIG_FILENAMES = ["vxil.config.ts", "vxil.config.mjs", "vxil.config.js"];
|
|
4464
|
-
var VXIL_CONFIG_PKG_VERSION = "0.
|
|
4465
|
-
var VXIL_SDK_PKG_VERSION = "0.
|
|
4466
|
-
var VXIL_CLI_PKG_VERSION = "0.
|
|
4464
|
+
var VXIL_CONFIG_PKG_VERSION = "0.8.0";
|
|
4465
|
+
var VXIL_SDK_PKG_VERSION = "0.13.0";
|
|
4466
|
+
var VXIL_CLI_PKG_VERSION = "0.13.0";
|
|
4467
4467
|
function ensureScaffoldPackageJson(cwd, opts = {}) {
|
|
4468
4468
|
const file = resolve(cwd, "package.json");
|
|
4469
4469
|
const wanted = {
|
|
@@ -4748,6 +4748,7 @@ async function serverApply({ api, bundle, resume, allowDestructive, onPlan }) {
|
|
|
4748
4748
|
`apply execute (resume with: vxil push --server --resume ${plan.apply_id})`
|
|
4749
4749
|
);
|
|
4750
4750
|
}
|
|
4751
|
+
var PUSH_READ_CONCURRENCY = 5;
|
|
4751
4752
|
async function planOrPush({ api, features, apply, allowDestructive }) {
|
|
4752
4753
|
const results = [];
|
|
4753
4754
|
const destructiveQs = allowDestructive ? "&allow_destructive=true" : "";
|
|
@@ -4755,8 +4756,20 @@ async function planOrPush({ api, features, apply, allowDestructive }) {
|
|
|
4755
4756
|
const v = r.body.data?.api_state;
|
|
4756
4757
|
return v && typeof v === "object" && typeof v.ok === "boolean" ? v : void 0;
|
|
4757
4758
|
};
|
|
4758
|
-
|
|
4759
|
-
|
|
4759
|
+
const entries = Object.entries(features);
|
|
4760
|
+
const reads = await mapPool(entries, PUSH_READ_CONCURRENCY, async ([feature, manifest]) => {
|
|
4761
|
+
try {
|
|
4762
|
+
const dry = await api("PUT", `/v1/config/${feature}?dry_run=true${destructiveQs}`, manifest);
|
|
4763
|
+
if (dry.status !== 200) return { dry };
|
|
4764
|
+
return { dry, cur: await api("GET", `/v1/config/${feature}`) };
|
|
4765
|
+
} catch (err) {
|
|
4766
|
+
return { err };
|
|
4767
|
+
}
|
|
4768
|
+
});
|
|
4769
|
+
for (const [i, [feature, manifest]] of entries.entries()) {
|
|
4770
|
+
const read = reads[i];
|
|
4771
|
+
if ("err" in read) throw read.err;
|
|
4772
|
+
const dry = read.dry;
|
|
4760
4773
|
if (dry.status !== 200) {
|
|
4761
4774
|
const e = dry.body.error ?? {};
|
|
4762
4775
|
throw new Error(
|
|
@@ -4765,7 +4778,7 @@ async function planOrPush({ api, features, apply, allowDestructive }) {
|
|
|
4765
4778
|
}
|
|
4766
4779
|
const effective = dry.body.data?.manifest;
|
|
4767
4780
|
const apiStatePlan = apiStateOf(dry);
|
|
4768
|
-
const cur =
|
|
4781
|
+
const cur = read.cur;
|
|
4769
4782
|
const unavailable = readUnavailable(`GET /v1/config/${feature}`, cur);
|
|
4770
4783
|
if (unavailable) {
|
|
4771
4784
|
if (apply) {
|
|
@@ -7706,6 +7719,58 @@ function hookReconcileOf(data2) {
|
|
|
7706
7719
|
function hookReconcileRemedy(fnName) {
|
|
7707
7720
|
return fnName ? `re-run the reconcile with \`vxil functions deploy ${fnName} --force\` or \`vxil push --server\` (a plain re-push sends no deploy for an unchanged function, so it would not retry it)` : "re-run the reconcile with `vxil push --server` (a plain re-push sends no deploy, so it would not retry it)";
|
|
7708
7721
|
}
|
|
7722
|
+
var WIRING_WARNING = /trigger subscription|cron schedule/i;
|
|
7723
|
+
function triggerWiringOf(data2) {
|
|
7724
|
+
if (!data2) return null;
|
|
7725
|
+
const counts = (v) => {
|
|
7726
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return void 0;
|
|
7727
|
+
const o = v;
|
|
7728
|
+
const n = (x) => typeof x === "number" && Number.isFinite(x) ? x : 0;
|
|
7729
|
+
const opt = (x) => typeof x === "number" && Number.isFinite(x) ? x : void 0;
|
|
7730
|
+
const updated = opt(o.updated);
|
|
7731
|
+
const unchanged = opt(o.unchanged);
|
|
7732
|
+
return {
|
|
7733
|
+
created: n(o.created),
|
|
7734
|
+
...updated !== void 0 ? { updated } : {},
|
|
7735
|
+
...unchanged !== void 0 ? { unchanged } : {},
|
|
7736
|
+
deleted: n(o.deleted),
|
|
7737
|
+
failed: n(o.failed)
|
|
7738
|
+
};
|
|
7739
|
+
};
|
|
7740
|
+
const cms = counts(data2.cms_hook_subscriptions);
|
|
7741
|
+
const webhook = counts(data2.webhook_subscriptions);
|
|
7742
|
+
const cron = counts(data2.cron_schedules);
|
|
7743
|
+
const total = counts(data2.hook_reconcile);
|
|
7744
|
+
if (!total && !cms && !webhook && !cron) return null;
|
|
7745
|
+
const warnings = Array.isArray(data2.warnings) ? data2.warnings.filter((w) => typeof w === "string" && WIRING_WARNING.test(w)) : [];
|
|
7746
|
+
return {
|
|
7747
|
+
total: total ?? { created: 0, deleted: 0, failed: (cms?.failed ?? 0) + (webhook?.failed ?? 0) + (cron?.failed ?? 0) },
|
|
7748
|
+
...cms ? { cms_hook_subscriptions: cms } : {},
|
|
7749
|
+
...webhook ? { webhook_subscriptions: webhook } : {},
|
|
7750
|
+
...cron ? { cron_schedules: cron } : {},
|
|
7751
|
+
warnings
|
|
7752
|
+
};
|
|
7753
|
+
}
|
|
7754
|
+
function formatTriggerWiring(r) {
|
|
7755
|
+
const line = (c) => [
|
|
7756
|
+
`${c.created} created`,
|
|
7757
|
+
...c.updated !== void 0 ? [`${c.updated} updated`] : [],
|
|
7758
|
+
...c.unchanged !== void 0 ? [`${c.unchanged} unchanged`] : [],
|
|
7759
|
+
`${c.deleted} removed`,
|
|
7760
|
+
`${c.failed} failed`
|
|
7761
|
+
].join(" \xB7 ");
|
|
7762
|
+
const lanes = [
|
|
7763
|
+
["hook subscriptions (cms/auth)", r.cms_hook_subscriptions],
|
|
7764
|
+
["webhook-trigger subscriptions", r.webhook_subscriptions],
|
|
7765
|
+
["cron schedules", r.cron_schedules]
|
|
7766
|
+
];
|
|
7767
|
+
const out = [r.total.failed > 0 ? `trigger wiring (reconciled by the server) \u2014 ${r.total.failed} FAILED:` : "trigger wiring (reconciled by the server):"];
|
|
7768
|
+
const shown = lanes.filter(([, c]) => c);
|
|
7769
|
+
if (shown.length) for (const [label, c] of shown) out.push(` ${label.padEnd(30)} ${line(c)}`);
|
|
7770
|
+
else out.push(` ${"all triggers".padEnd(30)} ${line(r.total)}`);
|
|
7771
|
+
for (const w of r.warnings) out.push(` ! ${w}`);
|
|
7772
|
+
return out;
|
|
7773
|
+
}
|
|
7709
7774
|
function sourceSha12(source, runtime) {
|
|
7710
7775
|
const suffix = runtime ? functionsRuntimeHashSuffix(runtime) : "";
|
|
7711
7776
|
return createHash("sha256").update(source + suffix, "utf8").digest("hex").slice(0, 12);
|
|
@@ -8085,6 +8150,14 @@ function formatLogLines(lines) {
|
|
|
8085
8150
|
}
|
|
8086
8151
|
|
|
8087
8152
|
// src/cms.ts
|
|
8153
|
+
function indexReadyOf(rf) {
|
|
8154
|
+
return rf.index_ready === true;
|
|
8155
|
+
}
|
|
8156
|
+
var EQ_INDEX_INLINE_MAX_ROWS = 2e3;
|
|
8157
|
+
function indexArmNote(rows) {
|
|
8158
|
+
if (rows === null) return "arms inline or through the re-index push runs (live row count unavailable)";
|
|
8159
|
+
return rows <= EQ_INDEX_INLINE_MAX_ROWS ? `arms inline (${rows} rows)` : `needs reindex (${rows} rows; push runs it)`;
|
|
8160
|
+
}
|
|
8088
8161
|
function normalizeActions(raw) {
|
|
8089
8162
|
if (!Array.isArray(raw)) return [];
|
|
8090
8163
|
return raw.filter((a) => !!a && typeof a === "object").map((a) => ({ key: String(a.key), label: String(a.label), fn: String(a.fn) }));
|
|
@@ -8104,7 +8177,10 @@ var FIELD_ATTRS = [
|
|
|
8104
8177
|
// (the server stores NULL for "ungated" and normalizes `[]` to NULL), so an
|
|
8105
8178
|
// omitted attribute diffs clean; CLEARING it WIDENS access and therefore rides
|
|
8106
8179
|
// the same --allow-destructive gate as clearing unique/on_delete/owner_field.
|
|
8107
|
-
{ config: "readRoles", wire: "read_roles", remote: "read_roles", blank: null, alter: "tighten-only" }
|
|
8180
|
+
{ config: "readRoles", wire: "read_roles", remote: "read_roles", blank: null, alter: "tighten-only" },
|
|
8181
|
+
// PERF-26b: the equality index of an unslotted field (≤4 per collection).
|
|
8182
|
+
// Present-key on the server; arming on > 2,000 live rows runs the re-index.
|
|
8183
|
+
{ config: "indexed", wire: "indexed", remote: "indexed", blank: false, alter: "index" }
|
|
8108
8184
|
];
|
|
8109
8185
|
function fieldToInput(name, def) {
|
|
8110
8186
|
const out = { field: name, type: def.type };
|
|
@@ -8127,7 +8203,7 @@ function remoteFieldToConfig(rf) {
|
|
|
8127
8203
|
function fieldAttrDelta(def, rf) {
|
|
8128
8204
|
const d = def;
|
|
8129
8205
|
const r = rf;
|
|
8130
|
-
const out = { flags: [], clearing: [], inPlace: [], reindex: [], retarget: [], drift: [] };
|
|
8206
|
+
const out = { flags: [], clearing: [], inPlace: [], reindex: [], retarget: [], index: [], drift: [] };
|
|
8131
8207
|
for (const a of FIELD_ATTRS) {
|
|
8132
8208
|
const wantBlank = stableStringify(a.blank);
|
|
8133
8209
|
const want = stableStringify(d[a.config] ?? a.blank);
|
|
@@ -8144,6 +8220,7 @@ function fieldAttrDelta(def, rf) {
|
|
|
8144
8220
|
}
|
|
8145
8221
|
if (a.alter === "reindex") out.reindex.push(line);
|
|
8146
8222
|
else if (a.alter === "destructive") out.retarget.push(line);
|
|
8223
|
+
else if (a.alter === "index") out.index.push(line);
|
|
8147
8224
|
else out.inPlace.push(line);
|
|
8148
8225
|
}
|
|
8149
8226
|
return out;
|
|
@@ -8189,6 +8266,7 @@ async function reindexCollection({ api, collection, field, onProgress }) {
|
|
|
8189
8266
|
let updated = 0;
|
|
8190
8267
|
let pages = 0;
|
|
8191
8268
|
let skipped = 0;
|
|
8269
|
+
let indexCertify;
|
|
8192
8270
|
const page = async () => {
|
|
8193
8271
|
const res = await api("POST", `/v1/cms/collections/${encodeURIComponent(collection)}/reindex`, {
|
|
8194
8272
|
...field ? { field } : {},
|
|
@@ -8202,6 +8280,7 @@ async function reindexCollection({ api, collection, field, onProgress }) {
|
|
|
8202
8280
|
scanned += d.scanned ?? 0;
|
|
8203
8281
|
updated += d.updated ?? 0;
|
|
8204
8282
|
pages++;
|
|
8283
|
+
if (d.index_certify) indexCertify = d.index_certify;
|
|
8205
8284
|
return d;
|
|
8206
8285
|
};
|
|
8207
8286
|
for (; ; ) {
|
|
@@ -8218,7 +8297,7 @@ async function reindexCollection({ api, collection, field, onProgress }) {
|
|
|
8218
8297
|
}
|
|
8219
8298
|
cursor = d.next_cursor;
|
|
8220
8299
|
}
|
|
8221
|
-
return { scanned, updated, pages, skipped };
|
|
8300
|
+
return { scanned, updated, pages, skipped, ...indexCertify ? { indexCertify } : {} };
|
|
8222
8301
|
}
|
|
8223
8302
|
async function planCmsSchema({ api, collections, apply, allowDestructive = false, onProgress }) {
|
|
8224
8303
|
const read = await readRemote2(api);
|
|
@@ -8247,11 +8326,17 @@ async function planCmsSchema({ api, collections, apply, allowDestructive = false
|
|
|
8247
8326
|
const collChanges = [];
|
|
8248
8327
|
const post = [];
|
|
8249
8328
|
const slotMoved = [];
|
|
8329
|
+
const indexArming = [];
|
|
8330
|
+
const indexVacating = [];
|
|
8331
|
+
let rowsMemo;
|
|
8332
|
+
const rows = () => rowsMemo ??= liveRowCount(api, name);
|
|
8250
8333
|
const remoteByName = new Map(rc.fields.map((f) => [f.field, f]));
|
|
8251
8334
|
for (const [fn, fd] of declaredFields) {
|
|
8252
8335
|
const existing = remoteByName.get(fn);
|
|
8253
8336
|
if (!existing) {
|
|
8254
|
-
|
|
8337
|
+
const arm = fd.indexed === true ? `; indexed: ${indexArmNote(await rows())}` : "";
|
|
8338
|
+
if (fd.indexed === true) indexArming.push(fn);
|
|
8339
|
+
collChanges.push({ collection: name, kind: "add-field", field: fn, detail: `${name}.${fn} (${fd.type})${arm}` });
|
|
8255
8340
|
} else if (existing.type !== fd.type) {
|
|
8256
8341
|
if (allowDestructive && (fd.indexSlot ?? null) !== (existing.index_slot ?? null)) {
|
|
8257
8342
|
if (existing.index_slot) {
|
|
@@ -8274,10 +8359,14 @@ async function planCmsSchema({ api, collections, apply, allowDestructive = false
|
|
|
8274
8359
|
detail: `${name}.${fn} type ${existing.type} \u2192 ${fd.type} (destructive alter)`
|
|
8275
8360
|
});
|
|
8276
8361
|
} else {
|
|
8277
|
-
const { flags, clearing, inPlace, reindex, retarget, drift } = fieldAttrDelta(fd, existing);
|
|
8362
|
+
const { flags, clearing, inPlace, reindex, retarget, index, drift } = fieldAttrDelta(fd, existing);
|
|
8363
|
+
const armsIndex = fd.indexed === true && (index.length > 0 || !indexReadyOf(existing));
|
|
8364
|
+
const indexNote = armsIndex ? ` \u2014 ${fn} indexed: ${indexArmNote(await rows())}` : "";
|
|
8365
|
+
if (armsIndex) indexArming.push(fn);
|
|
8366
|
+
if (index.length > 0 && fd.indexed !== true && existing.indexed === true) indexVacating.push(fn);
|
|
8278
8367
|
const destructiveAttrs = [...clearing, ...reindex, ...retarget];
|
|
8279
8368
|
if (destructiveAttrs.length) {
|
|
8280
|
-
const all = [...destructiveAttrs, ...flags, ...inPlace];
|
|
8369
|
+
const all = [...destructiveAttrs, ...flags, ...inPlace, ...index];
|
|
8281
8370
|
if (allowDestructive) {
|
|
8282
8371
|
if (reindex.length && existing.index_slot) {
|
|
8283
8372
|
pre.push({
|
|
@@ -8294,7 +8383,7 @@ async function planCmsSchema({ api, collections, apply, allowDestructive = false
|
|
|
8294
8383
|
kind: "destructive",
|
|
8295
8384
|
op: "alter-field",
|
|
8296
8385
|
field: fn,
|
|
8297
|
-
detail: `${name}.${fn} ${all.join(", ")}` + (clearing.length ? " \u2014 clearing a live invariant (unique claims / on_delete cascade / read_roles gate)" : "") + (reindex.length ? " \u2014 an index_slot move: stored rows keep the OLD projection until the re-index runs" : "") + (retarget.length ? " \u2014 a relation retarget: every stored id now names a different collection" : "") + " (full-config reconciliation)"
|
|
8386
|
+
detail: `${name}.${fn} ${all.join(", ")}` + (clearing.length ? " \u2014 clearing a live invariant (unique claims / on_delete cascade / read_roles gate)" : "") + (reindex.length ? " \u2014 an index_slot move: stored rows keep the OLD projection until the re-index runs" : "") + (retarget.length ? " \u2014 a relation retarget: every stored id now names a different collection" : "") + indexNote + " (full-config reconciliation)"
|
|
8298
8387
|
});
|
|
8299
8388
|
if (reindex.length) slotMoved.push(fn);
|
|
8300
8389
|
} else {
|
|
@@ -8305,12 +8394,12 @@ async function planCmsSchema({ api, collections, apply, allowDestructive = false
|
|
|
8305
8394
|
detail: `${name}.${fn} ${all.join(", ")}: this is DESTRUCTIVE-SHAPED \u2014 ` + (clearing.length ? "it CLEARS a live invariant (clearing unique DROPS the uniqueness claims \u2014 duplicates then accepted, unrecoverable; clearing on_delete stops cascades; clearing read_roles WIDENS the field to every end-user); " : "") + (reindex.length ? "an index_slot move leaves stored rows on the OLD projection until a re-index, so queries on the field return the WRONG rows; " : "") + (retarget.length ? "a relation retarget repoints every stored id at another collection; " : "") + "it is NOT applied (nor is any co-declared in-place change on this field). Re-run with --allow-destructive to apply."
|
|
8306
8395
|
});
|
|
8307
8396
|
}
|
|
8308
|
-
} else if (flags.length || inPlace.length) {
|
|
8397
|
+
} else if (flags.length || inPlace.length || index.length) {
|
|
8309
8398
|
collChanges.push({
|
|
8310
8399
|
collection: name,
|
|
8311
8400
|
kind: "alter-flags",
|
|
8312
8401
|
field: fn,
|
|
8313
|
-
detail: `${name}.${fn} ${[...flags, ...inPlace].join(", ")} (non-destructive in-place alter)`
|
|
8402
|
+
detail: `${name}.${fn} ${[...flags, ...inPlace, ...index].join(", ")} (non-destructive in-place alter)${indexNote}`
|
|
8314
8403
|
});
|
|
8315
8404
|
}
|
|
8316
8405
|
if (drift.length) {
|
|
@@ -8374,12 +8463,25 @@ async function planCmsSchema({ api, collections, apply, allowDestructive = false
|
|
|
8374
8463
|
});
|
|
8375
8464
|
}
|
|
8376
8465
|
}
|
|
8377
|
-
|
|
8378
|
-
|
|
8466
|
+
let armReindex = false;
|
|
8467
|
+
let vacateReindex = false;
|
|
8468
|
+
if (indexArming.length > 0 || indexVacating.length > 0) {
|
|
8469
|
+
const n = await rows();
|
|
8470
|
+
const large = n === null || n > EQ_INDEX_INLINE_MAX_ROWS;
|
|
8471
|
+
armReindex = large && indexArming.length > 0;
|
|
8472
|
+
vacateReindex = large && indexVacating.length > 0;
|
|
8473
|
+
}
|
|
8474
|
+
if (slotMoved.length || armReindex || vacateReindex) {
|
|
8475
|
+
const n = await rows();
|
|
8476
|
+
const why = [
|
|
8477
|
+
...slotMoved.length ? [`the index_slot move on ${slotMoved.join(", ")}`] : [],
|
|
8478
|
+
...armReindex ? [`arming the equality index on ${indexArming.join(", ")}`] : [],
|
|
8479
|
+
...vacateReindex ? [`clearing the equality index of ${indexVacating.join(", ")}`] : []
|
|
8480
|
+
];
|
|
8379
8481
|
post.push({
|
|
8380
8482
|
collection: name,
|
|
8381
8483
|
kind: "reindex",
|
|
8382
|
-
detail: `${name} re-index ${n === null ? "live rows" : `${n} live rows`} after
|
|
8484
|
+
detail: `${name} re-index ${n === null ? "live rows" : `${n} live rows`} after ${why.join(" and ")}`
|
|
8383
8485
|
});
|
|
8384
8486
|
}
|
|
8385
8487
|
changes.push(...pre, ...collChanges, ...post);
|
|
@@ -8469,6 +8571,8 @@ async function planCmsSchema({ api, collections, apply, allowDestructive = false
|
|
|
8469
8571
|
if (done.skipped > 0) {
|
|
8470
8572
|
onProgress?.(` \u26A0 ${done.skipped} row(s) of ${c.collection} were written while the re-index ran and were NOT re-projected \u2014 re-run \`vxil cms reindex ` + c.collection + "` when writes are quiet");
|
|
8471
8573
|
}
|
|
8574
|
+
const certifyWarning = indexCertifyWarning(c.collection, done.indexCertify);
|
|
8575
|
+
if (certifyWarning) onProgress?.(certifyWarning);
|
|
8472
8576
|
applied++;
|
|
8473
8577
|
} else if (c.kind === "destructive") {
|
|
8474
8578
|
applied += await applyDestructive(api, collections, c);
|
|
@@ -8543,6 +8647,10 @@ function formatCollectionDrop(collection, plan, declaredLocally) {
|
|
|
8543
8647
|
}
|
|
8544
8648
|
return lines.join("\n");
|
|
8545
8649
|
}
|
|
8650
|
+
function indexCertifyWarning(collection, certify) {
|
|
8651
|
+
if (certify !== "timeout" && certify !== "pending") return null;
|
|
8652
|
+
return ` \u26A0 the index on ${collection} is NOT armed yet (` + (certify === "timeout" ? "its certification ran out of time" : "rows changed during the pass") + ") \u2014 filters stay correct but unaccelerated; re-run `vxil cms reindex " + collection + "`";
|
|
8653
|
+
}
|
|
8546
8654
|
|
|
8547
8655
|
// src/apiCmd.ts
|
|
8548
8656
|
var API_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
@@ -11668,7 +11776,7 @@ var TOOLS = [
|
|
|
11668
11776
|
{
|
|
11669
11777
|
name: "cms_list_collections",
|
|
11670
11778
|
feature: "cms",
|
|
11671
|
-
description: "List the tenant's content model: collections with their typed field definitions (the schema items are validated against). Each field row carries its attributes \u2014 required, validation, index_slot, relation_to, computed/compute, is_unique, on_delete,
|
|
11779
|
+
description: "List the tenant's content model: collections with their typed field definitions (the schema items are validated against). Each field row carries its attributes \u2014 required, validation, index_slot, relation_to, computed/compute, is_unique, on_delete, read_roles (the end-user org roles allowed to READ that field; null = ungated), and on an equality-indexed field indexed: true + index_ready (equality filters on it are index-served once ready; until then, and on unindexed unslotted fields, they are a bounded scan \u2014 same results). A field with read_roles is omitted from end-user reads without a matching role and is never served on the public lane, so a missing key in an item's data may be a read gate rather than an unset value.",
|
|
11672
11780
|
inputSchema: { type: "object", properties: {} },
|
|
11673
11781
|
method: "GET",
|
|
11674
11782
|
path: "/v1/cms/collections"
|
|
@@ -11692,7 +11800,7 @@ var TOOLS = [
|
|
|
11692
11800
|
{
|
|
11693
11801
|
name: "cms_query_items",
|
|
11694
11802
|
feature: "cms",
|
|
11695
|
-
description: 'Query content items with the bounded DSL. filter is a JSON object (\u22648 terms): {"price":{"$gte":10},"status":"published"}; ops $eq $ne $gt $gte $lt $lte $in $contains $startsWith (substring/prefix, LIKE-escaped) $arrayContains/$anyOf (array fields: all-of / any-of). sort: "-field" (slot-indexed fields + created_at/updated_at/published_at). Paging: pass the returned next_cursor back as cursor WITH THE SAME filter and sort (works for the default order and for any slot/timestamp sort; not for a dotted join sort). $expand inlines relation/file fields. count=true returns {count} instead of a page (filter only \u2014 no sort/limit/cursor/$expand).',
|
|
11803
|
+
description: 'Query content items with the bounded DSL. filter is a JSON object (\u22648 terms): {"price":{"$gte":10},"status":"published"}; ops $eq $ne $gt $gte $lt $lte $in $contains $startsWith (substring/prefix, LIKE-escaped) $arrayContains/$anyOf (array fields: all-of / any-of). Equality ($eq / bare value / $in) is index-served on slot-indexed and `indexed` fields; on other fields it is a bounded scan (same results). sort: "-field" (slot-indexed fields + created_at/updated_at/published_at). Paging: pass the returned next_cursor back as cursor WITH THE SAME filter and sort (works for the default order and for any slot/timestamp sort; not for a dotted join sort). $expand inlines relation/file fields. count=true returns {count} instead of a page (filter only \u2014 no sort/limit/cursor/$expand).',
|
|
11696
11804
|
inputSchema: {
|
|
11697
11805
|
type: "object",
|
|
11698
11806
|
properties: {
|
|
@@ -12302,7 +12410,7 @@ var TOOLS = [
|
|
|
12302
12410
|
{
|
|
12303
12411
|
name: "search_query",
|
|
12304
12412
|
feature: "vector-search",
|
|
12305
|
-
description: "Retrieve the top-k most relevant chunks from a vector-search collection. mode 'hybrid' (default) fuses semantic + keyword via RRF; 'vector' is pure semantic; 'keyword' is lexical. filter is a JSON object of equality constraints over chunk metadata. Returns
|
|
12413
|
+
description: "Retrieve the top-k most relevant chunks from a vector-search collection. mode 'hybrid' (default) fuses semantic + keyword via RRF; 'vector' is pure semantic; 'keyword' is lexical. filter is a JSON object of equality constraints over chunk metadata. Returns ranked results with document/chunk attribution: score is the fused rank value (orders the hits, not a similarity); similarity is the cosine similarity to the query (-1..1, null in keyword mode) \u2014 threshold on similarity.",
|
|
12306
12414
|
inputSchema: {
|
|
12307
12415
|
type: "object",
|
|
12308
12416
|
properties: {
|
|
@@ -12496,7 +12604,7 @@ var TOOLS = [
|
|
|
12496
12604
|
filter: { type: "object", additionalProperties: true, description: "Metadata equality filter applied to retrieval." },
|
|
12497
12605
|
rerank: { type: "boolean", description: "Forward rerank:true to vector-search's BYO rerank pass (inert until configured there)." },
|
|
12498
12606
|
boosts: { type: "object", additionalProperties: true, description: "Per-metadata-field boost specs: { field: { kind:'recency', halfLifeDays?, weight? } | { kind:'value', weights:{\u2026} } }." },
|
|
12499
|
-
min_score: { type: "number", description: "Drop hits below this EFFECTIVE (post-boost) score." }
|
|
12607
|
+
min_score: { type: "number", description: "Drop hits below this EFFECTIVE (post-boost) score. score is a rank value (about 0.016-0.033 at the top), so a floor above ~0.033 drops every hit; judge relevance by each hit's similarity instead." }
|
|
12500
12608
|
},
|
|
12501
12609
|
required: ["query"]
|
|
12502
12610
|
},
|
|
@@ -13933,7 +14041,7 @@ var VERB_USAGE = {
|
|
|
13933
14041
|
mcp: "usage: vxil mcp install [--client cursor|claude|vscode] [--project] [--print] [--key <api_key>] [--scopes a,b] [--name <server>]",
|
|
13934
14042
|
plan: "usage: vxil plan [--dev|--target <name>] [--explain] [--skip-functions] \u2014 what `vxil push` would change: cms schema, feature config, declared API state, functions (a deployed function the config no longer declares shows as `-`); writes nothing",
|
|
13935
14043
|
diff: "usage: vxil diff [--against <target>|--exit-code] [--explain] [--ignore <k,\u2026>] [--strict-secrets] [--strict-api-state] [--api-state] [--json] \u2014 the CI gate: exit 0 no drift \xB7 1 drift \xB7 2 error",
|
|
13936
|
-
push: "usage: vxil push [--dev|--target <name>|--prod] [--allow-destructive] [--strict] [--server [--resume <apply_id>]] [--no-gen] [--skip-functions] [--yes] \u2014 apply the config; --allow-destructive also deletes deployed functions and stored AI templates the config no longer declares; --strict exits 1 when anything declared was refused, deferred or left unwired",
|
|
14044
|
+
push: "usage: vxil push [--dev|--target <name>|--prod] [--allow-destructive] [--strict] [--server [--resume <apply_id>]] [--no-gen] [--skip-functions] [--yes] \u2014 apply the config; --allow-destructive also deletes deployed functions and stored AI templates the config no longer declares; --strict exits 1 when anything declared was refused, deferred or left unwired; --server prints the trigger wiring the server reconciled (--json: one result document on stdout)",
|
|
13937
14045
|
gen: "usage: vxil gen [--check] [--offline] [--out <f>] [--no-mcp] [--mcp-out <f>] \u2014 generate vxil.types.ts (+ vxil.mcp.json); --offline keeps an existing API_VERSIONS block; --check compares instead of writing",
|
|
13938
14046
|
types: "usage: vxil types \u2014 alias of `vxil gen` (run `vxil gen --help`)",
|
|
13939
14047
|
doctor: "usage: vxil doctor [--dev|--target <name>] \u2014 preflight: the binding's tenant vs the key's tenant, secrets referenced-vs-stored, per-function hashes, keys (read-only)",
|
|
@@ -14022,6 +14130,32 @@ function pushExitCode(p, strict) {
|
|
|
14022
14130
|
if (p.strict.length) lines.push(`${p.strict.length} warning(s) \u2014 \`vxil push --strict\` turns them into exit 1 for CI`);
|
|
14023
14131
|
return { code: 0, lines };
|
|
14024
14132
|
}
|
|
14133
|
+
function serverApplyJson(input) {
|
|
14134
|
+
const { view, triggerWiring, problems, exitCode, error } = input;
|
|
14135
|
+
return {
|
|
14136
|
+
ok: exitCode === 0,
|
|
14137
|
+
exit_code: exitCode,
|
|
14138
|
+
apply_id: view.apply_id,
|
|
14139
|
+
status: view.status,
|
|
14140
|
+
steps: view.steps,
|
|
14141
|
+
...view.failed_step ? { failed_step: view.failed_step } : {},
|
|
14142
|
+
trigger_wiring: triggerWiring ?? null,
|
|
14143
|
+
problems: { hard: problems.hard, strict: problems.strict },
|
|
14144
|
+
...error ? { error } : {}
|
|
14145
|
+
};
|
|
14146
|
+
}
|
|
14147
|
+
function serverApplyErrorJson(input) {
|
|
14148
|
+
return {
|
|
14149
|
+
ok: false,
|
|
14150
|
+
exit_code: input.exitCode,
|
|
14151
|
+
apply_id: null,
|
|
14152
|
+
status: null,
|
|
14153
|
+
steps: [],
|
|
14154
|
+
trigger_wiring: null,
|
|
14155
|
+
problems: { hard: [], strict: [] },
|
|
14156
|
+
error: input.error
|
|
14157
|
+
};
|
|
14158
|
+
}
|
|
14025
14159
|
|
|
14026
14160
|
// src/paymentsCmd.ts
|
|
14027
14161
|
var SIMULATE_SCENARIOS = [
|
|
@@ -19669,12 +19803,19 @@ function hasFlag(name) {
|
|
|
19669
19803
|
return rest.includes(`--${name}`);
|
|
19670
19804
|
}
|
|
19671
19805
|
var jsonOut = rest.includes("--json");
|
|
19806
|
+
var jsonFailDoc = null;
|
|
19672
19807
|
function ttlFlag(fallback) {
|
|
19673
19808
|
const parsed = parseTtlFlag(flag("ttl"), fallback);
|
|
19674
19809
|
if ("error" in parsed) fail(parsed.error);
|
|
19675
19810
|
return parsed.hours;
|
|
19676
19811
|
}
|
|
19677
19812
|
function fail(msg, code = 1) {
|
|
19813
|
+
if (jsonFailDoc) {
|
|
19814
|
+
const doc = jsonFailDoc;
|
|
19815
|
+
jsonFailDoc = null;
|
|
19816
|
+
process.stdout.write(`${doc(msg, code)}
|
|
19817
|
+
`);
|
|
19818
|
+
}
|
|
19678
19819
|
console.error(`vxil: ${msg}`);
|
|
19679
19820
|
process.exit(code);
|
|
19680
19821
|
}
|
|
@@ -20566,22 +20707,79 @@ async function backendListsFunctions(api) {
|
|
|
20566
20707
|
}
|
|
20567
20708
|
}
|
|
20568
20709
|
async function runServerApply(sel) {
|
|
20710
|
+
armServerApplyJsonFail();
|
|
20569
20711
|
const { api, target } = requireApi(sel, { write: "push --server" });
|
|
20712
|
+
const say = (s) => {
|
|
20713
|
+
if (jsonOut) console.error(s);
|
|
20714
|
+
else console.log(s);
|
|
20715
|
+
};
|
|
20716
|
+
const strict = hasFlag("strict");
|
|
20717
|
+
const finish = async (done2, problems2, code, error) => {
|
|
20718
|
+
jsonFailDoc = null;
|
|
20719
|
+
if (jsonOut) {
|
|
20720
|
+
const doc = JSON.stringify(serverApplyJson({
|
|
20721
|
+
view: done2,
|
|
20722
|
+
triggerWiring: triggerWiringOf(done2),
|
|
20723
|
+
problems: problems2,
|
|
20724
|
+
exitCode: code,
|
|
20725
|
+
...error ? { error } : {}
|
|
20726
|
+
}));
|
|
20727
|
+
await new Promise((r) => {
|
|
20728
|
+
process.stdout.write(`${doc}
|
|
20729
|
+
`, () => r());
|
|
20730
|
+
});
|
|
20731
|
+
}
|
|
20732
|
+
if (error) fail(error, code);
|
|
20733
|
+
process.exit(code);
|
|
20734
|
+
};
|
|
20735
|
+
const armWith = (done2, problems2) => {
|
|
20736
|
+
if (!jsonOut) return;
|
|
20737
|
+
jsonFailDoc = (error, exitCode) => JSON.stringify(serverApplyJson({
|
|
20738
|
+
view: done2,
|
|
20739
|
+
triggerWiring: triggerWiringOf(done2),
|
|
20740
|
+
problems: problems2,
|
|
20741
|
+
exitCode,
|
|
20742
|
+
error
|
|
20743
|
+
}));
|
|
20744
|
+
};
|
|
20745
|
+
const report = (done2, wiring) => {
|
|
20746
|
+
say(formatApplySteps(done2.steps));
|
|
20747
|
+
say(`
|
|
20748
|
+
apply ${done2.apply_id}: ${done2.status}`);
|
|
20749
|
+
if (!wiring) return;
|
|
20750
|
+
const w = triggerWiringOf(done2);
|
|
20751
|
+
if (w) say(`
|
|
20752
|
+
${formatTriggerWiring(w).join("\n")}`);
|
|
20753
|
+
};
|
|
20754
|
+
const gen = async () => {
|
|
20755
|
+
if (hasFlag("no-gen")) return;
|
|
20756
|
+
if (!jsonOut) {
|
|
20757
|
+
await runGen(sel);
|
|
20758
|
+
return;
|
|
20759
|
+
}
|
|
20760
|
+
const log = console.log;
|
|
20761
|
+
console.log = (...a) => {
|
|
20762
|
+
console.error(...a);
|
|
20763
|
+
};
|
|
20764
|
+
try {
|
|
20765
|
+
await runGen(sel);
|
|
20766
|
+
} finally {
|
|
20767
|
+
console.log = log;
|
|
20768
|
+
}
|
|
20769
|
+
};
|
|
20570
20770
|
const resume = flag("resume");
|
|
20571
20771
|
if (resume) {
|
|
20572
20772
|
const done2 = await serverApply({ api, resume });
|
|
20573
|
-
|
|
20574
|
-
console.log(`
|
|
20575
|
-
apply ${done2.apply_id}: ${done2.status}`);
|
|
20576
|
-
if (done2.status === "running") console.log(`resume with: vxil push --server --resume ${done2.apply_id}`);
|
|
20577
|
-
if (done2.status !== "succeeded" && done2.status !== "running") process.exit(1);
|
|
20578
|
-
if (done2.status === "succeeded" && !hasFlag("no-gen")) await runGen(sel);
|
|
20773
|
+
report(done2, true);
|
|
20579
20774
|
const resumed = { hard: [], strict: [] };
|
|
20775
|
+
armWith(done2, resumed);
|
|
20776
|
+
if (done2.status === "running") say(`resume with: vxil push --server --resume ${done2.apply_id}`);
|
|
20777
|
+
if (done2.status !== "succeeded" && done2.status !== "running") return finish(done2, resumed, 1);
|
|
20778
|
+
if (done2.status === "succeeded") await gen();
|
|
20580
20779
|
applyHookProblems(done2, resumed);
|
|
20581
|
-
const exitResumed = pushExitCode(resumed,
|
|
20780
|
+
const exitResumed = pushExitCode(resumed, strict);
|
|
20582
20781
|
for (const line of exitResumed.lines) console.error(`vxil: ${line}`);
|
|
20583
|
-
|
|
20584
|
-
return;
|
|
20782
|
+
return finish(done2, resumed, exitResumed.code);
|
|
20585
20783
|
}
|
|
20586
20784
|
const loaded = await loadVxilConfig();
|
|
20587
20785
|
const cwd = process.cwd();
|
|
@@ -20589,7 +20787,7 @@ apply ${done2.apply_id}: ${done2.status}`);
|
|
|
20589
20787
|
const cfg = hasFlag("skip-functions") ? rest2 : loaded;
|
|
20590
20788
|
const fnNames = Object.keys(cfg.functions ?? {});
|
|
20591
20789
|
if (hasFlag("skip-functions") && declaredFns && Object.keys(declaredFns).length) {
|
|
20592
|
-
|
|
20790
|
+
say(`functions: skipped (--skip-functions) \u2014 ${Object.keys(declaredFns).length} declared function(s) not deployed`);
|
|
20593
20791
|
}
|
|
20594
20792
|
if (fnNames.length) {
|
|
20595
20793
|
const ent = await probeFunctionsEntitlement(api, fnNames[0]);
|
|
@@ -20614,33 +20812,30 @@ apply ${done2.apply_id}: ${done2.status}`);
|
|
|
20614
20812
|
bundle,
|
|
20615
20813
|
allowDestructive: hasFlag("allow-destructive"),
|
|
20616
20814
|
onPlan: (plan) => {
|
|
20617
|
-
|
|
20618
|
-
|
|
20619
|
-
for (const w of plan.warnings ?? [])
|
|
20620
|
-
|
|
20815
|
+
say(`plan (apply ${plan.apply_id}):`);
|
|
20816
|
+
say(formatApplySteps(plan.steps));
|
|
20817
|
+
for (const w of plan.warnings ?? []) say(` ! ${w}`);
|
|
20818
|
+
say("\napplying server-side\u2026");
|
|
20621
20819
|
}
|
|
20622
20820
|
});
|
|
20623
|
-
console.log(formatApplySteps(done.steps));
|
|
20624
|
-
console.log(`
|
|
20625
|
-
apply ${done.apply_id}: ${done.status}`);
|
|
20626
20821
|
if (done.status === "running") {
|
|
20627
20822
|
const errored = done.steps.filter((st) => st.status === "pending" && st.error);
|
|
20628
20823
|
const onlyDeferredApiState = errored.length > 0 && errored.every((st) => st.kind === "api-state" && (st.error ?? "").startsWith("deferred:"));
|
|
20629
20824
|
if (onlyDeferredApiState) {
|
|
20630
|
-
|
|
20825
|
+
report(done, false);
|
|
20826
|
+
say(" \u2026 declared API state deferred (the feature config is still propagating) \u2014 waiting 30 s, then resuming the same apply");
|
|
20631
20827
|
await new Promise((r) => setTimeout(r, 3e4));
|
|
20632
20828
|
done = await serverApply({ api, resume: done.apply_id });
|
|
20633
|
-
console.log(formatApplySteps(done.steps));
|
|
20634
|
-
console.log(`
|
|
20635
|
-
apply ${done.apply_id}: ${done.status}`);
|
|
20636
20829
|
}
|
|
20637
20830
|
}
|
|
20831
|
+
report(done, true);
|
|
20832
|
+
const problems = { hard: [], strict: [] };
|
|
20833
|
+
armWith(done, problems);
|
|
20638
20834
|
if (done.status === "running") {
|
|
20639
|
-
|
|
20835
|
+
return finish(done, problems, 1, `a transient step failure interrupted the apply \u2014 resume with: vxil push --server --resume ${done.apply_id}`);
|
|
20640
20836
|
}
|
|
20641
20837
|
const standing = done.steps.filter((st) => st.kind === "api-state" && st.status === "failed");
|
|
20642
20838
|
const onlyApiStateFailed = done.steps.every((st) => st.status !== "failed" || st.kind === "api-state");
|
|
20643
|
-
const problems = { hard: [], strict: [] };
|
|
20644
20839
|
if (done.status === "partial" && standing.length && onlyApiStateFailed && done.failed_step?.kind === "api-state" && done.steps.every((st) => st.status !== "rolled_back")) {
|
|
20645
20840
|
for (const st of standing) {
|
|
20646
20841
|
console.error(`vxil: warning \u2014 ${st.target} not converged: ${st.error ?? ""} (the config stands; fix the cause and re-push)`);
|
|
@@ -20648,7 +20843,7 @@ apply ${done.apply_id}: ${done.status}`);
|
|
|
20648
20843
|
}
|
|
20649
20844
|
} else if (done.status !== "succeeded") {
|
|
20650
20845
|
const f = done.failed_step;
|
|
20651
|
-
|
|
20846
|
+
return finish(done, problems, 1, `apply ${done.status}${f ? ` \u2014 step ${f.seq} (${f.kind} ${f.target}): ${f.error}` : ""} (config/functions were rolled back to their pre-apply versions; cms schema additions were kept)`);
|
|
20652
20847
|
}
|
|
20653
20848
|
applyHookProblems(done, problems);
|
|
20654
20849
|
if (!hasFlag("skip-functions") && (fnNames.length || cfg.features.functions || await backendListsFunctions(api))) {
|
|
@@ -20662,24 +20857,26 @@ apply ${done.apply_id}: ${done.status}`);
|
|
|
20662
20857
|
for (const name of deployed.filter((n) => !fnNames.includes(n)).sort()) {
|
|
20663
20858
|
if (hasFlag("allow-destructive")) {
|
|
20664
20859
|
const del = await deleteRemoteFunction(api, name);
|
|
20665
|
-
|
|
20860
|
+
say(` - ${name} [deployed, not in vxil.config] \u2014 ${del.gone ? "already gone" : "deleted (--allow-destructive)"}`);
|
|
20666
20861
|
if (del.hook && del.hook.failed > 0) problems.hard.push(`trigger wiring: ${del.hook.failed} subscription(s) failed to reconcile after deleting ${name} \u2014 ${hookReconcileRemedy(fnNames[0])}`);
|
|
20667
20862
|
} else {
|
|
20668
|
-
|
|
20863
|
+
say(` - ${name} [deployed, not in vxil.config] \u2014 kept; \`vxil push --server --allow-destructive\` deletes it`);
|
|
20669
20864
|
problems.strict.push(`deployed but not declared (kept): ${name}`);
|
|
20670
20865
|
}
|
|
20671
20866
|
}
|
|
20672
20867
|
}
|
|
20673
20868
|
}
|
|
20674
|
-
|
|
20675
|
-
const exitWith = pushExitCode(problems,
|
|
20869
|
+
await gen();
|
|
20870
|
+
const exitWith = pushExitCode(problems, strict);
|
|
20676
20871
|
for (const line of exitWith.lines) console.error(`vxil: ${line}`);
|
|
20677
|
-
|
|
20872
|
+
return finish(done, problems, exitWith.code);
|
|
20873
|
+
}
|
|
20874
|
+
function armServerApplyJsonFail() {
|
|
20875
|
+
if (jsonOut) jsonFailDoc = (error, exitCode) => JSON.stringify(serverApplyErrorJson({ error, exitCode }));
|
|
20678
20876
|
}
|
|
20679
20877
|
function applyHookProblems(done, problems) {
|
|
20680
20878
|
const hook = hookReconcileOf(done);
|
|
20681
20879
|
if (!hook || hook.failed === 0) return;
|
|
20682
|
-
console.error(` ! trigger wiring: ${hook.warning ?? `${hook.failed} trigger subscription(s) could not be written`}`);
|
|
20683
20880
|
problems.hard.push(`trigger wiring: ${hook.failed} subscription(s) failed to reconcile \u2014 a function may be wired to nothing; re-run \`vxil push --server\` (every executed apply re-runs the reconcile)`);
|
|
20684
20881
|
}
|
|
20685
20882
|
async function runSeed(sel, opts = {}) {
|
|
@@ -21877,6 +22074,7 @@ ${formatSummary(state)}`);
|
|
|
21877
22074
|
break;
|
|
21878
22075
|
}
|
|
21879
22076
|
case "push": {
|
|
22077
|
+
if (hasFlag("server") || flag("resume") !== void 0) armServerApplyJsonFail();
|
|
21880
22078
|
if (hasFlag("prod") && (hasFlag("dev") || flag("target") !== void 0)) fail("--prod cannot be combined with --dev / --target \u2014 pick one target");
|
|
21881
22079
|
const sel = selector();
|
|
21882
22080
|
const pushTarget = resolveTargetOrFail(sel);
|
|
@@ -22374,6 +22572,8 @@ export default defineConfig(${JSON.stringify({ features: featBlocks, cms: { coll
|
|
|
22374
22572
|
if (done.skipped > 0) {
|
|
22375
22573
|
console.log(` \u26A0 ${done.skipped} row(s) were written while the re-index ran and were NOT re-projected \u2014 re-run \`vxil cms reindex ${collection}\` when writes are quiet`);
|
|
22376
22574
|
}
|
|
22575
|
+
const certifyWarning = indexCertifyWarning(collection, done.indexCertify);
|
|
22576
|
+
if (certifyWarning) console.log(certifyWarning);
|
|
22377
22577
|
} else {
|
|
22378
22578
|
fail("usage: vxil cms status|pull|reindex <collection> [--field <field>]|drop <collection> [--yes]|bulk-rm <collection> --filter '<json>' [--dry-run] [--yes]");
|
|
22379
22579
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vxil/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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",
|
|
@@ -46,9 +46,9 @@
|
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"miniflare": "^4.20260611.0",
|
|
49
|
-
"@vxil/config": "0.
|
|
50
|
-
"@vxil/feature-configs": "0.7.0",
|
|
49
|
+
"@vxil/config": "0.8.0",
|
|
51
50
|
"@vxil/runtime": "0.0.1",
|
|
51
|
+
"@vxil/feature-configs": "0.7.0",
|
|
52
52
|
"@vxil/types": "0.0.1"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|