@abloatai/ablo 0.27.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 +59 -3
- package/README.md +1 -1
- package/dist/BaseSyncedStore.js +8 -9
- package/dist/Database.d.ts +14 -1
- package/dist/Database.js +228 -28
- package/dist/Model.d.ts +17 -0
- package/dist/Model.js +32 -1
- package/dist/SyncClient.d.ts +10 -6
- package/dist/SyncClient.js +390 -75
- package/dist/adapters/inMemoryStorage.d.ts +1 -0
- package/dist/adapters/inMemoryStorage.js +29 -13
- 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 +821 -402
- package/dist/client/Ablo.d.ts +53 -24
- package/dist/client/Ablo.js +12 -199
- package/dist/client/claimHeartbeatLoop.d.ts +1 -1
- package/dist/client/claimHeartbeatLoop.js +1 -1
- package/dist/client/createInternalComponents.d.ts +4 -0
- 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 +21 -45
- package/dist/client/httpClient.js +104 -26
- package/dist/client/httpTransport.d.ts +8 -0
- package/dist/client/{ApiClient.js → httpTransport.js} +361 -308
- package/dist/client/modelRegistration.js +11 -0
- package/dist/client/options.d.ts +54 -7
- 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/client/wsMutationExecutor.d.ts +3 -2
- package/dist/client/wsMutationExecutor.js +3 -2
- package/dist/coordination/schema.d.ts +30 -0
- package/dist/coordination/schema.js +14 -0
- package/dist/core/StoreManager.d.ts +2 -0
- package/dist/core/StoreManager.js +12 -0
- package/dist/core/index.d.ts +1 -1
- package/dist/errorCodes.js +6 -2
- package/dist/errors.d.ts +4 -40
- package/dist/errors.js +5 -5
- package/dist/index.d.ts +20 -8
- package/dist/index.js +9 -5
- package/dist/interfaces/index.d.ts +5 -2
- package/dist/mutators/UndoManager.d.ts +2 -0
- package/dist/mutators/UndoManager.js +32 -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/react/useAblo.d.ts +6 -4
- package/dist/react/useAblo.js +25 -3
- package/dist/schema/index.d.ts +2 -2
- package/dist/schema/index.js +1 -1
- package/dist/schema/schema.d.ts +31 -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.d.ts +14 -1
- package/dist/stores/ObjectStore.js +33 -10
- package/dist/stores/ObjectStoreContract.d.ts +2 -0
- package/dist/surface.d.ts +1 -1
- package/dist/surface.js +3 -0
- package/dist/sync/BootstrapFetcher.d.ts +10 -0
- package/dist/sync/BootstrapFetcher.js +27 -4
- package/dist/sync/SyncWebSocket.d.ts +2 -1
- package/dist/sync/createClaimStream.d.ts +11 -2
- package/dist/sync/createClaimStream.js +4 -3
- package/dist/sync/persistedPrefix.d.ts +12 -0
- package/dist/sync/persistedPrefix.js +22 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +1 -0
- package/dist/testing/mocks/FakeDatabase.d.ts +18 -0
- package/dist/testing/mocks/FakeDatabase.js +10 -0
- package/dist/testing/mocks/MockWebSocket.d.ts +2 -1
- package/dist/transactions/TransactionQueue.d.ts +66 -8
- package/dist/transactions/TransactionQueue.js +607 -89
- package/dist/transactions/commitEnvelope.d.ts +132 -0
- package/dist/transactions/commitEnvelope.js +139 -0
- package/dist/transactions/commitOutboxStore.d.ts +32 -0
- package/dist/transactions/commitOutboxStore.js +26 -0
- package/dist/transactions/commitPayload.d.ts +15 -0
- package/dist/transactions/commitPayload.js +6 -0
- package/dist/transactions/durableWriteStore.d.ts +123 -0
- package/dist/transactions/durableWriteStore.js +30 -0
- package/dist/transactions/httpCommitEnvelope.d.ts +44 -0
- package/dist/transactions/httpCommitEnvelope.js +181 -0
- package/dist/transactions/idempotencyKey.d.ts +10 -0
- package/dist/transactions/idempotencyKey.js +9 -0
- package/dist/transactions/replayValidation.d.ts +83 -0
- package/dist/transactions/replayValidation.js +46 -1
- 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/bootstrapReason.d.ts +9 -0
- package/dist/wire/bootstrapReason.js +8 -0
- package/dist/wire/frames.d.ts +6 -21
- package/dist/wire/frames.js +4 -4
- package/dist/wire/index.d.ts +6 -3
- package/dist/wire/index.js +3 -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 +16 -14
- package/docs/client-behavior.md +6 -3
- package/docs/coordination.md +4 -5
- package/docs/data-sources.md +5 -8
- package/docs/guarantees.md +21 -0
- package/docs/integration-guide.md +1 -0
- package/docs/mcp.md +1 -1
- 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 +3 -3
- package/dist/client/ApiClient.d.ts +0 -177
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);
|
|
@@ -276796,7 +276796,7 @@ var ERROR_CODES = {
|
|
|
276796
276796
|
jwt_invalid: wire("auth", 401, false, "The bearer JWT failed validation for a reason the server could not classify further. Check the token's issuer, signature, audience, and expiry."),
|
|
276797
276797
|
jwt_malformed: wire("auth", 401, false, "The bearer token is not a well-formed JWT and could not be decoded. Check that the full, unmodified token was sent."),
|
|
276798
276798
|
jwt_missing_issuer: wire("auth", 401, false, "The bearer JWT has no `iss` (issuer) claim, so it cannot be routed to a trusted issuer."),
|
|
276799
|
-
jwt_issuer_untrusted: wire("auth", 401, false, "The bearer JWT's `iss` is not a registered trusted issuer.
|
|
276799
|
+
jwt_issuer_untrusted: wire("auth", 401, false, "The bearer JWT's `iss` is not a registered trusted issuer. Check the token's issuer claim, or register the issuer with your deployment before retrying."),
|
|
276800
276800
|
jwt_signature_invalid: wire("auth", 401, false, "The bearer JWT's signature could not be verified against the issuer's JWKS (wrong key, rotated key, or forged token)."),
|
|
276801
276801
|
jwt_audience_mismatch: wire("auth", 401, false, "The bearer JWT's `aud` (audience) claim does not match the audience this issuer is registered with."),
|
|
276802
276802
|
jwt_missing_subject: wire("auth", 401, false, "The bearer JWT has no `sub` (subject) claim to identify the user."),
|
|
@@ -276845,7 +276845,11 @@ var ERROR_CODES = {
|
|
|
276845
276845
|
model_claim_not_configured: client("claim", "Claiming requires the collaboration runtime, which the standard Ablo({ schema, apiKey }) client wires up for every model automatically \u2014 there is no per-model claim configuration to add. This appears only when a model proxy is constructed directly without that runtime (an internal/advanced path)."),
|
|
276846
276846
|
model_watch_not_configured: client("claim", "watch() opens a presence/claim subscription and needs a live WebSocket, so it is unavailable on the HTTP transport and on model proxies built without a socket. Use the standard Ablo({ schema, apiKey }) client (default WebSocket transport)."),
|
|
276847
276847
|
// ── stale context / idempotency (409) ──────────────────────────────
|
|
276848
|
-
|
|
276848
|
+
// Not retryable at the transport: the rejected request carries its frozen
|
|
276849
|
+
// `readAt`, so resending the identical payload can never succeed. Recovery
|
|
276850
|
+
// is a caller-level re-read that produces a NEW request with a fresh
|
|
276851
|
+
// watermark — the same shape as `claim_conflict`.
|
|
276852
|
+
stale_context: wire("conflict", 409, false, "The row changed after you read it \u2014 the write's `readAt` watermark is older than the current row version. Re-read the row and retry."),
|
|
276849
276853
|
// Raised by the functional `update(id, current => next)` form once its
|
|
276850
276854
|
// internal reconcile budget is exhausted, because the row stayed continuously
|
|
276851
276855
|
// contended. The SDK has already retried; the caller decides whether to back
|
|
@@ -277258,6 +277262,11 @@ var claimRejectionSchema = import_zod3.z.object({
|
|
|
277258
277262
|
heldByClaim: wireClaimSummarySchema.optional(),
|
|
277259
277263
|
policyReason: import_zod3.z.string().optional()
|
|
277260
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
|
+
});
|
|
277261
277270
|
var modelTargetSchema = import_zod3.z.object({
|
|
277262
277271
|
model: import_zod3.z.string(),
|
|
277263
277272
|
id: import_zod3.z.string(),
|
|
@@ -277472,9 +277481,61 @@ var AbloError = class extends Error {
|
|
|
277472
277481
|
function docUrlForCode(code) {
|
|
277473
277482
|
return `https://docs.abloatai.com/errors#${code}`;
|
|
277474
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
|
+
};
|
|
277475
277503
|
var AbloValidationError = class extends AbloError {
|
|
277476
277504
|
type = "AbloValidationError";
|
|
277477
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
|
+
};
|
|
277478
277539
|
var OptionalWireStringSchema = import_zod4.z.preprocess(
|
|
277479
277540
|
(value) => typeof value === "string" ? value : void 0,
|
|
277480
277541
|
import_zod4.z.string().optional()
|
|
@@ -277502,7 +277563,7 @@ var ErrorFieldSchema = import_zod4.z.preprocess(
|
|
|
277502
277563
|
).catch(void 0);
|
|
277503
277564
|
var ErrorBodyShapeSchema = import_zod4.z.object({
|
|
277504
277565
|
/** The `error` field may be a flat code string, as some endpoints return,
|
|
277505
|
-
* or a nested error object, as
|
|
277566
|
+
* or a nested error object, as commit endpoints return on rejection. */
|
|
277506
277567
|
error: ErrorFieldSchema,
|
|
277507
277568
|
code: OptionalWireStringSchema,
|
|
277508
277569
|
reason: OptionalWireStringSchema,
|
|
@@ -277513,9 +277574,58 @@ var ErrorBodyShapeSchema = import_zod4.z.object({
|
|
|
277513
277574
|
heldByClaim: wireClaimSummarySchema.optional().catch(void 0),
|
|
277514
277575
|
claims: import_zod4.z.array(wireClaimSummarySchema).optional().catch(void 0)
|
|
277515
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
|
+
}
|
|
277516
277626
|
|
|
277517
277627
|
// src/cli/migrate.ts
|
|
277518
|
-
var
|
|
277628
|
+
var import_picocolors5 = __toESM(require_picocolors(), 1);
|
|
277519
277629
|
var import_fs5 = require("fs");
|
|
277520
277630
|
|
|
277521
277631
|
// node_modules/postgres/src/index.js
|
|
@@ -279684,7 +279794,7 @@ var import_source = require("@abloatai/ablo/source");
|
|
|
279684
279794
|
|
|
279685
279795
|
// src/cli/push.ts
|
|
279686
279796
|
init_cjs_shims();
|
|
279687
|
-
var
|
|
279797
|
+
var import_picocolors4 = __toESM(require_picocolors(), 1);
|
|
279688
279798
|
|
|
279689
279799
|
// src/auth/credentialPolicy.ts
|
|
279690
279800
|
init_cjs_shims();
|
|
@@ -279958,6 +280068,174 @@ function resolvePushPlan() {
|
|
|
279958
280068
|
return { flow: getMode(), apiKey: key, source };
|
|
279959
280069
|
}
|
|
279960
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
|
+
|
|
279961
280239
|
// src/cli/theme.ts
|
|
279962
280240
|
init_cjs_shims();
|
|
279963
280241
|
var RESET = "\x1B[0m";
|
|
@@ -279971,6 +280249,288 @@ function brand(label = "ablo") {
|
|
|
279971
280249
|
return `${PAPER_BG}${BLACK_FG} ${label} ${RESET}`;
|
|
279972
280250
|
}
|
|
279973
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
|
+
|
|
279974
280534
|
// src/cli/push.ts
|
|
279975
280535
|
function coerceBackfill(raw) {
|
|
279976
280536
|
if (raw === "true") return true;
|
|
@@ -279984,14 +280544,14 @@ var DEFAULT_URL = ABLO_DEFAULT_BASE_URL;
|
|
|
279984
280544
|
function fmtSignal(s) {
|
|
279985
280545
|
const sig = s;
|
|
279986
280546
|
const where = sig.field ? `${sig.model}.${sig.field}` : sig.model;
|
|
279987
|
-
let line = ` \u2022 ${
|
|
280547
|
+
let line = ` \u2022 ${import_picocolors4.default.bold(where ?? "?")} \u2014 ${sig.detail ?? ""}`;
|
|
279988
280548
|
if (sig.shadowed) {
|
|
279989
280549
|
const env = sig.shadowed.environment ?? "production";
|
|
279990
280550
|
const ver = sig.shadowed.version != null ? `v${sig.shadowed.version}` : "active";
|
|
279991
280551
|
const when = sig.shadowed.pushedAt ? new Date(sig.shadowed.pushedAt).toISOString().slice(0, 10) : "unknown date";
|
|
279992
280552
|
const by = sig.shadowed.pushedBy ? ` by ${sig.shadowed.pushedBy}` : "";
|
|
279993
280553
|
line += `
|
|
279994
|
-
${
|
|
280554
|
+
${import_picocolors4.default.dim(`\u21B3 baseline: ${env} ${ver}, pushed ${when}${by}`)}`;
|
|
279995
280555
|
}
|
|
279996
280556
|
return line;
|
|
279997
280557
|
}
|
|
@@ -280088,7 +280648,7 @@ async function loadSchema(schemaPath, exportName) {
|
|
|
280088
280648
|
const abs = (0, import_path3.resolve)(process.cwd(), schemaPath);
|
|
280089
280649
|
if (!(0, import_fs4.existsSync)(abs)) {
|
|
280090
280650
|
throw new AbloValidationError(
|
|
280091
|
-
`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>")}.`,
|
|
280092
280652
|
{ code: "cli_invalid_arguments" }
|
|
280093
280653
|
);
|
|
280094
280654
|
}
|
|
@@ -280099,7 +280659,7 @@ async function loadSchema(schemaPath, exportName) {
|
|
|
280099
280659
|
const schema = mod[exportName] ?? nested?.[exportName];
|
|
280100
280660
|
if (!schema || typeof schema !== "object" || !("models" in schema)) {
|
|
280101
280661
|
throw new AbloValidationError(
|
|
280102
|
-
`${
|
|
280662
|
+
`${import_picocolors4.default.bold(schemaPath)} has no \`${exportName}\` export that looks like a Schema. Did you \`export const ${exportName} = defineSchema({ ... })\`?`,
|
|
280103
280663
|
{ code: "cli_invalid_arguments" }
|
|
280104
280664
|
);
|
|
280105
280665
|
}
|
|
@@ -280152,7 +280712,7 @@ function localModels(schema) {
|
|
|
280152
280712
|
}
|
|
280153
280713
|
function printPlan(local, remote) {
|
|
280154
280714
|
if (!remote?.models) {
|
|
280155
|
-
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)")}
|
|
280156
280716
|
`);
|
|
280157
280717
|
return;
|
|
280158
280718
|
}
|
|
@@ -280163,36 +280723,37 @@ function printPlan(local, remote) {
|
|
|
280163
280723
|
const changed = [...local.keys()].filter((k3) => remoteMap.has(k3) && remoteMap.get(k3) !== local.get(k3));
|
|
280164
280724
|
const verLabel = remote.version != null ? `v${remote.version}` : "active";
|
|
280165
280725
|
if (added.length === 0 && removed.length === 0 && changed.length === 0) {
|
|
280166
|
-
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)`)}
|
|
280167
280727
|
`);
|
|
280168
280728
|
return;
|
|
280169
280729
|
}
|
|
280170
|
-
console.log(` ${
|
|
280171
|
-
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)")}`);
|
|
280172
280732
|
for (const k3 of changed)
|
|
280173
|
-
console.log(` ${
|
|
280174
|
-
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)")}`);
|
|
280175
280735
|
console.log("");
|
|
280176
280736
|
}
|
|
280177
|
-
async function confirmPush(args,
|
|
280737
|
+
async function confirmPush(args, target) {
|
|
280738
|
+
const env = target.confirmed?.environment ?? target.keyEnv;
|
|
280178
280739
|
const isProd = env === "production";
|
|
280179
280740
|
const tty = Boolean(process.stdout.isTTY && process.stdin.isTTY);
|
|
280180
280741
|
if (isProd && !args.yes) {
|
|
280181
280742
|
const git = schemaGitState(args.schemaPath);
|
|
280182
280743
|
if (git?.dirty && !args.allowDirty) {
|
|
280183
|
-
console.error(` ${
|
|
280184
|
-
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.`));
|
|
280185
280746
|
process.exit(1);
|
|
280186
280747
|
}
|
|
280187
280748
|
if (!tty) {
|
|
280188
|
-
console.error(` ${
|
|
280189
|
-
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.`));
|
|
280190
280751
|
process.exit(1);
|
|
280191
280752
|
}
|
|
280192
|
-
const
|
|
280193
|
-
const expected =
|
|
280753
|
+
const confirmedProject = target.confirmed?.project;
|
|
280754
|
+
const expected = confirmedProject ? confirmedProject.isDefault ? "default" : confirmedProject.slug : target.localProject?.slug ?? "production";
|
|
280194
280755
|
const typed = await he({
|
|
280195
|
-
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:`,
|
|
280196
280757
|
placeholder: expected
|
|
280197
280758
|
});
|
|
280198
280759
|
if (pD(typed) || String(typed).trim() !== expected) {
|
|
@@ -280202,32 +280763,53 @@ async function confirmPush(args, env) {
|
|
|
280202
280763
|
return;
|
|
280203
280764
|
}
|
|
280204
280765
|
if (!isProd && !args.yes && tty) {
|
|
280205
|
-
const ok = await ye({ message: `Apply to ${
|
|
280766
|
+
const ok = await ye({ message: `Apply to ${import_picocolors4.default.bold("sandbox")}?` });
|
|
280206
280767
|
if (pD(ok) || !ok) {
|
|
280207
280768
|
xe("Aborted.");
|
|
280208
280769
|
process.exit(1);
|
|
280209
280770
|
}
|
|
280210
280771
|
}
|
|
280211
280772
|
}
|
|
280212
|
-
function printPushTarget(
|
|
280213
|
-
const
|
|
280214
|
-
const
|
|
280215
|
-
const
|
|
280216
|
-
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");
|
|
280217
280777
|
const cliMode = getMode();
|
|
280218
|
-
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
|
+
}
|
|
280219
280791
|
console.log(`
|
|
280220
|
-
${brand("ablo")} ${
|
|
280221
|
-
console.log(` ${
|
|
280222
|
-
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)}`);
|
|
280223
280796
|
console.log(
|
|
280224
|
-
` ${
|
|
280797
|
+
` ${import_picocolors4.default.dim("key")} ${target.keyPrefix} ${import_picocolors4.default.dim(`(${describeKeySource(target.keySource)})`)}`
|
|
280225
280798
|
);
|
|
280226
280799
|
console.log(
|
|
280227
|
-
` ${
|
|
280800
|
+
` ${import_picocolors4.default.dim("schema")} ${import_picocolors4.default.bold(schema.path)} ${import_picocolors4.default.dim(`${schema.modelCount} models, hash ${schema.hash}`)}
|
|
280228
280801
|
`
|
|
280229
280802
|
);
|
|
280230
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
|
+
}
|
|
280231
280813
|
function describeKeySource(source) {
|
|
280232
280814
|
switch (source) {
|
|
280233
280815
|
case "env":
|
|
@@ -280245,7 +280827,7 @@ async function push(argv) {
|
|
|
280245
280827
|
try {
|
|
280246
280828
|
args = parsePushArgs(argv);
|
|
280247
280829
|
} catch (err) {
|
|
280248
|
-
console.error(
|
|
280830
|
+
console.error(import_picocolors4.default.red(` ${err instanceof Error ? err.message : String(err)}`));
|
|
280249
280831
|
process.exit(1);
|
|
280250
280832
|
}
|
|
280251
280833
|
let keySource = "env";
|
|
@@ -280256,44 +280838,43 @@ async function push(argv) {
|
|
|
280256
280838
|
}
|
|
280257
280839
|
if (!args.apiKey) {
|
|
280258
280840
|
console.error(
|
|
280259
|
-
|
|
280260
|
-
` 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()}'.`
|
|
280261
280843
|
)
|
|
280262
280844
|
);
|
|
280263
280845
|
process.exit(1);
|
|
280264
280846
|
}
|
|
280265
280847
|
const schema = await loadSchema(args.schemaPath, args.exportName);
|
|
280266
280848
|
const hash = (0, import_schema2.schemaHash)(schema);
|
|
280267
|
-
|
|
280268
|
-
|
|
280269
|
-
|
|
280270
|
-
apiKey: args.apiKey,
|
|
280271
|
-
keySource,
|
|
280849
|
+
const target = await resolveTarget({ url: args.url, apiKey: args.apiKey, keySource });
|
|
280850
|
+
printPushTarget(target, {
|
|
280851
|
+
path: args.schemaPath,
|
|
280272
280852
|
modelCount: Object.keys(schema.models).length,
|
|
280273
280853
|
hash
|
|
280274
280854
|
});
|
|
280275
280855
|
const remote = await fetchActiveSchema(args.url, args.apiKey);
|
|
280276
280856
|
printPlan(localModels(schema), remote);
|
|
280857
|
+
warnMismatches(target);
|
|
280277
280858
|
const git = schemaGitState(args.schemaPath);
|
|
280278
280859
|
if (git?.dirty) {
|
|
280279
280860
|
const what = git.untracked ? "is untracked (not committed)" : "has uncommitted changes";
|
|
280280
|
-
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.
|
|
280281
280862
|
`);
|
|
280282
280863
|
}
|
|
280283
280864
|
if (args.dryRun) {
|
|
280284
|
-
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.`);
|
|
280285
280866
|
return;
|
|
280286
280867
|
}
|
|
280287
|
-
await confirmPush(args,
|
|
280868
|
+
await confirmPush(args, target);
|
|
280288
280869
|
const { ok: resOk, status: status2, body, bodyText } = await pushSchema(schema, args);
|
|
280289
280870
|
if (resOk) {
|
|
280290
280871
|
if (body.unchanged) {
|
|
280291
|
-
console.log(` ${
|
|
280872
|
+
console.log(` ${import_picocolors4.default.dim("\u25CB")} No changes \u2014 schema already active (v${body.version}).`);
|
|
280292
280873
|
} else {
|
|
280293
|
-
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})`)}`);
|
|
280294
280875
|
if (Array.isArray(body.warnings) && body.warnings.length > 0) {
|
|
280295
|
-
console.log(
|
|
280296
|
-
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)));
|
|
280297
280878
|
}
|
|
280298
280879
|
}
|
|
280299
280880
|
return;
|
|
@@ -280301,75 +280882,75 @@ async function push(argv) {
|
|
|
280301
280882
|
if (status2 === 409) {
|
|
280302
280883
|
const unexecutable = Array.isArray(body.unexecutable) ? body.unexecutable : [];
|
|
280303
280884
|
const warnings = Array.isArray(body.warnings) ? body.warnings : [];
|
|
280304
|
-
console.error(
|
|
280885
|
+
console.error(import_picocolors4.default.red(" Incompatible change \u2014 this push is not safe to apply as-is."));
|
|
280305
280886
|
if (unexecutable.length > 0) {
|
|
280306
|
-
console.error(
|
|
280307
|
-
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)));
|
|
280308
280889
|
}
|
|
280309
280890
|
if (warnings.length > 0) {
|
|
280310
|
-
console.error(
|
|
280311
|
-
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)));
|
|
280312
280893
|
}
|
|
280313
280894
|
const hasShadowed = [...unexecutable, ...warnings].some(
|
|
280314
280895
|
(s) => s.shadowed != null
|
|
280315
280896
|
);
|
|
280316
280897
|
if (hasShadowed) {
|
|
280317
280898
|
console.error(
|
|
280318
|
-
|
|
280899
|
+
import_picocolors4.default.dim(
|
|
280319
280900
|
" These models exist in the baseline above but not in your push. Sandbox readers fall"
|
|
280320
280901
|
)
|
|
280321
280902
|
);
|
|
280322
280903
|
console.error(
|
|
280323
|
-
|
|
280904
|
+
import_picocolors4.default.dim(
|
|
280324
280905
|
" back to the production schema until you push your own, so applying this drops them."
|
|
280325
280906
|
)
|
|
280326
280907
|
);
|
|
280327
280908
|
}
|
|
280328
|
-
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.`));
|
|
280329
280910
|
} else if (status2 === 403) {
|
|
280330
280911
|
const code = body.code ?? body.reason;
|
|
280331
280912
|
const serverMsg = body.message ?? body.reason;
|
|
280332
|
-
console.error(
|
|
280333
|
-
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)}.`));
|
|
280334
280915
|
if (code === "database_role_cannot_enforce_rls") {
|
|
280335
280916
|
console.error(
|
|
280336
|
-
|
|
280337
|
-
` 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.`
|
|
280338
280919
|
)
|
|
280339
280920
|
);
|
|
280340
280921
|
} else if (code === "database_tables_unforced_rls") {
|
|
280341
280922
|
console.error(
|
|
280342
|
-
|
|
280343
|
-
` 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.`
|
|
280344
280925
|
)
|
|
280345
280926
|
);
|
|
280346
280927
|
} else if (code === "capability_scope_denied" || code === "capability_invalid") {
|
|
280347
280928
|
console.error(
|
|
280348
|
-
|
|
280349
|
-
` 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.`
|
|
280350
280931
|
)
|
|
280351
280932
|
);
|
|
280352
280933
|
} else if (code === "schema_provisioning_forbidden") {
|
|
280353
280934
|
console.error(
|
|
280354
|
-
|
|
280355
|
-
` 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.`
|
|
280356
280937
|
)
|
|
280357
280938
|
);
|
|
280358
280939
|
} else if (args.apiKey != null && classifyCredentialKind(args.apiKey) === "restricted") {
|
|
280359
280940
|
console.error(
|
|
280360
|
-
|
|
280361
|
-
` 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")}).`
|
|
280362
280943
|
)
|
|
280363
280944
|
);
|
|
280364
280945
|
} else {
|
|
280365
280946
|
console.error(
|
|
280366
|
-
|
|
280367
|
-
` 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`
|
|
280368
280949
|
)
|
|
280369
280950
|
);
|
|
280370
280951
|
}
|
|
280371
280952
|
} else {
|
|
280372
|
-
console.error(
|
|
280953
|
+
console.error(import_picocolors4.default.red(` Push failed (${status2}): ${body.message ?? body.reason ?? bodyText}`));
|
|
280373
280954
|
}
|
|
280374
280955
|
process.exit(1);
|
|
280375
280956
|
}
|
|
@@ -280432,10 +281013,10 @@ var log = {
|
|
|
280432
281013
|
console.log(`[migrate] ${msg}`, fields);
|
|
280433
281014
|
},
|
|
280434
281015
|
warn: (msg, fields) => {
|
|
280435
|
-
console.warn(
|
|
281016
|
+
console.warn(import_picocolors5.default.yellow(`[migrate] ${msg}`), fields);
|
|
280436
281017
|
},
|
|
280437
281018
|
error: (msg, fields) => {
|
|
280438
|
-
console.error(
|
|
281019
|
+
console.error(import_picocolors5.default.red(`[migrate] ${msg}`), fields);
|
|
280439
281020
|
}
|
|
280440
281021
|
};
|
|
280441
281022
|
async function applyStatements(dbUrl, targetSchema, statements, concurrent = []) {
|
|
@@ -280509,7 +281090,7 @@ async function migrate(argv) {
|
|
|
280509
281090
|
try {
|
|
280510
281091
|
args = parseMigrateArgs(argv);
|
|
280511
281092
|
} catch (err) {
|
|
280512
|
-
console.error(
|
|
281093
|
+
console.error(import_picocolors5.default.red(` ${err instanceof Error ? err.message : String(err)}`));
|
|
280513
281094
|
process.exit(1);
|
|
280514
281095
|
}
|
|
280515
281096
|
const schema = await loadSchema(args.schemaPath, args.exportName);
|
|
@@ -280520,11 +281101,11 @@ async function migrate(argv) {
|
|
|
280520
281101
|
].join("\n");
|
|
280521
281102
|
const totalStatements = plan.statements.length + plan.concurrent.length;
|
|
280522
281103
|
console.log(
|
|
280523
|
-
` ${
|
|
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`)}`
|
|
280524
281105
|
);
|
|
280525
281106
|
if (args.outputFile) {
|
|
280526
281107
|
(0, import_fs5.writeFileSync)(args.outputFile, sql + "\n");
|
|
280527
|
-
console.log(` ${
|
|
281108
|
+
console.log(` ${import_picocolors5.default.green("\u2713")} SQL written to ${import_picocolors5.default.bold(args.outputFile)}`);
|
|
280528
281109
|
return;
|
|
280529
281110
|
}
|
|
280530
281111
|
if (args.dryRun) {
|
|
@@ -280534,7 +281115,7 @@ async function migrate(argv) {
|
|
|
280534
281115
|
const dbUrl = readProjectDatabaseUrl();
|
|
280535
281116
|
if (!dbUrl) {
|
|
280536
281117
|
console.error(
|
|
280537
|
-
|
|
281118
|
+
import_picocolors5.default.red(
|
|
280538
281119
|
" No DATABASE_URL found (checked process env, .env.local, .env). Set it to apply, or use --dry-run to preview."
|
|
280539
281120
|
)
|
|
280540
281121
|
);
|
|
@@ -280542,7 +281123,7 @@ async function migrate(argv) {
|
|
|
280542
281123
|
}
|
|
280543
281124
|
try {
|
|
280544
281125
|
await applyStatements(dbUrl, args.targetSchema, plan.statements, plan.concurrent);
|
|
280545
|
-
console.log(` ${
|
|
281126
|
+
console.log(` ${import_picocolors5.default.green("\u2713")} Migration complete`);
|
|
280546
281127
|
} catch {
|
|
280547
281128
|
process.exit(1);
|
|
280548
281129
|
}
|
|
@@ -280550,7 +281131,7 @@ async function migrate(argv) {
|
|
|
280550
281131
|
|
|
280551
281132
|
// src/cli/connect.ts
|
|
280552
281133
|
init_cjs_shims();
|
|
280553
|
-
var
|
|
281134
|
+
var import_picocolors6 = __toESM(require_picocolors(), 1);
|
|
280554
281135
|
var ABLO_PUBLICATION = "ablo_publication";
|
|
280555
281136
|
var ABLO_REPLICATION_ROLE = "ablo_replicator";
|
|
280556
281137
|
function parseConnectArgs(argv) {
|
|
@@ -280607,50 +281188,50 @@ function connectSetupSql(input) {
|
|
|
280607
281188
|
function printConnectRecipe(args) {
|
|
280608
281189
|
const sql = connectSetupSql({ tables: args.tables, role: args.role });
|
|
280609
281190
|
console.log(`
|
|
280610
|
-
${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)")}
|
|
280611
281192
|
`);
|
|
280612
281193
|
console.log(
|
|
280613
|
-
` 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,
|
|
280614
281195
|
owns, hosts, or migrates your schema. Your app continues to own the write path \u2014 Ablo tails
|
|
280615
|
-
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)")}:
|
|
280616
281197
|
`
|
|
280617
281198
|
);
|
|
280618
|
-
console.log(` ${
|
|
280619
|
-
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])}`);
|
|
280620
281201
|
console.log(
|
|
280621
|
-
|
|
280622
|
-
` 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
|
|
280623
281204
|
instance's parameter group instead, then reboot.`
|
|
280624
281205
|
)
|
|
280625
281206
|
);
|
|
280626
281207
|
console.log(`
|
|
280627
|
-
${
|
|
280628
|
-
console.log(` ${
|
|
281208
|
+
${import_picocolors6.default.bold("2.")} Publish the tables Ablo should read`);
|
|
281209
|
+
console.log(` ${import_picocolors6.default.cyan(sql[1])}`);
|
|
280629
281210
|
if (args.tables.length === 0) {
|
|
280630
|
-
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.)`));
|
|
280631
281212
|
}
|
|
280632
281213
|
console.log(`
|
|
280633
|
-
${
|
|
280634
|
-
console.log(` ${
|
|
280635
|
-
console.log(` ${
|
|
280636
|
-
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])}`);
|
|
280637
281218
|
console.log(
|
|
280638
|
-
|
|
281219
|
+
import_picocolors6.default.dim(
|
|
280639
281220
|
` On Amazon RDS, the REPLICATION attribute is granted, not set directly:
|
|
280640
|
-
${
|
|
281221
|
+
${import_picocolors6.default.bold(`GRANT rds_replication TO ${quoteIdent(args.role)};`)}`
|
|
280641
281222
|
)
|
|
280642
281223
|
);
|
|
280643
281224
|
console.log(
|
|
280644
281225
|
`
|
|
280645
|
-
${
|
|
280646
|
-
${
|
|
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")}
|
|
280647
281228
|
`
|
|
280648
281229
|
);
|
|
280649
281230
|
console.log(
|
|
280650
|
-
|
|
281231
|
+
import_picocolors6.default.dim(
|
|
280651
281232
|
` Reminder: this is the READ path \u2014 Ablo tails your WAL and never writes to your database.
|
|
280652
281233
|
Your writes keep going through your own backend. (Ablo HOSTING your rows, or dialing in via
|
|
280653
|
-
${
|
|
281234
|
+
${import_picocolors6.default.bold("databaseUrl")}, is the deprecated posture \u2014 see the read-path decision doc.)`
|
|
280654
281235
|
)
|
|
280655
281236
|
);
|
|
280656
281237
|
console.log();
|
|
@@ -280668,11 +281249,11 @@ var SYNC_INFRA_TYPES = [
|
|
|
280668
281249
|
];
|
|
280669
281250
|
function printCheckItem(item) {
|
|
280670
281251
|
if (item.ok) {
|
|
280671
|
-
console.log(` ${
|
|
281252
|
+
console.log(` ${import_picocolors6.default.green("\u2713")} ${item.label}`);
|
|
280672
281253
|
} else {
|
|
280673
|
-
console.log(` ${
|
|
281254
|
+
console.log(` ${import_picocolors6.default.red("\u2717")} ${item.label}`);
|
|
280674
281255
|
if (item.fix) {
|
|
280675
|
-
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}`);
|
|
280676
281257
|
}
|
|
280677
281258
|
}
|
|
280678
281259
|
}
|
|
@@ -280684,9 +281265,9 @@ async function probeReadiness(sql, opts = {}) {
|
|
|
280684
281265
|
);
|
|
280685
281266
|
const walLevel = walRows[0]?.setting ?? "unknown";
|
|
280686
281267
|
items.push(
|
|
280687
|
-
walLevel === "logical" ? { ok: true, label: `wal_level is ${
|
|
281268
|
+
walLevel === "logical" ? { ok: true, label: `wal_level is ${import_picocolors6.default.bold("logical")}` } : {
|
|
280688
281269
|
ok: false,
|
|
280689
|
-
label: `wal_level is ${
|
|
281270
|
+
label: `wal_level is ${import_picocolors6.default.bold(walLevel)} (need ${import_picocolors6.default.bold("logical")})`,
|
|
280690
281271
|
fix: `ALTER SYSTEM SET wal_level = 'logical'; then RESTART Postgres.
|
|
280691
281272
|
On RDS/Aurora set rds.logical_replication = 1 in the parameter group, then reboot.
|
|
280692
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.`
|
|
@@ -280700,10 +281281,10 @@ On Neon enable Logical Replication in the project (Console \u2192 Settings \u219
|
|
|
280700
281281
|
items.push(
|
|
280701
281282
|
pubRow ? {
|
|
280702
281283
|
ok: true,
|
|
280703
|
-
label: `publication ${
|
|
281284
|
+
label: `publication ${import_picocolors6.default.bold(publication)} exists ${import_picocolors6.default.dim(pubRow.puballtables ? "(all tables)" : "(table subset)")}`
|
|
280704
281285
|
} : {
|
|
280705
281286
|
ok: false,
|
|
280706
|
-
label: `publication ${
|
|
281287
|
+
label: `publication ${import_picocolors6.default.bold(publication)} not found`,
|
|
280707
281288
|
fix: `CREATE PUBLICATION ${quoteIdent(publication)} FOR ALL TABLES;`
|
|
280708
281289
|
}
|
|
280709
281290
|
);
|
|
@@ -280713,9 +281294,9 @@ On Neon enable Logical Replication in the project (Console \u2192 Settings \u219
|
|
|
280713
281294
|
const role = roleRows[0];
|
|
280714
281295
|
const hasReplication = Boolean(role && (role.rolreplication || role.rolsuper));
|
|
280715
281296
|
items.push(
|
|
280716
|
-
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)")}` } : {
|
|
280717
281298
|
ok: false,
|
|
280718
|
-
label: `DATABASE_URL role lacks the ${
|
|
281299
|
+
label: `DATABASE_URL role lacks the ${import_picocolors6.default.bold("REPLICATION")} attribute`,
|
|
280719
281300
|
fix: `ALTER ROLE current_user WITH REPLICATION;
|
|
280720
281301
|
On RDS: GRANT rds_replication TO <your_role>;`
|
|
280721
281302
|
}
|
|
@@ -280773,7 +281354,7 @@ function requireDatabaseUrl(verb) {
|
|
|
280773
281354
|
const dbUrl = readProjectDatabaseUrl();
|
|
280774
281355
|
if (!dbUrl) {
|
|
280775
281356
|
console.error(
|
|
280776
|
-
|
|
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}`)}.`)
|
|
280777
281358
|
);
|
|
280778
281359
|
process.exit(1);
|
|
280779
281360
|
}
|
|
@@ -280787,7 +281368,7 @@ async function probeAndReport(dbUrl) {
|
|
|
280787
281368
|
items = await probeReadiness(sql);
|
|
280788
281369
|
} catch (err) {
|
|
280789
281370
|
const pg = err ?? {};
|
|
280790
|
-
console.error(
|
|
281371
|
+
console.error(import_picocolors6.default.red(` Couldn't read the database: ${pg.message ?? String(err)}`));
|
|
280791
281372
|
await sql.end({ timeout: 2 });
|
|
280792
281373
|
process.exit(1);
|
|
280793
281374
|
}
|
|
@@ -280798,17 +281379,17 @@ async function probeAndReport(dbUrl) {
|
|
|
280798
281379
|
async function runCheck() {
|
|
280799
281380
|
const dbUrl = requireDatabaseUrl("--check");
|
|
280800
281381
|
console.log(`
|
|
280801
|
-
${brand("ablo")} ${
|
|
281382
|
+
${brand("ablo")} ${import_picocolors6.default.dim("connect --check")} ${import_picocolors6.default.dim("logical-replication readiness")}
|
|
280802
281383
|
`);
|
|
280803
281384
|
const failures = await probeAndReport(dbUrl);
|
|
280804
281385
|
console.log();
|
|
280805
281386
|
if (failures === 0) {
|
|
280806
|
-
console.log(` ${
|
|
281387
|
+
console.log(` ${import_picocolors6.default.green("\u2713")} Ready \u2014 Ablo can connect and tail this database's WAL.
|
|
280807
281388
|
`);
|
|
280808
281389
|
process.exit(0);
|
|
280809
281390
|
}
|
|
280810
281391
|
console.log(
|
|
280811
|
-
` ${
|
|
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")}.`)}
|
|
280812
281393
|
`
|
|
280813
281394
|
);
|
|
280814
281395
|
process.exit(1);
|
|
@@ -280821,18 +281402,18 @@ async function runRegister() {
|
|
|
280821
281402
|
const apiKey = resolveApiKey();
|
|
280822
281403
|
if (!apiKey) {
|
|
280823
281404
|
console.error(
|
|
280824
|
-
|
|
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.`)
|
|
280825
281406
|
);
|
|
280826
281407
|
process.exit(1);
|
|
280827
281408
|
}
|
|
280828
281409
|
console.log(`
|
|
280829
|
-
${brand("ablo")} ${
|
|
281410
|
+
${brand("ablo")} ${import_picocolors6.default.dim("connect --register")} ${import_picocolors6.default.dim("register this database for replication")}
|
|
280830
281411
|
`);
|
|
280831
281412
|
const failures = await probeAndReport(dbUrl);
|
|
280832
281413
|
if (failures > 0) {
|
|
280833
281414
|
console.log(
|
|
280834
281415
|
`
|
|
280835
|
-
${
|
|
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.")}
|
|
280836
281417
|
`
|
|
280837
281418
|
);
|
|
280838
281419
|
process.exit(1);
|
|
@@ -280846,7 +281427,7 @@ async function runRegister() {
|
|
|
280846
281427
|
body: JSON.stringify({ connectionString: dbUrl })
|
|
280847
281428
|
});
|
|
280848
281429
|
} catch (err) {
|
|
280849
|
-
console.error(
|
|
281430
|
+
console.error(import_picocolors6.default.red(`
|
|
280850
281431
|
Couldn't reach ${apiUrl2}: ${err instanceof Error ? err.message : String(err)}
|
|
280851
281432
|
`));
|
|
280852
281433
|
process.exit(1);
|
|
@@ -280855,7 +281436,7 @@ async function runRegister() {
|
|
|
280855
281436
|
const body2 = await res.json().catch(() => ({}));
|
|
280856
281437
|
console.log(
|
|
280857
281438
|
`
|
|
280858
|
-
${
|
|
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.
|
|
280859
281440
|
`
|
|
280860
281441
|
);
|
|
280861
281442
|
process.exit(0);
|
|
@@ -280863,28 +281444,28 @@ async function runRegister() {
|
|
|
280863
281444
|
const body = await res.json().catch(() => ({}));
|
|
280864
281445
|
const code = body.code ?? body.error?.code;
|
|
280865
281446
|
const message = body.message ?? body.error?.message ?? `HTTP ${res.status}`;
|
|
280866
|
-
console.error(
|
|
281447
|
+
console.error(import_picocolors6.default.red(`
|
|
280867
281448
|
Registration failed: ${message}`));
|
|
280868
281449
|
if (code === "forbidden") {
|
|
280869
|
-
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.`));
|
|
280870
281451
|
} else if (code === "datasource_connection_unsupported") {
|
|
280871
281452
|
console.error(
|
|
280872
|
-
|
|
281453
|
+
import_picocolors6.default.dim(` This deployment can\u2019t accept connection strings \u2014 use a self-hosted/hosted engine, or the signed endpoint fallback.`)
|
|
280873
281454
|
);
|
|
280874
281455
|
} else if (code === "database_not_replication_ready") {
|
|
280875
281456
|
for (const f of body.details?.failures ?? []) {
|
|
280876
|
-
console.error(` ${
|
|
280877
|
-
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}`);
|
|
280878
281459
|
}
|
|
280879
|
-
console.error(
|
|
280880
|
-
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.`));
|
|
280881
281462
|
} else if (code === "database_unreachable") {
|
|
280882
|
-
if (body.details?.reason) console.error(
|
|
281463
|
+
if (body.details?.reason) console.error(import_picocolors6.default.dim(` ${body.details.reason}`));
|
|
280883
281464
|
console.error(
|
|
280884
|
-
|
|
281465
|
+
import_picocolors6.default.dim(
|
|
280885
281466
|
` Ablo's servers must be able to reach this database \u2014 a localhost or private-network
|
|
280886
281467
|
Postgres can't be replicated by the hosted service. Use a reachable host (or a tunnel),
|
|
280887
|
-
or the signed ${
|
|
281468
|
+
or the signed ${import_picocolors6.default.bold("dataSource()")} endpoint fallback.`
|
|
280888
281469
|
)
|
|
280889
281470
|
);
|
|
280890
281471
|
}
|
|
@@ -280900,27 +281481,27 @@ async function runAuditInfra() {
|
|
|
280900
281481
|
artifacts = await auditTenantSyncInfra(sql);
|
|
280901
281482
|
} catch (err) {
|
|
280902
281483
|
const pg = err ?? {};
|
|
280903
|
-
console.error(
|
|
281484
|
+
console.error(import_picocolors6.default.red(` Couldn't audit the database: ${pg.message ?? String(err)}`));
|
|
280904
281485
|
await sql.end({ timeout: 2 });
|
|
280905
281486
|
process.exit(1);
|
|
280906
281487
|
}
|
|
280907
281488
|
await sql.end({ timeout: 2 });
|
|
280908
281489
|
console.log(`
|
|
280909
|
-
${brand("ablo")} ${
|
|
281490
|
+
${brand("ablo")} ${import_picocolors6.default.dim("connect --audit-infra")} ${import_picocolors6.default.dim("Stage 5 tenant DB sync-infra audit")}
|
|
280910
281491
|
`);
|
|
280911
281492
|
const present = artifacts.filter((a) => a.present);
|
|
280912
281493
|
if (present.length === 0) {
|
|
280913
|
-
console.log(` ${
|
|
281494
|
+
console.log(` ${import_picocolors6.default.green("\u2713")} No deprecated Ablo sync infrastructure found in public.
|
|
280914
281495
|
`);
|
|
280915
281496
|
process.exit(0);
|
|
280916
281497
|
}
|
|
280917
281498
|
for (const artifact of present) {
|
|
280918
281499
|
const label = artifact.kind === "type" ? "type" : "relation";
|
|
280919
|
-
console.log(` ${
|
|
281500
|
+
console.log(` ${import_picocolors6.default.yellow("!")} ${label} ${import_picocolors6.default.bold(`public.${artifact.name}`)} exists`);
|
|
280920
281501
|
}
|
|
280921
281502
|
console.log(
|
|
280922
281503
|
`
|
|
280923
|
-
${
|
|
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")
|
|
280924
281505
|
);
|
|
280925
281506
|
process.exit(1);
|
|
280926
281507
|
}
|
|
@@ -280929,7 +281510,7 @@ async function connect(argv) {
|
|
|
280929
281510
|
try {
|
|
280930
281511
|
args = parseConnectArgs(argv);
|
|
280931
281512
|
} catch (err) {
|
|
280932
|
-
console.error(
|
|
281513
|
+
console.error(import_picocolors6.default.red(` ${err instanceof Error ? err.message : String(err)}`));
|
|
280933
281514
|
process.exit(1);
|
|
280934
281515
|
}
|
|
280935
281516
|
if (args.check) {
|
|
@@ -280963,7 +281544,7 @@ var CONNECT_USAGE = ` ablo connect \u2014 connect your database for the read pa
|
|
|
280963
281544
|
init_cjs_shims();
|
|
280964
281545
|
var import_fs6 = require("fs");
|
|
280965
281546
|
var import_path4 = require("path");
|
|
280966
|
-
var
|
|
281547
|
+
var import_picocolors7 = __toESM(require_picocolors(), 1);
|
|
280967
281548
|
var import_schema4 = require("@abloatai/ablo/schema");
|
|
280968
281549
|
var DEFAULT_SCHEMA_PATH3 = "ablo/schema.ts";
|
|
280969
281550
|
var DEFAULT_EXPORT3 = "schema";
|
|
@@ -280995,7 +281576,7 @@ async function generate(argv) {
|
|
|
280995
281576
|
try {
|
|
280996
281577
|
args = parseGenerateArgs(argv);
|
|
280997
281578
|
} catch (err) {
|
|
280998
|
-
console.error(
|
|
281579
|
+
console.error(import_picocolors7.default.red(` ${err instanceof Error ? err.message : String(err)}`));
|
|
280999
281580
|
process.exit(1);
|
|
281000
281581
|
}
|
|
281001
281582
|
let source;
|
|
@@ -281004,18 +281585,18 @@ async function generate(argv) {
|
|
|
281004
281585
|
const schemaJson = JSON.parse((0, import_schema4.serializeSchema)(schema));
|
|
281005
281586
|
source = (0, import_schema4.generateTypes)(schemaJson);
|
|
281006
281587
|
} catch (err) {
|
|
281007
|
-
console.error(
|
|
281588
|
+
console.error(import_picocolors7.default.red(` ${err instanceof Error ? err.message : String(err)}`));
|
|
281008
281589
|
process.exit(1);
|
|
281009
281590
|
}
|
|
281010
281591
|
const abs = (0, import_path4.resolve)(process.cwd(), args.out);
|
|
281011
281592
|
(0, import_fs6.mkdirSync)((0, import_path4.dirname)(abs), { recursive: true });
|
|
281012
281593
|
(0, import_fs6.writeFileSync)(abs, source);
|
|
281013
|
-
console.log(` ${
|
|
281594
|
+
console.log(` ${import_picocolors7.default.green("\u2713")} Generated types \u2192 ${import_picocolors7.default.bold(args.out)}`);
|
|
281014
281595
|
}
|
|
281015
281596
|
|
|
281016
281597
|
// src/cli/dev.ts
|
|
281017
281598
|
init_cjs_shims();
|
|
281018
|
-
var
|
|
281599
|
+
var import_picocolors8 = __toESM(require_picocolors(), 1);
|
|
281019
281600
|
var import_fs7 = require("fs");
|
|
281020
281601
|
var import_path5 = require("path");
|
|
281021
281602
|
var import_schema5 = require("@abloatai/ablo/schema");
|
|
@@ -281053,25 +281634,25 @@ function classifyKey(apiKey, activeMode) {
|
|
|
281053
281634
|
if (!apiKey) {
|
|
281054
281635
|
return {
|
|
281055
281636
|
ok: false,
|
|
281056
|
-
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}'.`)
|
|
281057
281638
|
};
|
|
281058
281639
|
}
|
|
281059
281640
|
if (apiKey.startsWith("sk_test_")) return { ok: true };
|
|
281060
281641
|
if (apiKey.startsWith("sk_live_")) {
|
|
281061
281642
|
return {
|
|
281062
281643
|
ok: false,
|
|
281063
|
-
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.`
|
|
281064
281645
|
};
|
|
281065
281646
|
}
|
|
281066
281647
|
if (classifyCredentialKind(apiKey) === "restricted") {
|
|
281067
281648
|
return {
|
|
281068
281649
|
ok: false,
|
|
281069
|
-
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.`
|
|
281070
281651
|
};
|
|
281071
281652
|
}
|
|
281072
281653
|
return {
|
|
281073
281654
|
ok: false,
|
|
281074
|
-
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")}).`
|
|
281075
281656
|
};
|
|
281076
281657
|
}
|
|
281077
281658
|
function wireEnvLocal(apiKey, cwd = process.cwd()) {
|
|
@@ -281081,7 +281662,7 @@ function wireEnvLocal(apiKey, cwd = process.cwd()) {
|
|
|
281081
281662
|
if (!(0, import_fs7.existsSync)(envPath)) {
|
|
281082
281663
|
(0, import_fs7.writeFileSync)(envPath, `${line}
|
|
281083
281664
|
`, { mode: 384 });
|
|
281084
|
-
action = `Created ${
|
|
281665
|
+
action = `Created ${import_picocolors8.default.bold(".env.local")} with ${import_picocolors8.default.bold("ABLO_API_KEY")}`;
|
|
281085
281666
|
} else {
|
|
281086
281667
|
const content = (0, import_fs7.readFileSync)(envPath, "utf8");
|
|
281087
281668
|
const match = /^ABLO_API_KEY=(.*)$/m.exec(content);
|
|
@@ -281089,12 +281670,12 @@ function wireEnvLocal(apiKey, cwd = process.cwd()) {
|
|
|
281089
281670
|
if (!match) {
|
|
281090
281671
|
(0, import_fs7.appendFileSync)(envPath, `${content.endsWith("\n") || content.length === 0 ? "" : "\n"}${line}
|
|
281091
281672
|
`);
|
|
281092
|
-
action = `Added ${
|
|
281673
|
+
action = `Added ${import_picocolors8.default.bold("ABLO_API_KEY")} to ${import_picocolors8.default.bold(".env.local")}`;
|
|
281093
281674
|
} else if (existing === apiKey) {
|
|
281094
|
-
action = `${
|
|
281675
|
+
action = `${import_picocolors8.default.bold(".env.local")} already has this key`;
|
|
281095
281676
|
} else {
|
|
281096
281677
|
(0, import_fs7.writeFileSync)(envPath, content.replace(/^ABLO_API_KEY=.*$/m, line));
|
|
281097
|
-
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)`)}`;
|
|
281098
281679
|
}
|
|
281099
281680
|
}
|
|
281100
281681
|
const gitignorePath = (0, import_path5.resolve)(cwd, ".gitignore");
|
|
@@ -281108,7 +281689,7 @@ function wireEnvLocal(apiKey, cwd = process.cwd()) {
|
|
|
281108
281689
|
`}.env.local
|
|
281109
281690
|
`
|
|
281110
281691
|
);
|
|
281111
|
-
gitignoreNote = ` Added ${
|
|
281692
|
+
gitignoreNote = ` Added ${import_picocolors8.default.bold(".env.local")} to ${import_picocolors8.default.bold(".gitignore")} so the key can't be committed.`;
|
|
281112
281693
|
}
|
|
281113
281694
|
return `${action}.${gitignoreNote}`;
|
|
281114
281695
|
}
|
|
@@ -281123,7 +281704,7 @@ async function runPush(schema, args) {
|
|
|
281123
281704
|
if (ok) {
|
|
281124
281705
|
return {
|
|
281125
281706
|
ok: true,
|
|
281126
|
-
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})`)}`
|
|
281127
281708
|
};
|
|
281128
281709
|
}
|
|
281129
281710
|
if (status2 === 409) {
|
|
@@ -281133,33 +281714,33 @@ async function runPush(schema, args) {
|
|
|
281133
281714
|
(s) => s.shadowed != null
|
|
281134
281715
|
);
|
|
281135
281716
|
const lines = [
|
|
281136
|
-
|
|
281717
|
+
import_picocolors8.default.bold("Incompatible schema change \u2014 not safe to apply as-is."),
|
|
281137
281718
|
"",
|
|
281138
|
-
...unexecutable.map((u2) =>
|
|
281139
|
-
...warnings.map((w2) =>
|
|
281719
|
+
...unexecutable.map((u2) => import_picocolors8.default.red(fmtSignal(u2))),
|
|
281720
|
+
...warnings.map((w2) => import_picocolors8.default.yellow(fmtSignal(w2))),
|
|
281140
281721
|
"",
|
|
281141
281722
|
...hasShadowed ? [
|
|
281142
|
-
|
|
281723
|
+
import_picocolors8.default.dim(
|
|
281143
281724
|
" These models exist in the baseline above but not in your push. Sandbox readers"
|
|
281144
281725
|
),
|
|
281145
|
-
|
|
281726
|
+
import_picocolors8.default.dim(
|
|
281146
281727
|
" fall back to the production schema until you push your own, so applying this drops them."
|
|
281147
281728
|
),
|
|
281148
281729
|
""
|
|
281149
281730
|
] : [],
|
|
281150
|
-
|
|
281151
|
-
` 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.`
|
|
281152
281733
|
)
|
|
281153
281734
|
];
|
|
281154
281735
|
return { ok: false, message: lines.join("\n") };
|
|
281155
281736
|
}
|
|
281156
281737
|
if (status2 === 403) {
|
|
281157
281738
|
const serverSays = body.message ?? body.reason;
|
|
281158
|
-
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")}.`;
|
|
281159
281740
|
return {
|
|
281160
281741
|
ok: false,
|
|
281161
281742
|
message: `${serverSays ?? "This key can't author schema (missing schema:push scope)."}
|
|
281162
|
-
` +
|
|
281743
|
+
` + import_picocolors8.default.dim(hint)
|
|
281163
281744
|
};
|
|
281164
281745
|
}
|
|
281165
281746
|
return { ok: false, message: `Push failed (${status2}): ${body.message ?? body.reason ?? bodyText}` };
|
|
@@ -281169,25 +281750,25 @@ async function dev(argv) {
|
|
|
281169
281750
|
try {
|
|
281170
281751
|
args = parseDevArgs(argv);
|
|
281171
281752
|
} catch (err) {
|
|
281172
|
-
console.error(
|
|
281753
|
+
console.error(import_picocolors8.default.red(` ${err instanceof Error ? err.message : String(err)}`));
|
|
281173
281754
|
process.exit(1);
|
|
281174
281755
|
}
|
|
281175
281756
|
if (!args.apiKey) args.apiKey = resolveEffectiveApiKey("sandbox").key;
|
|
281176
281757
|
const key = classifyKey(args.apiKey, getMode());
|
|
281177
281758
|
if (!key.ok) {
|
|
281178
|
-
console.error(
|
|
281759
|
+
console.error(import_picocolors8.default.red(` ${key.reason}`));
|
|
281179
281760
|
process.exit(1);
|
|
281180
281761
|
}
|
|
281181
281762
|
console.log(`
|
|
281182
|
-
${brand("ablo")} ${
|
|
281763
|
+
${brand("ablo")} ${import_picocolors8.default.dim("push")} ${import_picocolors8.default.dim("(sandbox)")}
|
|
281183
281764
|
`);
|
|
281184
281765
|
const schema = await loadSchema(args.schemaPath, args.exportName);
|
|
281185
281766
|
const modelCount = Object.keys(schema.models).length;
|
|
281186
281767
|
console.log(
|
|
281187
|
-
` ${
|
|
281768
|
+
` ${import_picocolors8.default.dim("schema")} ${import_picocolors8.default.bold(args.schemaPath)} ${import_picocolors8.default.dim(`(${modelCount} models, hash ${(0, import_schema5.schemaHash)(schema)})`)}`
|
|
281188
281769
|
);
|
|
281189
|
-
console.log(` ${
|
|
281190
|
-
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}
|
|
281191
281772
|
`);
|
|
281192
281773
|
const s = Y2();
|
|
281193
281774
|
s.start("Pushing schema definition (sandbox)");
|
|
@@ -281196,16 +281777,16 @@ async function dev(argv) {
|
|
|
281196
281777
|
if (!first.ok) process.exit(1);
|
|
281197
281778
|
if (process.env.ABLO_API_KEY) {
|
|
281198
281779
|
console.log(`
|
|
281199
|
-
${
|
|
281780
|
+
${import_picocolors8.default.green("\u2713")} ${import_picocolors8.default.bold("ABLO_API_KEY")} is set in this shell \u2014 the SDK reads it directly.`);
|
|
281200
281781
|
} else {
|
|
281201
281782
|
console.log(`
|
|
281202
|
-
${
|
|
281203
|
-
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")}`);
|
|
281204
281785
|
}
|
|
281205
281786
|
console.log(` Your app is wired for the sandbox.`);
|
|
281206
281787
|
if (!args.watch) return;
|
|
281207
281788
|
const abs = (0, import_path5.resolve)(process.cwd(), args.schemaPath);
|
|
281208
|
-
console.log(` ${
|
|
281789
|
+
console.log(` ${import_picocolors8.default.dim(`watching ${args.schemaPath} \u2026 (Ctrl-C to stop)`)}
|
|
281209
281790
|
`);
|
|
281210
281791
|
let timer2 = null;
|
|
281211
281792
|
let pushing = false;
|
|
@@ -281225,7 +281806,7 @@ async function dev(argv) {
|
|
|
281225
281806
|
const r2 = await runPush(next, args);
|
|
281226
281807
|
s2.stop(r2.message, r2.ok ? 0 : 1);
|
|
281227
281808
|
} catch (err) {
|
|
281228
|
-
s2.stop(
|
|
281809
|
+
s2.stop(import_picocolors8.default.red(`schema reload failed: ${err instanceof Error ? err.message : String(err)}`), 1);
|
|
281229
281810
|
} finally {
|
|
281230
281811
|
pushing = false;
|
|
281231
281812
|
}
|
|
@@ -281233,7 +281814,7 @@ async function dev(argv) {
|
|
|
281233
281814
|
const stop = () => {
|
|
281234
281815
|
watcher.close();
|
|
281235
281816
|
console.log(`
|
|
281236
|
-
${
|
|
281817
|
+
${import_picocolors8.default.dim("stopped.")}`);
|
|
281237
281818
|
process.exit(0);
|
|
281238
281819
|
};
|
|
281239
281820
|
process.on("SIGINT", stop);
|
|
@@ -281245,7 +281826,7 @@ async function dev(argv) {
|
|
|
281245
281826
|
// src/cli/login.ts
|
|
281246
281827
|
init_cjs_shims();
|
|
281247
281828
|
var import_child_process2 = require("child_process");
|
|
281248
|
-
var
|
|
281829
|
+
var import_picocolors9 = __toESM(require_picocolors(), 1);
|
|
281249
281830
|
var CLIENT_ID = "ablo-cli";
|
|
281250
281831
|
var stripSlash = (u2) => u2.replace(/\/+$/, "");
|
|
281251
281832
|
var AUTH_URL = stripSlash(process.env.ABLO_AUTH_URL ?? "https://auth.abloatai.com");
|
|
@@ -281303,9 +281884,9 @@ async function deviceLogin(argv, deps = {}) {
|
|
|
281303
281884
|
const code = await codeRes.json();
|
|
281304
281885
|
const approvePath = `/cli?user_code=${code.user_code}`;
|
|
281305
281886
|
const url = account === "signup" ? `${DASHBOARD_URL}/signup?next=${encodeURIComponent(approvePath)}` : `${DASHBOARD_URL}${approvePath}`;
|
|
281306
|
-
Me(`${
|
|
281887
|
+
Me(`${import_picocolors9.default.bold(code.user_code)}
|
|
281307
281888
|
|
|
281308
|
-
${
|
|
281889
|
+
${import_picocolors9.default.dim(url)}`, "Approve in your browser");
|
|
281309
281890
|
openUrl(url);
|
|
281310
281891
|
const s = Y2();
|
|
281311
281892
|
s.start("Waiting for approval\u2026");
|
|
@@ -281372,7 +281953,7 @@ ${import_picocolors8.default.dim(url)}`, "Approve in your browser");
|
|
|
281372
281953
|
if (reason) M2.error(reason);
|
|
281373
281954
|
else if (provRes) M2.error(`Key provisioning returned ${provRes.status} from ${DASHBOARD_URL}/api/cli/provision-key.`);
|
|
281374
281955
|
M2.error(
|
|
281375
|
-
`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")}.`
|
|
281376
281957
|
);
|
|
281377
281958
|
process.exit(1);
|
|
281378
281959
|
}
|
|
@@ -281392,9 +281973,9 @@ ${import_picocolors8.default.dim(url)}`, "Approve in your browser");
|
|
|
281392
281973
|
{ mode: "sandbox", activeProject: prov.project ?? void 0 }
|
|
281393
281974
|
);
|
|
281394
281975
|
s.stop(`Saved keys to ${path}`);
|
|
281395
|
-
const where = prov.project ? ` ${
|
|
281976
|
+
const where = prov.project ? ` ${import_picocolors9.default.dim(`(project ${prov.project.slug})`)}` : "";
|
|
281396
281977
|
Se(
|
|
281397
|
-
`${
|
|
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.`
|
|
281398
281979
|
);
|
|
281399
281980
|
}
|
|
281400
281981
|
async function login(argv = [], deps = {}) {
|
|
@@ -281403,20 +281984,20 @@ async function login(argv = [], deps = {}) {
|
|
|
281403
281984
|
function logout() {
|
|
281404
281985
|
const removed = clearCredential();
|
|
281405
281986
|
if (removed) {
|
|
281406
|
-
console.log(` ${
|
|
281987
|
+
console.log(` ${import_picocolors9.default.green("\u2713")} Logged out ${import_picocolors9.default.dim(`(credentials removed from ${configDir()})`)}`);
|
|
281407
281988
|
} else {
|
|
281408
|
-
console.log(` ${
|
|
281989
|
+
console.log(` ${import_picocolors9.default.dim("\u25CB")} Not logged in \u2014 nothing to remove.`);
|
|
281409
281990
|
}
|
|
281410
281991
|
if (process.env.ABLO_API_KEY) {
|
|
281411
281992
|
console.log(
|
|
281412
|
-
|
|
281993
|
+
import_picocolors9.default.dim(` Note: ${import_picocolors9.default.bold("ABLO_API_KEY")} is still set in this shell and takes precedence.`)
|
|
281413
281994
|
);
|
|
281414
281995
|
}
|
|
281415
281996
|
}
|
|
281416
281997
|
|
|
281417
281998
|
// src/cli/mode.ts
|
|
281418
281999
|
init_cjs_shims();
|
|
281419
|
-
var
|
|
282000
|
+
var import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
281420
282001
|
var PREFIX = { sandbox: "sk_test_", production: "rk_live_" };
|
|
281421
282002
|
function hintFor(m2, current) {
|
|
281422
282003
|
const parts = [];
|
|
@@ -281426,10 +282007,10 @@ function hintFor(m2, current) {
|
|
|
281426
282007
|
}
|
|
281427
282008
|
function apply(m2) {
|
|
281428
282009
|
setMode(m2);
|
|
281429
|
-
console.log(` ${
|
|
282010
|
+
console.log(` ${import_picocolors10.default.green("\u2713")} now in ${import_picocolors10.default.bold(m2)}`);
|
|
281430
282011
|
if (!getKeyEntry(m2)) {
|
|
281431
282012
|
console.log(
|
|
281432
|
-
|
|
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`)}.`)
|
|
281433
282014
|
);
|
|
281434
282015
|
}
|
|
281435
282016
|
}
|
|
@@ -281442,14 +282023,14 @@ async function mode(argv) {
|
|
|
281442
282023
|
}
|
|
281443
282024
|
if (arg) {
|
|
281444
282025
|
console.error(
|
|
281445
|
-
|
|
282026
|
+
import_picocolors10.default.red(` unknown mode: ${arg}`) + import_picocolors10.default.dim(` (expected ${import_picocolors10.default.bold("sandbox")} or ${import_picocolors10.default.bold("production")})`)
|
|
281446
282027
|
);
|
|
281447
282028
|
process.exit(1);
|
|
281448
282029
|
}
|
|
281449
282030
|
const current = getMode();
|
|
281450
282031
|
if (!process.stdin.isTTY || process.env.CI) {
|
|
281451
282032
|
console.error(
|
|
281452
|
-
|
|
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})`)
|
|
281453
282034
|
);
|
|
281454
282035
|
process.exit(1);
|
|
281455
282036
|
}
|
|
@@ -281468,206 +282049,6 @@ async function mode(argv) {
|
|
|
281468
282049
|
apply(selected);
|
|
281469
282050
|
}
|
|
281470
282051
|
|
|
281471
|
-
// src/cli/projects.ts
|
|
281472
|
-
init_cjs_shims();
|
|
281473
|
-
var import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
281474
|
-
function apiUrl() {
|
|
281475
|
-
return (process.env.ABLO_API_URL ?? DEFAULT_URL).replace(/\/+$/, "");
|
|
281476
|
-
}
|
|
281477
|
-
function requireKey() {
|
|
281478
|
-
const apiKey = resolveApiKey();
|
|
281479
|
-
if (!apiKey) {
|
|
281480
|
-
console.error(
|
|
281481
|
-
import_picocolors10.default.red(" No API key.") + import_picocolors10.default.dim(
|
|
281482
|
-
` 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).`
|
|
281483
|
-
)
|
|
281484
|
-
);
|
|
281485
|
-
process.exit(1);
|
|
281486
|
-
}
|
|
281487
|
-
return apiKey;
|
|
281488
|
-
}
|
|
281489
|
-
async function request(path, apiKey, init2 = {}) {
|
|
281490
|
-
const res = await fetch(`${apiUrl()}${path}`, {
|
|
281491
|
-
method: init2.method ?? "GET",
|
|
281492
|
-
headers: {
|
|
281493
|
-
"content-type": "application/json",
|
|
281494
|
-
authorization: `Bearer ${apiKey}`
|
|
281495
|
-
},
|
|
281496
|
-
...init2.body !== void 0 ? { body: JSON.stringify(init2.body) } : {}
|
|
281497
|
-
});
|
|
281498
|
-
let body = {};
|
|
281499
|
-
try {
|
|
281500
|
-
body = await res.json();
|
|
281501
|
-
} catch {
|
|
281502
|
-
}
|
|
281503
|
-
return { status: res.status, body };
|
|
281504
|
-
}
|
|
281505
|
-
async function listProjects(apiKey) {
|
|
281506
|
-
try {
|
|
281507
|
-
const { status: status2, body } = await request("/api/v1/projects", apiKey);
|
|
281508
|
-
if (status2 !== 200 || !Array.isArray(body.data)) return null;
|
|
281509
|
-
return body.data;
|
|
281510
|
-
} catch {
|
|
281511
|
-
return null;
|
|
281512
|
-
}
|
|
281513
|
-
}
|
|
281514
|
-
async function fetchProjects() {
|
|
281515
|
-
const all = await listProjects(requireKey());
|
|
281516
|
-
if (!all) {
|
|
281517
|
-
console.error(import_picocolors10.default.red(" Could not list projects \u2014 is the API reachable and the key valid?"));
|
|
281518
|
-
process.exit(1);
|
|
281519
|
-
}
|
|
281520
|
-
return all;
|
|
281521
|
-
}
|
|
281522
|
-
function projectSlugFromPackageName(name) {
|
|
281523
|
-
if (typeof name !== "string") return void 0;
|
|
281524
|
-
const slug = name.toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/^[-_]+|[-_]+$/g, "").slice(0, 64);
|
|
281525
|
-
if (!slug || slug === "default") return void 0;
|
|
281526
|
-
return slug;
|
|
281527
|
-
}
|
|
281528
|
-
async function ensureProject(slug, name) {
|
|
281529
|
-
const apiKey = resolveApiKey();
|
|
281530
|
-
if (!apiKey) return null;
|
|
281531
|
-
try {
|
|
281532
|
-
const { status: status2, body } = await request("/api/v1/projects", apiKey, {
|
|
281533
|
-
method: "POST",
|
|
281534
|
-
body: { slug, ...name ? { name } : {} }
|
|
281535
|
-
});
|
|
281536
|
-
if (status2 === 201) {
|
|
281537
|
-
const created = body;
|
|
281538
|
-
setActiveProject({ id: created.id, slug: created.slug });
|
|
281539
|
-
return { id: created.id, slug: created.slug, created: true };
|
|
281540
|
-
}
|
|
281541
|
-
if (body.code === "project_slug_taken") {
|
|
281542
|
-
const all = await listProjects(apiKey);
|
|
281543
|
-
const existing = all?.find((p2) => p2.slug === slug);
|
|
281544
|
-
if (!existing) return null;
|
|
281545
|
-
setActiveProject({ id: existing.id, slug: existing.slug });
|
|
281546
|
-
return { id: existing.id, slug: existing.slug, created: false };
|
|
281547
|
-
}
|
|
281548
|
-
return null;
|
|
281549
|
-
} catch {
|
|
281550
|
-
return null;
|
|
281551
|
-
}
|
|
281552
|
-
}
|
|
281553
|
-
function printList(projects2) {
|
|
281554
|
-
const active = getActiveProject();
|
|
281555
|
-
for (const p2 of projects2) {
|
|
281556
|
-
const isActive = active ? active.id === p2.id : p2.default;
|
|
281557
|
-
const marker = isActive ? import_picocolors10.default.green("\u25CF") : import_picocolors10.default.dim("\u25CB");
|
|
281558
|
-
const tags = [p2.default ? import_picocolors10.default.dim("default") : "", isActive ? import_picocolors10.default.green("active") : ""].filter(Boolean).join(import_picocolors10.default.dim(", "));
|
|
281559
|
-
console.log(
|
|
281560
|
-
` ${marker} ${p2.slug.padEnd(20)} ${import_picocolors10.default.dim(p2.id)}${tags ? ` ${tags}` : ""}`
|
|
281561
|
-
);
|
|
281562
|
-
}
|
|
281563
|
-
}
|
|
281564
|
-
async function projects(argv) {
|
|
281565
|
-
const sub = argv[0];
|
|
281566
|
-
if (sub === "list" || sub === void 0) {
|
|
281567
|
-
console.log(`
|
|
281568
|
-
${brand("ablo")} ${import_picocolors10.default.dim("projects")}
|
|
281569
|
-
`);
|
|
281570
|
-
printList(await fetchProjects());
|
|
281571
|
-
console.log();
|
|
281572
|
-
return;
|
|
281573
|
-
}
|
|
281574
|
-
if (sub === "create") {
|
|
281575
|
-
const slug = argv[1];
|
|
281576
|
-
if (!slug || slug.startsWith("-")) {
|
|
281577
|
-
console.error(import_picocolors10.default.red(' usage: ablo projects create <slug> [--name "Display Name"]'));
|
|
281578
|
-
process.exit(1);
|
|
281579
|
-
}
|
|
281580
|
-
const nameIdx = argv.indexOf("--name");
|
|
281581
|
-
const name = nameIdx >= 0 ? argv[nameIdx + 1] : void 0;
|
|
281582
|
-
const { status: status2, body } = await request("/api/v1/projects", requireKey(), {
|
|
281583
|
-
method: "POST",
|
|
281584
|
-
body: { slug, ...name ? { name } : {} }
|
|
281585
|
-
});
|
|
281586
|
-
if (status2 !== 201) {
|
|
281587
|
-
console.error(
|
|
281588
|
-
import_picocolors10.default.red(` Create failed (${status2}): ${String(body.message ?? body.code ?? "")}`)
|
|
281589
|
-
);
|
|
281590
|
-
if (body.code === "project_slug_taken") {
|
|
281591
|
-
console.error(import_picocolors10.default.dim(` Pick another slug, or switch to it: ${import_picocolors10.default.bold(`ablo projects use ${slug}`)}`));
|
|
281592
|
-
}
|
|
281593
|
-
process.exit(1);
|
|
281594
|
-
}
|
|
281595
|
-
const created = body;
|
|
281596
|
-
console.log(` ${import_picocolors10.default.green("\u2713")} Created project ${import_picocolors10.default.bold(created.slug)} ${import_picocolors10.default.dim(`(${created.id})`)}`);
|
|
281597
|
-
console.log(
|
|
281598
|
-
import_picocolors10.default.dim(` Make it active with ${import_picocolors10.default.bold(`ablo projects use ${created.slug}`)}; mint its keys in the dashboard.`)
|
|
281599
|
-
);
|
|
281600
|
-
return;
|
|
281601
|
-
}
|
|
281602
|
-
if (sub === "rename") {
|
|
281603
|
-
const ref = argv[1];
|
|
281604
|
-
const name = argv.slice(2).join(" ").trim();
|
|
281605
|
-
if (!ref || ref.startsWith("-") || !name) {
|
|
281606
|
-
console.error(import_picocolors10.default.red(" usage: ablo projects rename <slug|id> <new name>"));
|
|
281607
|
-
process.exit(1);
|
|
281608
|
-
}
|
|
281609
|
-
const all = await fetchProjects();
|
|
281610
|
-
const target = all.find((p2) => p2.slug === ref || p2.id === ref);
|
|
281611
|
-
if (!target) {
|
|
281612
|
-
console.error(import_picocolors10.default.red(` No project "${ref}".`) + import_picocolors10.default.dim(" Run ablo projects list."));
|
|
281613
|
-
process.exit(1);
|
|
281614
|
-
}
|
|
281615
|
-
if (target.default) {
|
|
281616
|
-
console.error(import_picocolors10.default.red(" The default project cannot be renamed."));
|
|
281617
|
-
process.exit(1);
|
|
281618
|
-
}
|
|
281619
|
-
const { status: status2, body } = await request(`/api/v1/projects/${target.id}`, requireKey(), {
|
|
281620
|
-
method: "PATCH",
|
|
281621
|
-
body: { name }
|
|
281622
|
-
});
|
|
281623
|
-
if (status2 !== 200) {
|
|
281624
|
-
console.error(
|
|
281625
|
-
import_picocolors10.default.red(` Rename failed (${status2}): ${String(body.message ?? body.code ?? "")}`)
|
|
281626
|
-
);
|
|
281627
|
-
process.exit(1);
|
|
281628
|
-
}
|
|
281629
|
-
const updated = body;
|
|
281630
|
-
console.log(
|
|
281631
|
-
` ${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})`)}`
|
|
281632
|
-
);
|
|
281633
|
-
return;
|
|
281634
|
-
}
|
|
281635
|
-
if (sub === "use") {
|
|
281636
|
-
const ref = argv[1];
|
|
281637
|
-
if (!ref) {
|
|
281638
|
-
console.error(import_picocolors10.default.red(" usage: ablo projects use <slug|id|default>"));
|
|
281639
|
-
process.exit(1);
|
|
281640
|
-
}
|
|
281641
|
-
const all = await fetchProjects();
|
|
281642
|
-
const target = all.find((p2) => p2.slug === ref || p2.id === ref);
|
|
281643
|
-
if (!target) {
|
|
281644
|
-
console.error(import_picocolors10.default.red(` No project "${ref}".`) + import_picocolors10.default.dim(" Run ablo projects list."));
|
|
281645
|
-
process.exit(1);
|
|
281646
|
-
}
|
|
281647
|
-
if (target.default) {
|
|
281648
|
-
setActiveProject(void 0);
|
|
281649
|
-
console.log(` ${import_picocolors10.default.green("\u2713")} now targeting the ${import_picocolors10.default.bold("default")} project`);
|
|
281650
|
-
} else {
|
|
281651
|
-
setActiveProject({ id: target.id, slug: target.slug });
|
|
281652
|
-
console.log(` ${import_picocolors10.default.green("\u2713")} now targeting project ${import_picocolors10.default.bold(target.slug)} ${import_picocolors10.default.dim(`(${target.id})`)}`);
|
|
281653
|
-
}
|
|
281654
|
-
const guard = guardActiveProjectKey();
|
|
281655
|
-
if (!guard.ok) {
|
|
281656
|
-
const loginCmd = guard.activeProfile === DEFAULT_PROFILE ? "ablo login" : `ablo login --project ${guard.activeProfile}`;
|
|
281657
|
-
console.log(
|
|
281658
|
-
import_picocolors10.default.dim(` No key stored for this project yet \u2014 run ${import_picocolors10.default.bold(loginCmd)} to mint one.`)
|
|
281659
|
-
);
|
|
281660
|
-
}
|
|
281661
|
-
return;
|
|
281662
|
-
}
|
|
281663
|
-
console.error(
|
|
281664
|
-
import_picocolors10.default.red(` unknown subcommand: ${sub}`) + import_picocolors10.default.dim(
|
|
281665
|
-
` (expected ${import_picocolors10.default.bold("list")}, ${import_picocolors10.default.bold("create")}, ${import_picocolors10.default.bold("rename")}, or ${import_picocolors10.default.bold("use")})`
|
|
281666
|
-
)
|
|
281667
|
-
);
|
|
281668
|
-
process.exit(1);
|
|
281669
|
-
}
|
|
281670
|
-
|
|
281671
282052
|
// src/cli/status.ts
|
|
281672
282053
|
init_cjs_shims();
|
|
281673
282054
|
var import_picocolors11 = __toESM(require_picocolors(), 1);
|
|
@@ -281757,17 +282138,44 @@ function formatConflict(conflict) {
|
|
|
281757
282138
|
const parts = ["user", "agent", "system"].flatMap((k3) => conflict[k3] ? [`${k3}:${conflict[k3]}`] : []);
|
|
281758
282139
|
return parts.length ? `{${parts.join(",")}}` : "";
|
|
281759
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
|
+
}
|
|
281760
282167
|
async function status(args = []) {
|
|
281761
282168
|
const apiUrl2 = (process.env.ABLO_API_URL ?? DEFAULT_URL).replace(/\/+$/, "");
|
|
281762
282169
|
const cfg = readConfig();
|
|
281763
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;
|
|
281764
282173
|
if (args.includes("--json")) {
|
|
281765
282174
|
const entry = getKeyEntry(mode2);
|
|
281766
282175
|
const key2 = describeEffectiveKey(mode2, process.env.ABLO_API_KEY, entry);
|
|
281767
282176
|
const plan2 = resolvePushPlan();
|
|
281768
282177
|
const activeProject2 = getActiveProject();
|
|
281769
|
-
const
|
|
281770
|
-
const pushed = await fetchPushedSchema(apiUrl2, effective2.key);
|
|
282178
|
+
const pushed = await fetchPushedSchema(apiUrl2, effective.key);
|
|
281771
282179
|
const out = {
|
|
281772
282180
|
mode: mode2,
|
|
281773
282181
|
// The locally-active project (`ablo projects use`); null = org-default.
|
|
@@ -281776,8 +282184,8 @@ async function status(args = []) {
|
|
|
281776
282184
|
// 'env' | '.env.local' | '.env' | 'stored'. Key confusion is a common
|
|
281777
282185
|
// source of trouble, and the source is usually the answer.
|
|
281778
282186
|
effectiveKey: {
|
|
281779
|
-
prefix:
|
|
281780
|
-
source:
|
|
282187
|
+
prefix: effective.key ? effective.key.slice(0, 12) : null,
|
|
282188
|
+
source: effective.source
|
|
281781
282189
|
},
|
|
281782
282190
|
keyPrefix: key2.keyPrefix,
|
|
281783
282191
|
keySource: key2.keySource,
|
|
@@ -281787,6 +282195,19 @@ async function status(args = []) {
|
|
|
281787
282195
|
keyMatchesStoredActiveKey: key2.keyMatchesStoredActiveKey,
|
|
281788
282196
|
keyMismatch: key2.keyMismatch,
|
|
281789
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 ?? [],
|
|
281790
282211
|
// What `ablo push` would do right now — the answer to "why did push
|
|
281791
282212
|
// demand a different key".
|
|
281792
282213
|
push: {
|
|
@@ -281800,6 +282221,7 @@ async function status(args = []) {
|
|
|
281800
282221
|
schema: pushed ? {
|
|
281801
282222
|
active: pushed.active,
|
|
281802
282223
|
version: pushed.version ?? null,
|
|
282224
|
+
hash: pushed.hash ?? null,
|
|
281803
282225
|
pushedAt: pushed.pushedAt ?? null,
|
|
281804
282226
|
models: pushed.models
|
|
281805
282227
|
} : null,
|
|
@@ -281812,7 +282234,6 @@ async function status(args = []) {
|
|
|
281812
282234
|
console.log(`
|
|
281813
282235
|
${brand("ablo")} ${import_picocolors11.default.dim("status")}
|
|
281814
282236
|
`);
|
|
281815
|
-
const effective = resolveEffectiveApiKey();
|
|
281816
282237
|
if (effective.key && effective.source && effective.source !== "stored") {
|
|
281817
282238
|
const label = effective.source === "env" ? "ABLO_API_KEY env" : effective.source;
|
|
281818
282239
|
console.log(
|
|
@@ -281828,9 +282249,7 @@ async function status(args = []) {
|
|
|
281828
282249
|
console.log(` ${import_picocolors11.default.yellow("!")} ${import_picocolors11.default.yellow(key.keyMismatch.message)}`);
|
|
281829
282250
|
}
|
|
281830
282251
|
const activeProject = getActiveProject();
|
|
281831
|
-
|
|
281832
|
-
` ${import_picocolors11.default.dim("project")} ${activeProject ? `${import_picocolors11.default.bold(activeProject.slug)} ${import_picocolors11.default.dim(`(${activeProject.id})`)}` : import_picocolors11.default.bold("default")}`
|
|
281833
|
-
);
|
|
282252
|
+
printTargetLines(target, activeProject);
|
|
281834
282253
|
for (const m2 of ["sandbox", "production"]) {
|
|
281835
282254
|
const entry = getKeyEntry(m2);
|
|
281836
282255
|
const marker = m2 === mode2 ? import_picocolors11.default.green("\u25CF") : import_picocolors11.default.dim("\u25CB");
|
|
@@ -281841,8 +282260,7 @@ async function status(args = []) {
|
|
|
281841
282260
|
console.log(` ${marker} ${m2.padEnd(10)} ${import_picocolors11.default.dim("\u2014 no key")}`);
|
|
281842
282261
|
}
|
|
281843
282262
|
}
|
|
281844
|
-
const org = activeEntry?.organizationId;
|
|
281845
|
-
if (org) console.log(` ${import_picocolors11.default.dim("org")} ${org}`);
|
|
282263
|
+
const org = target?.confirmed?.organizationId ?? activeEntry?.organizationId;
|
|
281846
282264
|
const plan = resolvePushPlan();
|
|
281847
282265
|
console.log(
|
|
281848
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")})`)}`}`
|
|
@@ -281856,7 +282274,8 @@ async function status(args = []) {
|
|
|
281856
282274
|
if (pushed?.active) {
|
|
281857
282275
|
const when = pushed.pushedAt ? ` ${import_picocolors11.default.dim(`@ ${pushed.pushedAt.slice(0, 10)}`)}` : "";
|
|
281858
282276
|
const ver = pushed.version != null ? ` ${import_picocolors11.default.dim(`(rev ${pushed.version})`)}` : "";
|
|
281859
|
-
|
|
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}`);
|
|
281860
282279
|
for (const m2 of pushed.models) {
|
|
281861
282280
|
const tn = m2.typename === m2.key ? import_picocolors11.default.dim(`typename=${m2.typename}`) : import_picocolors11.default.yellow(`typename=${m2.typename}`);
|
|
281862
282281
|
const conflict = formatConflict(m2.conflict);
|