@tarout/cli 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +206 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -61,7 +61,7 @@ import { Command } from "commander";
|
|
|
61
61
|
// package.json
|
|
62
62
|
var package_default = {
|
|
63
63
|
name: "@tarout/cli",
|
|
64
|
-
version: "0.
|
|
64
|
+
version: "0.6.0",
|
|
65
65
|
description: "Tarout CLI \u2014 the Saudi cloud platform for coding agents",
|
|
66
66
|
type: "module",
|
|
67
67
|
bin: {
|
|
@@ -6529,6 +6529,20 @@ function resolveEntitlementRemedy(failedKey, catalog, opts) {
|
|
|
6529
6529
|
hint: `A dedicated host slot is required \u2014 upgrade to ${plan2?.name ?? target2}.`
|
|
6530
6530
|
};
|
|
6531
6531
|
}
|
|
6532
|
+
if (failedKey === "db.free.slots" || failedKey === "storage.free.slots") {
|
|
6533
|
+
const target2 = nextPlanForRequested(opts?.requestedPlan);
|
|
6534
|
+
const plan2 = plans.find((p) => planKeyOf(p) === target2);
|
|
6535
|
+
const resource = failedKey === "db.free.slots" ? "database" : "storage bucket";
|
|
6536
|
+
return {
|
|
6537
|
+
kind: "plan",
|
|
6538
|
+
failedKey,
|
|
6539
|
+
targetKey: target2,
|
|
6540
|
+
targetName: plan2?.name,
|
|
6541
|
+
priceHalalas: plan2?.priceHalalas,
|
|
6542
|
+
command: `tarout billing upgrade ${target2} --wait`,
|
|
6543
|
+
hint: `The free plan includes a single ${resource} for the whole org \u2014 delete the existing free ${resource}, or upgrade to ${plan2?.name ?? target2} to add more.`
|
|
6544
|
+
};
|
|
6545
|
+
}
|
|
6532
6546
|
if (failedKey?.startsWith("db.") || failedKey?.startsWith("storage.") || failedKey?.startsWith("domain") || failedKey?.startsWith("email")) {
|
|
6533
6547
|
const matched = addons.find(
|
|
6534
6548
|
(a) => a.grants?.some((g) => g.entitlementKey === failedKey)
|
|
@@ -7369,6 +7383,97 @@ async function runInlineUpgrade(client, planKey) {
|
|
|
7369
7383
|
}
|
|
7370
7384
|
return false;
|
|
7371
7385
|
}
|
|
7386
|
+
async function getCurrentPlanQuantitySafely(client) {
|
|
7387
|
+
try {
|
|
7388
|
+
const sub = await client.subscription.getCurrent.query();
|
|
7389
|
+
const q = Number(sub?.planQuantity);
|
|
7390
|
+
return Number.isFinite(q) && q > 0 ? q : 1;
|
|
7391
|
+
} catch {
|
|
7392
|
+
return 1;
|
|
7393
|
+
}
|
|
7394
|
+
}
|
|
7395
|
+
async function runInlineTargetedRemedy(client, remedy) {
|
|
7396
|
+
let input2;
|
|
7397
|
+
let label;
|
|
7398
|
+
if (remedy.kind === "addon") {
|
|
7399
|
+
input2 = { kind: "addon", addonKey: remedy.targetKey, quantity: 1 };
|
|
7400
|
+
label = remedy.targetName ?? remedy.targetKey;
|
|
7401
|
+
} else {
|
|
7402
|
+
const next = await getCurrentPlanQuantitySafely(client) + 1;
|
|
7403
|
+
input2 = { kind: "plan_quantity", planKey: remedy.targetKey, quantity: next };
|
|
7404
|
+
label = `${remedy.targetName ?? remedy.targetKey} \xD7${next}`;
|
|
7405
|
+
}
|
|
7406
|
+
const _spinner = startSpinner(`Adding ${label}...`);
|
|
7407
|
+
const result = await performBillingChange(client, {
|
|
7408
|
+
...input2,
|
|
7409
|
+
wait: true,
|
|
7410
|
+
timeoutMs: 6e5,
|
|
7411
|
+
openBrowser: isJsonMode() ? void 0 : async (url) => {
|
|
7412
|
+
await open4(url);
|
|
7413
|
+
},
|
|
7414
|
+
onCheckoutOpened: ({ orderId, paymentUrl }) => {
|
|
7415
|
+
log("");
|
|
7416
|
+
log("Open this URL to complete payment:");
|
|
7417
|
+
log(` ${colors.cyan(paymentUrl)}`);
|
|
7418
|
+
log(`Order ID: ${colors.dim(orderId)}`);
|
|
7419
|
+
}
|
|
7420
|
+
});
|
|
7421
|
+
if (result.status === "applied" || result.status === "paid") {
|
|
7422
|
+
succeedSpinner(`${label} added.`);
|
|
7423
|
+
return true;
|
|
7424
|
+
}
|
|
7425
|
+
failSpinner(
|
|
7426
|
+
result.status === "deferred" ? "Change did not require an immediate payment." : result.status === "pending_timeout" ? "Payment still pending after 10 minutes." : `Payment ${result.status}.`
|
|
7427
|
+
);
|
|
7428
|
+
if (result.failureReason) log(colors.error(result.failureReason));
|
|
7429
|
+
if (result.orderId) {
|
|
7430
|
+
log(
|
|
7431
|
+
colors.dim(
|
|
7432
|
+
`Resume later with: tarout billing wait ${result.orderId.slice(0, 8)} --timeout 600`
|
|
7433
|
+
)
|
|
7434
|
+
);
|
|
7435
|
+
}
|
|
7436
|
+
return false;
|
|
7437
|
+
}
|
|
7438
|
+
async function promptEntitlementRemedy(client, err, requestedPlan) {
|
|
7439
|
+
const failedKey = extractEntitlementKeyFromError(err);
|
|
7440
|
+
const catalog = await fetchCatalogSafely(client);
|
|
7441
|
+
const remedy = resolveEntitlementRemedy(failedKey, catalog, { requestedPlan });
|
|
7442
|
+
if (remedy.kind === "plan") {
|
|
7443
|
+
return promptUpgradeFromEntitlementError(client, err, requestedPlan);
|
|
7444
|
+
}
|
|
7445
|
+
const price = remedy.priceHalalas !== void 0 ? ` (${formatPlanPrice(remedy.priceHalalas)})` : "";
|
|
7446
|
+
const targetedLabel = remedy.kind === "plan_quantity" ? `Add one more app slot${price}` : `Add just the ${remedy.targetName ?? remedy.targetKey}${price}`;
|
|
7447
|
+
log("");
|
|
7448
|
+
log(colors.warn("That resource isn't included in your current plan."));
|
|
7449
|
+
log("");
|
|
7450
|
+
const UPGRADE = "__upgrade__";
|
|
7451
|
+
const TARGETED = "__targeted__";
|
|
7452
|
+
const CANCEL = "__cancel__";
|
|
7453
|
+
const choice = await select(
|
|
7454
|
+
"How would you like to add it?",
|
|
7455
|
+
[
|
|
7456
|
+
{ name: "Upgrade the plan", value: UPGRADE },
|
|
7457
|
+
{ name: targetedLabel, value: TARGETED },
|
|
7458
|
+
{ name: "Cancel", value: CANCEL }
|
|
7459
|
+
],
|
|
7460
|
+
{
|
|
7461
|
+
field: "entitlement_remedy",
|
|
7462
|
+
flag: "--plan <key> (upgrade) | tarout billing addon:buy <key> (addon)",
|
|
7463
|
+
context: {
|
|
7464
|
+
failedEntitlementKey: failedKey,
|
|
7465
|
+
remedyKind: remedy.kind,
|
|
7466
|
+
targetKey: remedy.targetKey,
|
|
7467
|
+
command: remedy.command
|
|
7468
|
+
}
|
|
7469
|
+
}
|
|
7470
|
+
);
|
|
7471
|
+
if (choice === CANCEL) return false;
|
|
7472
|
+
if (choice === UPGRADE) {
|
|
7473
|
+
return promptUpgradeFromEntitlementError(client, err, requestedPlan);
|
|
7474
|
+
}
|
|
7475
|
+
return runInlineTargetedRemedy(client, remedy);
|
|
7476
|
+
}
|
|
7372
7477
|
async function getAppPlanChoices(client, preloadedOptions) {
|
|
7373
7478
|
try {
|
|
7374
7479
|
const options = preloadedOptions ?? await client.application.getCreateOptions.query();
|
|
@@ -7475,6 +7580,63 @@ async function emitNeedsUpgrade(client, err, requestedPlan, retryCommand) {
|
|
|
7475
7580
|
hint: `${remedy.hint} Then retry: ${retryCommand}.`
|
|
7476
7581
|
});
|
|
7477
7582
|
}
|
|
7583
|
+
async function listFreeDatabasesSafely(client) {
|
|
7584
|
+
try {
|
|
7585
|
+
const [pgRows, myRows] = await Promise.all([
|
|
7586
|
+
client.postgres.allByOrganization.query().catch(() => []),
|
|
7587
|
+
client.mysql.allByOrganization.query().catch(() => [])
|
|
7588
|
+
]);
|
|
7589
|
+
const out = [];
|
|
7590
|
+
for (const r of pgRows ?? []) {
|
|
7591
|
+
if ((r?.plan ?? "FREE") === "FREE" && r?.postgresId) {
|
|
7592
|
+
out.push({ kind: "postgres", id: r.postgresId, name: r.name });
|
|
7593
|
+
}
|
|
7594
|
+
}
|
|
7595
|
+
for (const r of myRows ?? []) {
|
|
7596
|
+
if ((r?.plan ?? "FREE") === "FREE" && r?.mysqlId) {
|
|
7597
|
+
out.push({ kind: "mysql", id: r.mysqlId, name: r.name });
|
|
7598
|
+
}
|
|
7599
|
+
}
|
|
7600
|
+
return out;
|
|
7601
|
+
} catch {
|
|
7602
|
+
return [];
|
|
7603
|
+
}
|
|
7604
|
+
}
|
|
7605
|
+
async function explainFreeResourceSlotExhaustion(client, failedKey) {
|
|
7606
|
+
const isDb = failedKey === "db.free.slots";
|
|
7607
|
+
const resource = isDb ? "database" : "storage bucket";
|
|
7608
|
+
log("");
|
|
7609
|
+
log(
|
|
7610
|
+
colors.warn(
|
|
7611
|
+
`The free plan includes one ${resource} for the whole organization (across all environments).`
|
|
7612
|
+
)
|
|
7613
|
+
);
|
|
7614
|
+
if (isDb) {
|
|
7615
|
+
const existing = await listFreeDatabasesSafely(client);
|
|
7616
|
+
if (existing.length > 0) {
|
|
7617
|
+
log("You already have:");
|
|
7618
|
+
for (const d of existing) {
|
|
7619
|
+
log(
|
|
7620
|
+
` \u2022 ${d.name} ${colors.dim(`(${d.kind} \xB7 ${d.id.slice(0, 8)})`)}`
|
|
7621
|
+
);
|
|
7622
|
+
}
|
|
7623
|
+
} else {
|
|
7624
|
+
log(
|
|
7625
|
+
colors.dim(
|
|
7626
|
+
"Your existing free database may be in another environment \u2014 run `tarout db list`."
|
|
7627
|
+
)
|
|
7628
|
+
);
|
|
7629
|
+
}
|
|
7630
|
+
}
|
|
7631
|
+
log("");
|
|
7632
|
+
log("To continue, pick one:");
|
|
7633
|
+
if (isDb) {
|
|
7634
|
+
log(` \u2022 Reuse it: ${colors.cyan("tarout deploy --reuse-database auto")}`);
|
|
7635
|
+
log(` \u2022 Delete it: ${colors.cyan("tarout db delete <id>")}`);
|
|
7636
|
+
}
|
|
7637
|
+
log(` \u2022 Upgrade: ${colors.cyan("tarout billing upgrade shared --wait")}`);
|
|
7638
|
+
log("");
|
|
7639
|
+
}
|
|
7478
7640
|
async function promptUpgradeFromEntitlementError(client, err, requestedPlan) {
|
|
7479
7641
|
const catalog = await fetchCatalogSafely(client);
|
|
7480
7642
|
const allPlans = catalog?.plans ?? [];
|
|
@@ -8593,7 +8755,11 @@ function registerDeployCommands(program2) {
|
|
|
8593
8755
|
}
|
|
8594
8756
|
log("");
|
|
8595
8757
|
log(colors.warn(message));
|
|
8596
|
-
const
|
|
8758
|
+
const failedKey = extractEntitlementKeyFromError(err);
|
|
8759
|
+
if (failedKey === "db.free.slots" || failedKey === "storage.free.slots") {
|
|
8760
|
+
await explainFreeResourceSlotExhaustion(getApiClient(), failedKey);
|
|
8761
|
+
}
|
|
8762
|
+
const upgraded = await promptEntitlementRemedy(
|
|
8597
8763
|
getApiClient(),
|
|
8598
8764
|
err,
|
|
8599
8765
|
options.plan
|
|
@@ -8607,7 +8773,7 @@ function registerDeployCommands(program2) {
|
|
|
8607
8773
|
);
|
|
8608
8774
|
exit(ExitCode.PERMISSION_DENIED);
|
|
8609
8775
|
}
|
|
8610
|
-
box("
|
|
8776
|
+
box("Billing updated", [
|
|
8611
8777
|
colors.success("Subscription updated."),
|
|
8612
8778
|
`Run ${colors.cyan("tarout deploy")} again to deploy on the new plan.`
|
|
8613
8779
|
]);
|
|
@@ -19210,7 +19376,7 @@ function registerUpCommand(program2) {
|
|
|
19210
19376
|
}
|
|
19211
19377
|
log("");
|
|
19212
19378
|
log(colors.warn(message));
|
|
19213
|
-
const upgraded = await
|
|
19379
|
+
const upgraded = await promptEntitlementRemedy(
|
|
19214
19380
|
client,
|
|
19215
19381
|
err,
|
|
19216
19382
|
options.plan
|
|
@@ -19219,7 +19385,7 @@ function registerUpCommand(program2) {
|
|
|
19219
19385
|
await emitNeedsUpgrade(client, err, options.plan, "tarout up");
|
|
19220
19386
|
exit(ExitCode.PERMISSION_DENIED);
|
|
19221
19387
|
}
|
|
19222
|
-
box("
|
|
19388
|
+
box("Billing updated", [
|
|
19223
19389
|
colors.success("Subscription updated."),
|
|
19224
19390
|
`Run ${colors.cyan("tarout up")} again to deploy on the new plan.`
|
|
19225
19391
|
]);
|
|
@@ -19316,6 +19482,39 @@ function registerUpCommand(program2) {
|
|
|
19316
19482
|
app.applicationId
|
|
19317
19483
|
);
|
|
19318
19484
|
} catch (err) {
|
|
19485
|
+
if (isEntitlementError(err)) {
|
|
19486
|
+
const message = err instanceof Error ? err.message : "Plan upgrade required";
|
|
19487
|
+
if (isJsonMode() || shouldSkipConfirmation()) {
|
|
19488
|
+
await emitNeedsUpgrade(
|
|
19489
|
+
getApiClient(),
|
|
19490
|
+
err,
|
|
19491
|
+
options.plan,
|
|
19492
|
+
"tarout up"
|
|
19493
|
+
);
|
|
19494
|
+
exit(ExitCode.PERMISSION_DENIED);
|
|
19495
|
+
}
|
|
19496
|
+
log("");
|
|
19497
|
+
log(colors.warn(message));
|
|
19498
|
+
const upgraded = await promptEntitlementRemedy(
|
|
19499
|
+
getApiClient(),
|
|
19500
|
+
err,
|
|
19501
|
+
options.plan
|
|
19502
|
+
);
|
|
19503
|
+
if (!upgraded) {
|
|
19504
|
+
await emitNeedsUpgrade(
|
|
19505
|
+
getApiClient(),
|
|
19506
|
+
err,
|
|
19507
|
+
options.plan,
|
|
19508
|
+
"tarout up"
|
|
19509
|
+
);
|
|
19510
|
+
exit(ExitCode.PERMISSION_DENIED);
|
|
19511
|
+
}
|
|
19512
|
+
box("Billing updated", [
|
|
19513
|
+
colors.success("Subscription updated."),
|
|
19514
|
+
`Run ${colors.cyan("tarout up")} again to deploy on the new plan.`
|
|
19515
|
+
]);
|
|
19516
|
+
return;
|
|
19517
|
+
}
|
|
19319
19518
|
if (err instanceof Error && err.message.startsWith("Invalid --source")) {
|
|
19320
19519
|
outputError("INVALID_ARGUMENTS", err.message);
|
|
19321
19520
|
if (!isJsonMode()) log(colors.error(err.message));
|
|
@@ -19544,10 +19743,11 @@ program.name("tarout").description("Tarout PaaS Command Line Interface").version
|
|
|
19544
19743
|
"Fail fast on missing input (emit needs_input + exit 6 instead of prompting on TTY)"
|
|
19545
19744
|
).option("-q, --quiet", "Minimal output").option("-v, --verbose", "Extra debug information").option("--no-color", "Disable colored output").hook("preAction", (thisCommand) => {
|
|
19546
19745
|
const opts = thisCommand.opts();
|
|
19746
|
+
const stdinIsTTY = Boolean(process.stdin.isTTY);
|
|
19547
19747
|
setGlobalOptions({
|
|
19548
19748
|
json: opts.json || false,
|
|
19549
19749
|
yes: opts.yes || false,
|
|
19550
|
-
nonInteractive: opts.nonInteractive ||
|
|
19750
|
+
nonInteractive: opts.nonInteractive || !stdinIsTTY,
|
|
19551
19751
|
quiet: opts.quiet || false,
|
|
19552
19752
|
verbose: opts.verbose || false,
|
|
19553
19753
|
noColor: opts.color === false
|