@abloatai/cli 0.55.0 → 0.57.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 +130 -54
- 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.57.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.57.0";
|
|
4301
4301
|
}
|
|
4302
4302
|
function cliOs() {
|
|
4303
4303
|
const value = (0, import_node_os.platform)();
|
|
@@ -4372,7 +4372,9 @@ function trackCliSchemaPushAttempted() {
|
|
|
4372
4372
|
}
|
|
4373
4373
|
function flushProductAnalytics(options = {}) {
|
|
4374
4374
|
if (flushInFlight) return flushInFlight;
|
|
4375
|
-
flushInFlight = flushOnce(options).catch(() =>
|
|
4375
|
+
flushInFlight = flushOnce(options).catch((error) => {
|
|
4376
|
+
void error;
|
|
4377
|
+
}).finally(() => {
|
|
4376
4378
|
flushInFlight = null;
|
|
4377
4379
|
});
|
|
4378
4380
|
return flushInFlight;
|
|
@@ -4395,8 +4397,8 @@ function queueEvent(eventName, properties) {
|
|
|
4395
4397
|
state.queue = [...state.queue, parsed].slice(-MAX_QUEUED_EVENTS);
|
|
4396
4398
|
writeState(state);
|
|
4397
4399
|
if (disclose) {
|
|
4398
|
-
|
|
4399
|
-
"Ablo collects limited usage analytics. Run `ablo telemetry disable` to opt out or `ablo telemetry status` for details
|
|
4400
|
+
process.stderr.write(
|
|
4401
|
+
"Ablo collects limited usage analytics. Run `ablo telemetry disable` to opt out or `ablo telemetry status` for details.\n"
|
|
4400
4402
|
);
|
|
4401
4403
|
}
|
|
4402
4404
|
} catch {
|
|
@@ -4473,8 +4475,9 @@ function writeState(state) {
|
|
|
4473
4475
|
function environmentBlocker() {
|
|
4474
4476
|
if (process.env.ABLO_TELEMETRY_DISABLED === "1") return "ABLO_TELEMETRY_DISABLED";
|
|
4475
4477
|
if (process.env.DO_NOT_TRACK === "1") return "DO_NOT_TRACK";
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
+
return TELEMETRY_BLOCKING_ENV.slice(2).find(
|
|
4479
|
+
(name) => truthyEnvironmentValue(process.env[name])
|
|
4480
|
+
) ?? null;
|
|
4478
4481
|
}
|
|
4479
4482
|
function truthyEnvironmentValue(value) {
|
|
4480
4483
|
return value !== void 0 && value !== "" && value !== "0" && value.toLowerCase() !== "false";
|
|
@@ -4502,7 +4505,7 @@ function runTelemetryCommand(argv) {
|
|
|
4502
4505
|
if (status2.blockedBy) console.log(`Environment override: ${status2.blockedBy}`);
|
|
4503
4506
|
console.log(`Queued events: ${status2.queuedEvents}`);
|
|
4504
4507
|
}
|
|
4505
|
-
var import_node_crypto2, import_node_fs, import_node_path, TELEMETRY_FILE_VERSION, MAX_QUEUED_EVENTS, MAX_FLUSH_EVENTS, DEFAULT_FLUSH_TIMEOUT_MS, flushInFlight;
|
|
4508
|
+
var import_node_crypto2, import_node_fs, import_node_path, TELEMETRY_FILE_VERSION, MAX_QUEUED_EVENTS, MAX_FLUSH_EVENTS, DEFAULT_FLUSH_TIMEOUT_MS, flushInFlight, TELEMETRY_BLOCKING_ENV;
|
|
4506
4509
|
var init_telemetry = __esm({
|
|
4507
4510
|
"src/telemetry.ts"() {
|
|
4508
4511
|
"use strict";
|
|
@@ -4519,6 +4522,16 @@ var init_telemetry = __esm({
|
|
|
4519
4522
|
MAX_FLUSH_EVENTS = 20;
|
|
4520
4523
|
DEFAULT_FLUSH_TIMEOUT_MS = 800;
|
|
4521
4524
|
flushInFlight = null;
|
|
4525
|
+
TELEMETRY_BLOCKING_ENV = [
|
|
4526
|
+
"ABLO_TELEMETRY_DISABLED",
|
|
4527
|
+
"DO_NOT_TRACK",
|
|
4528
|
+
"CI",
|
|
4529
|
+
"GITHUB_ACTIONS",
|
|
4530
|
+
"GITLAB_CI",
|
|
4531
|
+
"BUILDKITE",
|
|
4532
|
+
"CIRCLECI",
|
|
4533
|
+
"TF_BUILD"
|
|
4534
|
+
];
|
|
4522
4535
|
}
|
|
4523
4536
|
});
|
|
4524
4537
|
|
|
@@ -284792,21 +284805,14 @@ function classifyKey(apiKey) {
|
|
|
284792
284805
|
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
284806
|
};
|
|
284794
284807
|
}
|
|
284795
|
-
function wireEnvLocal(apiKey, cwd = process.cwd()
|
|
284808
|
+
function wireEnvLocal(apiKey, cwd = process.cwd()) {
|
|
284796
284809
|
const envPath = (0, import_path5.resolve)(cwd, ".env.local");
|
|
284797
284810
|
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
284811
|
let action;
|
|
284812
|
+
let removedPins = [];
|
|
284801
284813
|
if (!(0, import_fs7.existsSync)(envPath)) {
|
|
284802
|
-
(0, import_fs7.writeFileSync)(
|
|
284803
|
-
|
|
284804
|
-
`${line}
|
|
284805
|
-
${projectLine ? `${projectLine}
|
|
284806
|
-
` : ""}${branchLine ? `${branchLine}
|
|
284807
|
-
` : ""}`,
|
|
284808
|
-
{ mode: 384 }
|
|
284809
|
-
);
|
|
284814
|
+
(0, import_fs7.writeFileSync)(envPath, `${line}
|
|
284815
|
+
`, { mode: 384 });
|
|
284810
284816
|
action = `Created ${import_picocolors16.default.bold(".env.local")} with ${import_picocolors16.default.bold("ABLO_API_KEY")}`;
|
|
284811
284817
|
} else {
|
|
284812
284818
|
const content = (0, import_fs7.readFileSync)(envPath, "utf8");
|
|
@@ -284822,25 +284828,16 @@ ${projectLine ? `${projectLine}
|
|
|
284822
284828
|
(0, import_fs7.writeFileSync)(envPath, content.replace(/^ABLO_API_KEY=.*$/m, line));
|
|
284823
284829
|
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
284830
|
}
|
|
284825
|
-
|
|
284826
|
-
|
|
284827
|
-
|
|
284828
|
-
|
|
284829
|
-
|
|
284830
|
-
|
|
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
|
-
}
|
|
284831
|
+
const before = (0, import_fs7.readFileSync)(envPath, "utf8");
|
|
284832
|
+
const after = before.replace(/^ABLO_(?:PROJECT|BRANCH)_ID=.*\n?/gm, "");
|
|
284833
|
+
if (after !== before) {
|
|
284834
|
+
removedPins = ["ABLO_PROJECT_ID", "ABLO_BRANCH_ID"].filter(
|
|
284835
|
+
(key) => new RegExp(`^${key}=`, "m").test(before)
|
|
284836
|
+
);
|
|
284837
|
+
(0, import_fs7.writeFileSync)(envPath, after);
|
|
284842
284838
|
}
|
|
284843
284839
|
}
|
|
284840
|
+
const pinNote = removedPins.length ? ` Removed ${removedPins.map((key) => import_picocolors16.default.bold(key)).join(" and ")}; the key names its own project and branch.` : "";
|
|
284844
284841
|
const gitignorePath = (0, import_path5.resolve)(cwd, ".gitignore");
|
|
284845
284842
|
const gitignore = (0, import_fs7.existsSync)(gitignorePath) ? (0, import_fs7.readFileSync)(gitignorePath, "utf8") : "";
|
|
284846
284843
|
const ignored = /^(\.env\.local|\.env\*|\.env\.\*|\.env.*)$/m.test(gitignore);
|
|
@@ -284854,7 +284851,7 @@ ${projectLine ? `${projectLine}
|
|
|
284854
284851
|
);
|
|
284855
284852
|
gitignoreNote = ` Added ${import_picocolors16.default.bold(".env.local")} to ${import_picocolors16.default.bold(".gitignore")} so the key can't be committed.`;
|
|
284856
284853
|
}
|
|
284857
|
-
return `${action}.${gitignoreNote}`;
|
|
284854
|
+
return `${action}.${pinNote}${gitignoreNote}`;
|
|
284858
284855
|
}
|
|
284859
284856
|
async function runPush(schema, args) {
|
|
284860
284857
|
const { ok, status: status2, body, bodyText } = await pushSchema(schema, {
|
|
@@ -285015,7 +285012,7 @@ async function dev(argv, runtime = {}) {
|
|
|
285015
285012
|
if (runtime.branch) {
|
|
285016
285013
|
console.log(
|
|
285017
285014
|
`
|
|
285018
|
-
${import_picocolors16.default.green("\u2713")} ${wireEnvLocal(args.apiKey, process.cwd()
|
|
285015
|
+
${import_picocolors16.default.green("\u2713")} ${wireEnvLocal(args.apiKey, process.cwd())}`
|
|
285019
285016
|
);
|
|
285020
285017
|
console.log(
|
|
285021
285018
|
` ${import_picocolors16.default.dim(`Temporary branch credential expires ${runtime.branch.expiresAt}; rerun ablo dev to rotate it.`)}`
|
|
@@ -285446,6 +285443,10 @@ init_controlPlane();
|
|
|
285446
285443
|
init_config();
|
|
285447
285444
|
init_target();
|
|
285448
285445
|
init_readiness();
|
|
285446
|
+
function doctorVerdict(counts) {
|
|
285447
|
+
if (counts.blockers > 0 || counts.failed > 0) return "blocked";
|
|
285448
|
+
return counts.skipped > 0 ? "unverified" : "ready";
|
|
285449
|
+
}
|
|
285449
285450
|
function render(check2) {
|
|
285450
285451
|
const mark = check2.state === "ok" ? import_picocolors18.default.green("\u2713") : check2.state === "fail" ? import_picocolors18.default.red("\u2717") : import_picocolors18.default.dim("\u2013");
|
|
285451
285452
|
console.log(
|
|
@@ -285607,7 +285608,15 @@ async function inspectDoctor(options = {}) {
|
|
|
285607
285608
|
if (delivery.kind === "known") {
|
|
285608
285609
|
const when = windowWords(delivery.window_seconds);
|
|
285609
285610
|
checks.push(
|
|
285610
|
-
delivery.unroutable > 0 ? {
|
|
285611
|
+
delivery.unroutable > 0 ? delivery.unroutable === delivery.recorded ? {
|
|
285612
|
+
// Nothing at all was routable. One bad row does not look like
|
|
285613
|
+
// this, so the cause is the plane rather than the rows: reporting
|
|
285614
|
+
// it as "N of N" would send the reader hunting for the N.
|
|
285615
|
+
label: "delivery",
|
|
285616
|
+
state: "fail",
|
|
285617
|
+
detail: `no change reached anyone (${delivery.recorded} in ${when})`,
|
|
285618
|
+
fix: "run `ablo check`. When nothing routes, the tenancy value is usually missing for the whole plane rather than for particular rows."
|
|
285619
|
+
} : {
|
|
285611
285620
|
label: "delivery",
|
|
285612
285621
|
state: "fail",
|
|
285613
285622
|
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 +285651,8 @@ async function inspectDoctor(options = {}) {
|
|
|
285642
285651
|
dataSource,
|
|
285643
285652
|
pushedSchema: pushed,
|
|
285644
285653
|
schemaDrift: drift,
|
|
285645
|
-
delivery
|
|
285654
|
+
delivery,
|
|
285655
|
+
verdict: doctorVerdict({ blockers: blocking.length, failed, skipped })
|
|
285646
285656
|
};
|
|
285647
285657
|
}
|
|
285648
285658
|
function renderDoctorReport(report) {
|
|
@@ -285660,7 +285670,7 @@ function renderDoctorReport(report) {
|
|
|
285660
285670
|
);
|
|
285661
285671
|
} else if (report.skipped > 0) {
|
|
285662
285672
|
console.log(
|
|
285663
|
-
` ${import_picocolors18.default.yellow("?")} ${import_picocolors18.default.dim(`nothing
|
|
285673
|
+
` ${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
285674
|
);
|
|
285665
285675
|
} else {
|
|
285666
285676
|
console.log(
|
|
@@ -285669,10 +285679,26 @@ function renderDoctorReport(report) {
|
|
|
285669
285679
|
}
|
|
285670
285680
|
console.log();
|
|
285671
285681
|
}
|
|
285672
|
-
|
|
285682
|
+
function doctorReportAsJson(report) {
|
|
285683
|
+
return JSON.stringify(
|
|
285684
|
+
{
|
|
285685
|
+
object: "doctor_report",
|
|
285686
|
+
verdict: report.verdict,
|
|
285687
|
+
checks: report.checks,
|
|
285688
|
+
blockers: report.blockers
|
|
285689
|
+
},
|
|
285690
|
+
null,
|
|
285691
|
+
2
|
|
285692
|
+
);
|
|
285693
|
+
}
|
|
285694
|
+
async function doctor(argv = []) {
|
|
285673
285695
|
const report = await inspectDoctor();
|
|
285674
|
-
|
|
285675
|
-
|
|
285696
|
+
if (argv.includes("--json") || process.env.ABLO_JSON === "1") {
|
|
285697
|
+
console.log(doctorReportAsJson(report));
|
|
285698
|
+
} else {
|
|
285699
|
+
renderDoctorReport(report);
|
|
285700
|
+
}
|
|
285701
|
+
if (report.verdict !== "ready") process.exitCode = 1;
|
|
285676
285702
|
}
|
|
285677
285703
|
|
|
285678
285704
|
// src/setup/contracts.ts
|
|
@@ -287508,7 +287534,10 @@ var COMMANDS = [
|
|
|
287508
287534
|
core: { group: "Every day", does: "Check the whole setup at once and list everything that would block a write" },
|
|
287509
287535
|
full: {
|
|
287510
287536
|
group: "See what's happening",
|
|
287511
|
-
rows: [
|
|
287537
|
+
rows: [
|
|
287538
|
+
{ run: "doctor", does: "Every setup check at once. Exits non-zero when a write would fail, or when a check could not be run" },
|
|
287539
|
+
{ run: "doctor --json", does: "The same verdict as data, for a script or an agent" }
|
|
287540
|
+
]
|
|
287512
287541
|
}
|
|
287513
287542
|
},
|
|
287514
287543
|
{
|
|
@@ -288751,6 +288780,9 @@ async function check(argv) {
|
|
|
288751
288780
|
}
|
|
288752
288781
|
const schema = await loadSchema(args.schemaPath, args.exportName);
|
|
288753
288782
|
const schemaJson = JSON.parse((0, import_schema9.serializeSchema)(schema));
|
|
288783
|
+
const accessFindings = new Map(
|
|
288784
|
+
(0, import_schema9.auditSchemaAccessPolicies)(schemaJson).map((finding) => [finding.model, finding])
|
|
288785
|
+
);
|
|
288754
288786
|
console.log(`
|
|
288755
288787
|
${brand("ablo")} ${import_picocolors25.default.dim("check")} ${import_picocolors25.default.dim(`schema "${args.appSchema}"`)}
|
|
288756
288788
|
`);
|
|
@@ -288791,6 +288823,12 @@ async function check(argv) {
|
|
|
288791
288823
|
}
|
|
288792
288824
|
const problems = [];
|
|
288793
288825
|
const warns = [];
|
|
288826
|
+
const accessFinding = accessFindings.get(key);
|
|
288827
|
+
if (accessFinding) {
|
|
288828
|
+
problems.push(
|
|
288829
|
+
`[${accessFinding.code}] ${accessFinding.message} Fix: ${accessFinding.fix}`
|
|
288830
|
+
);
|
|
288831
|
+
}
|
|
288794
288832
|
if (!present.has("id")) problems.push('missing primary key "id"');
|
|
288795
288833
|
const orgCol = (0, import_schema9.tenancyColumn)((0, import_schema9.resolveTenancy)(model));
|
|
288796
288834
|
if (orgCol && !present.has(orgCol)) {
|
|
@@ -289735,8 +289773,21 @@ export async function POST(request: Request): Promise<Response> {
|
|
|
289735
289773
|
);
|
|
289736
289774
|
}
|
|
289737
289775
|
|
|
289776
|
+
const authorizedScope = await authorizeActiveWorkspace(user.id);
|
|
289777
|
+
if (!authorizedScope) {
|
|
289778
|
+
return Response.json(
|
|
289779
|
+
credentialEndpointErrorSchema.parse({
|
|
289780
|
+
error: { code: 'policy_denied', message: 'Workspace membership is stale or revoked' },
|
|
289781
|
+
}),
|
|
289782
|
+
{ status: 403, headers: noStore },
|
|
289783
|
+
);
|
|
289784
|
+
}
|
|
289785
|
+
|
|
289738
289786
|
const { token, expiresAt } = await sync.sessions.create({
|
|
289739
289787
|
user: { id: user.id },
|
|
289788
|
+
// These ids came from the server-side membership lookup below. Never take
|
|
289789
|
+
// organization, workspace, team, or group ids from the request body.
|
|
289790
|
+
syncGroups: authorizedScope.syncGroups,
|
|
289740
289791
|
can: { records: ['read', 'create', 'update'] },
|
|
289741
289792
|
});
|
|
289742
289793
|
return Response.json(
|
|
@@ -289760,6 +289811,27 @@ async function getCurrentUser(): Promise<{ id: string } | null> {
|
|
|
289760
289811
|
const session = await auth.api.getSession({ headers: await headers() });
|
|
289761
289812
|
return session?.user ? { id: session.user.id } : null;
|
|
289762
289813
|
}
|
|
289814
|
+
|
|
289815
|
+
type AuthorizedWorkspace = {
|
|
289816
|
+
workspaceId: string;
|
|
289817
|
+
syncGroups: readonly [\`workspace:\${string}\`, ...\`\${string}:\${string}\`[]];
|
|
289818
|
+
};
|
|
289819
|
+
|
|
289820
|
+
async function authorizeActiveWorkspace(userId: string): Promise<AuthorizedWorkspace | null> {
|
|
289821
|
+
void userId;
|
|
289822
|
+
// REQUIRED: query your membership table here, immediately before minting.
|
|
289823
|
+
// Read the active workspace from the server-side session, verify this user has
|
|
289824
|
+
// an active membership, and derive every team/group id on the server. Return
|
|
289825
|
+
// null for a stale or revoked membership. This refusal keeps a newly generated
|
|
289826
|
+
// route from turning "signed in" into workspace authorization by accident.
|
|
289827
|
+
//
|
|
289828
|
+
// Example after your membership query:
|
|
289829
|
+
// return {
|
|
289830
|
+
// workspaceId: membership.workspaceId,
|
|
289831
|
+
// syncGroups: [\`workspace:\${membership.workspaceId}\`],
|
|
289832
|
+
// };
|
|
289833
|
+
return null;
|
|
289834
|
+
}
|
|
289763
289835
|
`;
|
|
289764
289836
|
}
|
|
289765
289837
|
|
|
@@ -289792,7 +289864,7 @@ export const schema = defineSchema({
|
|
|
289792
289864
|
});
|
|
289793
289865
|
`;
|
|
289794
289866
|
}
|
|
289795
|
-
function generateSyncConfig(auth) {
|
|
289867
|
+
function generateSyncConfig(auth, options = {}) {
|
|
289796
289868
|
const authLine = auth === "apikey" ? "" : auth === "firebase" ? `
|
|
289797
289869
|
auth: async () => {
|
|
289798
289870
|
const { getAuth } = await import('firebase/auth');
|
|
@@ -289804,7 +289876,7 @@ function generateSyncConfig(auth) {
|
|
|
289804
289876
|
// auth: async () => { const { data } = await supabase.auth.getSession(); return data.session?.access_token ?? ''; },` : auth === "betterauth" ? `
|
|
289805
289877
|
// auth: async () => { const session = await authClient.getSession(); return session?.token ?? ''; },` : `
|
|
289806
289878
|
// auth: () => 'your-jwt-token', // replace with your auth provider`;
|
|
289807
|
-
return
|
|
289879
|
+
return `${options.serverOnly ? "import 'server-only';\n\n" : ""}import Ablo from '@abloatai/ablo';
|
|
289808
289880
|
import { schema } from './schema';
|
|
289809
289881
|
|
|
289810
289882
|
// SERVER-ONLY client \u2014 it holds your \`sk_\` key. Use it from server code: the
|
|
@@ -289813,8 +289885,6 @@ import { schema } from './schema';
|
|
|
289813
289885
|
// via the session route and never touches the key.
|
|
289814
289886
|
export const sync = Ablo({
|
|
289815
289887
|
apiKey: process.env.ABLO_API_KEY,${authLine}
|
|
289816
|
-
workspaceId: process.env.ABLO_PROJECT_ID,
|
|
289817
|
-
branchId: process.env.ABLO_BRANCH_ID,
|
|
289818
289888
|
schema,
|
|
289819
289889
|
});
|
|
289820
289890
|
|
|
@@ -289840,7 +289910,7 @@ function generateEnv(storage, opts = {}) {
|
|
|
289840
289910
|
const { includeApiKey = true } = opts;
|
|
289841
289911
|
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
289912
|
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`
|
|
289913
|
+
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
289914
|
return `${apiKeyBlock}${webhookBlock}${databaseBlock}`;
|
|
289845
289915
|
}
|
|
289846
289916
|
function generateDataSource(orm) {
|
|
@@ -289968,7 +290038,10 @@ export async function POST(req: Request): Promise<Response> {
|
|
|
289968
290038
|
const model: unknown = Reflect.get(prisma, event.model); // prisma.record, prisma.task, \u2026
|
|
289969
290039
|
if (!isModelDelegate(model)) continue; // a model you don't mirror locally \u2014 skip it
|
|
289970
290040
|
if (event.data === null) {
|
|
289971
|
-
await model.delete({ where: { id: event.objectId } }).catch(() => {
|
|
290041
|
+
await model.delete({ where: { id: event.objectId } }).catch((error) => {
|
|
290042
|
+
// Idempotent replay: a missing local row is already the desired state.
|
|
290043
|
+
void error;
|
|
290044
|
+
});
|
|
289972
290045
|
} else {
|
|
289973
290046
|
await model.upsert({ where: { id: event.objectId }, create: event.data, update: event.data });
|
|
289974
290047
|
}
|
|
@@ -290048,7 +290121,7 @@ async function main() {
|
|
|
290048
290121
|
main().catch((err) => {
|
|
290049
290122
|
// Ablo errors stringify to one clean line (code + message + docs link),
|
|
290050
290123
|
// never a stack/object dump \u2014 see AbloError.toString().
|
|
290051
|
-
|
|
290124
|
+
process.stderr.write(String(err) + '\\n');
|
|
290052
290125
|
process.exit(1);
|
|
290053
290126
|
});
|
|
290054
290127
|
`;
|
|
@@ -290399,7 +290472,10 @@ async function runInit(args = []) {
|
|
|
290399
290472
|
}
|
|
290400
290473
|
}
|
|
290401
290474
|
files.push({ path: (0, import_node_path5.join)(abloDir, "schema.ts"), content: schemaSource, note: schemaNote });
|
|
290402
|
-
files.push({
|
|
290475
|
+
files.push({
|
|
290476
|
+
path: (0, import_node_path5.join)(abloDir, "index.ts"),
|
|
290477
|
+
content: generateSyncConfig(auth, { serverOnly: framework === "nextjs" })
|
|
290478
|
+
});
|
|
290403
290479
|
files.push({ path: (0, import_node_path5.join)(abloDir, "register.ts"), content: generateRegister() });
|
|
290404
290480
|
const orm = detectOrm(opts.orm);
|
|
290405
290481
|
if (storage === "endpoint") {
|
|
@@ -290531,7 +290607,7 @@ var HANDLERS = {
|
|
|
290531
290607
|
branch: (argv) => branches([...argv]),
|
|
290532
290608
|
status: (argv) => status([...argv]),
|
|
290533
290609
|
whoami: (argv) => whoami([...argv]),
|
|
290534
|
-
doctor: () => doctor(),
|
|
290610
|
+
doctor: (argv) => doctor([...argv]),
|
|
290535
290611
|
logs: (argv) => logs([...argv]),
|
|
290536
290612
|
webhooks: (argv) => webhooks([...argv]),
|
|
290537
290613
|
check: (argv) => check([...argv]),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abloatai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.57.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.57.0",
|
|
39
39
|
"jiti": "^2.7.0",
|
|
40
40
|
"zod": "^4.4.3",
|
|
41
|
-
"@abloatai/humans": "^0.
|
|
41
|
+
"@abloatai/humans": "^0.57.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@ablo/product-analytics": "file:../product-analytics",
|