@abloatai/ablo 0.28.0 → 0.29.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/CHANGELOG.md +30 -0
- package/dist/Database.js +9 -6
- package/dist/SyncClient.d.ts +2 -2
- package/dist/SyncClient.js +22 -10
- package/dist/adapters/inMemoryStorage.js +18 -14
- package/dist/ai-sdk/coordinatedTool.d.ts +15 -2
- package/dist/auth/schemas.d.ts +6 -0
- package/dist/auth/schemas.js +7 -0
- package/dist/cli.cjs +815 -400
- package/dist/client/Ablo.d.ts +29 -23
- package/dist/client/Ablo.js +8 -196
- package/dist/client/claimHeartbeatLoop.d.ts +1 -1
- package/dist/client/claimHeartbeatLoop.js +1 -1
- package/dist/client/createInternalComponents.d.ts +4 -2
- package/dist/client/createInternalComponents.js +3 -1
- package/dist/client/durableWrites.d.ts +21 -0
- package/dist/client/durableWrites.js +46 -0
- package/dist/client/httpClient.d.ts +19 -45
- package/dist/client/httpClient.js +104 -26
- package/dist/client/httpTransport.d.ts +8 -0
- package/dist/client/{ApiClient.js → httpTransport.js} +78 -291
- package/dist/client/options.d.ts +43 -19
- package/dist/client/options.js +3 -2
- package/dist/client/resourceTypes.d.ts +9 -85
- package/dist/client/resourceTypes.js +1 -1
- package/dist/client/sessionMint.d.ts +5 -6
- package/dist/client/sessionMint.js +4 -5
- package/dist/client/validateAbloOptions.js +3 -3
- package/dist/coordination/schema.d.ts +30 -0
- package/dist/coordination/schema.js +14 -0
- package/dist/core/index.d.ts +1 -1
- package/dist/errors.d.ts +4 -40
- package/dist/errors.js +5 -5
- package/dist/index.d.ts +14 -8
- package/dist/index.js +7 -5
- package/dist/interfaces/index.d.ts +3 -0
- package/dist/mutators/defineMutators.d.ts +18 -0
- package/dist/mutators/defineMutators.js +4 -8
- package/dist/react/index.d.ts +1 -1
- package/dist/schema/index.d.ts +1 -1
- package/dist/schema/index.js +1 -1
- package/dist/schema/select.d.ts +15 -0
- package/dist/schema/select.js +27 -3
- package/dist/source/connector.d.ts +3 -3
- package/dist/source/factory.d.ts +4 -6
- package/dist/source/factory.js +4 -7
- package/dist/source/index.d.ts +4 -4
- package/dist/source/index.js +2 -2
- package/dist/source/signing.d.ts +0 -3
- package/dist/source/types.d.ts +0 -26
- package/dist/stores/ObjectStore.js +8 -8
- package/dist/surface.d.ts +1 -1
- package/dist/surface.js +1 -0
- package/dist/sync/BootstrapFetcher.d.ts +10 -0
- package/dist/sync/BootstrapFetcher.js +27 -4
- package/dist/sync/createClaimStream.d.ts +11 -2
- package/dist/sync/createClaimStream.js +4 -3
- package/dist/testing/mocks/FakeDatabase.d.ts +1 -1
- package/dist/transactions/TransactionQueue.d.ts +2 -2
- package/dist/transactions/TransactionQueue.js +3 -3
- package/dist/transactions/commitEnvelope.d.ts +1 -1
- package/dist/transactions/commitEnvelope.js +2 -2
- package/dist/transactions/commitOutboxStore.d.ts +19 -19
- package/dist/transactions/durableWriteStore.d.ts +123 -0
- package/dist/transactions/durableWriteStore.js +30 -0
- package/dist/transactions/httpCommitEnvelope.d.ts +1 -0
- package/dist/transactions/httpCommitEnvelope.js +14 -12
- package/dist/transactions/idempotencyKey.d.ts +10 -0
- package/dist/transactions/idempotencyKey.js +9 -0
- package/dist/types/global.d.ts +10 -29
- package/dist/types/global.js +4 -7
- package/dist/types/streams.d.ts +2 -28
- package/dist/wire/frames.d.ts +6 -257
- package/dist/wire/frames.js +4 -25
- package/dist/wire/index.d.ts +4 -3
- package/dist/wire/index.js +2 -2
- package/dist/wire/protocolVersion.d.ts +30 -17
- package/dist/wire/protocolVersion.js +34 -18
- package/docs/agents.md +8 -3
- package/docs/api.md +6 -4
- package/docs/client-behavior.md +6 -3
- package/docs/coordination.md +2 -3
- package/docs/data-sources.md +5 -8
- package/docs/guarantees.md +21 -0
- package/docs/integration-guide.md +1 -0
- package/docs/migration.md +21 -1
- package/docs/quickstart.md +11 -0
- package/docs/react.md +0 -46
- package/examples/README.md +6 -5
- package/llms.txt +15 -15
- package/package.json +2 -7
- package/dist/client/ApiClient.d.ts +0 -177
- package/dist/commit/contract.d.ts +0 -493
- package/dist/commit/contract.js +0 -187
- package/dist/commit/index.d.ts +0 -6
- package/dist/commit/index.js +0 -5
package/dist/cli.cjs
CHANGED
|
@@ -212184,11 +212184,11 @@ var require_commonjs2 = __commonJS({
|
|
|
212184
212184
|
if (pad) {
|
|
212185
212185
|
const need = width - c.length;
|
|
212186
212186
|
if (need > 0) {
|
|
212187
|
-
const
|
|
212187
|
+
const z7 = new Array(need + 1).join("0");
|
|
212188
212188
|
if (i < 0) {
|
|
212189
|
-
c = "-" +
|
|
212189
|
+
c = "-" + z7 + c.slice(1);
|
|
212190
212190
|
} else {
|
|
212191
|
-
c =
|
|
212191
|
+
c = z7 + c;
|
|
212192
212192
|
}
|
|
212193
212193
|
}
|
|
212194
212194
|
}
|
|
@@ -276724,8 +276724,8 @@ var Y2 = ({ indicator: t = "dots" } = {}) => {
|
|
|
276724
276724
|
if (i) process.stdout.write(`${I2} ${l2}...`);
|
|
276725
276725
|
else if (t === "timer") process.stdout.write(`${I2} ${l2} ${O2(g2)}`);
|
|
276726
276726
|
else {
|
|
276727
|
-
const
|
|
276728
|
-
process.stdout.write(`${I2} ${l2}${
|
|
276727
|
+
const z7 = ".".repeat(Math.floor(w2)).slice(0, 3);
|
|
276728
|
+
process.stdout.write(`${I2} ${l2}${z7}`);
|
|
276729
276729
|
}
|
|
276730
276730
|
h2 = h2 + 1 < n.length ? h2 + 1 : 0, w2 = w2 < n.length ? w2 + 0.125 : 0;
|
|
276731
276731
|
}, r2);
|
|
@@ -277262,6 +277262,11 @@ var claimRejectionSchema = import_zod3.z.object({
|
|
|
277262
277262
|
heldByClaim: wireClaimSummarySchema.optional(),
|
|
277263
277263
|
policyReason: import_zod3.z.string().optional()
|
|
277264
277264
|
});
|
|
277265
|
+
var claimLostSchema = import_zod3.z.object({
|
|
277266
|
+
claimId: import_zod3.z.string(),
|
|
277267
|
+
reason: import_zod3.z.enum(["expired", "preempted"]),
|
|
277268
|
+
target: targetRefSchema
|
|
277269
|
+
});
|
|
277265
277270
|
var modelTargetSchema = import_zod3.z.object({
|
|
277266
277271
|
model: import_zod3.z.string(),
|
|
277267
277272
|
id: import_zod3.z.string(),
|
|
@@ -277476,9 +277481,61 @@ var AbloError = class extends Error {
|
|
|
277476
277481
|
function docUrlForCode(code) {
|
|
277477
277482
|
return `https://docs.abloatai.com/errors#${code}`;
|
|
277478
277483
|
}
|
|
277484
|
+
var AbloAuthenticationError = class extends AbloError {
|
|
277485
|
+
type = "AbloAuthenticationError";
|
|
277486
|
+
};
|
|
277487
|
+
var AbloPermissionError = class extends AbloError {
|
|
277488
|
+
type = "AbloPermissionError";
|
|
277489
|
+
};
|
|
277490
|
+
var AbloRateLimitError = class extends AbloError {
|
|
277491
|
+
type = "AbloRateLimitError";
|
|
277492
|
+
retryAfterSeconds;
|
|
277493
|
+
constructor(message, options) {
|
|
277494
|
+
super(message, options);
|
|
277495
|
+
if (options?.retryAfterSeconds !== void 0) {
|
|
277496
|
+
this.retryAfterSeconds = options.retryAfterSeconds;
|
|
277497
|
+
}
|
|
277498
|
+
}
|
|
277499
|
+
};
|
|
277500
|
+
var AbloIdempotencyError = class extends AbloError {
|
|
277501
|
+
type = "AbloIdempotencyError";
|
|
277502
|
+
};
|
|
277479
277503
|
var AbloValidationError = class extends AbloError {
|
|
277480
277504
|
type = "AbloValidationError";
|
|
277481
277505
|
};
|
|
277506
|
+
var AbloServerError = class extends AbloError {
|
|
277507
|
+
type = "AbloServerError";
|
|
277508
|
+
};
|
|
277509
|
+
var AbloStaleContextError = class extends AbloError {
|
|
277510
|
+
type = "AbloStaleContextError";
|
|
277511
|
+
/** Sync id at the caller's `readAt` when the write was attempted. */
|
|
277512
|
+
readAt;
|
|
277513
|
+
/** Entities that received deltas between `readAt` and the write. */
|
|
277514
|
+
conflicts;
|
|
277515
|
+
constructor(message, options) {
|
|
277516
|
+
super(message, options);
|
|
277517
|
+
if (options?.readAt !== void 0) this.readAt = options.readAt;
|
|
277518
|
+
if (options?.conflicts !== void 0) this.conflicts = options.conflicts;
|
|
277519
|
+
}
|
|
277520
|
+
};
|
|
277521
|
+
var AbloClaimedError = class extends AbloError {
|
|
277522
|
+
type = "AbloClaimedError";
|
|
277523
|
+
claims;
|
|
277524
|
+
constructor(message, options) {
|
|
277525
|
+
super(message, options);
|
|
277526
|
+
if (options?.claims !== void 0) this.claims = options.claims;
|
|
277527
|
+
}
|
|
277528
|
+
};
|
|
277529
|
+
var CapabilityError = class extends AbloPermissionError {
|
|
277530
|
+
requiredCapability;
|
|
277531
|
+
constructor(code, message, requiredCapability) {
|
|
277532
|
+
super(`${code}: ${message}`, { code });
|
|
277533
|
+
this.name = "CapabilityError";
|
|
277534
|
+
if (requiredCapability !== void 0) {
|
|
277535
|
+
this.requiredCapability = requiredCapability;
|
|
277536
|
+
}
|
|
277537
|
+
}
|
|
277538
|
+
};
|
|
277482
277539
|
var OptionalWireStringSchema = import_zod4.z.preprocess(
|
|
277483
277540
|
(value) => typeof value === "string" ? value : void 0,
|
|
277484
277541
|
import_zod4.z.string().optional()
|
|
@@ -277506,7 +277563,7 @@ var ErrorFieldSchema = import_zod4.z.preprocess(
|
|
|
277506
277563
|
).catch(void 0);
|
|
277507
277564
|
var ErrorBodyShapeSchema = import_zod4.z.object({
|
|
277508
277565
|
/** The `error` field may be a flat code string, as some endpoints return,
|
|
277509
|
-
* or a nested error object, as
|
|
277566
|
+
* or a nested error object, as commit endpoints return on rejection. */
|
|
277510
277567
|
error: ErrorFieldSchema,
|
|
277511
277568
|
code: OptionalWireStringSchema,
|
|
277512
277569
|
reason: OptionalWireStringSchema,
|
|
@@ -277517,9 +277574,58 @@ var ErrorBodyShapeSchema = import_zod4.z.object({
|
|
|
277517
277574
|
heldByClaim: wireClaimSummarySchema.optional().catch(void 0),
|
|
277518
277575
|
claims: import_zod4.z.array(wireClaimSummarySchema).optional().catch(void 0)
|
|
277519
277576
|
}).passthrough();
|
|
277577
|
+
function parseErrorBodyShape(body) {
|
|
277578
|
+
if (typeof body !== "object" || body === null) return {};
|
|
277579
|
+
const parsed = ErrorBodyShapeSchema.safeParse(body);
|
|
277580
|
+
return parsed.success ? parsed.data : {};
|
|
277581
|
+
}
|
|
277582
|
+
function errorFromWire(message, opts = {}) {
|
|
277583
|
+
const { code, requestId, requiredCapability, claims } = opts;
|
|
277584
|
+
const httpStatus = opts.httpStatus ?? (code ? errorCodeSpec(code)?.httpStatus : void 0);
|
|
277585
|
+
const publicCode = code === "claim_conflict" ? "claim_conflict" : code;
|
|
277586
|
+
const baseOpts = { code: publicCode, httpStatus, requestId };
|
|
277587
|
+
if (code === "capability_scope_denied" || code === "capability_invalid") {
|
|
277588
|
+
return new CapabilityError(code, message, requiredCapability);
|
|
277589
|
+
}
|
|
277590
|
+
if (code === "claim_conflict" || code === "entity_claimed" || code === "claim_lost") {
|
|
277591
|
+
return new AbloClaimedError(message, { ...baseOpts, claims });
|
|
277592
|
+
}
|
|
277593
|
+
if (code === "stale_context") {
|
|
277594
|
+
return new AbloStaleContextError(message, baseOpts);
|
|
277595
|
+
}
|
|
277596
|
+
if (httpStatus === 401) return new AbloAuthenticationError(message, baseOpts);
|
|
277597
|
+
if (httpStatus === 403) return new AbloPermissionError(message, baseOpts);
|
|
277598
|
+
if (httpStatus === 409) return new AbloIdempotencyError(message, baseOpts);
|
|
277599
|
+
if (httpStatus === 422 || httpStatus === 400) return new AbloValidationError(message, baseOpts);
|
|
277600
|
+
if (httpStatus === 429) return new AbloRateLimitError(message, baseOpts);
|
|
277601
|
+
if (httpStatus !== void 0 && httpStatus >= 500) return new AbloServerError(message, baseOpts);
|
|
277602
|
+
return new AbloError(message, baseOpts);
|
|
277603
|
+
}
|
|
277604
|
+
function translateHttpError(status2, body, requestId) {
|
|
277605
|
+
const parsed = parseErrorBodyShape(body);
|
|
277606
|
+
const nested = parsed.error != null && typeof parsed.error === "object" ? parsed.error : void 0;
|
|
277607
|
+
const flatError = typeof parsed.error === "string" ? parsed.error : void 0;
|
|
277608
|
+
const code = parsed.code ?? nested?.code ?? flatError;
|
|
277609
|
+
const message = nested?.message ?? parsed.reason ?? parsed.message ?? flatError ?? (typeof body === "string" ? body : `HTTP ${status2}`);
|
|
277610
|
+
const requiredCapability = nested?.requiredCapability ?? parsed.requiredCapability;
|
|
277611
|
+
const claims = parsed.claims ?? nested?.claims ?? (parsed.heldByClaim ? [parsed.heldByClaim] : nested?.heldByClaim ? [nested.heldByClaim] : void 0);
|
|
277612
|
+
return errorFromWire(message, {
|
|
277613
|
+
code,
|
|
277614
|
+
httpStatus: status2,
|
|
277615
|
+
requestId,
|
|
277616
|
+
requiredCapability,
|
|
277617
|
+
claims
|
|
277618
|
+
});
|
|
277619
|
+
}
|
|
277620
|
+
function hasWireCode(body) {
|
|
277621
|
+
const parsed = parseErrorBodyShape(body);
|
|
277622
|
+
if (typeof parsed.code === "string") return true;
|
|
277623
|
+
if (typeof parsed.error === "string") return true;
|
|
277624
|
+
return typeof parsed.error === "object" && parsed.error !== null && typeof parsed.error.code === "string";
|
|
277625
|
+
}
|
|
277520
277626
|
|
|
277521
277627
|
// src/cli/migrate.ts
|
|
277522
|
-
var
|
|
277628
|
+
var import_picocolors5 = __toESM(require_picocolors(), 1);
|
|
277523
277629
|
var import_fs5 = require("fs");
|
|
277524
277630
|
|
|
277525
277631
|
// node_modules/postgres/src/index.js
|
|
@@ -279688,7 +279794,7 @@ var import_source = require("@abloatai/ablo/source");
|
|
|
279688
279794
|
|
|
279689
279795
|
// src/cli/push.ts
|
|
279690
279796
|
init_cjs_shims();
|
|
279691
|
-
var
|
|
279797
|
+
var import_picocolors4 = __toESM(require_picocolors(), 1);
|
|
279692
279798
|
|
|
279693
279799
|
// src/auth/credentialPolicy.ts
|
|
279694
279800
|
init_cjs_shims();
|
|
@@ -279962,6 +280068,174 @@ function resolvePushPlan() {
|
|
|
279962
280068
|
return { flow: getMode(), apiKey: key, source };
|
|
279963
280069
|
}
|
|
279964
280070
|
|
|
280071
|
+
// src/cli/target.ts
|
|
280072
|
+
init_cjs_shims();
|
|
280073
|
+
|
|
280074
|
+
// src/auth/index.ts
|
|
280075
|
+
init_cjs_shims();
|
|
280076
|
+
|
|
280077
|
+
// src/auth/schemas.ts
|
|
280078
|
+
init_cjs_shims();
|
|
280079
|
+
var import_zod5 = require("zod");
|
|
280080
|
+
var AuthParticipantKindSchema = import_zod5.z.enum(["user", "agent", "system"]);
|
|
280081
|
+
var AuthTokenSchema = import_zod5.z.string().trim().min(1);
|
|
280082
|
+
var CapabilityExchangeResponseSchema = import_zod5.z.object({
|
|
280083
|
+
capabilityId: import_zod5.z.string().min(1),
|
|
280084
|
+
token: AuthTokenSchema,
|
|
280085
|
+
expiresAt: import_zod5.z.string().min(1),
|
|
280086
|
+
organizationId: import_zod5.z.string().min(1),
|
|
280087
|
+
scope: import_zod5.z.object({
|
|
280088
|
+
organizationId: import_zod5.z.string().min(1),
|
|
280089
|
+
syncGroups: import_zod5.z.array(import_zod5.z.string()),
|
|
280090
|
+
operations: import_zod5.z.array(import_zod5.z.string()),
|
|
280091
|
+
participantKind: AuthParticipantKindSchema,
|
|
280092
|
+
participantId: import_zod5.z.string().min(1)
|
|
280093
|
+
}).passthrough(),
|
|
280094
|
+
userMeta: import_zod5.z.record(import_zod5.z.string(), import_zod5.z.unknown())
|
|
280095
|
+
}).passthrough();
|
|
280096
|
+
var IdentityResolveResponseSchema = import_zod5.z.object({
|
|
280097
|
+
participantKind: AuthParticipantKindSchema,
|
|
280098
|
+
participantId: import_zod5.z.string().min(1),
|
|
280099
|
+
accountScope: import_zod5.z.string().min(1),
|
|
280100
|
+
// The rest of the plane this credential resolves to. `nullish` (optional +
|
|
280101
|
+
// nullable) so a server too old to send them still parses, and a human
|
|
280102
|
+
// session — which carries no such scope — validates with them absent.
|
|
280103
|
+
// `projectId` equals the org id for the org-default project.
|
|
280104
|
+
projectId: import_zod5.z.string().min(1).nullish(),
|
|
280105
|
+
environment: import_zod5.z.enum(["sandbox", "production"]).nullish(),
|
|
280106
|
+
sandboxId: import_zod5.z.string().min(1).nullish(),
|
|
280107
|
+
syncGroups: import_zod5.z.array(import_zod5.z.string()),
|
|
280108
|
+
userMeta: import_zod5.z.record(import_zod5.z.string(), import_zod5.z.unknown())
|
|
280109
|
+
}).passthrough();
|
|
280110
|
+
var EphemeralKeyResponseSchema = import_zod5.z.object({
|
|
280111
|
+
object: import_zod5.z.literal("ephemeral_key").optional(),
|
|
280112
|
+
id: import_zod5.z.string().min(1),
|
|
280113
|
+
token: AuthTokenSchema,
|
|
280114
|
+
expiresAt: import_zod5.z.string().min(1),
|
|
280115
|
+
organizationId: import_zod5.z.string().min(1),
|
|
280116
|
+
participantId: import_zod5.z.string().min(1),
|
|
280117
|
+
syncGroups: import_zod5.z.array(import_zod5.z.string())
|
|
280118
|
+
}).passthrough();
|
|
280119
|
+
function formatIssues(error) {
|
|
280120
|
+
return error.issues.map((issue) => {
|
|
280121
|
+
const path = issue.path.length > 0 ? issue.path.join(".") : "<root>";
|
|
280122
|
+
return `${path}: ${issue.message}`;
|
|
280123
|
+
}).join("; ");
|
|
280124
|
+
}
|
|
280125
|
+
function parseIdentityResolveResponse(raw) {
|
|
280126
|
+
const parsed = IdentityResolveResponseSchema.safeParse(raw);
|
|
280127
|
+
if (!parsed.success) {
|
|
280128
|
+
throw new AbloAuthenticationError(
|
|
280129
|
+
`identity resolve response was malformed: ${formatIssues(parsed.error)}`,
|
|
280130
|
+
{ code: "identity_resolve_failed", cause: parsed.error }
|
|
280131
|
+
);
|
|
280132
|
+
}
|
|
280133
|
+
return parsed.data;
|
|
280134
|
+
}
|
|
280135
|
+
|
|
280136
|
+
// src/auth/index.ts
|
|
280137
|
+
async function resolveIdentity(options) {
|
|
280138
|
+
if (!options.baseUrl) {
|
|
280139
|
+
throw new AbloAuthenticationError("baseUrl is required for identity resolve", {
|
|
280140
|
+
code: "base_url_missing"
|
|
280141
|
+
});
|
|
280142
|
+
}
|
|
280143
|
+
const fetcher = options.fetch ?? fetch;
|
|
280144
|
+
const url = `${options.baseUrl.replace(/\/+$/, "")}/auth/identity`;
|
|
280145
|
+
const timeoutMs = options.timeoutMs ?? 1e4;
|
|
280146
|
+
const controller = new AbortController();
|
|
280147
|
+
const timer2 = setTimeout(() => {
|
|
280148
|
+
controller.abort();
|
|
280149
|
+
}, timeoutMs);
|
|
280150
|
+
let response;
|
|
280151
|
+
try {
|
|
280152
|
+
const headers = { Accept: "application/json" };
|
|
280153
|
+
if (options.authToken) {
|
|
280154
|
+
headers.Authorization = `Bearer ${options.authToken}`;
|
|
280155
|
+
}
|
|
280156
|
+
response = await fetcher(url, {
|
|
280157
|
+
method: "GET",
|
|
280158
|
+
headers,
|
|
280159
|
+
signal: controller.signal
|
|
280160
|
+
});
|
|
280161
|
+
} catch (err) {
|
|
280162
|
+
throw new AbloAuthenticationError(
|
|
280163
|
+
`identity resolve failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
280164
|
+
{ code: "identity_network_error", cause: err }
|
|
280165
|
+
);
|
|
280166
|
+
} finally {
|
|
280167
|
+
clearTimeout(timer2);
|
|
280168
|
+
}
|
|
280169
|
+
if (!response.ok) {
|
|
280170
|
+
let body = null;
|
|
280171
|
+
try {
|
|
280172
|
+
body = await response.json();
|
|
280173
|
+
} catch {
|
|
280174
|
+
}
|
|
280175
|
+
const requestId = response.headers.get("x-request-id") ?? void 0;
|
|
280176
|
+
throw hasWireCode(body) ? translateHttpError(response.status, body, requestId) : new AbloAuthenticationError(
|
|
280177
|
+
`identity resolve rejected (${response.status})`,
|
|
280178
|
+
{ code: "identity_resolve_failed", httpStatus: response.status }
|
|
280179
|
+
);
|
|
280180
|
+
}
|
|
280181
|
+
return parseIdentityResolveResponse(await response.json());
|
|
280182
|
+
}
|
|
280183
|
+
|
|
280184
|
+
// src/client/auth.ts
|
|
280185
|
+
init_cjs_shims();
|
|
280186
|
+
|
|
280187
|
+
// src/client/credentialEndpoint.ts
|
|
280188
|
+
init_cjs_shims();
|
|
280189
|
+
|
|
280190
|
+
// src/client/auth.ts
|
|
280191
|
+
var LEGACY_HOSTED_API_HOSTS = /* @__PURE__ */ new Set([
|
|
280192
|
+
"mesh.ablo.finance",
|
|
280193
|
+
"mesh-staging.ablo.finance",
|
|
280194
|
+
"api.ablo.finance",
|
|
280195
|
+
"sync-staging.ablo.finance"
|
|
280196
|
+
]);
|
|
280197
|
+
function normalizeAbloHostedBaseUrl(rawUrl) {
|
|
280198
|
+
const trimmed = rawUrl.trim();
|
|
280199
|
+
if (!trimmed) return trimmed;
|
|
280200
|
+
const schemed = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
280201
|
+
try {
|
|
280202
|
+
const url = new URL(schemed);
|
|
280203
|
+
if (url.protocol === "ws:") url.protocol = "http:";
|
|
280204
|
+
if (url.protocol === "wss:") url.protocol = "https:";
|
|
280205
|
+
if (!LEGACY_HOSTED_API_HOSTS.has(url.hostname)) {
|
|
280206
|
+
return url.toString().replace(/\/+$/, "");
|
|
280207
|
+
}
|
|
280208
|
+
url.hostname = ABLO_HOSTED_API_DOMAIN;
|
|
280209
|
+
if (url.protocol === "http:") url.protocol = "https:";
|
|
280210
|
+
return url.toString().replace(/\/+$/, "");
|
|
280211
|
+
} catch {
|
|
280212
|
+
return schemed;
|
|
280213
|
+
}
|
|
280214
|
+
}
|
|
280215
|
+
function resolveBootstrapBaseUrl(input) {
|
|
280216
|
+
if (input.bootstrapBaseUrl) {
|
|
280217
|
+
return ensureApiSuffix(normalizeAbloHostedBaseUrl(input.bootstrapBaseUrl).replace(/^ws/, "http"));
|
|
280218
|
+
}
|
|
280219
|
+
const url = normalizeAbloHostedBaseUrl(input.url);
|
|
280220
|
+
return ensureApiSuffix(url.replace(/^ws/, "http"));
|
|
280221
|
+
}
|
|
280222
|
+
function ensureApiSuffix(httpBase) {
|
|
280223
|
+
const trimmed = httpBase.replace(/\/+$/, "");
|
|
280224
|
+
try {
|
|
280225
|
+
const u2 = new URL(trimmed);
|
|
280226
|
+
const segments = u2.pathname.split("/").filter(Boolean);
|
|
280227
|
+
if (segments[segments.length - 1] === "api") return trimmed;
|
|
280228
|
+
u2.pathname = `${u2.pathname.replace(/\/+$/, "")}/api`;
|
|
280229
|
+
return u2.toString().replace(/\/+$/, "");
|
|
280230
|
+
} catch {
|
|
280231
|
+
return trimmed.endsWith("/api") ? trimmed : `${trimmed}/api`;
|
|
280232
|
+
}
|
|
280233
|
+
}
|
|
280234
|
+
|
|
280235
|
+
// src/cli/projects.ts
|
|
280236
|
+
init_cjs_shims();
|
|
280237
|
+
var import_picocolors3 = __toESM(require_picocolors(), 1);
|
|
280238
|
+
|
|
279965
280239
|
// src/cli/theme.ts
|
|
279966
280240
|
init_cjs_shims();
|
|
279967
280241
|
var RESET = "\x1B[0m";
|
|
@@ -279975,6 +280249,288 @@ function brand(label = "ablo") {
|
|
|
279975
280249
|
return `${PAPER_BG}${BLACK_FG} ${label} ${RESET}`;
|
|
279976
280250
|
}
|
|
279977
280251
|
|
|
280252
|
+
// src/cli/projects.ts
|
|
280253
|
+
function apiUrl() {
|
|
280254
|
+
return (process.env.ABLO_API_URL ?? ABLO_DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
280255
|
+
}
|
|
280256
|
+
function requireKey() {
|
|
280257
|
+
const apiKey = resolveApiKey();
|
|
280258
|
+
if (!apiKey) {
|
|
280259
|
+
console.error(
|
|
280260
|
+
import_picocolors3.default.red(" No API key.") + import_picocolors3.default.dim(
|
|
280261
|
+
` Run ${import_picocolors3.default.bold("npx ablo login")} \u2014 or set ${import_picocolors3.default.bold("ABLO_API_KEY")} (${import_picocolors3.default.bold("sk_test_")} = sandbox; ${import_picocolors3.default.bold("sk_live_")} = production).`
|
|
280262
|
+
)
|
|
280263
|
+
);
|
|
280264
|
+
process.exit(1);
|
|
280265
|
+
}
|
|
280266
|
+
return apiKey;
|
|
280267
|
+
}
|
|
280268
|
+
async function request(path, apiKey, init2 = {}, baseUrl2) {
|
|
280269
|
+
const res = await fetch(`${baseUrl2 ?? apiUrl()}${path}`, {
|
|
280270
|
+
method: init2.method ?? "GET",
|
|
280271
|
+
headers: {
|
|
280272
|
+
"content-type": "application/json",
|
|
280273
|
+
authorization: `Bearer ${apiKey}`
|
|
280274
|
+
},
|
|
280275
|
+
...init2.body !== void 0 ? { body: JSON.stringify(init2.body) } : {}
|
|
280276
|
+
});
|
|
280277
|
+
let body = {};
|
|
280278
|
+
try {
|
|
280279
|
+
body = await res.json();
|
|
280280
|
+
} catch {
|
|
280281
|
+
}
|
|
280282
|
+
return { status: res.status, body };
|
|
280283
|
+
}
|
|
280284
|
+
async function listProjects(apiKey, baseUrl2) {
|
|
280285
|
+
try {
|
|
280286
|
+
const { status: status2, body } = await request("/api/v1/projects", apiKey, {}, baseUrl2);
|
|
280287
|
+
if (status2 !== 200 || !Array.isArray(body.data)) return null;
|
|
280288
|
+
return body.data;
|
|
280289
|
+
} catch {
|
|
280290
|
+
return null;
|
|
280291
|
+
}
|
|
280292
|
+
}
|
|
280293
|
+
async function fetchProjects() {
|
|
280294
|
+
const all = await listProjects(requireKey());
|
|
280295
|
+
if (!all) {
|
|
280296
|
+
console.error(import_picocolors3.default.red(" Could not list projects \u2014 is the API reachable and the key valid?"));
|
|
280297
|
+
process.exit(1);
|
|
280298
|
+
}
|
|
280299
|
+
return all;
|
|
280300
|
+
}
|
|
280301
|
+
function projectSlugFromPackageName(name) {
|
|
280302
|
+
if (typeof name !== "string") return void 0;
|
|
280303
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/^[-_]+|[-_]+$/g, "").slice(0, 64);
|
|
280304
|
+
if (!slug || slug === "default") return void 0;
|
|
280305
|
+
return slug;
|
|
280306
|
+
}
|
|
280307
|
+
async function ensureProject(slug, name) {
|
|
280308
|
+
const apiKey = resolveApiKey();
|
|
280309
|
+
if (!apiKey) return null;
|
|
280310
|
+
try {
|
|
280311
|
+
const { status: status2, body } = await request("/api/v1/projects", apiKey, {
|
|
280312
|
+
method: "POST",
|
|
280313
|
+
body: { slug, ...name ? { name } : {} }
|
|
280314
|
+
});
|
|
280315
|
+
if (status2 === 201) {
|
|
280316
|
+
const created = body;
|
|
280317
|
+
setActiveProject({ id: created.id, slug: created.slug });
|
|
280318
|
+
return { id: created.id, slug: created.slug, created: true };
|
|
280319
|
+
}
|
|
280320
|
+
if (body.code === "project_slug_taken") {
|
|
280321
|
+
const all = await listProjects(apiKey);
|
|
280322
|
+
const existing = all?.find((p2) => p2.slug === slug);
|
|
280323
|
+
if (!existing) return null;
|
|
280324
|
+
setActiveProject({ id: existing.id, slug: existing.slug });
|
|
280325
|
+
return { id: existing.id, slug: existing.slug, created: false };
|
|
280326
|
+
}
|
|
280327
|
+
return null;
|
|
280328
|
+
} catch {
|
|
280329
|
+
return null;
|
|
280330
|
+
}
|
|
280331
|
+
}
|
|
280332
|
+
function printList(projects2) {
|
|
280333
|
+
const active = getActiveProject();
|
|
280334
|
+
for (const p2 of projects2) {
|
|
280335
|
+
const isActive = active ? active.id === p2.id : p2.default;
|
|
280336
|
+
const marker = isActive ? import_picocolors3.default.green("\u25CF") : import_picocolors3.default.dim("\u25CB");
|
|
280337
|
+
const tags = [p2.default ? import_picocolors3.default.dim("default") : "", isActive ? import_picocolors3.default.green("active") : ""].filter(Boolean).join(import_picocolors3.default.dim(", "));
|
|
280338
|
+
console.log(
|
|
280339
|
+
` ${marker} ${p2.slug.padEnd(20)} ${import_picocolors3.default.dim(p2.id)}${tags ? ` ${tags}` : ""}`
|
|
280340
|
+
);
|
|
280341
|
+
}
|
|
280342
|
+
}
|
|
280343
|
+
async function projects(argv) {
|
|
280344
|
+
const sub = argv[0];
|
|
280345
|
+
if (sub === "list" || sub === void 0) {
|
|
280346
|
+
console.log(`
|
|
280347
|
+
${brand("ablo")} ${import_picocolors3.default.dim("projects")}
|
|
280348
|
+
`);
|
|
280349
|
+
printList(await fetchProjects());
|
|
280350
|
+
console.log();
|
|
280351
|
+
return;
|
|
280352
|
+
}
|
|
280353
|
+
if (sub === "create") {
|
|
280354
|
+
const slug = argv[1];
|
|
280355
|
+
if (!slug || slug.startsWith("-")) {
|
|
280356
|
+
console.error(import_picocolors3.default.red(' usage: ablo projects create <slug> [--name "Display Name"]'));
|
|
280357
|
+
process.exit(1);
|
|
280358
|
+
}
|
|
280359
|
+
const nameIdx = argv.indexOf("--name");
|
|
280360
|
+
const name = nameIdx >= 0 ? argv[nameIdx + 1] : void 0;
|
|
280361
|
+
const { status: status2, body } = await request("/api/v1/projects", requireKey(), {
|
|
280362
|
+
method: "POST",
|
|
280363
|
+
body: { slug, ...name ? { name } : {} }
|
|
280364
|
+
});
|
|
280365
|
+
if (status2 !== 201) {
|
|
280366
|
+
console.error(
|
|
280367
|
+
import_picocolors3.default.red(` Create failed (${status2}): ${String(body.message ?? body.code ?? "")}`)
|
|
280368
|
+
);
|
|
280369
|
+
if (body.code === "project_slug_taken") {
|
|
280370
|
+
console.error(import_picocolors3.default.dim(` Pick another slug, or switch to it: ${import_picocolors3.default.bold(`ablo projects use ${slug}`)}`));
|
|
280371
|
+
}
|
|
280372
|
+
process.exit(1);
|
|
280373
|
+
}
|
|
280374
|
+
const created = body;
|
|
280375
|
+
console.log(` ${import_picocolors3.default.green("\u2713")} Created project ${import_picocolors3.default.bold(created.slug)} ${import_picocolors3.default.dim(`(${created.id})`)}`);
|
|
280376
|
+
console.log(
|
|
280377
|
+
import_picocolors3.default.dim(` Make it active with ${import_picocolors3.default.bold(`ablo projects use ${created.slug}`)}; mint its keys in the dashboard.`)
|
|
280378
|
+
);
|
|
280379
|
+
return;
|
|
280380
|
+
}
|
|
280381
|
+
if (sub === "rename") {
|
|
280382
|
+
const ref = argv[1];
|
|
280383
|
+
const name = argv.slice(2).join(" ").trim();
|
|
280384
|
+
if (!ref || ref.startsWith("-") || !name) {
|
|
280385
|
+
console.error(import_picocolors3.default.red(" usage: ablo projects rename <slug|id> <new name>"));
|
|
280386
|
+
process.exit(1);
|
|
280387
|
+
}
|
|
280388
|
+
const all = await fetchProjects();
|
|
280389
|
+
const target = all.find((p2) => p2.slug === ref || p2.id === ref);
|
|
280390
|
+
if (!target) {
|
|
280391
|
+
console.error(import_picocolors3.default.red(` No project "${ref}".`) + import_picocolors3.default.dim(" Run ablo projects list."));
|
|
280392
|
+
process.exit(1);
|
|
280393
|
+
}
|
|
280394
|
+
if (target.default) {
|
|
280395
|
+
console.error(import_picocolors3.default.red(" The default project cannot be renamed."));
|
|
280396
|
+
process.exit(1);
|
|
280397
|
+
}
|
|
280398
|
+
const { status: status2, body } = await request(`/api/v1/projects/${target.id}`, requireKey(), {
|
|
280399
|
+
method: "PATCH",
|
|
280400
|
+
body: { name }
|
|
280401
|
+
});
|
|
280402
|
+
if (status2 !== 200) {
|
|
280403
|
+
console.error(
|
|
280404
|
+
import_picocolors3.default.red(` Rename failed (${status2}): ${String(body.message ?? body.code ?? "")}`)
|
|
280405
|
+
);
|
|
280406
|
+
process.exit(1);
|
|
280407
|
+
}
|
|
280408
|
+
const updated = body;
|
|
280409
|
+
console.log(
|
|
280410
|
+
` ${import_picocolors3.default.green("\u2713")} Renamed ${import_picocolors3.default.bold(updated.slug)} \u2192 ${import_picocolors3.default.bold(updated.name ?? updated.slug)} ${import_picocolors3.default.dim(`(${updated.id})`)}`
|
|
280411
|
+
);
|
|
280412
|
+
return;
|
|
280413
|
+
}
|
|
280414
|
+
if (sub === "use") {
|
|
280415
|
+
const ref = argv[1];
|
|
280416
|
+
if (!ref) {
|
|
280417
|
+
console.error(import_picocolors3.default.red(" usage: ablo projects use <slug|id|default>"));
|
|
280418
|
+
process.exit(1);
|
|
280419
|
+
}
|
|
280420
|
+
const all = await fetchProjects();
|
|
280421
|
+
const target = all.find((p2) => p2.slug === ref || p2.id === ref);
|
|
280422
|
+
if (!target) {
|
|
280423
|
+
console.error(import_picocolors3.default.red(` No project "${ref}".`) + import_picocolors3.default.dim(" Run ablo projects list."));
|
|
280424
|
+
process.exit(1);
|
|
280425
|
+
}
|
|
280426
|
+
if (target.default) {
|
|
280427
|
+
setActiveProject(void 0);
|
|
280428
|
+
console.log(` ${import_picocolors3.default.green("\u2713")} now targeting the ${import_picocolors3.default.bold("default")} project`);
|
|
280429
|
+
} else {
|
|
280430
|
+
setActiveProject({ id: target.id, slug: target.slug });
|
|
280431
|
+
console.log(` ${import_picocolors3.default.green("\u2713")} now targeting project ${import_picocolors3.default.bold(target.slug)} ${import_picocolors3.default.dim(`(${target.id})`)}`);
|
|
280432
|
+
}
|
|
280433
|
+
const guard = guardActiveProjectKey();
|
|
280434
|
+
if (!guard.ok) {
|
|
280435
|
+
const loginCmd = guard.activeProfile === DEFAULT_PROFILE ? "ablo login" : `ablo login --project ${guard.activeProfile}`;
|
|
280436
|
+
console.log(
|
|
280437
|
+
import_picocolors3.default.dim(` No key stored for this project yet \u2014 run ${import_picocolors3.default.bold(loginCmd)} to mint one.`)
|
|
280438
|
+
);
|
|
280439
|
+
}
|
|
280440
|
+
return;
|
|
280441
|
+
}
|
|
280442
|
+
console.error(
|
|
280443
|
+
import_picocolors3.default.red(` unknown subcommand: ${sub}`) + import_picocolors3.default.dim(
|
|
280444
|
+
` (expected ${import_picocolors3.default.bold("list")}, ${import_picocolors3.default.bold("create")}, ${import_picocolors3.default.bold("rename")}, or ${import_picocolors3.default.bold("use")})`
|
|
280445
|
+
)
|
|
280446
|
+
);
|
|
280447
|
+
process.exit(1);
|
|
280448
|
+
}
|
|
280449
|
+
|
|
280450
|
+
// src/cli/target.ts
|
|
280451
|
+
var DEFAULT_PROJECT_SLUG = "default";
|
|
280452
|
+
async function resolveTarget(opts) {
|
|
280453
|
+
const keyEnv = modeFromKey(opts.apiKey) ?? null;
|
|
280454
|
+
const localProject = getActiveProject();
|
|
280455
|
+
const confirmed = await confirmFromServer(opts);
|
|
280456
|
+
return {
|
|
280457
|
+
url: opts.url,
|
|
280458
|
+
keyPrefix: `${opts.apiKey.slice(0, 12)}\u2026`,
|
|
280459
|
+
keySource: opts.keySource,
|
|
280460
|
+
keyEnv,
|
|
280461
|
+
confirmed,
|
|
280462
|
+
localProject,
|
|
280463
|
+
mismatches: reconcile({ confirmed, keyEnv, localProject })
|
|
280464
|
+
};
|
|
280465
|
+
}
|
|
280466
|
+
async function confirmFromServer(opts) {
|
|
280467
|
+
const keyEnv = modeFromKey(opts.apiKey) ?? null;
|
|
280468
|
+
try {
|
|
280469
|
+
const identity = await resolveIdentity({
|
|
280470
|
+
baseUrl: resolveBootstrapBaseUrl({ url: opts.url }),
|
|
280471
|
+
authToken: opts.apiKey,
|
|
280472
|
+
timeoutMs: opts.timeoutMs ?? 4e3
|
|
280473
|
+
});
|
|
280474
|
+
const projectId = identity.projectId ?? null;
|
|
280475
|
+
const environment = identity.environment ?? keyEnv;
|
|
280476
|
+
const project = projectId ? await nameProject(projectId, identity.accountScope, opts) : null;
|
|
280477
|
+
return {
|
|
280478
|
+
organizationId: identity.accountScope,
|
|
280479
|
+
environment,
|
|
280480
|
+
project,
|
|
280481
|
+
projectId,
|
|
280482
|
+
sandboxId: identity.sandboxId ?? null
|
|
280483
|
+
};
|
|
280484
|
+
} catch {
|
|
280485
|
+
return null;
|
|
280486
|
+
}
|
|
280487
|
+
}
|
|
280488
|
+
async function nameProject(projectId, organizationId, opts) {
|
|
280489
|
+
const list = await listProjects(opts.apiKey, resolveBootstrapBaseUrl({ url: opts.url }));
|
|
280490
|
+
const match = list?.find((p2) => p2.id === projectId);
|
|
280491
|
+
if (match) {
|
|
280492
|
+
return { id: match.id, slug: match.slug, name: match.name, isDefault: match.default };
|
|
280493
|
+
}
|
|
280494
|
+
return {
|
|
280495
|
+
id: projectId,
|
|
280496
|
+
slug: projectId,
|
|
280497
|
+
name: null,
|
|
280498
|
+
isDefault: projectId === organizationId
|
|
280499
|
+
};
|
|
280500
|
+
}
|
|
280501
|
+
function intendedProjectSlug(localProject) {
|
|
280502
|
+
return localProject?.slug ?? DEFAULT_PROJECT_SLUG;
|
|
280503
|
+
}
|
|
280504
|
+
function confirmedProjectSlug(project) {
|
|
280505
|
+
return project.isDefault ? DEFAULT_PROJECT_SLUG : project.slug;
|
|
280506
|
+
}
|
|
280507
|
+
function reconcile(input) {
|
|
280508
|
+
const { confirmed, keyEnv, localProject } = input;
|
|
280509
|
+
const mismatches = [];
|
|
280510
|
+
const realEnv = confirmed?.environment ?? keyEnv;
|
|
280511
|
+
const cliMode = getMode();
|
|
280512
|
+
if (realEnv && realEnv !== cliMode) {
|
|
280513
|
+
mismatches.push({ kind: "environment", keyEnv: realEnv, cliMode });
|
|
280514
|
+
}
|
|
280515
|
+
if (confirmed?.projectId) {
|
|
280516
|
+
const intendedId = localProject?.id ?? confirmed.organizationId;
|
|
280517
|
+
if (intendedId !== confirmed.projectId) {
|
|
280518
|
+
mismatches.push({
|
|
280519
|
+
kind: "project",
|
|
280520
|
+
intended: intendedProjectSlug(localProject),
|
|
280521
|
+
actual: confirmed.project ? confirmedProjectSlug(confirmed.project) : confirmed.projectId
|
|
280522
|
+
});
|
|
280523
|
+
}
|
|
280524
|
+
}
|
|
280525
|
+
return mismatches;
|
|
280526
|
+
}
|
|
280527
|
+
function describeMismatch(m2) {
|
|
280528
|
+
if (m2.kind === "project") {
|
|
280529
|
+
return `The key targets project ${m2.actual}, but ${m2.intended} is selected locally. This key routes the push, so it lands on ${m2.actual}. Run \`ablo projects use ${m2.actual}\` to match, or use a key minted for ${m2.intended}.`;
|
|
280530
|
+
}
|
|
280531
|
+
return `The key deploys to ${m2.keyEnv}, but the CLI mode is ${m2.cliMode}. The key wins, so this acts on ${m2.keyEnv}. Run \`ablo mode ${m2.keyEnv}\` to match, or present a ${m2.cliMode} key.`;
|
|
280532
|
+
}
|
|
280533
|
+
|
|
279978
280534
|
// src/cli/push.ts
|
|
279979
280535
|
function coerceBackfill(raw) {
|
|
279980
280536
|
if (raw === "true") return true;
|
|
@@ -279988,14 +280544,14 @@ var DEFAULT_URL = ABLO_DEFAULT_BASE_URL;
|
|
|
279988
280544
|
function fmtSignal(s) {
|
|
279989
280545
|
const sig = s;
|
|
279990
280546
|
const where = sig.field ? `${sig.model}.${sig.field}` : sig.model;
|
|
279991
|
-
let line = ` \u2022 ${
|
|
280547
|
+
let line = ` \u2022 ${import_picocolors4.default.bold(where ?? "?")} \u2014 ${sig.detail ?? ""}`;
|
|
279992
280548
|
if (sig.shadowed) {
|
|
279993
280549
|
const env = sig.shadowed.environment ?? "production";
|
|
279994
280550
|
const ver = sig.shadowed.version != null ? `v${sig.shadowed.version}` : "active";
|
|
279995
280551
|
const when = sig.shadowed.pushedAt ? new Date(sig.shadowed.pushedAt).toISOString().slice(0, 10) : "unknown date";
|
|
279996
280552
|
const by = sig.shadowed.pushedBy ? ` by ${sig.shadowed.pushedBy}` : "";
|
|
279997
280553
|
line += `
|
|
279998
|
-
${
|
|
280554
|
+
${import_picocolors4.default.dim(`\u21B3 baseline: ${env} ${ver}, pushed ${when}${by}`)}`;
|
|
279999
280555
|
}
|
|
280000
280556
|
return line;
|
|
280001
280557
|
}
|
|
@@ -280092,7 +280648,7 @@ async function loadSchema(schemaPath, exportName) {
|
|
|
280092
280648
|
const abs = (0, import_path3.resolve)(process.cwd(), schemaPath);
|
|
280093
280649
|
if (!(0, import_fs4.existsSync)(abs)) {
|
|
280094
280650
|
throw new AbloValidationError(
|
|
280095
|
-
`schema not found at ${
|
|
280651
|
+
`schema not found at ${import_picocolors4.default.bold(schemaPath)}. Run ${import_picocolors4.default.bold("npx ablo init")} or pass ${import_picocolors4.default.bold("--schema <path>")}.`,
|
|
280096
280652
|
{ code: "cli_invalid_arguments" }
|
|
280097
280653
|
);
|
|
280098
280654
|
}
|
|
@@ -280103,7 +280659,7 @@ async function loadSchema(schemaPath, exportName) {
|
|
|
280103
280659
|
const schema = mod[exportName] ?? nested?.[exportName];
|
|
280104
280660
|
if (!schema || typeof schema !== "object" || !("models" in schema)) {
|
|
280105
280661
|
throw new AbloValidationError(
|
|
280106
|
-
`${
|
|
280662
|
+
`${import_picocolors4.default.bold(schemaPath)} has no \`${exportName}\` export that looks like a Schema. Did you \`export const ${exportName} = defineSchema({ ... })\`?`,
|
|
280107
280663
|
{ code: "cli_invalid_arguments" }
|
|
280108
280664
|
);
|
|
280109
280665
|
}
|
|
@@ -280156,7 +280712,7 @@ function localModels(schema) {
|
|
|
280156
280712
|
}
|
|
280157
280713
|
function printPlan(local, remote) {
|
|
280158
280714
|
if (!remote?.models) {
|
|
280159
|
-
console.log(` ${
|
|
280715
|
+
console.log(` ${import_picocolors4.default.dim("plan")} ${import_picocolors4.default.dim("(deployed schema unavailable \u2014 the server computes the diff on apply)")}
|
|
280160
280716
|
`);
|
|
280161
280717
|
return;
|
|
280162
280718
|
}
|
|
@@ -280167,36 +280723,37 @@ function printPlan(local, remote) {
|
|
|
280167
280723
|
const changed = [...local.keys()].filter((k3) => remoteMap.has(k3) && remoteMap.get(k3) !== local.get(k3));
|
|
280168
280724
|
const verLabel = remote.version != null ? `v${remote.version}` : "active";
|
|
280169
280725
|
if (added.length === 0 && removed.length === 0 && changed.length === 0) {
|
|
280170
|
-
console.log(` ${
|
|
280726
|
+
console.log(` ${import_picocolors4.default.dim("plan")} ${import_picocolors4.default.dim(`no model-level changes vs deployed ${verLabel} (any field changes apply on push)`)}
|
|
280171
280727
|
`);
|
|
280172
280728
|
return;
|
|
280173
280729
|
}
|
|
280174
|
-
console.log(` ${
|
|
280175
|
-
for (const k3 of added) console.log(` ${
|
|
280730
|
+
console.log(` ${import_picocolors4.default.dim("plan")} ${import_picocolors4.default.dim(`vs deployed ${verLabel}:`)}`);
|
|
280731
|
+
for (const k3 of added) console.log(` ${import_picocolors4.default.green(`+ ${k3}`)} ${import_picocolors4.default.dim("(new model)")}`);
|
|
280176
280732
|
for (const k3 of changed)
|
|
280177
|
-
console.log(` ${
|
|
280178
|
-
for (const k3 of removed) console.log(` ${
|
|
280733
|
+
console.log(` ${import_picocolors4.default.yellow(`~ ${k3}`)} ${import_picocolors4.default.dim(`conflict ${remoteMap.get(k3) || "(default)"} \u2192 ${local.get(k3) || "(default)"}`)}`);
|
|
280734
|
+
for (const k3 of removed) console.log(` ${import_picocolors4.default.red(`- ${k3}`)} ${import_picocolors4.default.dim("(removed \u2014 destructive, needs --force)")}`);
|
|
280179
280735
|
console.log("");
|
|
280180
280736
|
}
|
|
280181
|
-
async function confirmPush(args,
|
|
280737
|
+
async function confirmPush(args, target) {
|
|
280738
|
+
const env = target.confirmed?.environment ?? target.keyEnv;
|
|
280182
280739
|
const isProd = env === "production";
|
|
280183
280740
|
const tty = Boolean(process.stdout.isTTY && process.stdin.isTTY);
|
|
280184
280741
|
if (isProd && !args.yes) {
|
|
280185
280742
|
const git = schemaGitState(args.schemaPath);
|
|
280186
280743
|
if (git?.dirty && !args.allowDirty) {
|
|
280187
|
-
console.error(` ${
|
|
280188
|
-
console.error(
|
|
280744
|
+
console.error(` ${import_picocolors4.default.red("\u2717")} Refusing to deploy uncommitted schema to ${import_picocolors4.default.red(import_picocolors4.default.bold("production"))}.`);
|
|
280745
|
+
console.error(import_picocolors4.default.dim(` Commit ${import_picocolors4.default.bold(args.schemaPath)} first, or pass ${import_picocolors4.default.bold("--allow-dirty")} to override.`));
|
|
280189
280746
|
process.exit(1);
|
|
280190
280747
|
}
|
|
280191
280748
|
if (!tty) {
|
|
280192
|
-
console.error(` ${
|
|
280193
|
-
console.error(
|
|
280749
|
+
console.error(` ${import_picocolors4.default.red("\u2717")} Refusing to deploy to ${import_picocolors4.default.red(import_picocolors4.default.bold("production"))} non-interactively without confirmation.`);
|
|
280750
|
+
console.error(import_picocolors4.default.dim(` Re-run with ${import_picocolors4.default.bold("--yes")} to confirm in CI/scripts.`));
|
|
280194
280751
|
process.exit(1);
|
|
280195
280752
|
}
|
|
280196
|
-
const
|
|
280197
|
-
const expected =
|
|
280753
|
+
const confirmedProject = target.confirmed?.project;
|
|
280754
|
+
const expected = confirmedProject ? confirmedProject.isDefault ? "default" : confirmedProject.slug : target.localProject?.slug ?? "production";
|
|
280198
280755
|
const typed = await he({
|
|
280199
|
-
message: `This deploys to ${
|
|
280756
|
+
message: `This deploys to ${import_picocolors4.default.bold("production")} project ${import_picocolors4.default.bold(expected)}. Type ${import_picocolors4.default.bold(expected)} to confirm:`,
|
|
280200
280757
|
placeholder: expected
|
|
280201
280758
|
});
|
|
280202
280759
|
if (pD(typed) || String(typed).trim() !== expected) {
|
|
@@ -280206,32 +280763,53 @@ async function confirmPush(args, env) {
|
|
|
280206
280763
|
return;
|
|
280207
280764
|
}
|
|
280208
280765
|
if (!isProd && !args.yes && tty) {
|
|
280209
|
-
const ok = await ye({ message: `Apply to ${
|
|
280766
|
+
const ok = await ye({ message: `Apply to ${import_picocolors4.default.bold("sandbox")}?` });
|
|
280210
280767
|
if (pD(ok) || !ok) {
|
|
280211
280768
|
xe("Aborted.");
|
|
280212
280769
|
process.exit(1);
|
|
280213
280770
|
}
|
|
280214
280771
|
}
|
|
280215
280772
|
}
|
|
280216
|
-
function printPushTarget(
|
|
280217
|
-
const
|
|
280218
|
-
const
|
|
280219
|
-
const
|
|
280220
|
-
const projectLabel = project ? `${import_picocolors3.default.bold(project.slug)} ${import_picocolors3.default.dim(`(${project.id})`)}` : `${import_picocolors3.default.bold("default")} ${import_picocolors3.default.dim("(org-default)")}`;
|
|
280773
|
+
function printPushTarget(target, schema) {
|
|
280774
|
+
const confirmed = target.confirmed;
|
|
280775
|
+
const env = confirmed?.environment ?? target.keyEnv;
|
|
280776
|
+
const envLabel = env === "production" ? import_picocolors4.default.bold("production") : env === "sandbox" ? import_picocolors4.default.bold("sandbox") : import_picocolors4.default.yellow("unknown env");
|
|
280221
280777
|
const cliMode = getMode();
|
|
280222
|
-
const modeNote = env && env !== cliMode ? ` ${
|
|
280778
|
+
const modeNote = env && env !== cliMode ? ` ${import_picocolors4.default.yellow(`(cli mode: ${cliMode})`)}` : "";
|
|
280779
|
+
let projectLabel;
|
|
280780
|
+
if (confirmed?.project) {
|
|
280781
|
+
const p2 = confirmed.project;
|
|
280782
|
+
const name = p2.isDefault ? `${import_picocolors4.default.bold("default")} ${import_picocolors4.default.dim("(org-default)")}` : import_picocolors4.default.bold(p2.slug);
|
|
280783
|
+
projectLabel = `${name} ${import_picocolors4.default.dim(`(${p2.id})`)}`;
|
|
280784
|
+
} else if (confirmed) {
|
|
280785
|
+
projectLabel = `${import_picocolors4.default.bold("default")} ${import_picocolors4.default.dim("(org-default)")}`;
|
|
280786
|
+
} else {
|
|
280787
|
+
const local = target.localProject;
|
|
280788
|
+
const shown = local ? `${import_picocolors4.default.bold(local.slug)} ${import_picocolors4.default.dim(`(${local.id})`)}` : `${import_picocolors4.default.bold("default")} ${import_picocolors4.default.dim("(org-default)")}`;
|
|
280789
|
+
projectLabel = `${shown} ${import_picocolors4.default.yellow("(unconfirmed \u2014 server did not answer)")}`;
|
|
280790
|
+
}
|
|
280223
280791
|
console.log(`
|
|
280224
|
-
${brand("ablo")} ${
|
|
280225
|
-
console.log(` ${
|
|
280226
|
-
console.log(` ${
|
|
280792
|
+
${brand("ablo")} ${import_picocolors4.default.dim("push")} ${import_picocolors4.default.dim("\u2192")} ${envLabel}${modeNote}`);
|
|
280793
|
+
if (confirmed?.organizationId) console.log(` ${import_picocolors4.default.dim("org")} ${import_picocolors4.default.dim(confirmed.organizationId)}`);
|
|
280794
|
+
console.log(` ${import_picocolors4.default.dim("project")} ${projectLabel}`);
|
|
280795
|
+
console.log(` ${import_picocolors4.default.dim("target")} ${import_picocolors4.default.dim(target.url)}`);
|
|
280227
280796
|
console.log(
|
|
280228
|
-
` ${
|
|
280797
|
+
` ${import_picocolors4.default.dim("key")} ${target.keyPrefix} ${import_picocolors4.default.dim(`(${describeKeySource(target.keySource)})`)}`
|
|
280229
280798
|
);
|
|
280230
280799
|
console.log(
|
|
280231
|
-
` ${
|
|
280800
|
+
` ${import_picocolors4.default.dim("schema")} ${import_picocolors4.default.bold(schema.path)} ${import_picocolors4.default.dim(`${schema.modelCount} models, hash ${schema.hash}`)}
|
|
280232
280801
|
`
|
|
280233
280802
|
);
|
|
280234
280803
|
}
|
|
280804
|
+
function warnMismatches(target) {
|
|
280805
|
+
let projectDrift = false;
|
|
280806
|
+
for (const m2 of target.mismatches) {
|
|
280807
|
+
if (m2.kind === "project") projectDrift = true;
|
|
280808
|
+
console.log(` ${import_picocolors4.default.yellow("\u26A0")} ${import_picocolors4.default.yellow(describeMismatch(m2))}
|
|
280809
|
+
`);
|
|
280810
|
+
}
|
|
280811
|
+
return { projectDrift };
|
|
280812
|
+
}
|
|
280235
280813
|
function describeKeySource(source) {
|
|
280236
280814
|
switch (source) {
|
|
280237
280815
|
case "env":
|
|
@@ -280249,7 +280827,7 @@ async function push(argv) {
|
|
|
280249
280827
|
try {
|
|
280250
280828
|
args = parsePushArgs(argv);
|
|
280251
280829
|
} catch (err) {
|
|
280252
|
-
console.error(
|
|
280830
|
+
console.error(import_picocolors4.default.red(` ${err instanceof Error ? err.message : String(err)}`));
|
|
280253
280831
|
process.exit(1);
|
|
280254
280832
|
}
|
|
280255
280833
|
let keySource = "env";
|
|
@@ -280260,44 +280838,43 @@ async function push(argv) {
|
|
|
280260
280838
|
}
|
|
280261
280839
|
if (!args.apiKey) {
|
|
280262
280840
|
console.error(
|
|
280263
|
-
|
|
280264
|
-
` Run ${
|
|
280841
|
+
import_picocolors4.default.red(` No API key.`) + import_picocolors4.default.dim(
|
|
280842
|
+
` Run ${import_picocolors4.default.bold("npx ablo login")} for the sandbox dev loop \u2014 or set ${import_picocolors4.default.bold("ABLO_API_KEY")} (${import_picocolors4.default.bold("sk_test_")} = sandbox; ${import_picocolors4.default.bold("sk_live_")} = deliberate production deploy). Mode is currently '${getMode()}'.`
|
|
280265
280843
|
)
|
|
280266
280844
|
);
|
|
280267
280845
|
process.exit(1);
|
|
280268
280846
|
}
|
|
280269
280847
|
const schema = await loadSchema(args.schemaPath, args.exportName);
|
|
280270
280848
|
const hash = (0, import_schema2.schemaHash)(schema);
|
|
280271
|
-
|
|
280272
|
-
|
|
280273
|
-
|
|
280274
|
-
apiKey: args.apiKey,
|
|
280275
|
-
keySource,
|
|
280849
|
+
const target = await resolveTarget({ url: args.url, apiKey: args.apiKey, keySource });
|
|
280850
|
+
printPushTarget(target, {
|
|
280851
|
+
path: args.schemaPath,
|
|
280276
280852
|
modelCount: Object.keys(schema.models).length,
|
|
280277
280853
|
hash
|
|
280278
280854
|
});
|
|
280279
280855
|
const remote = await fetchActiveSchema(args.url, args.apiKey);
|
|
280280
280856
|
printPlan(localModels(schema), remote);
|
|
280857
|
+
warnMismatches(target);
|
|
280281
280858
|
const git = schemaGitState(args.schemaPath);
|
|
280282
280859
|
if (git?.dirty) {
|
|
280283
280860
|
const what = git.untracked ? "is untracked (not committed)" : "has uncommitted changes";
|
|
280284
|
-
console.log(` ${
|
|
280861
|
+
console.log(` ${import_picocolors4.default.yellow("\u26A0")} ${import_picocolors4.default.bold(args.schemaPath)} ${what} \u2014 this deploy won't match a git commit.
|
|
280285
280862
|
`);
|
|
280286
280863
|
}
|
|
280287
280864
|
if (args.dryRun) {
|
|
280288
|
-
console.log(` ${
|
|
280865
|
+
console.log(` ${import_picocolors4.default.dim("\u25CB")} dry run \u2014 nothing applied. Re-run without ${import_picocolors4.default.bold("--dry-run")} to deploy.`);
|
|
280289
280866
|
return;
|
|
280290
280867
|
}
|
|
280291
|
-
await confirmPush(args,
|
|
280868
|
+
await confirmPush(args, target);
|
|
280292
280869
|
const { ok: resOk, status: status2, body, bodyText } = await pushSchema(schema, args);
|
|
280293
280870
|
if (resOk) {
|
|
280294
280871
|
if (body.unchanged) {
|
|
280295
|
-
console.log(` ${
|
|
280872
|
+
console.log(` ${import_picocolors4.default.dim("\u25CB")} No changes \u2014 schema already active (v${body.version}).`);
|
|
280296
280873
|
} else {
|
|
280297
|
-
console.log(` ${
|
|
280874
|
+
console.log(` ${import_picocolors4.default.green("\u2713")} Activated ${import_picocolors4.default.bold(`v${body.version}`)} ${import_picocolors4.default.dim(`(hash ${body.hash})`)}`);
|
|
280298
280875
|
if (Array.isArray(body.warnings) && body.warnings.length > 0) {
|
|
280299
|
-
console.log(
|
|
280300
|
-
for (const w2 of body.warnings) console.log(
|
|
280876
|
+
console.log(import_picocolors4.default.yellow(` Applied ${body.warnings.length} destructive change(s):`));
|
|
280877
|
+
for (const w2 of body.warnings) console.log(import_picocolors4.default.yellow(fmtSignal(w2)));
|
|
280301
280878
|
}
|
|
280302
280879
|
}
|
|
280303
280880
|
return;
|
|
@@ -280305,75 +280882,75 @@ async function push(argv) {
|
|
|
280305
280882
|
if (status2 === 409) {
|
|
280306
280883
|
const unexecutable = Array.isArray(body.unexecutable) ? body.unexecutable : [];
|
|
280307
280884
|
const warnings = Array.isArray(body.warnings) ? body.warnings : [];
|
|
280308
|
-
console.error(
|
|
280885
|
+
console.error(import_picocolors4.default.red(" Incompatible change \u2014 this push is not safe to apply as-is."));
|
|
280309
280886
|
if (unexecutable.length > 0) {
|
|
280310
|
-
console.error(
|
|
280311
|
-
for (const u2 of unexecutable) console.error(
|
|
280887
|
+
console.error(import_picocolors4.default.red(` Unexecutable (would fail on existing rows):`));
|
|
280888
|
+
for (const u2 of unexecutable) console.error(import_picocolors4.default.red(fmtSignal(u2)));
|
|
280312
280889
|
}
|
|
280313
280890
|
if (warnings.length > 0) {
|
|
280314
|
-
console.error(
|
|
280315
|
-
for (const w2 of warnings) console.error(
|
|
280891
|
+
console.error(import_picocolors4.default.yellow(` Destructive (data loss):`));
|
|
280892
|
+
for (const w2 of warnings) console.error(import_picocolors4.default.yellow(fmtSignal(w2)));
|
|
280316
280893
|
}
|
|
280317
280894
|
const hasShadowed = [...unexecutable, ...warnings].some(
|
|
280318
280895
|
(s) => s.shadowed != null
|
|
280319
280896
|
);
|
|
280320
280897
|
if (hasShadowed) {
|
|
280321
280898
|
console.error(
|
|
280322
|
-
|
|
280899
|
+
import_picocolors4.default.dim(
|
|
280323
280900
|
" These models exist in the baseline above but not in your push. Sandbox readers fall"
|
|
280324
280901
|
)
|
|
280325
280902
|
);
|
|
280326
280903
|
console.error(
|
|
280327
|
-
|
|
280904
|
+
import_picocolors4.default.dim(
|
|
280328
280905
|
" back to the production schema until you push your own, so applying this drops them."
|
|
280329
280906
|
)
|
|
280330
280907
|
);
|
|
280331
280908
|
}
|
|
280332
|
-
console.error(
|
|
280909
|
+
console.error(import_picocolors4.default.dim(` Re-push with ${import_picocolors4.default.bold("--force")} to override, or use ${import_picocolors4.default.bold("--rename old:new")} if you renamed a model.`));
|
|
280333
280910
|
} else if (status2 === 403) {
|
|
280334
280911
|
const code = body.code ?? body.reason;
|
|
280335
280912
|
const serverMsg = body.message ?? body.reason;
|
|
280336
|
-
console.error(
|
|
280337
|
-
console.error(
|
|
280913
|
+
console.error(import_picocolors4.default.red(` Forbidden${code ? ` [${code}]` : ""}: ${serverMsg ?? "permission denied"}`));
|
|
280914
|
+
console.error(import_picocolors4.default.dim(` Push used ${import_picocolors4.default.bold(maskKey(args.apiKey))} from ${describeKeySource(keySource)}.`));
|
|
280338
280915
|
if (code === "database_role_cannot_enforce_rls") {
|
|
280339
280916
|
console.error(
|
|
280340
|
-
|
|
280341
|
-
` Your database role bypasses row-level security. Run ${
|
|
280917
|
+
import_picocolors4.default.dim(
|
|
280918
|
+
` Your database role bypasses row-level security. Run ${import_picocolors4.default.bold("npx ablo migrate")} to create a scoped (NOBYPASSRLS) role and repoint DATABASE_URL, then re-push.`
|
|
280342
280919
|
)
|
|
280343
280920
|
);
|
|
280344
280921
|
} else if (code === "database_tables_unforced_rls") {
|
|
280345
280922
|
console.error(
|
|
280346
|
-
|
|
280347
|
-
` One or more synced tables don't have FORCE ROW LEVEL SECURITY. Run ${
|
|
280923
|
+
import_picocolors4.default.dim(
|
|
280924
|
+
` One or more synced tables don't have FORCE ROW LEVEL SECURITY. Run ${import_picocolors4.default.bold("npx ablo migrate")} to (re)apply the tenant policies, then re-push.`
|
|
280348
280925
|
)
|
|
280349
280926
|
);
|
|
280350
280927
|
} else if (code === "capability_scope_denied" || code === "capability_invalid") {
|
|
280351
280928
|
console.error(
|
|
280352
|
-
|
|
280353
|
-
` This is a ${
|
|
280929
|
+
import_picocolors4.default.dim(
|
|
280930
|
+
` This is a ${import_picocolors4.default.bold("database privilege")} error (Postgres 42501 / row-level security), not a key scope \u2014 a different API key won't help. The role behind this org's database can't write the target. Provision a writable, RLS-scoped role with ${import_picocolors4.default.bold("npx ablo migrate")}, or check the org's database registration. See docs/plans/read-path-logical-replication-vs-hosting.md.`
|
|
280354
280931
|
)
|
|
280355
280932
|
);
|
|
280356
280933
|
} else if (code === "schema_provisioning_forbidden") {
|
|
280357
280934
|
console.error(
|
|
280358
|
-
|
|
280359
|
-
` This is not a key problem \u2014 the push was authorized, but the target database refused to let the engine create tables (Postgres 42501). On the replication read path Ablo never runs DDL: register your database as a data source with ${
|
|
280935
|
+
import_picocolors4.default.dim(
|
|
280936
|
+
` This is not a key problem \u2014 the push was authorized, but the target database refused to let the engine create tables (Postgres 42501). On the replication read path Ablo never runs DDL: register your database as a data source with ${import_picocolors4.default.bold("npx ablo connect --register")}, and pushes to that environment record the schema as metadata only \u2014 no tables are created anywhere.`
|
|
280360
280937
|
)
|
|
280361
280938
|
);
|
|
280362
280939
|
} else if (args.apiKey != null && classifyCredentialKind(args.apiKey) === "restricted") {
|
|
280363
280940
|
console.error(
|
|
280364
|
-
|
|
280365
|
-
` Schema pushes need a SECRET key: ${
|
|
280941
|
+
import_picocolors4.default.dim(
|
|
280942
|
+
` Schema pushes need a SECRET key: ${import_picocolors4.default.bold("sk_test_")} (sandbox dev loop) or a dashboard ${import_picocolors4.default.bold("sk_live_")} (production deploy: ${import_picocolors4.default.bold("ABLO_API_KEY=sk_live_\u2026 npx ablo push")}).`
|
|
280366
280943
|
)
|
|
280367
280944
|
);
|
|
280368
280945
|
} else {
|
|
280369
280946
|
console.error(
|
|
280370
|
-
|
|
280371
|
-
` This key isn't authorized to push schema (needs ${
|
|
280947
|
+
import_picocolors4.default.dim(
|
|
280948
|
+
` This key isn't authorized to push schema (needs ${import_picocolors4.default.bold("schema:push")}). ` + (keySource === "stored" ? `It's your stored ${import_picocolors4.default.bold("ablo login")} sandbox key \u2014 a key in ${import_picocolors4.default.bold(".env.local")} or ${import_picocolors4.default.bold("ABLO_API_KEY")} takes precedence, so put a schema:push key there (sandbox ${import_picocolors4.default.bold("sk_test_")} or production ${import_picocolors4.default.bold("sk_live_")}) and re-push. ` : `Use a schema:push key \u2014 a sandbox ${import_picocolors4.default.bold("sk_test_")} or production ${import_picocolors4.default.bold("sk_live_")}. `) + `Manage keys at https://abloatai.com`
|
|
280372
280949
|
)
|
|
280373
280950
|
);
|
|
280374
280951
|
}
|
|
280375
280952
|
} else {
|
|
280376
|
-
console.error(
|
|
280953
|
+
console.error(import_picocolors4.default.red(` Push failed (${status2}): ${body.message ?? body.reason ?? bodyText}`));
|
|
280377
280954
|
}
|
|
280378
280955
|
process.exit(1);
|
|
280379
280956
|
}
|
|
@@ -280436,10 +281013,10 @@ var log = {
|
|
|
280436
281013
|
console.log(`[migrate] ${msg}`, fields);
|
|
280437
281014
|
},
|
|
280438
281015
|
warn: (msg, fields) => {
|
|
280439
|
-
console.warn(
|
|
281016
|
+
console.warn(import_picocolors5.default.yellow(`[migrate] ${msg}`), fields);
|
|
280440
281017
|
},
|
|
280441
281018
|
error: (msg, fields) => {
|
|
280442
|
-
console.error(
|
|
281019
|
+
console.error(import_picocolors5.default.red(`[migrate] ${msg}`), fields);
|
|
280443
281020
|
}
|
|
280444
281021
|
};
|
|
280445
281022
|
async function applyStatements(dbUrl, targetSchema, statements, concurrent = []) {
|
|
@@ -280513,7 +281090,7 @@ async function migrate(argv) {
|
|
|
280513
281090
|
try {
|
|
280514
281091
|
args = parseMigrateArgs(argv);
|
|
280515
281092
|
} catch (err) {
|
|
280516
|
-
console.error(
|
|
281093
|
+
console.error(import_picocolors5.default.red(` ${err instanceof Error ? err.message : String(err)}`));
|
|
280517
281094
|
process.exit(1);
|
|
280518
281095
|
}
|
|
280519
281096
|
const schema = await loadSchema(args.schemaPath, args.exportName);
|
|
@@ -280524,11 +281101,11 @@ async function migrate(argv) {
|
|
|
280524
281101
|
].join("\n");
|
|
280525
281102
|
const totalStatements = plan.statements.length + plan.concurrent.length;
|
|
280526
281103
|
console.log(
|
|
280527
|
-
` ${
|
|
281104
|
+
` ${import_picocolors5.default.dim("Schema")} ${import_picocolors5.default.bold(args.schemaPath)} \u2192 ${import_picocolors5.default.dim(`${Object.keys(schema.models).length} models, ${totalStatements} statements`)}`
|
|
280528
281105
|
);
|
|
280529
281106
|
if (args.outputFile) {
|
|
280530
281107
|
(0, import_fs5.writeFileSync)(args.outputFile, sql + "\n");
|
|
280531
|
-
console.log(` ${
|
|
281108
|
+
console.log(` ${import_picocolors5.default.green("\u2713")} SQL written to ${import_picocolors5.default.bold(args.outputFile)}`);
|
|
280532
281109
|
return;
|
|
280533
281110
|
}
|
|
280534
281111
|
if (args.dryRun) {
|
|
@@ -280538,7 +281115,7 @@ async function migrate(argv) {
|
|
|
280538
281115
|
const dbUrl = readProjectDatabaseUrl();
|
|
280539
281116
|
if (!dbUrl) {
|
|
280540
281117
|
console.error(
|
|
280541
|
-
|
|
281118
|
+
import_picocolors5.default.red(
|
|
280542
281119
|
" No DATABASE_URL found (checked process env, .env.local, .env). Set it to apply, or use --dry-run to preview."
|
|
280543
281120
|
)
|
|
280544
281121
|
);
|
|
@@ -280546,7 +281123,7 @@ async function migrate(argv) {
|
|
|
280546
281123
|
}
|
|
280547
281124
|
try {
|
|
280548
281125
|
await applyStatements(dbUrl, args.targetSchema, plan.statements, plan.concurrent);
|
|
280549
|
-
console.log(` ${
|
|
281126
|
+
console.log(` ${import_picocolors5.default.green("\u2713")} Migration complete`);
|
|
280550
281127
|
} catch {
|
|
280551
281128
|
process.exit(1);
|
|
280552
281129
|
}
|
|
@@ -280554,7 +281131,7 @@ async function migrate(argv) {
|
|
|
280554
281131
|
|
|
280555
281132
|
// src/cli/connect.ts
|
|
280556
281133
|
init_cjs_shims();
|
|
280557
|
-
var
|
|
281134
|
+
var import_picocolors6 = __toESM(require_picocolors(), 1);
|
|
280558
281135
|
var ABLO_PUBLICATION = "ablo_publication";
|
|
280559
281136
|
var ABLO_REPLICATION_ROLE = "ablo_replicator";
|
|
280560
281137
|
function parseConnectArgs(argv) {
|
|
@@ -280611,50 +281188,50 @@ function connectSetupSql(input) {
|
|
|
280611
281188
|
function printConnectRecipe(args) {
|
|
280612
281189
|
const sql = connectSetupSql({ tables: args.tables, role: args.role });
|
|
280613
281190
|
console.log(`
|
|
280614
|
-
${brand("ablo")} ${
|
|
281191
|
+
${brand("ablo")} ${import_picocolors6.default.dim("connect")} ${import_picocolors6.default.dim("logical replication \u2014 the read path (your writes stay on your own backend)")}
|
|
280615
281192
|
`);
|
|
280616
281193
|
console.log(
|
|
280617
|
-
` Ablo consumes your write-ahead log (WAL) via logical replication and ${
|
|
281194
|
+
` Ablo consumes your write-ahead log (WAL) via logical replication and ${import_picocolors6.default.bold("never")} runs DDL,
|
|
280618
281195
|
owns, hosts, or migrates your schema. Your app continues to own the write path \u2014 Ablo tails
|
|
280619
|
-
the changes and serves them as live shapes. Run this once against your Postgres ${
|
|
281196
|
+
the changes and serves them as live shapes. Run this once against your Postgres ${import_picocolors6.default.dim("(as a superuser / DB owner)")}:
|
|
280620
281197
|
`
|
|
280621
281198
|
);
|
|
280622
|
-
console.log(` ${
|
|
280623
|
-
console.log(` ${
|
|
281199
|
+
console.log(` ${import_picocolors6.default.bold("1.")} Enable logical decoding ${import_picocolors6.default.dim("(then RESTART Postgres \u2014 wal_level is not reloadable)")}`);
|
|
281200
|
+
console.log(` ${import_picocolors6.default.cyan(sql[0])}`);
|
|
280624
281201
|
console.log(
|
|
280625
|
-
|
|
280626
|
-
` On Amazon RDS / Aurora you can't ALTER SYSTEM: set ${
|
|
281202
|
+
import_picocolors6.default.dim(
|
|
281203
|
+
` On Amazon RDS / Aurora you can't ALTER SYSTEM: set ${import_picocolors6.default.bold("rds.logical_replication = 1")} in the
|
|
280627
281204
|
instance's parameter group instead, then reboot.`
|
|
280628
281205
|
)
|
|
280629
281206
|
);
|
|
280630
281207
|
console.log(`
|
|
280631
|
-
${
|
|
280632
|
-
console.log(` ${
|
|
281208
|
+
${import_picocolors6.default.bold("2.")} Publish the tables Ablo should read`);
|
|
281209
|
+
console.log(` ${import_picocolors6.default.cyan(sql[1])}`);
|
|
280633
281210
|
if (args.tables.length === 0) {
|
|
280634
|
-
console.log(
|
|
281211
|
+
console.log(import_picocolors6.default.dim(` (Scope it with ${import_picocolors6.default.bold("ablo connect --tables a,b,c")} to publish a subset.)`));
|
|
280635
281212
|
}
|
|
280636
281213
|
console.log(`
|
|
280637
|
-
${
|
|
280638
|
-
console.log(` ${
|
|
280639
|
-
console.log(` ${
|
|
280640
|
-
console.log(` ${
|
|
281214
|
+
${import_picocolors6.default.bold("3.")} Create a least-privilege replication role ${import_picocolors6.default.dim("(pick your own password)")}`);
|
|
281215
|
+
console.log(` ${import_picocolors6.default.cyan(sql[2])}`);
|
|
281216
|
+
console.log(` ${import_picocolors6.default.cyan(sql[3])}`);
|
|
281217
|
+
console.log(` ${import_picocolors6.default.cyan(sql[4])}`);
|
|
280641
281218
|
console.log(
|
|
280642
|
-
|
|
281219
|
+
import_picocolors6.default.dim(
|
|
280643
281220
|
` On Amazon RDS, the REPLICATION attribute is granted, not set directly:
|
|
280644
|
-
${
|
|
281221
|
+
${import_picocolors6.default.bold(`GRANT rds_replication TO ${quoteIdent(args.role)};`)}`
|
|
280645
281222
|
)
|
|
280646
281223
|
);
|
|
280647
281224
|
console.log(
|
|
280648
281225
|
`
|
|
280649
|
-
${
|
|
280650
|
-
${
|
|
281226
|
+
${import_picocolors6.default.bold("4.")} Put the role's connection string in ${import_picocolors6.default.bold("DATABASE_URL")}, then verify:
|
|
281227
|
+
${import_picocolors6.default.cyan("npx ablo connect --check")}
|
|
280651
281228
|
`
|
|
280652
281229
|
);
|
|
280653
281230
|
console.log(
|
|
280654
|
-
|
|
281231
|
+
import_picocolors6.default.dim(
|
|
280655
281232
|
` Reminder: this is the READ path \u2014 Ablo tails your WAL and never writes to your database.
|
|
280656
281233
|
Your writes keep going through your own backend. (Ablo HOSTING your rows, or dialing in via
|
|
280657
|
-
${
|
|
281234
|
+
${import_picocolors6.default.bold("databaseUrl")}, is the deprecated posture \u2014 see the read-path decision doc.)`
|
|
280658
281235
|
)
|
|
280659
281236
|
);
|
|
280660
281237
|
console.log();
|
|
@@ -280672,11 +281249,11 @@ var SYNC_INFRA_TYPES = [
|
|
|
280672
281249
|
];
|
|
280673
281250
|
function printCheckItem(item) {
|
|
280674
281251
|
if (item.ok) {
|
|
280675
|
-
console.log(` ${
|
|
281252
|
+
console.log(` ${import_picocolors6.default.green("\u2713")} ${item.label}`);
|
|
280676
281253
|
} else {
|
|
280677
|
-
console.log(` ${
|
|
281254
|
+
console.log(` ${import_picocolors6.default.red("\u2717")} ${item.label}`);
|
|
280678
281255
|
if (item.fix) {
|
|
280679
|
-
for (const line of item.fix.split("\n")) console.log(` ${
|
|
281256
|
+
for (const line of item.fix.split("\n")) console.log(` ${import_picocolors6.default.red("\u2022")} ${line}`);
|
|
280680
281257
|
}
|
|
280681
281258
|
}
|
|
280682
281259
|
}
|
|
@@ -280688,9 +281265,9 @@ async function probeReadiness(sql, opts = {}) {
|
|
|
280688
281265
|
);
|
|
280689
281266
|
const walLevel = walRows[0]?.setting ?? "unknown";
|
|
280690
281267
|
items.push(
|
|
280691
|
-
walLevel === "logical" ? { ok: true, label: `wal_level is ${
|
|
281268
|
+
walLevel === "logical" ? { ok: true, label: `wal_level is ${import_picocolors6.default.bold("logical")}` } : {
|
|
280692
281269
|
ok: false,
|
|
280693
|
-
label: `wal_level is ${
|
|
281270
|
+
label: `wal_level is ${import_picocolors6.default.bold(walLevel)} (need ${import_picocolors6.default.bold("logical")})`,
|
|
280694
281271
|
fix: `ALTER SYSTEM SET wal_level = 'logical'; then RESTART Postgres.
|
|
280695
281272
|
On RDS/Aurora set rds.logical_replication = 1 in the parameter group, then reboot.
|
|
280696
281273
|
On Neon enable Logical Replication in the project (Console \u2192 Settings \u2192 Logical Replication, or the API) \u2014 Neon forbids ALTER SYSTEM; the toggle sets wal_level=logical.`
|
|
@@ -280704,10 +281281,10 @@ On Neon enable Logical Replication in the project (Console \u2192 Settings \u219
|
|
|
280704
281281
|
items.push(
|
|
280705
281282
|
pubRow ? {
|
|
280706
281283
|
ok: true,
|
|
280707
|
-
label: `publication ${
|
|
281284
|
+
label: `publication ${import_picocolors6.default.bold(publication)} exists ${import_picocolors6.default.dim(pubRow.puballtables ? "(all tables)" : "(table subset)")}`
|
|
280708
281285
|
} : {
|
|
280709
281286
|
ok: false,
|
|
280710
|
-
label: `publication ${
|
|
281287
|
+
label: `publication ${import_picocolors6.default.bold(publication)} not found`,
|
|
280711
281288
|
fix: `CREATE PUBLICATION ${quoteIdent(publication)} FOR ALL TABLES;`
|
|
280712
281289
|
}
|
|
280713
281290
|
);
|
|
@@ -280717,9 +281294,9 @@ On Neon enable Logical Replication in the project (Console \u2192 Settings \u219
|
|
|
280717
281294
|
const role = roleRows[0];
|
|
280718
281295
|
const hasReplication = Boolean(role && (role.rolreplication || role.rolsuper));
|
|
280719
281296
|
items.push(
|
|
280720
|
-
hasReplication ? { ok: true, label: `DATABASE_URL role can stream replication ${
|
|
281297
|
+
hasReplication ? { ok: true, label: `DATABASE_URL role can stream replication ${import_picocolors6.default.dim("(REPLICATION)")}` } : {
|
|
280721
281298
|
ok: false,
|
|
280722
|
-
label: `DATABASE_URL role lacks the ${
|
|
281299
|
+
label: `DATABASE_URL role lacks the ${import_picocolors6.default.bold("REPLICATION")} attribute`,
|
|
280723
281300
|
fix: `ALTER ROLE current_user WITH REPLICATION;
|
|
280724
281301
|
On RDS: GRANT rds_replication TO <your_role>;`
|
|
280725
281302
|
}
|
|
@@ -280777,7 +281354,7 @@ function requireDatabaseUrl(verb) {
|
|
|
280777
281354
|
const dbUrl = readProjectDatabaseUrl();
|
|
280778
281355
|
if (!dbUrl) {
|
|
280779
281356
|
console.error(
|
|
280780
|
-
|
|
281357
|
+
import_picocolors6.default.red(" No DATABASE_URL found (checked process env, .env.local, .env).") + import_picocolors6.default.dim(` Set it to the Postgres you want Ablo to read, then re-run ${import_picocolors6.default.bold(`ablo connect ${verb}`)}.`)
|
|
280781
281358
|
);
|
|
280782
281359
|
process.exit(1);
|
|
280783
281360
|
}
|
|
@@ -280791,7 +281368,7 @@ async function probeAndReport(dbUrl) {
|
|
|
280791
281368
|
items = await probeReadiness(sql);
|
|
280792
281369
|
} catch (err) {
|
|
280793
281370
|
const pg = err ?? {};
|
|
280794
|
-
console.error(
|
|
281371
|
+
console.error(import_picocolors6.default.red(` Couldn't read the database: ${pg.message ?? String(err)}`));
|
|
280795
281372
|
await sql.end({ timeout: 2 });
|
|
280796
281373
|
process.exit(1);
|
|
280797
281374
|
}
|
|
@@ -280802,17 +281379,17 @@ async function probeAndReport(dbUrl) {
|
|
|
280802
281379
|
async function runCheck() {
|
|
280803
281380
|
const dbUrl = requireDatabaseUrl("--check");
|
|
280804
281381
|
console.log(`
|
|
280805
|
-
${brand("ablo")} ${
|
|
281382
|
+
${brand("ablo")} ${import_picocolors6.default.dim("connect --check")} ${import_picocolors6.default.dim("logical-replication readiness")}
|
|
280806
281383
|
`);
|
|
280807
281384
|
const failures = await probeAndReport(dbUrl);
|
|
280808
281385
|
console.log();
|
|
280809
281386
|
if (failures === 0) {
|
|
280810
|
-
console.log(` ${
|
|
281387
|
+
console.log(` ${import_picocolors6.default.green("\u2713")} Ready \u2014 Ablo can connect and tail this database's WAL.
|
|
280811
281388
|
`);
|
|
280812
281389
|
process.exit(0);
|
|
280813
281390
|
}
|
|
280814
281391
|
console.log(
|
|
280815
|
-
` ${
|
|
281392
|
+
` ${import_picocolors6.default.red(`${failures} item${failures === 1 ? "" : "s"} to fix`)} ${import_picocolors6.default.dim(`\u2014 apply the fixes above, then re-run ${import_picocolors6.default.bold("ablo connect --check")}.`)}
|
|
280816
281393
|
`
|
|
280817
281394
|
);
|
|
280818
281395
|
process.exit(1);
|
|
@@ -280825,18 +281402,18 @@ async function runRegister() {
|
|
|
280825
281402
|
const apiKey = resolveApiKey();
|
|
280826
281403
|
if (!apiKey) {
|
|
280827
281404
|
console.error(
|
|
280828
|
-
|
|
281405
|
+
import_picocolors6.default.red(" Not logged in.") + import_picocolors6.default.dim(` Run ${import_picocolors6.default.bold("ablo login")} (or set ${import_picocolors6.default.bold("ABLO_API_KEY")}) so Ablo knows which project to register this database for.`)
|
|
280829
281406
|
);
|
|
280830
281407
|
process.exit(1);
|
|
280831
281408
|
}
|
|
280832
281409
|
console.log(`
|
|
280833
|
-
${brand("ablo")} ${
|
|
281410
|
+
${brand("ablo")} ${import_picocolors6.default.dim("connect --register")} ${import_picocolors6.default.dim("register this database for replication")}
|
|
280834
281411
|
`);
|
|
280835
281412
|
const failures = await probeAndReport(dbUrl);
|
|
280836
281413
|
if (failures > 0) {
|
|
280837
281414
|
console.log(
|
|
280838
281415
|
`
|
|
280839
|
-
${
|
|
281416
|
+
${import_picocolors6.default.red(`${failures} item${failures === 1 ? "" : "s"} to fix`)} ${import_picocolors6.default.dim("\u2014 a database that isn\u2019t replication-ready can\u2019t stream. Fix the above, then re-run.")}
|
|
280840
281417
|
`
|
|
280841
281418
|
);
|
|
280842
281419
|
process.exit(1);
|
|
@@ -280850,7 +281427,7 @@ async function runRegister() {
|
|
|
280850
281427
|
body: JSON.stringify({ connectionString: dbUrl })
|
|
280851
281428
|
});
|
|
280852
281429
|
} catch (err) {
|
|
280853
|
-
console.error(
|
|
281430
|
+
console.error(import_picocolors6.default.red(`
|
|
280854
281431
|
Couldn't reach ${apiUrl2}: ${err instanceof Error ? err.message : String(err)}
|
|
280855
281432
|
`));
|
|
280856
281433
|
process.exit(1);
|
|
@@ -280859,7 +281436,7 @@ async function runRegister() {
|
|
|
280859
281436
|
const body2 = await res.json().catch(() => ({}));
|
|
280860
281437
|
console.log(
|
|
280861
281438
|
`
|
|
280862
|
-
${
|
|
281439
|
+
${import_picocolors6.default.green("\u2713")} Registered${body2.host ? ` ${import_picocolors6.default.dim(body2.host)}` : ""}${body2.id ? ` ${import_picocolors6.default.dim(`(${body2.id})`)}` : ""} \u2014 Ablo will replicate this database on the next sync.
|
|
280863
281440
|
`
|
|
280864
281441
|
);
|
|
280865
281442
|
process.exit(0);
|
|
@@ -280867,28 +281444,28 @@ async function runRegister() {
|
|
|
280867
281444
|
const body = await res.json().catch(() => ({}));
|
|
280868
281445
|
const code = body.code ?? body.error?.code;
|
|
280869
281446
|
const message = body.message ?? body.error?.message ?? `HTTP ${res.status}`;
|
|
280870
|
-
console.error(
|
|
281447
|
+
console.error(import_picocolors6.default.red(`
|
|
280871
281448
|
Registration failed: ${message}`));
|
|
280872
281449
|
if (code === "forbidden") {
|
|
280873
|
-
console.error(
|
|
281450
|
+
console.error(import_picocolors6.default.dim(` Registering a database needs a ${import_picocolors6.default.bold("secret")} key (sk_\u2026). Run ${import_picocolors6.default.bold("ablo login")} for one.`));
|
|
280874
281451
|
} else if (code === "datasource_connection_unsupported") {
|
|
280875
281452
|
console.error(
|
|
280876
|
-
|
|
281453
|
+
import_picocolors6.default.dim(` This deployment can\u2019t accept connection strings \u2014 use a self-hosted/hosted engine, or the signed endpoint fallback.`)
|
|
280877
281454
|
);
|
|
280878
281455
|
} else if (code === "database_not_replication_ready") {
|
|
280879
281456
|
for (const f of body.details?.failures ?? []) {
|
|
280880
|
-
console.error(` ${
|
|
280881
|
-
if (f.fix) for (const line of f.fix.split("\n")) console.error(` ${
|
|
281457
|
+
console.error(` ${import_picocolors6.default.red("\u2717")} ${import_picocolors6.default.bold(f.item ?? "item")}${f.actual ? import_picocolors6.default.dim(` (${f.actual})`) : ""}`);
|
|
281458
|
+
if (f.fix) for (const line of f.fix.split("\n")) console.error(` ${import_picocolors6.default.red("\u2022")} ${line}`);
|
|
280882
281459
|
}
|
|
280883
|
-
console.error(
|
|
280884
|
-
Apply the fixes, verify with ${
|
|
281460
|
+
console.error(import_picocolors6.default.dim(`
|
|
281461
|
+
Apply the fixes, verify with ${import_picocolors6.default.bold("ablo connect --check")}, then re-run.`));
|
|
280885
281462
|
} else if (code === "database_unreachable") {
|
|
280886
|
-
if (body.details?.reason) console.error(
|
|
281463
|
+
if (body.details?.reason) console.error(import_picocolors6.default.dim(` ${body.details.reason}`));
|
|
280887
281464
|
console.error(
|
|
280888
|
-
|
|
281465
|
+
import_picocolors6.default.dim(
|
|
280889
281466
|
` Ablo's servers must be able to reach this database \u2014 a localhost or private-network
|
|
280890
281467
|
Postgres can't be replicated by the hosted service. Use a reachable host (or a tunnel),
|
|
280891
|
-
or the signed ${
|
|
281468
|
+
or the signed ${import_picocolors6.default.bold("dataSource()")} endpoint fallback.`
|
|
280892
281469
|
)
|
|
280893
281470
|
);
|
|
280894
281471
|
}
|
|
@@ -280904,27 +281481,27 @@ async function runAuditInfra() {
|
|
|
280904
281481
|
artifacts = await auditTenantSyncInfra(sql);
|
|
280905
281482
|
} catch (err) {
|
|
280906
281483
|
const pg = err ?? {};
|
|
280907
|
-
console.error(
|
|
281484
|
+
console.error(import_picocolors6.default.red(` Couldn't audit the database: ${pg.message ?? String(err)}`));
|
|
280908
281485
|
await sql.end({ timeout: 2 });
|
|
280909
281486
|
process.exit(1);
|
|
280910
281487
|
}
|
|
280911
281488
|
await sql.end({ timeout: 2 });
|
|
280912
281489
|
console.log(`
|
|
280913
|
-
${brand("ablo")} ${
|
|
281490
|
+
${brand("ablo")} ${import_picocolors6.default.dim("connect --audit-infra")} ${import_picocolors6.default.dim("Stage 5 tenant DB sync-infra audit")}
|
|
280914
281491
|
`);
|
|
280915
281492
|
const present = artifacts.filter((a) => a.present);
|
|
280916
281493
|
if (present.length === 0) {
|
|
280917
|
-
console.log(` ${
|
|
281494
|
+
console.log(` ${import_picocolors6.default.green("\u2713")} No deprecated Ablo sync infrastructure found in public.
|
|
280918
281495
|
`);
|
|
280919
281496
|
process.exit(0);
|
|
280920
281497
|
}
|
|
280921
281498
|
for (const artifact of present) {
|
|
280922
281499
|
const label = artifact.kind === "type" ? "type" : "relation";
|
|
280923
|
-
console.log(` ${
|
|
281500
|
+
console.log(` ${import_picocolors6.default.yellow("!")} ${label} ${import_picocolors6.default.bold(`public.${artifact.name}`)} exists`);
|
|
280924
281501
|
}
|
|
280925
281502
|
console.log(
|
|
280926
281503
|
`
|
|
280927
|
-
${
|
|
281504
|
+
${import_picocolors6.default.yellow(`${present.length} artifact${present.length === 1 ? "" : "s"} found`)} ` + import_picocolors6.default.dim("\u2014 do not drop automatically. Confirm the org/environment is log-authoritative, then follow ") + import_picocolors6.default.bold("docs/runbooks/wal-stage5-customer-db-infra-cleanup.md") + import_picocolors6.default.dim(".\n")
|
|
280928
281505
|
);
|
|
280929
281506
|
process.exit(1);
|
|
280930
281507
|
}
|
|
@@ -280933,7 +281510,7 @@ async function connect(argv) {
|
|
|
280933
281510
|
try {
|
|
280934
281511
|
args = parseConnectArgs(argv);
|
|
280935
281512
|
} catch (err) {
|
|
280936
|
-
console.error(
|
|
281513
|
+
console.error(import_picocolors6.default.red(` ${err instanceof Error ? err.message : String(err)}`));
|
|
280937
281514
|
process.exit(1);
|
|
280938
281515
|
}
|
|
280939
281516
|
if (args.check) {
|
|
@@ -280967,7 +281544,7 @@ var CONNECT_USAGE = ` ablo connect \u2014 connect your database for the read pa
|
|
|
280967
281544
|
init_cjs_shims();
|
|
280968
281545
|
var import_fs6 = require("fs");
|
|
280969
281546
|
var import_path4 = require("path");
|
|
280970
|
-
var
|
|
281547
|
+
var import_picocolors7 = __toESM(require_picocolors(), 1);
|
|
280971
281548
|
var import_schema4 = require("@abloatai/ablo/schema");
|
|
280972
281549
|
var DEFAULT_SCHEMA_PATH3 = "ablo/schema.ts";
|
|
280973
281550
|
var DEFAULT_EXPORT3 = "schema";
|
|
@@ -280999,7 +281576,7 @@ async function generate(argv) {
|
|
|
280999
281576
|
try {
|
|
281000
281577
|
args = parseGenerateArgs(argv);
|
|
281001
281578
|
} catch (err) {
|
|
281002
|
-
console.error(
|
|
281579
|
+
console.error(import_picocolors7.default.red(` ${err instanceof Error ? err.message : String(err)}`));
|
|
281003
281580
|
process.exit(1);
|
|
281004
281581
|
}
|
|
281005
281582
|
let source;
|
|
@@ -281008,18 +281585,18 @@ async function generate(argv) {
|
|
|
281008
281585
|
const schemaJson = JSON.parse((0, import_schema4.serializeSchema)(schema));
|
|
281009
281586
|
source = (0, import_schema4.generateTypes)(schemaJson);
|
|
281010
281587
|
} catch (err) {
|
|
281011
|
-
console.error(
|
|
281588
|
+
console.error(import_picocolors7.default.red(` ${err instanceof Error ? err.message : String(err)}`));
|
|
281012
281589
|
process.exit(1);
|
|
281013
281590
|
}
|
|
281014
281591
|
const abs = (0, import_path4.resolve)(process.cwd(), args.out);
|
|
281015
281592
|
(0, import_fs6.mkdirSync)((0, import_path4.dirname)(abs), { recursive: true });
|
|
281016
281593
|
(0, import_fs6.writeFileSync)(abs, source);
|
|
281017
|
-
console.log(` ${
|
|
281594
|
+
console.log(` ${import_picocolors7.default.green("\u2713")} Generated types \u2192 ${import_picocolors7.default.bold(args.out)}`);
|
|
281018
281595
|
}
|
|
281019
281596
|
|
|
281020
281597
|
// src/cli/dev.ts
|
|
281021
281598
|
init_cjs_shims();
|
|
281022
|
-
var
|
|
281599
|
+
var import_picocolors8 = __toESM(require_picocolors(), 1);
|
|
281023
281600
|
var import_fs7 = require("fs");
|
|
281024
281601
|
var import_path5 = require("path");
|
|
281025
281602
|
var import_schema5 = require("@abloatai/ablo/schema");
|
|
@@ -281057,25 +281634,25 @@ function classifyKey(apiKey, activeMode) {
|
|
|
281057
281634
|
if (!apiKey) {
|
|
281058
281635
|
return {
|
|
281059
281636
|
ok: false,
|
|
281060
|
-
reason: `No API key. Run ${
|
|
281637
|
+
reason: `No API key. Run ${import_picocolors8.default.bold("npx ablo login")} for the sandbox dev loop \u2014 or set ${import_picocolors8.default.bold("ABLO_API_KEY")} (${import_picocolors8.default.bold("sk_test_")} = sandbox; ${import_picocolors8.default.bold("sk_live_")} = deliberate production deploy). ` + import_picocolors8.default.dim(`Mode is currently '${activeMode}'.`)
|
|
281061
281638
|
};
|
|
281062
281639
|
}
|
|
281063
281640
|
if (apiKey.startsWith("sk_test_")) return { ok: true };
|
|
281064
281641
|
if (apiKey.startsWith("sk_live_")) {
|
|
281065
281642
|
return {
|
|
281066
281643
|
ok: false,
|
|
281067
|
-
reason: `Production schema deploys run one-shot: ${
|
|
281644
|
+
reason: `Production schema deploys run one-shot: ${import_picocolors8.default.bold("ABLO_API_KEY=sk_live_\u2026 npx ablo push")} (or ${import_picocolors8.default.bold("ablo mode production")}). ${import_picocolors8.default.bold("--watch")} is sandbox-only.`
|
|
281068
281645
|
};
|
|
281069
281646
|
}
|
|
281070
281647
|
if (classifyCredentialKind(apiKey) === "restricted") {
|
|
281071
281648
|
return {
|
|
281072
281649
|
ok: false,
|
|
281073
|
-
reason: `Restricted (${
|
|
281650
|
+
reason: `Restricted (${import_picocolors8.default.bold("rk_")}) keys can't push schema. Use a secret key: ${import_picocolors8.default.bold("sk_test_")} for the sandbox dev loop, or ${import_picocolors8.default.bold("sk_live_")} with ${import_picocolors8.default.bold("npx ablo push")} for a production deploy.`
|
|
281074
281651
|
};
|
|
281075
281652
|
}
|
|
281076
281653
|
return {
|
|
281077
281654
|
ok: false,
|
|
281078
|
-
reason: `${
|
|
281655
|
+
reason: `${import_picocolors8.default.bold("ABLO_API_KEY")} is not an Ablo key \u2014 expected ${import_picocolors8.default.bold("sk_test_\u2026")} (sandbox) or ${import_picocolors8.default.bold("sk_live_\u2026")} (production deploy via ${import_picocolors8.default.bold("npx ablo push")}).`
|
|
281079
281656
|
};
|
|
281080
281657
|
}
|
|
281081
281658
|
function wireEnvLocal(apiKey, cwd = process.cwd()) {
|
|
@@ -281085,7 +281662,7 @@ function wireEnvLocal(apiKey, cwd = process.cwd()) {
|
|
|
281085
281662
|
if (!(0, import_fs7.existsSync)(envPath)) {
|
|
281086
281663
|
(0, import_fs7.writeFileSync)(envPath, `${line}
|
|
281087
281664
|
`, { mode: 384 });
|
|
281088
|
-
action = `Created ${
|
|
281665
|
+
action = `Created ${import_picocolors8.default.bold(".env.local")} with ${import_picocolors8.default.bold("ABLO_API_KEY")}`;
|
|
281089
281666
|
} else {
|
|
281090
281667
|
const content = (0, import_fs7.readFileSync)(envPath, "utf8");
|
|
281091
281668
|
const match = /^ABLO_API_KEY=(.*)$/m.exec(content);
|
|
@@ -281093,12 +281670,12 @@ function wireEnvLocal(apiKey, cwd = process.cwd()) {
|
|
|
281093
281670
|
if (!match) {
|
|
281094
281671
|
(0, import_fs7.appendFileSync)(envPath, `${content.endsWith("\n") || content.length === 0 ? "" : "\n"}${line}
|
|
281095
281672
|
`);
|
|
281096
|
-
action = `Added ${
|
|
281673
|
+
action = `Added ${import_picocolors8.default.bold("ABLO_API_KEY")} to ${import_picocolors8.default.bold(".env.local")}`;
|
|
281097
281674
|
} else if (existing === apiKey) {
|
|
281098
|
-
action = `${
|
|
281675
|
+
action = `${import_picocolors8.default.bold(".env.local")} already has this key`;
|
|
281099
281676
|
} else {
|
|
281100
281677
|
(0, import_fs7.writeFileSync)(envPath, content.replace(/^ABLO_API_KEY=.*$/m, line));
|
|
281101
|
-
action = `Updated ${
|
|
281678
|
+
action = `Updated ${import_picocolors8.default.bold("ABLO_API_KEY")} in ${import_picocolors8.default.bold(".env.local")} ${import_picocolors8.default.dim(`(was ${existing.slice(0, 12)}\u2026)`)}`;
|
|
281102
281679
|
}
|
|
281103
281680
|
}
|
|
281104
281681
|
const gitignorePath = (0, import_path5.resolve)(cwd, ".gitignore");
|
|
@@ -281112,7 +281689,7 @@ function wireEnvLocal(apiKey, cwd = process.cwd()) {
|
|
|
281112
281689
|
`}.env.local
|
|
281113
281690
|
`
|
|
281114
281691
|
);
|
|
281115
|
-
gitignoreNote = ` Added ${
|
|
281692
|
+
gitignoreNote = ` Added ${import_picocolors8.default.bold(".env.local")} to ${import_picocolors8.default.bold(".gitignore")} so the key can't be committed.`;
|
|
281116
281693
|
}
|
|
281117
281694
|
return `${action}.${gitignoreNote}`;
|
|
281118
281695
|
}
|
|
@@ -281127,7 +281704,7 @@ async function runPush(schema, args) {
|
|
|
281127
281704
|
if (ok) {
|
|
281128
281705
|
return {
|
|
281129
281706
|
ok: true,
|
|
281130
|
-
message: body.unchanged ? `schema unchanged ${
|
|
281707
|
+
message: body.unchanged ? `schema unchanged ${import_picocolors8.default.dim(`(v${body.version})`)}` : `schema pushed (sandbox) ${import_picocolors8.default.dim(`(v${body.version}, hash ${body.hash})`)}`
|
|
281131
281708
|
};
|
|
281132
281709
|
}
|
|
281133
281710
|
if (status2 === 409) {
|
|
@@ -281137,33 +281714,33 @@ async function runPush(schema, args) {
|
|
|
281137
281714
|
(s) => s.shadowed != null
|
|
281138
281715
|
);
|
|
281139
281716
|
const lines = [
|
|
281140
|
-
|
|
281717
|
+
import_picocolors8.default.bold("Incompatible schema change \u2014 not safe to apply as-is."),
|
|
281141
281718
|
"",
|
|
281142
|
-
...unexecutable.map((u2) =>
|
|
281143
|
-
...warnings.map((w2) =>
|
|
281719
|
+
...unexecutable.map((u2) => import_picocolors8.default.red(fmtSignal(u2))),
|
|
281720
|
+
...warnings.map((w2) => import_picocolors8.default.yellow(fmtSignal(w2))),
|
|
281144
281721
|
"",
|
|
281145
281722
|
...hasShadowed ? [
|
|
281146
|
-
|
|
281723
|
+
import_picocolors8.default.dim(
|
|
281147
281724
|
" These models exist in the baseline above but not in your push. Sandbox readers"
|
|
281148
281725
|
),
|
|
281149
|
-
|
|
281726
|
+
import_picocolors8.default.dim(
|
|
281150
281727
|
" fall back to the production schema until you push your own, so applying this drops them."
|
|
281151
281728
|
),
|
|
281152
281729
|
""
|
|
281153
281730
|
] : [],
|
|
281154
|
-
|
|
281155
|
-
` Fix: ${
|
|
281731
|
+
import_picocolors8.default.dim(
|
|
281732
|
+
` Fix: ${import_picocolors8.default.bold("ablo push --force")} to apply anyway, or ${import_picocolors8.default.bold("--rename old:new")} if you renamed a model.`
|
|
281156
281733
|
)
|
|
281157
281734
|
];
|
|
281158
281735
|
return { ok: false, message: lines.join("\n") };
|
|
281159
281736
|
}
|
|
281160
281737
|
if (status2 === 403) {
|
|
281161
281738
|
const serverSays = body.message ?? body.reason;
|
|
281162
|
-
const hint = body.code === "database_role_cannot_enforce_rls" ? `Run ${
|
|
281739
|
+
const hint = body.code === "database_role_cannot_enforce_rls" ? `Run ${import_picocolors8.default.bold("npx ablo migrate")} \u2014 it creates the scoped role for you (your DB credential never leaves this machine).` : `Schema authoring needs a ${import_picocolors8.default.bold("sandbox")} key with ${import_picocolors8.default.bold("schema:push")} \u2014 manage keys at ${import_picocolors8.default.cyan("https://abloatai.com")}.`;
|
|
281163
281740
|
return {
|
|
281164
281741
|
ok: false,
|
|
281165
281742
|
message: `${serverSays ?? "This key can't author schema (missing schema:push scope)."}
|
|
281166
|
-
` +
|
|
281743
|
+
` + import_picocolors8.default.dim(hint)
|
|
281167
281744
|
};
|
|
281168
281745
|
}
|
|
281169
281746
|
return { ok: false, message: `Push failed (${status2}): ${body.message ?? body.reason ?? bodyText}` };
|
|
@@ -281173,25 +281750,25 @@ async function dev(argv) {
|
|
|
281173
281750
|
try {
|
|
281174
281751
|
args = parseDevArgs(argv);
|
|
281175
281752
|
} catch (err) {
|
|
281176
|
-
console.error(
|
|
281753
|
+
console.error(import_picocolors8.default.red(` ${err instanceof Error ? err.message : String(err)}`));
|
|
281177
281754
|
process.exit(1);
|
|
281178
281755
|
}
|
|
281179
281756
|
if (!args.apiKey) args.apiKey = resolveEffectiveApiKey("sandbox").key;
|
|
281180
281757
|
const key = classifyKey(args.apiKey, getMode());
|
|
281181
281758
|
if (!key.ok) {
|
|
281182
|
-
console.error(
|
|
281759
|
+
console.error(import_picocolors8.default.red(` ${key.reason}`));
|
|
281183
281760
|
process.exit(1);
|
|
281184
281761
|
}
|
|
281185
281762
|
console.log(`
|
|
281186
|
-
${brand("ablo")} ${
|
|
281763
|
+
${brand("ablo")} ${import_picocolors8.default.dim("push")} ${import_picocolors8.default.dim("(sandbox)")}
|
|
281187
281764
|
`);
|
|
281188
281765
|
const schema = await loadSchema(args.schemaPath, args.exportName);
|
|
281189
281766
|
const modelCount = Object.keys(schema.models).length;
|
|
281190
281767
|
console.log(
|
|
281191
|
-
` ${
|
|
281768
|
+
` ${import_picocolors8.default.dim("schema")} ${import_picocolors8.default.bold(args.schemaPath)} ${import_picocolors8.default.dim(`(${modelCount} models, hash ${(0, import_schema5.schemaHash)(schema)})`)}`
|
|
281192
281769
|
);
|
|
281193
|
-
console.log(` ${
|
|
281194
|
-
console.log(` ${
|
|
281770
|
+
console.log(` ${import_picocolors8.default.dim("key")} ${args.apiKey.slice(0, 12)}\u2026`);
|
|
281771
|
+
console.log(` ${import_picocolors8.default.dim("api")} ${args.url}
|
|
281195
281772
|
`);
|
|
281196
281773
|
const s = Y2();
|
|
281197
281774
|
s.start("Pushing schema definition (sandbox)");
|
|
@@ -281200,16 +281777,16 @@ async function dev(argv) {
|
|
|
281200
281777
|
if (!first.ok) process.exit(1);
|
|
281201
281778
|
if (process.env.ABLO_API_KEY) {
|
|
281202
281779
|
console.log(`
|
|
281203
|
-
${
|
|
281780
|
+
${import_picocolors8.default.green("\u2713")} ${import_picocolors8.default.bold("ABLO_API_KEY")} is set in this shell \u2014 the SDK reads it directly.`);
|
|
281204
281781
|
} else {
|
|
281205
281782
|
console.log(`
|
|
281206
|
-
${
|
|
281207
|
-
console.log(` ${
|
|
281783
|
+
${import_picocolors8.default.green("\u2713")} ${wireEnvLocal(args.apiKey)}`);
|
|
281784
|
+
console.log(` ${import_picocolors8.default.dim("Frameworks load it automatically; plain Node: node --env-file=.env.local app.ts")}`);
|
|
281208
281785
|
}
|
|
281209
281786
|
console.log(` Your app is wired for the sandbox.`);
|
|
281210
281787
|
if (!args.watch) return;
|
|
281211
281788
|
const abs = (0, import_path5.resolve)(process.cwd(), args.schemaPath);
|
|
281212
|
-
console.log(` ${
|
|
281789
|
+
console.log(` ${import_picocolors8.default.dim(`watching ${args.schemaPath} \u2026 (Ctrl-C to stop)`)}
|
|
281213
281790
|
`);
|
|
281214
281791
|
let timer2 = null;
|
|
281215
281792
|
let pushing = false;
|
|
@@ -281229,7 +281806,7 @@ async function dev(argv) {
|
|
|
281229
281806
|
const r2 = await runPush(next, args);
|
|
281230
281807
|
s2.stop(r2.message, r2.ok ? 0 : 1);
|
|
281231
281808
|
} catch (err) {
|
|
281232
|
-
s2.stop(
|
|
281809
|
+
s2.stop(import_picocolors8.default.red(`schema reload failed: ${err instanceof Error ? err.message : String(err)}`), 1);
|
|
281233
281810
|
} finally {
|
|
281234
281811
|
pushing = false;
|
|
281235
281812
|
}
|
|
@@ -281237,7 +281814,7 @@ async function dev(argv) {
|
|
|
281237
281814
|
const stop = () => {
|
|
281238
281815
|
watcher.close();
|
|
281239
281816
|
console.log(`
|
|
281240
|
-
${
|
|
281817
|
+
${import_picocolors8.default.dim("stopped.")}`);
|
|
281241
281818
|
process.exit(0);
|
|
281242
281819
|
};
|
|
281243
281820
|
process.on("SIGINT", stop);
|
|
@@ -281249,7 +281826,7 @@ async function dev(argv) {
|
|
|
281249
281826
|
// src/cli/login.ts
|
|
281250
281827
|
init_cjs_shims();
|
|
281251
281828
|
var import_child_process2 = require("child_process");
|
|
281252
|
-
var
|
|
281829
|
+
var import_picocolors9 = __toESM(require_picocolors(), 1);
|
|
281253
281830
|
var CLIENT_ID = "ablo-cli";
|
|
281254
281831
|
var stripSlash = (u2) => u2.replace(/\/+$/, "");
|
|
281255
281832
|
var AUTH_URL = stripSlash(process.env.ABLO_AUTH_URL ?? "https://auth.abloatai.com");
|
|
@@ -281307,9 +281884,9 @@ async function deviceLogin(argv, deps = {}) {
|
|
|
281307
281884
|
const code = await codeRes.json();
|
|
281308
281885
|
const approvePath = `/cli?user_code=${code.user_code}`;
|
|
281309
281886
|
const url = account === "signup" ? `${DASHBOARD_URL}/signup?next=${encodeURIComponent(approvePath)}` : `${DASHBOARD_URL}${approvePath}`;
|
|
281310
|
-
Me(`${
|
|
281887
|
+
Me(`${import_picocolors9.default.bold(code.user_code)}
|
|
281311
281888
|
|
|
281312
|
-
${
|
|
281889
|
+
${import_picocolors9.default.dim(url)}`, "Approve in your browser");
|
|
281313
281890
|
openUrl(url);
|
|
281314
281891
|
const s = Y2();
|
|
281315
281892
|
s.start("Waiting for approval\u2026");
|
|
@@ -281376,7 +281953,7 @@ ${import_picocolors8.default.dim(url)}`, "Approve in your browser");
|
|
|
281376
281953
|
if (reason) M2.error(reason);
|
|
281377
281954
|
else if (provRes) M2.error(`Key provisioning returned ${provRes.status} from ${DASHBOARD_URL}/api/cli/provision-key.`);
|
|
281378
281955
|
M2.error(
|
|
281379
|
-
`The browser approval succeeded but the key handoff failed. Try again, or grab a ${
|
|
281956
|
+
`The browser approval succeeded but the key handoff failed. Try again, or grab a ${import_picocolors9.default.bold("sk_test_")} key from the dashboard and set ${import_picocolors9.default.bold("ABLO_API_KEY")}.`
|
|
281380
281957
|
);
|
|
281381
281958
|
process.exit(1);
|
|
281382
281959
|
}
|
|
@@ -281396,9 +281973,9 @@ ${import_picocolors8.default.dim(url)}`, "Approve in your browser");
|
|
|
281396
281973
|
{ mode: "sandbox", activeProject: prov.project ?? void 0 }
|
|
281397
281974
|
);
|
|
281398
281975
|
s.stop(`Saved keys to ${path}`);
|
|
281399
|
-
const where = prov.project ? ` ${
|
|
281976
|
+
const where = prov.project ? ` ${import_picocolors9.default.dim(`(project ${prov.project.slug})`)}` : "";
|
|
281400
281977
|
Se(
|
|
281401
|
-
`${
|
|
281978
|
+
`${import_picocolors9.default.green("\u2713")} Logged in ${import_picocolors9.default.dim("(sandbox)")}${where}. Run ${import_picocolors9.default.bold("npx ablo push")} to push your schema.`
|
|
281402
281979
|
);
|
|
281403
281980
|
}
|
|
281404
281981
|
async function login(argv = [], deps = {}) {
|
|
@@ -281407,20 +281984,20 @@ async function login(argv = [], deps = {}) {
|
|
|
281407
281984
|
function logout() {
|
|
281408
281985
|
const removed = clearCredential();
|
|
281409
281986
|
if (removed) {
|
|
281410
|
-
console.log(` ${
|
|
281987
|
+
console.log(` ${import_picocolors9.default.green("\u2713")} Logged out ${import_picocolors9.default.dim(`(credentials removed from ${configDir()})`)}`);
|
|
281411
281988
|
} else {
|
|
281412
|
-
console.log(` ${
|
|
281989
|
+
console.log(` ${import_picocolors9.default.dim("\u25CB")} Not logged in \u2014 nothing to remove.`);
|
|
281413
281990
|
}
|
|
281414
281991
|
if (process.env.ABLO_API_KEY) {
|
|
281415
281992
|
console.log(
|
|
281416
|
-
|
|
281993
|
+
import_picocolors9.default.dim(` Note: ${import_picocolors9.default.bold("ABLO_API_KEY")} is still set in this shell and takes precedence.`)
|
|
281417
281994
|
);
|
|
281418
281995
|
}
|
|
281419
281996
|
}
|
|
281420
281997
|
|
|
281421
281998
|
// src/cli/mode.ts
|
|
281422
281999
|
init_cjs_shims();
|
|
281423
|
-
var
|
|
282000
|
+
var import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
281424
282001
|
var PREFIX = { sandbox: "sk_test_", production: "rk_live_" };
|
|
281425
282002
|
function hintFor(m2, current) {
|
|
281426
282003
|
const parts = [];
|
|
@@ -281430,10 +282007,10 @@ function hintFor(m2, current) {
|
|
|
281430
282007
|
}
|
|
281431
282008
|
function apply(m2) {
|
|
281432
282009
|
setMode(m2);
|
|
281433
|
-
console.log(` ${
|
|
282010
|
+
console.log(` ${import_picocolors10.default.green("\u2713")} now in ${import_picocolors10.default.bold(m2)}`);
|
|
281434
282011
|
if (!getKeyEntry(m2)) {
|
|
281435
282012
|
console.log(
|
|
281436
|
-
|
|
282013
|
+
import_picocolors10.default.dim(` No ${m2} key stored \u2014 run ${import_picocolors10.default.bold("ablo login")} or ${import_picocolors10.default.bold(`ablo login --api-key ${PREFIX[m2]}\u2026`)}.`)
|
|
281437
282014
|
);
|
|
281438
282015
|
}
|
|
281439
282016
|
}
|
|
@@ -281446,14 +282023,14 @@ async function mode(argv) {
|
|
|
281446
282023
|
}
|
|
281447
282024
|
if (arg) {
|
|
281448
282025
|
console.error(
|
|
281449
|
-
|
|
282026
|
+
import_picocolors10.default.red(` unknown mode: ${arg}`) + import_picocolors10.default.dim(` (expected ${import_picocolors10.default.bold("sandbox")} or ${import_picocolors10.default.bold("production")})`)
|
|
281450
282027
|
);
|
|
281451
282028
|
process.exit(1);
|
|
281452
282029
|
}
|
|
281453
282030
|
const current = getMode();
|
|
281454
282031
|
if (!process.stdin.isTTY || process.env.CI) {
|
|
281455
282032
|
console.error(
|
|
281456
|
-
|
|
282033
|
+
import_picocolors10.default.red(" `ablo mode` needs an argument without a TTY: ") + import_picocolors10.default.bold("ablo mode sandbox") + import_picocolors10.default.dim(" | ") + import_picocolors10.default.bold("ablo mode production") + import_picocolors10.default.dim(` (current: ${current})`)
|
|
281457
282034
|
);
|
|
281458
282035
|
process.exit(1);
|
|
281459
282036
|
}
|
|
@@ -281472,206 +282049,6 @@ async function mode(argv) {
|
|
|
281472
282049
|
apply(selected);
|
|
281473
282050
|
}
|
|
281474
282051
|
|
|
281475
|
-
// src/cli/projects.ts
|
|
281476
|
-
init_cjs_shims();
|
|
281477
|
-
var import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
281478
|
-
function apiUrl() {
|
|
281479
|
-
return (process.env.ABLO_API_URL ?? DEFAULT_URL).replace(/\/+$/, "");
|
|
281480
|
-
}
|
|
281481
|
-
function requireKey() {
|
|
281482
|
-
const apiKey = resolveApiKey();
|
|
281483
|
-
if (!apiKey) {
|
|
281484
|
-
console.error(
|
|
281485
|
-
import_picocolors10.default.red(" No API key.") + import_picocolors10.default.dim(
|
|
281486
|
-
` Run ${import_picocolors10.default.bold("npx ablo login")} \u2014 or set ${import_picocolors10.default.bold("ABLO_API_KEY")} (${import_picocolors10.default.bold("sk_test_")} = sandbox; ${import_picocolors10.default.bold("sk_live_")} = production).`
|
|
281487
|
-
)
|
|
281488
|
-
);
|
|
281489
|
-
process.exit(1);
|
|
281490
|
-
}
|
|
281491
|
-
return apiKey;
|
|
281492
|
-
}
|
|
281493
|
-
async function request(path, apiKey, init2 = {}) {
|
|
281494
|
-
const res = await fetch(`${apiUrl()}${path}`, {
|
|
281495
|
-
method: init2.method ?? "GET",
|
|
281496
|
-
headers: {
|
|
281497
|
-
"content-type": "application/json",
|
|
281498
|
-
authorization: `Bearer ${apiKey}`
|
|
281499
|
-
},
|
|
281500
|
-
...init2.body !== void 0 ? { body: JSON.stringify(init2.body) } : {}
|
|
281501
|
-
});
|
|
281502
|
-
let body = {};
|
|
281503
|
-
try {
|
|
281504
|
-
body = await res.json();
|
|
281505
|
-
} catch {
|
|
281506
|
-
}
|
|
281507
|
-
return { status: res.status, body };
|
|
281508
|
-
}
|
|
281509
|
-
async function listProjects(apiKey) {
|
|
281510
|
-
try {
|
|
281511
|
-
const { status: status2, body } = await request("/api/v1/projects", apiKey);
|
|
281512
|
-
if (status2 !== 200 || !Array.isArray(body.data)) return null;
|
|
281513
|
-
return body.data;
|
|
281514
|
-
} catch {
|
|
281515
|
-
return null;
|
|
281516
|
-
}
|
|
281517
|
-
}
|
|
281518
|
-
async function fetchProjects() {
|
|
281519
|
-
const all = await listProjects(requireKey());
|
|
281520
|
-
if (!all) {
|
|
281521
|
-
console.error(import_picocolors10.default.red(" Could not list projects \u2014 is the API reachable and the key valid?"));
|
|
281522
|
-
process.exit(1);
|
|
281523
|
-
}
|
|
281524
|
-
return all;
|
|
281525
|
-
}
|
|
281526
|
-
function projectSlugFromPackageName(name) {
|
|
281527
|
-
if (typeof name !== "string") return void 0;
|
|
281528
|
-
const slug = name.toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/^[-_]+|[-_]+$/g, "").slice(0, 64);
|
|
281529
|
-
if (!slug || slug === "default") return void 0;
|
|
281530
|
-
return slug;
|
|
281531
|
-
}
|
|
281532
|
-
async function ensureProject(slug, name) {
|
|
281533
|
-
const apiKey = resolveApiKey();
|
|
281534
|
-
if (!apiKey) return null;
|
|
281535
|
-
try {
|
|
281536
|
-
const { status: status2, body } = await request("/api/v1/projects", apiKey, {
|
|
281537
|
-
method: "POST",
|
|
281538
|
-
body: { slug, ...name ? { name } : {} }
|
|
281539
|
-
});
|
|
281540
|
-
if (status2 === 201) {
|
|
281541
|
-
const created = body;
|
|
281542
|
-
setActiveProject({ id: created.id, slug: created.slug });
|
|
281543
|
-
return { id: created.id, slug: created.slug, created: true };
|
|
281544
|
-
}
|
|
281545
|
-
if (body.code === "project_slug_taken") {
|
|
281546
|
-
const all = await listProjects(apiKey);
|
|
281547
|
-
const existing = all?.find((p2) => p2.slug === slug);
|
|
281548
|
-
if (!existing) return null;
|
|
281549
|
-
setActiveProject({ id: existing.id, slug: existing.slug });
|
|
281550
|
-
return { id: existing.id, slug: existing.slug, created: false };
|
|
281551
|
-
}
|
|
281552
|
-
return null;
|
|
281553
|
-
} catch {
|
|
281554
|
-
return null;
|
|
281555
|
-
}
|
|
281556
|
-
}
|
|
281557
|
-
function printList(projects2) {
|
|
281558
|
-
const active = getActiveProject();
|
|
281559
|
-
for (const p2 of projects2) {
|
|
281560
|
-
const isActive = active ? active.id === p2.id : p2.default;
|
|
281561
|
-
const marker = isActive ? import_picocolors10.default.green("\u25CF") : import_picocolors10.default.dim("\u25CB");
|
|
281562
|
-
const tags = [p2.default ? import_picocolors10.default.dim("default") : "", isActive ? import_picocolors10.default.green("active") : ""].filter(Boolean).join(import_picocolors10.default.dim(", "));
|
|
281563
|
-
console.log(
|
|
281564
|
-
` ${marker} ${p2.slug.padEnd(20)} ${import_picocolors10.default.dim(p2.id)}${tags ? ` ${tags}` : ""}`
|
|
281565
|
-
);
|
|
281566
|
-
}
|
|
281567
|
-
}
|
|
281568
|
-
async function projects(argv) {
|
|
281569
|
-
const sub = argv[0];
|
|
281570
|
-
if (sub === "list" || sub === void 0) {
|
|
281571
|
-
console.log(`
|
|
281572
|
-
${brand("ablo")} ${import_picocolors10.default.dim("projects")}
|
|
281573
|
-
`);
|
|
281574
|
-
printList(await fetchProjects());
|
|
281575
|
-
console.log();
|
|
281576
|
-
return;
|
|
281577
|
-
}
|
|
281578
|
-
if (sub === "create") {
|
|
281579
|
-
const slug = argv[1];
|
|
281580
|
-
if (!slug || slug.startsWith("-")) {
|
|
281581
|
-
console.error(import_picocolors10.default.red(' usage: ablo projects create <slug> [--name "Display Name"]'));
|
|
281582
|
-
process.exit(1);
|
|
281583
|
-
}
|
|
281584
|
-
const nameIdx = argv.indexOf("--name");
|
|
281585
|
-
const name = nameIdx >= 0 ? argv[nameIdx + 1] : void 0;
|
|
281586
|
-
const { status: status2, body } = await request("/api/v1/projects", requireKey(), {
|
|
281587
|
-
method: "POST",
|
|
281588
|
-
body: { slug, ...name ? { name } : {} }
|
|
281589
|
-
});
|
|
281590
|
-
if (status2 !== 201) {
|
|
281591
|
-
console.error(
|
|
281592
|
-
import_picocolors10.default.red(` Create failed (${status2}): ${String(body.message ?? body.code ?? "")}`)
|
|
281593
|
-
);
|
|
281594
|
-
if (body.code === "project_slug_taken") {
|
|
281595
|
-
console.error(import_picocolors10.default.dim(` Pick another slug, or switch to it: ${import_picocolors10.default.bold(`ablo projects use ${slug}`)}`));
|
|
281596
|
-
}
|
|
281597
|
-
process.exit(1);
|
|
281598
|
-
}
|
|
281599
|
-
const created = body;
|
|
281600
|
-
console.log(` ${import_picocolors10.default.green("\u2713")} Created project ${import_picocolors10.default.bold(created.slug)} ${import_picocolors10.default.dim(`(${created.id})`)}`);
|
|
281601
|
-
console.log(
|
|
281602
|
-
import_picocolors10.default.dim(` Make it active with ${import_picocolors10.default.bold(`ablo projects use ${created.slug}`)}; mint its keys in the dashboard.`)
|
|
281603
|
-
);
|
|
281604
|
-
return;
|
|
281605
|
-
}
|
|
281606
|
-
if (sub === "rename") {
|
|
281607
|
-
const ref = argv[1];
|
|
281608
|
-
const name = argv.slice(2).join(" ").trim();
|
|
281609
|
-
if (!ref || ref.startsWith("-") || !name) {
|
|
281610
|
-
console.error(import_picocolors10.default.red(" usage: ablo projects rename <slug|id> <new name>"));
|
|
281611
|
-
process.exit(1);
|
|
281612
|
-
}
|
|
281613
|
-
const all = await fetchProjects();
|
|
281614
|
-
const target = all.find((p2) => p2.slug === ref || p2.id === ref);
|
|
281615
|
-
if (!target) {
|
|
281616
|
-
console.error(import_picocolors10.default.red(` No project "${ref}".`) + import_picocolors10.default.dim(" Run ablo projects list."));
|
|
281617
|
-
process.exit(1);
|
|
281618
|
-
}
|
|
281619
|
-
if (target.default) {
|
|
281620
|
-
console.error(import_picocolors10.default.red(" The default project cannot be renamed."));
|
|
281621
|
-
process.exit(1);
|
|
281622
|
-
}
|
|
281623
|
-
const { status: status2, body } = await request(`/api/v1/projects/${target.id}`, requireKey(), {
|
|
281624
|
-
method: "PATCH",
|
|
281625
|
-
body: { name }
|
|
281626
|
-
});
|
|
281627
|
-
if (status2 !== 200) {
|
|
281628
|
-
console.error(
|
|
281629
|
-
import_picocolors10.default.red(` Rename failed (${status2}): ${String(body.message ?? body.code ?? "")}`)
|
|
281630
|
-
);
|
|
281631
|
-
process.exit(1);
|
|
281632
|
-
}
|
|
281633
|
-
const updated = body;
|
|
281634
|
-
console.log(
|
|
281635
|
-
` ${import_picocolors10.default.green("\u2713")} Renamed ${import_picocolors10.default.bold(updated.slug)} \u2192 ${import_picocolors10.default.bold(updated.name ?? updated.slug)} ${import_picocolors10.default.dim(`(${updated.id})`)}`
|
|
281636
|
-
);
|
|
281637
|
-
return;
|
|
281638
|
-
}
|
|
281639
|
-
if (sub === "use") {
|
|
281640
|
-
const ref = argv[1];
|
|
281641
|
-
if (!ref) {
|
|
281642
|
-
console.error(import_picocolors10.default.red(" usage: ablo projects use <slug|id|default>"));
|
|
281643
|
-
process.exit(1);
|
|
281644
|
-
}
|
|
281645
|
-
const all = await fetchProjects();
|
|
281646
|
-
const target = all.find((p2) => p2.slug === ref || p2.id === ref);
|
|
281647
|
-
if (!target) {
|
|
281648
|
-
console.error(import_picocolors10.default.red(` No project "${ref}".`) + import_picocolors10.default.dim(" Run ablo projects list."));
|
|
281649
|
-
process.exit(1);
|
|
281650
|
-
}
|
|
281651
|
-
if (target.default) {
|
|
281652
|
-
setActiveProject(void 0);
|
|
281653
|
-
console.log(` ${import_picocolors10.default.green("\u2713")} now targeting the ${import_picocolors10.default.bold("default")} project`);
|
|
281654
|
-
} else {
|
|
281655
|
-
setActiveProject({ id: target.id, slug: target.slug });
|
|
281656
|
-
console.log(` ${import_picocolors10.default.green("\u2713")} now targeting project ${import_picocolors10.default.bold(target.slug)} ${import_picocolors10.default.dim(`(${target.id})`)}`);
|
|
281657
|
-
}
|
|
281658
|
-
const guard = guardActiveProjectKey();
|
|
281659
|
-
if (!guard.ok) {
|
|
281660
|
-
const loginCmd = guard.activeProfile === DEFAULT_PROFILE ? "ablo login" : `ablo login --project ${guard.activeProfile}`;
|
|
281661
|
-
console.log(
|
|
281662
|
-
import_picocolors10.default.dim(` No key stored for this project yet \u2014 run ${import_picocolors10.default.bold(loginCmd)} to mint one.`)
|
|
281663
|
-
);
|
|
281664
|
-
}
|
|
281665
|
-
return;
|
|
281666
|
-
}
|
|
281667
|
-
console.error(
|
|
281668
|
-
import_picocolors10.default.red(` unknown subcommand: ${sub}`) + import_picocolors10.default.dim(
|
|
281669
|
-
` (expected ${import_picocolors10.default.bold("list")}, ${import_picocolors10.default.bold("create")}, ${import_picocolors10.default.bold("rename")}, or ${import_picocolors10.default.bold("use")})`
|
|
281670
|
-
)
|
|
281671
|
-
);
|
|
281672
|
-
process.exit(1);
|
|
281673
|
-
}
|
|
281674
|
-
|
|
281675
282052
|
// src/cli/status.ts
|
|
281676
282053
|
init_cjs_shims();
|
|
281677
282054
|
var import_picocolors11 = __toESM(require_picocolors(), 1);
|
|
@@ -281761,17 +282138,44 @@ function formatConflict(conflict) {
|
|
|
281761
282138
|
const parts = ["user", "agent", "system"].flatMap((k3) => conflict[k3] ? [`${k3}:${conflict[k3]}`] : []);
|
|
281762
282139
|
return parts.length ? `{${parts.join(",")}}` : "";
|
|
281763
282140
|
}
|
|
282141
|
+
function printTargetLines(target, localProject) {
|
|
282142
|
+
const confirmed = target?.confirmed ?? null;
|
|
282143
|
+
if (confirmed?.organizationId) {
|
|
282144
|
+
console.log(` ${import_picocolors11.default.dim("org")} ${import_picocolors11.default.dim(confirmed.organizationId)}`);
|
|
282145
|
+
}
|
|
282146
|
+
let projectLine;
|
|
282147
|
+
if (confirmed?.project) {
|
|
282148
|
+
const p2 = confirmed.project;
|
|
282149
|
+
projectLine = p2.isDefault ? `${import_picocolors11.default.bold("default")} ${import_picocolors11.default.dim("(org-default)")}` : `${import_picocolors11.default.bold(p2.slug)} ${import_picocolors11.default.dim(`(${p2.id})`)}`;
|
|
282150
|
+
} else if (confirmed) {
|
|
282151
|
+
projectLine = `${import_picocolors11.default.bold("default")} ${import_picocolors11.default.dim("(org-default)")}`;
|
|
282152
|
+
} else if (localProject) {
|
|
282153
|
+
projectLine = `${import_picocolors11.default.bold(localProject.slug)} ${import_picocolors11.default.dim(`(${localProject.id})`)} ${import_picocolors11.default.yellow("(unconfirmed)")}`;
|
|
282154
|
+
} else {
|
|
282155
|
+
projectLine = `${import_picocolors11.default.bold("default")} ${target ? import_picocolors11.default.yellow("(unconfirmed)") : import_picocolors11.default.dim("(org-default)")}`;
|
|
282156
|
+
}
|
|
282157
|
+
console.log(` ${import_picocolors11.default.dim("project")} ${projectLine}`);
|
|
282158
|
+
const env = confirmed?.environment ?? target?.keyEnv ?? null;
|
|
282159
|
+
if (env) {
|
|
282160
|
+
const suffix = confirmed ? "" : ` ${import_picocolors11.default.yellow("(unconfirmed)")}`;
|
|
282161
|
+
console.log(` ${import_picocolors11.default.dim("env")} ${import_picocolors11.default.bold(env)}${suffix}`);
|
|
282162
|
+
}
|
|
282163
|
+
for (const m2 of target?.mismatches ?? []) {
|
|
282164
|
+
console.log(` ${import_picocolors11.default.yellow("\u26A0")} ${import_picocolors11.default.yellow(describeMismatch(m2))}`);
|
|
282165
|
+
}
|
|
282166
|
+
}
|
|
281764
282167
|
async function status(args = []) {
|
|
281765
282168
|
const apiUrl2 = (process.env.ABLO_API_URL ?? DEFAULT_URL).replace(/\/+$/, "");
|
|
281766
282169
|
const cfg = readConfig();
|
|
281767
282170
|
const mode2 = getMode();
|
|
282171
|
+
const effective = resolveEffectiveApiKey();
|
|
282172
|
+
const target = effective.key ? await resolveTarget({ url: apiUrl2, apiKey: effective.key, keySource: effective.source ?? "stored" }) : null;
|
|
281768
282173
|
if (args.includes("--json")) {
|
|
281769
282174
|
const entry = getKeyEntry(mode2);
|
|
281770
282175
|
const key2 = describeEffectiveKey(mode2, process.env.ABLO_API_KEY, entry);
|
|
281771
282176
|
const plan2 = resolvePushPlan();
|
|
281772
282177
|
const activeProject2 = getActiveProject();
|
|
281773
|
-
const
|
|
281774
|
-
const pushed = await fetchPushedSchema(apiUrl2, effective2.key);
|
|
282178
|
+
const pushed = await fetchPushedSchema(apiUrl2, effective.key);
|
|
281775
282179
|
const out = {
|
|
281776
282180
|
mode: mode2,
|
|
281777
282181
|
// The locally-active project (`ablo projects use`); null = org-default.
|
|
@@ -281780,8 +282184,8 @@ async function status(args = []) {
|
|
|
281780
282184
|
// 'env' | '.env.local' | '.env' | 'stored'. Key confusion is a common
|
|
281781
282185
|
// source of trouble, and the source is usually the answer.
|
|
281782
282186
|
effectiveKey: {
|
|
281783
|
-
prefix:
|
|
281784
|
-
source:
|
|
282187
|
+
prefix: effective.key ? effective.key.slice(0, 12) : null,
|
|
282188
|
+
source: effective.source
|
|
281785
282189
|
},
|
|
281786
282190
|
keyPrefix: key2.keyPrefix,
|
|
281787
282191
|
keySource: key2.keySource,
|
|
@@ -281791,6 +282195,19 @@ async function status(args = []) {
|
|
|
281791
282195
|
keyMatchesStoredActiveKey: key2.keyMatchesStoredActiveKey,
|
|
281792
282196
|
keyMismatch: key2.keyMismatch,
|
|
281793
282197
|
organizationId: entry?.organizationId ?? null,
|
|
282198
|
+
// The SERVER-CONFIRMED plane this key resolves to — the authoritative
|
|
282199
|
+
// answer to "where does a push land", independent of the local
|
|
282200
|
+
// `project`/`mode` preferences above. Null when the server didn't answer.
|
|
282201
|
+
confirmedTarget: target?.confirmed ? {
|
|
282202
|
+
organizationId: target.confirmed.organizationId,
|
|
282203
|
+
environment: target.confirmed.environment,
|
|
282204
|
+
project: target.confirmed.project,
|
|
282205
|
+
projectId: target.confirmed.projectId,
|
|
282206
|
+
sandboxId: target.confirmed.sandboxId
|
|
282207
|
+
} : null,
|
|
282208
|
+
// Divergences between local intent and the confirmed plane (project not
|
|
282209
|
+
// selected, mode not the key's environment). Empty when aligned.
|
|
282210
|
+
mismatches: target?.mismatches ?? [],
|
|
281794
282211
|
// What `ablo push` would do right now — the answer to "why did push
|
|
281795
282212
|
// demand a different key".
|
|
281796
282213
|
push: {
|
|
@@ -281804,6 +282221,7 @@ async function status(args = []) {
|
|
|
281804
282221
|
schema: pushed ? {
|
|
281805
282222
|
active: pushed.active,
|
|
281806
282223
|
version: pushed.version ?? null,
|
|
282224
|
+
hash: pushed.hash ?? null,
|
|
281807
282225
|
pushedAt: pushed.pushedAt ?? null,
|
|
281808
282226
|
models: pushed.models
|
|
281809
282227
|
} : null,
|
|
@@ -281816,7 +282234,6 @@ async function status(args = []) {
|
|
|
281816
282234
|
console.log(`
|
|
281817
282235
|
${brand("ablo")} ${import_picocolors11.default.dim("status")}
|
|
281818
282236
|
`);
|
|
281819
|
-
const effective = resolveEffectiveApiKey();
|
|
281820
282237
|
if (effective.key && effective.source && effective.source !== "stored") {
|
|
281821
282238
|
const label = effective.source === "env" ? "ABLO_API_KEY env" : effective.source;
|
|
281822
282239
|
console.log(
|
|
@@ -281832,9 +282249,7 @@ async function status(args = []) {
|
|
|
281832
282249
|
console.log(` ${import_picocolors11.default.yellow("!")} ${import_picocolors11.default.yellow(key.keyMismatch.message)}`);
|
|
281833
282250
|
}
|
|
281834
282251
|
const activeProject = getActiveProject();
|
|
281835
|
-
|
|
281836
|
-
` ${import_picocolors11.default.dim("project")} ${activeProject ? `${import_picocolors11.default.bold(activeProject.slug)} ${import_picocolors11.default.dim(`(${activeProject.id})`)}` : import_picocolors11.default.bold("default")}`
|
|
281837
|
-
);
|
|
282252
|
+
printTargetLines(target, activeProject);
|
|
281838
282253
|
for (const m2 of ["sandbox", "production"]) {
|
|
281839
282254
|
const entry = getKeyEntry(m2);
|
|
281840
282255
|
const marker = m2 === mode2 ? import_picocolors11.default.green("\u25CF") : import_picocolors11.default.dim("\u25CB");
|
|
@@ -281845,8 +282260,7 @@ async function status(args = []) {
|
|
|
281845
282260
|
console.log(` ${marker} ${m2.padEnd(10)} ${import_picocolors11.default.dim("\u2014 no key")}`);
|
|
281846
282261
|
}
|
|
281847
282262
|
}
|
|
281848
|
-
const org = activeEntry?.organizationId;
|
|
281849
|
-
if (org) console.log(` ${import_picocolors11.default.dim("org")} ${org}`);
|
|
282263
|
+
const org = target?.confirmed?.organizationId ?? activeEntry?.organizationId;
|
|
281850
282264
|
const plan = resolvePushPlan();
|
|
281851
282265
|
console.log(
|
|
281852
282266
|
` ${import_picocolors11.default.dim("push")} ${plan.apiKey ? `${import_picocolors11.default.bold(plan.flow)} ${import_picocolors11.default.dim(`with ${plan.apiKey.slice(0, 12)}\u2026 (${plan.source})`)}` : `${import_picocolors11.default.bold(plan.flow)} ${import_picocolors11.default.yellow("\u2014 no credential")} ${import_picocolors11.default.dim(`(run ${import_picocolors11.default.bold("ablo login")} or set ${import_picocolors11.default.bold("ABLO_API_KEY")})`)}`}`
|
|
@@ -281860,7 +282274,8 @@ async function status(args = []) {
|
|
|
281860
282274
|
if (pushed?.active) {
|
|
281861
282275
|
const when = pushed.pushedAt ? ` ${import_picocolors11.default.dim(`@ ${pushed.pushedAt.slice(0, 10)}`)}` : "";
|
|
281862
282276
|
const ver = pushed.version != null ? ` ${import_picocolors11.default.dim(`(rev ${pushed.version})`)}` : "";
|
|
281863
|
-
|
|
282277
|
+
const hashLabel = pushed.hash ? ` ${import_picocolors11.default.dim(`hash ${pushed.hash}`)}` : "";
|
|
282278
|
+
console.log(` ${import_picocolors11.default.dim("schema")} ${import_picocolors11.default.bold(`${pushed.models.length} models pushed`)}${ver}${hashLabel}${when}`);
|
|
281864
282279
|
for (const m2 of pushed.models) {
|
|
281865
282280
|
const tn = m2.typename === m2.key ? import_picocolors11.default.dim(`typename=${m2.typename}`) : import_picocolors11.default.yellow(`typename=${m2.typename}`);
|
|
281866
282281
|
const conflict = formatConflict(m2.conflict);
|