@abloatai/cli 0.55.0 → 0.56.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.
Files changed (2) hide show
  1. package/dist/cli.cjs +57 -43
  2. 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.55.0";
3973
+ release = process.env.ABLO_CLI_RELEASE ?? "@abloatai/cli@0.56.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.55.0";
4300
+ return "0.56.0";
4301
4301
  }
4302
4302
  function cliOs() {
4303
4303
  const value = (0, import_node_os.platform)();
@@ -284792,21 +284792,14 @@ function classifyKey(apiKey) {
284792
284792
  reason: `${import_picocolors16.default.bold("ABLO_API_KEY")} is not a secret Ablo key. Expected a branch-bound ${import_picocolors16.default.bold("sk_\u2026")} credential. Run ${import_picocolors16.default.bold("npx ablo dev")} to prepare a development branch.`
284793
284793
  };
284794
284794
  }
284795
- function wireEnvLocal(apiKey, cwd = process.cwd(), projectId, branchId) {
284795
+ function wireEnvLocal(apiKey, cwd = process.cwd()) {
284796
284796
  const envPath = (0, import_path5.resolve)(cwd, ".env.local");
284797
284797
  const line = `ABLO_API_KEY=${apiKey}`;
284798
- const projectLine = projectId ? `ABLO_PROJECT_ID=${projectId}` : null;
284799
- const branchLine = branchId ? `ABLO_BRANCH_ID=${branchId}` : null;
284800
284798
  let action;
284799
+ let removedPins = [];
284801
284800
  if (!(0, import_fs7.existsSync)(envPath)) {
284802
- (0, import_fs7.writeFileSync)(
284803
- envPath,
284804
- `${line}
284805
- ${projectLine ? `${projectLine}
284806
- ` : ""}${branchLine ? `${branchLine}
284807
- ` : ""}`,
284808
- { mode: 384 }
284809
- );
284801
+ (0, import_fs7.writeFileSync)(envPath, `${line}
284802
+ `, { mode: 384 });
284810
284803
  action = `Created ${import_picocolors16.default.bold(".env.local")} with ${import_picocolors16.default.bold("ABLO_API_KEY")}`;
284811
284804
  } else {
284812
284805
  const content = (0, import_fs7.readFileSync)(envPath, "utf8");
@@ -284822,25 +284815,16 @@ ${projectLine ? `${projectLine}
284822
284815
  (0, import_fs7.writeFileSync)(envPath, content.replace(/^ABLO_API_KEY=.*$/m, line));
284823
284816
  action = `Updated ${import_picocolors16.default.bold("ABLO_API_KEY")} in ${import_picocolors16.default.bold(".env.local")} ${import_picocolors16.default.dim(`(was ${existing.slice(0, 12)}\u2026)`)}`;
284824
284817
  }
284825
- if (projectLine) {
284826
- const next = (0, import_fs7.readFileSync)(envPath, "utf8");
284827
- if (/^ABLO_PROJECT_ID=.*$/m.test(next)) {
284828
- (0, import_fs7.writeFileSync)(envPath, next.replace(/^ABLO_PROJECT_ID=.*$/m, projectLine));
284829
- } else {
284830
- (0, import_fs7.appendFileSync)(envPath, `${next.endsWith("\n") || next.length === 0 ? "" : "\n"}${projectLine}
284831
- `);
284832
- }
284833
- }
284834
- if (branchLine) {
284835
- const next = (0, import_fs7.readFileSync)(envPath, "utf8");
284836
- if (/^ABLO_BRANCH_ID=.*$/m.test(next)) {
284837
- (0, import_fs7.writeFileSync)(envPath, next.replace(/^ABLO_BRANCH_ID=.*$/m, branchLine));
284838
- } else {
284839
- (0, import_fs7.appendFileSync)(envPath, `${next.endsWith("\n") || next.length === 0 ? "" : "\n"}${branchLine}
284840
- `);
284841
- }
284818
+ const before = (0, import_fs7.readFileSync)(envPath, "utf8");
284819
+ const after = before.replace(/^ABLO_(?:PROJECT|BRANCH)_ID=.*\n?/gm, "");
284820
+ if (after !== before) {
284821
+ removedPins = ["ABLO_PROJECT_ID", "ABLO_BRANCH_ID"].filter(
284822
+ (key) => new RegExp(`^${key}=`, "m").test(before)
284823
+ );
284824
+ (0, import_fs7.writeFileSync)(envPath, after);
284842
284825
  }
284843
284826
  }
284827
+ const pinNote = removedPins.length ? ` Removed ${removedPins.map((key) => import_picocolors16.default.bold(key)).join(" and ")}; the key names its own project and branch.` : "";
284844
284828
  const gitignorePath = (0, import_path5.resolve)(cwd, ".gitignore");
284845
284829
  const gitignore = (0, import_fs7.existsSync)(gitignorePath) ? (0, import_fs7.readFileSync)(gitignorePath, "utf8") : "";
284846
284830
  const ignored = /^(\.env\.local|\.env\*|\.env\.\*|\.env.*)$/m.test(gitignore);
@@ -284854,7 +284838,7 @@ ${projectLine ? `${projectLine}
284854
284838
  );
284855
284839
  gitignoreNote = ` Added ${import_picocolors16.default.bold(".env.local")} to ${import_picocolors16.default.bold(".gitignore")} so the key can't be committed.`;
284856
284840
  }
284857
- return `${action}.${gitignoreNote}`;
284841
+ return `${action}.${pinNote}${gitignoreNote}`;
284858
284842
  }
284859
284843
  async function runPush(schema, args) {
284860
284844
  const { ok, status: status2, body, bodyText } = await pushSchema(schema, {
@@ -285015,7 +284999,7 @@ async function dev(argv, runtime = {}) {
285015
284999
  if (runtime.branch) {
285016
285000
  console.log(
285017
285001
  `
285018
- ${import_picocolors16.default.green("\u2713")} ${wireEnvLocal(args.apiKey, process.cwd(), runtime.branch.projectId, runtime.branch.id)}`
285002
+ ${import_picocolors16.default.green("\u2713")} ${wireEnvLocal(args.apiKey, process.cwd())}`
285019
285003
  );
285020
285004
  console.log(
285021
285005
  ` ${import_picocolors16.default.dim(`Temporary branch credential expires ${runtime.branch.expiresAt}; rerun ablo dev to rotate it.`)}`
@@ -285446,6 +285430,10 @@ init_controlPlane();
285446
285430
  init_config();
285447
285431
  init_target();
285448
285432
  init_readiness();
285433
+ function doctorVerdict(counts) {
285434
+ if (counts.blockers > 0 || counts.failed > 0) return "blocked";
285435
+ return counts.skipped > 0 ? "unverified" : "ready";
285436
+ }
285449
285437
  function render(check2) {
285450
285438
  const mark = check2.state === "ok" ? import_picocolors18.default.green("\u2713") : check2.state === "fail" ? import_picocolors18.default.red("\u2717") : import_picocolors18.default.dim("\u2013");
285451
285439
  console.log(
@@ -285607,7 +285595,15 @@ async function inspectDoctor(options = {}) {
285607
285595
  if (delivery.kind === "known") {
285608
285596
  const when = windowWords(delivery.window_seconds);
285609
285597
  checks.push(
285610
- delivery.unroutable > 0 ? {
285598
+ delivery.unroutable > 0 ? delivery.unroutable === delivery.recorded ? {
285599
+ // Nothing at all was routable. One bad row does not look like
285600
+ // this, so the cause is the plane rather than the rows: reporting
285601
+ // it as "N of N" would send the reader hunting for the N.
285602
+ label: "delivery",
285603
+ state: "fail",
285604
+ detail: `no change reached anyone (${delivery.recorded} in ${when})`,
285605
+ fix: "run `ablo check`. When nothing routes, the tenancy value is usually missing for the whole plane rather than for particular rows."
285606
+ } : {
285611
285607
  label: "delivery",
285612
285608
  state: "fail",
285613
285609
  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})` : ""),
@@ -285642,7 +285638,8 @@ async function inspectDoctor(options = {}) {
285642
285638
  dataSource,
285643
285639
  pushedSchema: pushed,
285644
285640
  schemaDrift: drift,
285645
- delivery
285641
+ delivery,
285642
+ verdict: doctorVerdict({ blockers: blocking.length, failed, skipped })
285646
285643
  };
285647
285644
  }
285648
285645
  function renderDoctorReport(report) {
@@ -285660,7 +285657,7 @@ function renderDoctorReport(report) {
285660
285657
  );
285661
285658
  } else if (report.skipped > 0) {
285662
285659
  console.log(
285663
- ` ${import_picocolors18.default.yellow("?")} ${import_picocolors18.default.dim(`nothing is blocking a write, but ${report.skipped} check${report.skipped === 1 ? "" : "s"} could not be run`)}`
285660
+ ` ${import_picocolors18.default.yellow("?")} ${import_picocolors18.default.dim(`nothing found a problem, but ${report.skipped} check${report.skipped === 1 ? "" : "s"} could not be run. This is not a clean bill of health.`)}`
285664
285661
  );
285665
285662
  } else {
285666
285663
  console.log(
@@ -285669,10 +285666,26 @@ function renderDoctorReport(report) {
285669
285666
  }
285670
285667
  console.log();
285671
285668
  }
285672
- async function doctor() {
285669
+ function doctorReportAsJson(report) {
285670
+ return JSON.stringify(
285671
+ {
285672
+ object: "doctor_report",
285673
+ verdict: report.verdict,
285674
+ checks: report.checks,
285675
+ blockers: report.blockers
285676
+ },
285677
+ null,
285678
+ 2
285679
+ );
285680
+ }
285681
+ async function doctor(argv = []) {
285673
285682
  const report = await inspectDoctor();
285674
- renderDoctorReport(report);
285675
- if (!report.ready) process.exitCode = 1;
285683
+ if (argv.includes("--json") || process.env.ABLO_JSON === "1") {
285684
+ console.log(doctorReportAsJson(report));
285685
+ } else {
285686
+ renderDoctorReport(report);
285687
+ }
285688
+ if (report.verdict !== "ready") process.exitCode = 1;
285676
285689
  }
285677
285690
 
285678
285691
  // src/setup/contracts.ts
@@ -287508,7 +287521,10 @@ var COMMANDS = [
287508
287521
  core: { group: "Every day", does: "Check the whole setup at once and list everything that would block a write" },
287509
287522
  full: {
287510
287523
  group: "See what's happening",
287511
- rows: [{ run: "doctor", does: "Every setup check at once \u2014 exits non-zero when a write would fail" }]
287524
+ rows: [
287525
+ { run: "doctor", does: "Every setup check at once. Exits non-zero when a write would fail, or when a check could not be run" },
287526
+ { run: "doctor --json", does: "The same verdict as data, for a script or an agent" }
287527
+ ]
287512
287528
  }
287513
287529
  },
287514
287530
  {
@@ -289813,8 +289829,6 @@ import { schema } from './schema';
289813
289829
  // via the session route and never touches the key.
289814
289830
  export const sync = Ablo({
289815
289831
  apiKey: process.env.ABLO_API_KEY,${authLine}
289816
- workspaceId: process.env.ABLO_PROJECT_ID,
289817
- branchId: process.env.ABLO_BRANCH_ID,
289818
289832
  schema,
289819
289833
  });
289820
289834
 
@@ -289840,7 +289854,7 @@ function generateEnv(storage, opts = {}) {
289840
289854
  const { includeApiKey = true } = opts;
289841
289855
  const databaseBlock = storage === "replication" ? "# Used by `npx ablo connect` to set up + register logical replication \u2014 the\n# DIRECT (un-pooled) endpoint. Ablo TAILS your WAL from here; it never writes.\n# The client never sees it; the browser never sees it. Your DB stays yours.\nDATABASE_URL=postgres://user:password@host:5432/db\n" : "# Used by ablo/data-source.ts (your DB endpoint) + `ablo migrate` \u2014 NOT the client.\n# Ablo never sees it; the browser never sees it. Your DB stays in your app.\nDATABASE_URL=postgres://user:password@host:5432/db\n";
289842
289856
  const webhookBlock = storage === "endpoint" ? "# Signing secret for the webhook receiver (app/api/ablo/webhooks/route.ts).\n# Ablo mints this when you register the endpoint's URL (POST /v1/webhook_endpoints\n# or the dashboard) and returns it once \u2014 paste it here.\nABLO_WEBHOOK_SECRET=whsec_your_endpoint_secret_here\n" : "";
289843
- const apiKeyBlock = includeApiKey ? "# Ablo: a branch-bound sk_ key (`npx ablo dev` wires both values for you).\n# Project + branch are assertions: the SDK rejects a key for another app or environment.\nABLO_API_KEY=sk_your_key_here\nABLO_PROJECT_ID=proj_your_project_id\nABLO_BRANCH_ID=br_your_branch_id\n" : "";
289857
+ const apiKeyBlock = includeApiKey ? "# Ablo: a branch-bound sk_ key (`npx ablo dev` writes it for you).\n# The key names its own project and branch, so nothing else is needed here.\nABLO_API_KEY=sk_your_key_here\n" : "";
289844
289858
  return `${apiKeyBlock}${webhookBlock}${databaseBlock}`;
289845
289859
  }
289846
289860
  function generateDataSource(orm) {
@@ -290531,7 +290545,7 @@ var HANDLERS = {
290531
290545
  branch: (argv) => branches([...argv]),
290532
290546
  status: (argv) => status([...argv]),
290533
290547
  whoami: (argv) => whoami([...argv]),
290534
- doctor: () => doctor(),
290548
+ doctor: (argv) => doctor([...argv]),
290535
290549
  logs: (argv) => logs([...argv]),
290536
290550
  webhooks: (argv) => webhooks([...argv]),
290537
290551
  check: (argv) => check([...argv]),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abloatai/cli",
3
- "version": "0.55.0",
3
+ "version": "0.56.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.55.0",
38
+ "@abloatai/transaction": "^0.56.0",
39
39
  "jiti": "^2.7.0",
40
40
  "zod": "^4.4.3",
41
- "@abloatai/humans": "^0.55.0"
41
+ "@abloatai/humans": "^0.56.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@ablo/product-analytics": "file:../product-analytics",