@abloatai/cli 0.54.0 → 0.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +84 -7
- package/package.json +3 -3
package/dist/cli.cjs
CHANGED
|
@@ -3970,7 +3970,7 @@ var init_observeCliError = __esm({
|
|
|
3970
3970
|
import_errorObservation = require("@abloatai/transaction/errorObservation");
|
|
3971
3971
|
import_errors5 = require("@abloatai/transaction/errors");
|
|
3972
3972
|
dsn = process.env.ABLO_CLI_SENTRY_DSN ?? "https://1ac154bff10b06836e1ea9de9e0d92f0@o4510928209772544.ingest.de.sentry.io/4511660691423312" ?? "";
|
|
3973
|
-
release = process.env.ABLO_CLI_RELEASE ?? "@abloatai/cli@0.
|
|
3973
|
+
release = process.env.ABLO_CLI_RELEASE ?? "@abloatai/cli@0.55.0";
|
|
3974
3974
|
initialized = false;
|
|
3975
3975
|
nativeProcessExit = process.exit.bind(process);
|
|
3976
3976
|
exitBoundaryInstalled = false;
|
|
@@ -4297,7 +4297,7 @@ var init_src2 = __esm({
|
|
|
4297
4297
|
|
|
4298
4298
|
// src/cliEnvironment.ts
|
|
4299
4299
|
function cliVersion() {
|
|
4300
|
-
return "0.
|
|
4300
|
+
return "0.55.0";
|
|
4301
4301
|
}
|
|
4302
4302
|
function cliOs() {
|
|
4303
4303
|
const value = (0, import_node_os.platform)();
|
|
@@ -5968,6 +5968,28 @@ async function fetchPushedSchema(apiUrl3, apiKey) {
|
|
|
5968
5968
|
clearTimeout(t);
|
|
5969
5969
|
}
|
|
5970
5970
|
}
|
|
5971
|
+
async function fetchDeliveryState(apiUrl3, apiKey, timeoutMs = 4e3) {
|
|
5972
|
+
if (!apiKey) return { kind: "unknown", detail: "no key" };
|
|
5973
|
+
const ctrl = new AbortController();
|
|
5974
|
+
const t = setTimeout(() => {
|
|
5975
|
+
ctrl.abort();
|
|
5976
|
+
}, timeoutMs);
|
|
5977
|
+
try {
|
|
5978
|
+
const res = await fetch(`${apiUrl3}/api/v1/logs/delivery`, {
|
|
5979
|
+
headers: { authorization: `Bearer ${apiKey}` },
|
|
5980
|
+
signal: ctrl.signal
|
|
5981
|
+
});
|
|
5982
|
+
if (!res.ok) return { kind: "unknown", detail: `HTTP ${res.status}` };
|
|
5983
|
+
const parsed = import_wire5.logDeliveryResponseSchema.safeParse(await res.json());
|
|
5984
|
+
if (!parsed.success) return { kind: "unknown", detail: "unrecognized response" };
|
|
5985
|
+
const { window_seconds, recorded, unroutable, sample } = parsed.data;
|
|
5986
|
+
return { kind: "known", window_seconds, recorded, unroutable, sample: sample ?? null };
|
|
5987
|
+
} catch {
|
|
5988
|
+
return { kind: "unknown", detail: "unreachable" };
|
|
5989
|
+
} finally {
|
|
5990
|
+
clearTimeout(t);
|
|
5991
|
+
}
|
|
5992
|
+
}
|
|
5971
5993
|
async function fetchDataSourceState(apiUrl3, apiKey, timeoutMs = 4e3) {
|
|
5972
5994
|
if (!apiKey) return { kind: "unknown", detail: "no key" };
|
|
5973
5995
|
const ctrl = new AbortController();
|
|
@@ -6069,7 +6091,7 @@ function blockers(input) {
|
|
|
6069
6091
|
}
|
|
6070
6092
|
return found;
|
|
6071
6093
|
}
|
|
6072
|
-
var import_wire5, import_schema4;
|
|
6094
|
+
var import_wire5, import_schema4, WRITE_READY_VERDICT;
|
|
6073
6095
|
var init_readiness = __esm({
|
|
6074
6096
|
"src/readiness.ts"() {
|
|
6075
6097
|
"use strict";
|
|
@@ -6079,6 +6101,7 @@ var init_readiness = __esm({
|
|
|
6079
6101
|
init_dbProvider();
|
|
6080
6102
|
init_remoteValidation();
|
|
6081
6103
|
import_schema4 = require("@abloatai/transaction/schema");
|
|
6104
|
+
WRITE_READY_VERDICT = "write infrastructure is ready. Your database constraints and row-level policies still apply.";
|
|
6082
6105
|
}
|
|
6083
6106
|
});
|
|
6084
6107
|
|
|
@@ -285430,6 +285453,14 @@ function render(check2) {
|
|
|
285430
285453
|
);
|
|
285431
285454
|
if (check2.fix) console.log(` ${" ".repeat(11)}${import_picocolors18.default.dim(`\u2192 ${check2.fix}`)}`);
|
|
285432
285455
|
}
|
|
285456
|
+
function windowWords(seconds) {
|
|
285457
|
+
if (seconds % 3600 === 0) {
|
|
285458
|
+
const hours = seconds / 3600;
|
|
285459
|
+
return hours === 1 ? "the last hour" : `the last ${hours} hours`;
|
|
285460
|
+
}
|
|
285461
|
+
const minutes = Math.max(1, Math.round(seconds / 60));
|
|
285462
|
+
return minutes === 1 ? "the last minute" : `the last ${minutes} minutes`;
|
|
285463
|
+
}
|
|
285433
285464
|
async function ping(apiUrl3) {
|
|
285434
285465
|
const ctrl = new AbortController();
|
|
285435
285466
|
const t = setTimeout(() => {
|
|
@@ -285572,6 +285603,24 @@ async function inspectDoctor(options = {}) {
|
|
|
285572
285603
|
} else {
|
|
285573
285604
|
checks.push({ label: "database", state: "skip", detail: "nothing connected to check" });
|
|
285574
285605
|
}
|
|
285606
|
+
const delivery = reachable ? await fetchDeliveryState(apiUrl3, runtimeKey.key) : { kind: "unknown", detail: "unreachable" };
|
|
285607
|
+
if (delivery.kind === "known") {
|
|
285608
|
+
const when = windowWords(delivery.window_seconds);
|
|
285609
|
+
checks.push(
|
|
285610
|
+
delivery.unroutable > 0 ? {
|
|
285611
|
+
label: "delivery",
|
|
285612
|
+
state: "fail",
|
|
285613
|
+
detail: `${delivery.unroutable} of ${delivery.recorded} change${delivery.recorded === 1 ? "" : "s"} in ${when} reached nobody` + (delivery.sample ? ` (e.g. ${delivery.sample.model}/${delivery.sample.id})` : ""),
|
|
285614
|
+
fix: "run `ablo check`. Rows written to Postgres outside Ablo carry no tenancy value, so nothing can route the change."
|
|
285615
|
+
} : {
|
|
285616
|
+
label: "delivery",
|
|
285617
|
+
state: "ok",
|
|
285618
|
+
detail: delivery.recorded === 0 ? `no changes in ${when}` : `${delivery.recorded} change${delivery.recorded === 1 ? "" : "s"} in ${when}, all deliverable`
|
|
285619
|
+
}
|
|
285620
|
+
);
|
|
285621
|
+
} else {
|
|
285622
|
+
checks.push({ label: "delivery", state: "skip", detail: `not determined (${delivery.detail})` });
|
|
285623
|
+
}
|
|
285575
285624
|
const blocking = blockers({
|
|
285576
285625
|
reachable,
|
|
285577
285626
|
hasKey: Boolean(runtimeKey.key),
|
|
@@ -285592,7 +285641,8 @@ async function inspectDoctor(options = {}) {
|
|
|
285592
285641
|
target,
|
|
285593
285642
|
dataSource,
|
|
285594
285643
|
pushedSchema: pushed,
|
|
285595
|
-
schemaDrift: drift
|
|
285644
|
+
schemaDrift: drift,
|
|
285645
|
+
delivery
|
|
285596
285646
|
};
|
|
285597
285647
|
}
|
|
285598
285648
|
function renderDoctorReport(report) {
|
|
@@ -285614,7 +285664,7 @@ function renderDoctorReport(report) {
|
|
|
285614
285664
|
);
|
|
285615
285665
|
} else {
|
|
285616
285666
|
console.log(
|
|
285617
|
-
` ${import_picocolors18.default.green("\u2713")} ${import_picocolors18.default.dim(
|
|
285667
|
+
` ${import_picocolors18.default.green("\u2713")} ${import_picocolors18.default.dim(WRITE_READY_VERDICT)}`
|
|
285618
285668
|
);
|
|
285619
285669
|
}
|
|
285620
285670
|
console.log();
|
|
@@ -288136,7 +288186,7 @@ async function status(args = []) {
|
|
|
288136
288186
|
);
|
|
288137
288187
|
} else {
|
|
288138
288188
|
console.log(
|
|
288139
|
-
` ${import_picocolors22.default.green("\u2713")} ${import_picocolors22.default.dim(
|
|
288189
|
+
` ${import_picocolors22.default.green("\u2713")} ${import_picocolors22.default.dim(WRITE_READY_VERDICT)}`
|
|
288140
288190
|
);
|
|
288141
288191
|
}
|
|
288142
288192
|
console.log();
|
|
@@ -288623,6 +288673,24 @@ function parseCheckArgs(argv) {
|
|
|
288623
288673
|
}
|
|
288624
288674
|
return { schemaPath, exportName, appSchema };
|
|
288625
288675
|
}
|
|
288676
|
+
function quoteIdent2(raw) {
|
|
288677
|
+
return `"${raw.replace(/"/g, '""')}"`;
|
|
288678
|
+
}
|
|
288679
|
+
var UNSTAMPED_CAP = 500;
|
|
288680
|
+
async function countUnstamped(sql, appSchema, table, column) {
|
|
288681
|
+
try {
|
|
288682
|
+
const rows = await sql.unsafe(
|
|
288683
|
+
`SELECT count(*)::int AS n FROM (
|
|
288684
|
+
SELECT 1 FROM ${quoteIdent2(appSchema)}.${quoteIdent2(table)}
|
|
288685
|
+
WHERE ${quoteIdent2(column)} IS NULL
|
|
288686
|
+
LIMIT ${UNSTAMPED_CAP + 1}
|
|
288687
|
+
) s`
|
|
288688
|
+
);
|
|
288689
|
+
return rows[0]?.n ?? 0;
|
|
288690
|
+
} catch {
|
|
288691
|
+
return null;
|
|
288692
|
+
}
|
|
288693
|
+
}
|
|
288626
288694
|
function hostOf(connectionString) {
|
|
288627
288695
|
try {
|
|
288628
288696
|
return new URL(connectionString).hostname || null;
|
|
@@ -288700,7 +288768,6 @@ async function check(argv) {
|
|
|
288700
288768
|
await sql.end({ timeout: 2 });
|
|
288701
288769
|
process.exit(1);
|
|
288702
288770
|
}
|
|
288703
|
-
await sql.end({ timeout: 2 });
|
|
288704
288771
|
const colsByTable = /* @__PURE__ */ new Map();
|
|
288705
288772
|
for (const r2 of rows) {
|
|
288706
288773
|
let set = colsByTable.get(r2.table_name);
|
|
@@ -288739,6 +288806,15 @@ async function check(argv) {
|
|
|
288739
288806
|
if (BASE_COLUMNS.has(col) || col === orgCol) continue;
|
|
288740
288807
|
if (!present.has(col)) problems.push(`missing column "${col}" (field ${fieldName})`);
|
|
288741
288808
|
}
|
|
288809
|
+
if (orgCol && present.has(orgCol)) {
|
|
288810
|
+
const unstamped = await countUnstamped(sql, args.appSchema, table, orgCol);
|
|
288811
|
+
if (unstamped !== null && unstamped > 0) {
|
|
288812
|
+
const count = unstamped > UNSTAMPED_CAP ? `${UNSTAMPED_CAP}+ rows` : `${unstamped} row${unstamped === 1 ? "" : "s"}`;
|
|
288813
|
+
problems.push(
|
|
288814
|
+
`${count} have no "${orgCol}", so nothing can route them. Ablo stamps it on writes it makes; an insert straight into Postgres does not. Backfill the value, then run \`ablo connect resnapshot\`.`
|
|
288815
|
+
);
|
|
288816
|
+
}
|
|
288817
|
+
}
|
|
288742
288818
|
if (problems.length > 0) {
|
|
288743
288819
|
console.log(` ${import_picocolors25.default.red("\u2717")} ${import_picocolors25.default.bold(key)} ${import_picocolors25.default.dim("\u2192")} ${table}`);
|
|
288744
288820
|
for (const p2 of problems) console.log(` ${import_picocolors25.default.red("\u2022")} ${p2}`);
|
|
@@ -288752,6 +288828,7 @@ async function check(argv) {
|
|
|
288752
288828
|
console.log(` ${import_picocolors25.default.green("\u2713")} ${import_picocolors25.default.bold(key)} ${import_picocolors25.default.dim(`\u2192 ${table} (id, ${orgCol ?? "no org"} ok)`)}`);
|
|
288753
288829
|
}
|
|
288754
288830
|
}
|
|
288831
|
+
await sql.end({ timeout: 2 });
|
|
288755
288832
|
const modelCount = Object.keys(schemaJson.models).length;
|
|
288756
288833
|
const ignored = [...colsByTable.keys()].filter((t) => !declaredTables.has(t)).length;
|
|
288757
288834
|
console.log(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abloatai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.55.0",
|
|
4
4
|
"description": "The ablo command line: set up Ablo, connect your database, and push your schema from the terminal.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@sentry/node": "^10.18.0",
|
|
38
|
-
"@abloatai/transaction": "^0.
|
|
38
|
+
"@abloatai/transaction": "^0.55.0",
|
|
39
39
|
"jiti": "^2.7.0",
|
|
40
40
|
"zod": "^4.4.3",
|
|
41
|
-
"@abloatai/humans": "^0.
|
|
41
|
+
"@abloatai/humans": "^0.55.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@ablo/product-analytics": "file:../product-analytics",
|