@tarout/cli 0.6.0 → 0.7.1
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 +166 -112
- 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.7.1",
|
|
65
65
|
description: "Tarout CLI \u2014 the Saudi cloud platform for coding agents",
|
|
66
66
|
type: "module",
|
|
67
67
|
bin: {
|
|
@@ -7133,87 +7133,91 @@ async function resolveDeploymentTarget(client, profile, appIdentifier, options =
|
|
|
7133
7133
|
);
|
|
7134
7134
|
throw new NotFoundError("Application", appIdentifier, suggestions);
|
|
7135
7135
|
}
|
|
7136
|
-
const
|
|
7136
|
+
const details = await getApplicationDetails(client, app2.applicationId);
|
|
7137
7137
|
return {
|
|
7138
7138
|
app: app2,
|
|
7139
7139
|
createdApp: false,
|
|
7140
|
-
hasConfiguredSource: hasConfiguredSource(
|
|
7141
|
-
shouldUploadSource: shouldUseLocalSource(
|
|
7140
|
+
hasConfiguredSource: hasConfiguredSource(details),
|
|
7141
|
+
shouldUploadSource: shouldUseLocalSource(details, {
|
|
7142
7142
|
explicitApp: true
|
|
7143
7143
|
})
|
|
7144
7144
|
};
|
|
7145
7145
|
}
|
|
7146
|
-
|
|
7147
|
-
|
|
7148
|
-
|
|
7149
|
-
if (app2) {
|
|
7150
|
-
const details2 = await getApplicationDetails(client, app2.applicationId);
|
|
7151
|
-
return {
|
|
7152
|
-
app: app2,
|
|
7153
|
-
createdApp: false,
|
|
7154
|
-
hasConfiguredSource: hasConfiguredSource(details2),
|
|
7155
|
-
shouldUploadSource: shouldUseLocalSource(details2, {
|
|
7156
|
-
linkedProject: true
|
|
7157
|
-
})
|
|
7158
|
-
};
|
|
7159
|
-
}
|
|
7160
|
-
if (!isJsonMode()) {
|
|
7161
|
-
log("");
|
|
7162
|
-
log(
|
|
7163
|
-
colors.warn(
|
|
7164
|
-
`Linked application "${linkedProject.name}" was not found in this organization.`
|
|
7165
|
-
)
|
|
7166
|
-
);
|
|
7167
|
-
}
|
|
7146
|
+
if (options.newApp) {
|
|
7147
|
+
assertConfiguredSourceAllowsCreate(sourcePreference);
|
|
7148
|
+
return createNewAppTarget(client, profile, options);
|
|
7168
7149
|
}
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7150
|
+
const linkedProject = getProjectConfig();
|
|
7151
|
+
const linkedApp = linkedProject ? findApp3(apps, linkedProject.applicationId) ?? findApp3(apps, linkedProject.name) : void 0;
|
|
7152
|
+
if (linkedProject && !linkedApp && !isJsonMode()) {
|
|
7153
|
+
log("");
|
|
7154
|
+
log(
|
|
7155
|
+
colors.warn(
|
|
7156
|
+
`Linked application "${linkedProject.name}" was not found in this organization.`
|
|
7157
|
+
)
|
|
7172
7158
|
);
|
|
7173
7159
|
}
|
|
7174
7160
|
if (apps.length === 0) {
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
return
|
|
7182
|
-
app: app2,
|
|
7183
|
-
createdApp: true,
|
|
7184
|
-
hasConfiguredSource: false,
|
|
7185
|
-
shouldUploadSource: true
|
|
7186
|
-
};
|
|
7161
|
+
assertConfiguredSourceAllowsCreate(sourcePreference, true);
|
|
7162
|
+
return createNewAppTarget(client, profile, options);
|
|
7163
|
+
}
|
|
7164
|
+
if (shouldSkipConfirmation()) {
|
|
7165
|
+
if (linkedApp) return reuseAppTarget(client, profile, linkedApp);
|
|
7166
|
+
assertConfiguredSourceAllowsCreate(sourcePreference, true);
|
|
7167
|
+
return createNewAppTarget(client, profile, options);
|
|
7187
7168
|
}
|
|
7188
7169
|
const createValue = "__create__";
|
|
7189
|
-
const
|
|
7170
|
+
const orderedApps = linkedApp ? [linkedApp, ...apps.filter((a) => a.applicationId !== linkedApp.applicationId)] : apps;
|
|
7171
|
+
const selected = await select(
|
|
7172
|
+
"Create a new app or reuse an existing one?",
|
|
7173
|
+
[
|
|
7174
|
+
{
|
|
7175
|
+
name: `Create a new app from ${colors.cyan(basename(process.cwd()) || "this directory")}`,
|
|
7176
|
+
value: createValue
|
|
7177
|
+
},
|
|
7178
|
+
...orderedApps.map((app2) => ({
|
|
7179
|
+
name: `Reuse ${app2.name} ${colors.dim(`(${app2.applicationId.slice(0, 8)})`)}${linkedApp && app2.applicationId === linkedApp.applicationId ? colors.dim(" \u2014 linked") : ""}`,
|
|
7180
|
+
value: app2.applicationId
|
|
7181
|
+
}))
|
|
7182
|
+
],
|
|
7190
7183
|
{
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
if (sourcePreference === "configured") {
|
|
7201
|
-
throw new InvalidArgumentError(
|
|
7202
|
-
'New apps do not have a configured Git provider source yet. Connect a Git provider first, or rerun with "--source upload".'
|
|
7203
|
-
);
|
|
7184
|
+
field: "deploy_app",
|
|
7185
|
+
flag: "--new-app to create a new app, or --app <id|name> to reuse an existing one",
|
|
7186
|
+
context: {
|
|
7187
|
+
linkedApp: linkedApp ? { id: linkedApp.applicationId, name: linkedApp.name } : null,
|
|
7188
|
+
apps: orderedApps.map((a) => ({
|
|
7189
|
+
id: a.applicationId,
|
|
7190
|
+
name: a.name
|
|
7191
|
+
}))
|
|
7192
|
+
}
|
|
7204
7193
|
}
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
hasConfiguredSource: false,
|
|
7210
|
-
shouldUploadSource: true
|
|
7211
|
-
};
|
|
7194
|
+
);
|
|
7195
|
+
if (selected === createValue) {
|
|
7196
|
+
assertConfiguredSourceAllowsCreate(sourcePreference);
|
|
7197
|
+
return createNewAppTarget(client, profile, options);
|
|
7212
7198
|
}
|
|
7213
7199
|
const app = findApp3(apps, selected);
|
|
7214
7200
|
if (!app) {
|
|
7215
7201
|
throw new NotFoundError("Application", selected);
|
|
7216
7202
|
}
|
|
7203
|
+
return reuseAppTarget(client, profile, app);
|
|
7204
|
+
}
|
|
7205
|
+
function assertConfiguredSourceAllowsCreate(sourcePreference, noAppsExist = false) {
|
|
7206
|
+
if (sourcePreference !== "configured") return;
|
|
7207
|
+
throw new InvalidArgumentError(
|
|
7208
|
+
noAppsExist ? 'No Tarout app exists to deploy from a configured source. Create or select an app with a configured Git provider first, or rerun with "--source upload".' : 'New apps do not have a configured Git provider source yet. Connect a Git provider first, or rerun with "--source upload".'
|
|
7209
|
+
);
|
|
7210
|
+
}
|
|
7211
|
+
async function createNewAppTarget(client, profile, options) {
|
|
7212
|
+
const app = await createAppFromCurrentDirectory(client, profile, options);
|
|
7213
|
+
return {
|
|
7214
|
+
app,
|
|
7215
|
+
createdApp: true,
|
|
7216
|
+
hasConfiguredSource: false,
|
|
7217
|
+
shouldUploadSource: true
|
|
7218
|
+
};
|
|
7219
|
+
}
|
|
7220
|
+
async function reuseAppTarget(client, profile, app) {
|
|
7217
7221
|
setProjectConfig({
|
|
7218
7222
|
applicationId: app.applicationId,
|
|
7219
7223
|
name: app.name,
|
|
@@ -7282,9 +7286,30 @@ async function createAppFromCurrentDirectory(client, profile, options = {}) {
|
|
|
7282
7286
|
sourceType: application.sourceType
|
|
7283
7287
|
};
|
|
7284
7288
|
}
|
|
7289
|
+
async function isOrgPaidSafely(client) {
|
|
7290
|
+
try {
|
|
7291
|
+
const opts = await client.application.getCreateOptions.query();
|
|
7292
|
+
return opts?.isPaid === true;
|
|
7293
|
+
} catch {
|
|
7294
|
+
return false;
|
|
7295
|
+
}
|
|
7296
|
+
}
|
|
7285
7297
|
async function resolveAppPlanForCreate(client, options) {
|
|
7286
7298
|
const explicitPlan = normalizeAppPlan(options.plan);
|
|
7287
|
-
if (explicitPlan) return explicitPlan;
|
|
7299
|
+
if (explicitPlan && explicitPlan !== "FREE") return explicitPlan;
|
|
7300
|
+
if (explicitPlan === "FREE") {
|
|
7301
|
+
if (await isOrgPaidSafely(client)) {
|
|
7302
|
+
if (!isJsonMode()) {
|
|
7303
|
+
log(
|
|
7304
|
+
colors.dim(
|
|
7305
|
+
"Your org is on a paid plan \u2014 free apps aren't available, so this app will use your paid tier."
|
|
7306
|
+
)
|
|
7307
|
+
);
|
|
7308
|
+
}
|
|
7309
|
+
return void 0;
|
|
7310
|
+
}
|
|
7311
|
+
return "FREE";
|
|
7312
|
+
}
|
|
7288
7313
|
if (isJsonMode() || shouldSkipConfirmation()) return void 0;
|
|
7289
7314
|
let createOptions;
|
|
7290
7315
|
try {
|
|
@@ -7564,20 +7589,50 @@ function extractEntitlementKeyFromError(err) {
|
|
|
7564
7589
|
const m = msg.match(/Plan limit reached for ([\w.]+)/i);
|
|
7565
7590
|
return m?.[1];
|
|
7566
7591
|
}
|
|
7592
|
+
function buildRemedyOptions(remedy, requestedPlan, catalog) {
|
|
7593
|
+
if (remedy.kind === "addon" || remedy.kind === "plan_quantity") {
|
|
7594
|
+
const upgradePlan = nextPlanForRequested(requestedPlan);
|
|
7595
|
+
const planDef = (catalog?.plans ?? []).find(
|
|
7596
|
+
(p) => (p.planKey ?? p.key) === upgradePlan
|
|
7597
|
+
);
|
|
7598
|
+
return [
|
|
7599
|
+
{
|
|
7600
|
+
action: remedy.kind === "addon" ? "buy_addon" : "add_app_slot",
|
|
7601
|
+
label: remedy.kind === "addon" ? `Buy just the ${remedy.targetName ?? remedy.targetKey}` : "Add one more app slot",
|
|
7602
|
+
command: remedy.command
|
|
7603
|
+
},
|
|
7604
|
+
{
|
|
7605
|
+
action: "upgrade_plan",
|
|
7606
|
+
label: `Upgrade to ${planDef?.name ?? upgradePlan}`,
|
|
7607
|
+
command: `tarout billing upgrade ${upgradePlan} --wait`
|
|
7608
|
+
}
|
|
7609
|
+
];
|
|
7610
|
+
}
|
|
7611
|
+
return [
|
|
7612
|
+
{
|
|
7613
|
+
action: "upgrade_plan",
|
|
7614
|
+
label: `Upgrade to ${remedy.targetName ?? remedy.targetKey}`,
|
|
7615
|
+
command: remedy.command
|
|
7616
|
+
}
|
|
7617
|
+
];
|
|
7618
|
+
}
|
|
7567
7619
|
async function emitNeedsUpgrade(client, err, requestedPlan, retryCommand) {
|
|
7568
7620
|
const message = err instanceof Error ? err.message : "Plan upgrade required";
|
|
7569
7621
|
const failedKey = extractEntitlementKeyFromError(err);
|
|
7570
7622
|
const catalog = await fetchCatalogSafely(client);
|
|
7571
7623
|
const remedy = resolveEntitlementRemedy(failedKey, catalog, { requestedPlan });
|
|
7624
|
+
const options = buildRemedyOptions(remedy, requestedPlan, catalog);
|
|
7625
|
+
const hint = options.length > 1 ? `Two ways to resolve this \u2014 ask the user which they prefer, do not choose for them: (1) ${options[0]?.label}: \`${options[0]?.command}\`; (2) ${options[1]?.label}: \`${options[1]?.command}\`. Then retry: ${retryCommand}.` : `${remedy.hint} Then retry: ${retryCommand}.`;
|
|
7572
7626
|
outputError("NEEDS_UPGRADE", message, {
|
|
7573
7627
|
failedEntitlementKey: failedKey,
|
|
7574
7628
|
remedyKind: remedy.kind,
|
|
7575
|
-
// `suggestedPlan` retained for back-compat
|
|
7576
|
-
//
|
|
7629
|
+
// `suggestedPlan`/`nextCommand` retained for back-compat (the recommended
|
|
7630
|
+
// targeted action); `options` is the authoritative choice list.
|
|
7577
7631
|
suggestedPlan: remedy.targetKey,
|
|
7578
7632
|
suggestedTarget: remedy.targetKey,
|
|
7579
7633
|
nextCommand: remedy.command,
|
|
7580
|
-
|
|
7634
|
+
options,
|
|
7635
|
+
hint
|
|
7581
7636
|
});
|
|
7582
7637
|
}
|
|
7583
7638
|
async function listFreeDatabasesSafely(client) {
|
|
@@ -8656,6 +8711,12 @@ function registerDeployCommands(program2) {
|
|
|
8656
8711
|
).option(
|
|
8657
8712
|
"--reuse-storage <ref>",
|
|
8658
8713
|
"Reuse an existing storage bucket in this project: <id>, <name>, or 'auto' (exactly one match)"
|
|
8714
|
+
).option(
|
|
8715
|
+
"--new-app",
|
|
8716
|
+
"Create a new app instead of being prompted to reuse an existing one"
|
|
8717
|
+
).option(
|
|
8718
|
+
"--name <name>",
|
|
8719
|
+
"Name for a newly created app (defaults to the directory name)"
|
|
8659
8720
|
).option("--token <token>", "API token to use for this deployment").option("-r, --region <region>", "Deployment region", DEFAULT_REGION).option("--description <text>", "Description for a newly created app").option(
|
|
8660
8721
|
"--framework-preset <preset>",
|
|
8661
8722
|
"Framework preset override (e.g. nextjs, vite, astro)"
|
|
@@ -8744,7 +8805,7 @@ function registerDeployCommands(program2) {
|
|
|
8744
8805
|
} catch (err) {
|
|
8745
8806
|
if (isEntitlementError(err)) {
|
|
8746
8807
|
const message = err instanceof Error ? err.message : "Plan upgrade required";
|
|
8747
|
-
if (isJsonMode() || shouldSkipConfirmation()) {
|
|
8808
|
+
if (isJsonMode() || isNonInteractiveMode() || shouldSkipConfirmation()) {
|
|
8748
8809
|
await emitNeedsUpgrade(
|
|
8749
8810
|
getApiClient(),
|
|
8750
8811
|
err,
|
|
@@ -19204,9 +19265,15 @@ function registerUpCommand(program2) {
|
|
|
19204
19265
|
).option(
|
|
19205
19266
|
"--api-url <url>",
|
|
19206
19267
|
"Custom API URL (defaults to saved profile or https://tarout.sa)"
|
|
19207
|
-
).option("--token <token>", "API token for this run").option("--name <name>", "Application name (defaults to directory name)").option(
|
|
19268
|
+
).option("--token <token>", "API token for this run").option("--name <name>", "Application name (defaults to directory name)").option(
|
|
19269
|
+
"--plan <plan>",
|
|
19270
|
+
"App hosting plan: free, shared, or dedicated (defaults to your org's tier)"
|
|
19271
|
+
).option("--source <source>", "Source: upload (default) or github", "upload").option(
|
|
19208
19272
|
"--app <ref>",
|
|
19209
19273
|
"Deploy to an existing app by id or name (skips create-or-pick prompt; 'auto' picks the lone match)"
|
|
19274
|
+
).option(
|
|
19275
|
+
"--new-app",
|
|
19276
|
+
"Create a new app instead of being prompted to reuse an existing one"
|
|
19210
19277
|
).option("--repo <owner/repo>", "GitHub repository (when --source github)").option("--branch <branch>", "GitHub branch (with --source github)", "main").option("-r, --region <region>", "Deployment region", DEFAULT_REGION3).option(
|
|
19211
19278
|
"--database <type>",
|
|
19212
19279
|
"Provision and attach a database: none, postgres, or mysql (defaults to auto-detected)"
|
|
@@ -19268,9 +19335,7 @@ function registerUpCommand(program2) {
|
|
|
19268
19335
|
}
|
|
19269
19336
|
return allApps;
|
|
19270
19337
|
};
|
|
19271
|
-
|
|
19272
|
-
const apps = await loadApps();
|
|
19273
|
-
const picked = resolveAppRef(apps, options.app);
|
|
19338
|
+
const reuse = (picked, via) => {
|
|
19274
19339
|
app = picked;
|
|
19275
19340
|
reused = true;
|
|
19276
19341
|
setProjectConfig({
|
|
@@ -19283,45 +19348,47 @@ function registerUpCommand(program2) {
|
|
|
19283
19348
|
event: "app_reused",
|
|
19284
19349
|
applicationId: picked.applicationId,
|
|
19285
19350
|
name: picked.name,
|
|
19286
|
-
via
|
|
19351
|
+
via
|
|
19287
19352
|
});
|
|
19288
|
-
}
|
|
19353
|
+
};
|
|
19354
|
+
if (options.app) {
|
|
19289
19355
|
const apps = await loadApps();
|
|
19290
|
-
|
|
19291
|
-
|
|
19292
|
-
app = existing;
|
|
19293
|
-
reused = true;
|
|
19294
|
-
emitEvent2({
|
|
19295
|
-
event: "app_reused",
|
|
19296
|
-
applicationId: app.applicationId,
|
|
19297
|
-
name: app.name,
|
|
19298
|
-
via: "linked"
|
|
19299
|
-
});
|
|
19300
|
-
}
|
|
19301
|
-
}
|
|
19302
|
-
if (!app && !isJsonMode() && !shouldSkipConfirmation()) {
|
|
19356
|
+
reuse(resolveAppRef(apps, options.app), "--app");
|
|
19357
|
+
} else if (!options.newApp) {
|
|
19303
19358
|
const apps = await loadApps();
|
|
19304
|
-
|
|
19359
|
+
const linkedApp = linked ? findApp3(apps, linked.applicationId) ?? findApp3(apps, linked.name) : void 0;
|
|
19360
|
+
if (shouldSkipConfirmation()) {
|
|
19361
|
+
if (linkedApp) reuse(linkedApp, "linked");
|
|
19362
|
+
} else if (apps.length > 0) {
|
|
19305
19363
|
const createValue = "__create__";
|
|
19306
|
-
|
|
19307
|
-
|
|
19308
|
-
|
|
19309
|
-
|
|
19364
|
+
const orderedApps = linkedApp ? [
|
|
19365
|
+
linkedApp,
|
|
19366
|
+
...apps.filter(
|
|
19367
|
+
(a) => a.applicationId !== linkedApp.applicationId
|
|
19368
|
+
)
|
|
19369
|
+
] : apps;
|
|
19310
19370
|
const selected = await select(
|
|
19311
|
-
"
|
|
19371
|
+
"Create a new app or reuse an existing one?",
|
|
19312
19372
|
[
|
|
19313
19373
|
{
|
|
19314
19374
|
name: `Create a new app${options.name ? ` named "${options.name}"` : ""}`,
|
|
19315
19375
|
value: createValue
|
|
19316
19376
|
},
|
|
19317
|
-
...
|
|
19318
|
-
name:
|
|
19377
|
+
...orderedApps.map((existing) => ({
|
|
19378
|
+
name: `Reuse ${existing.name} ${colors.dim(`(${existing.applicationId.slice(0, 8)})`)}${linkedApp && existing.applicationId === linkedApp.applicationId ? colors.dim(" \u2014 linked") : ""}`,
|
|
19319
19379
|
value: existing.applicationId
|
|
19320
19380
|
}))
|
|
19321
19381
|
],
|
|
19322
19382
|
{
|
|
19323
|
-
field: "
|
|
19324
|
-
flag: "--app <id|name
|
|
19383
|
+
field: "deploy_app",
|
|
19384
|
+
flag: "--new-app to create a new app, or --app <id|name> to reuse an existing one",
|
|
19385
|
+
context: {
|
|
19386
|
+
linkedApp: linkedApp ? { id: linkedApp.applicationId, name: linkedApp.name } : null,
|
|
19387
|
+
apps: orderedApps.map((a) => ({
|
|
19388
|
+
id: a.applicationId,
|
|
19389
|
+
name: a.name
|
|
19390
|
+
}))
|
|
19391
|
+
}
|
|
19325
19392
|
}
|
|
19326
19393
|
);
|
|
19327
19394
|
if (selected !== createValue) {
|
|
@@ -19329,20 +19396,7 @@ function registerUpCommand(program2) {
|
|
|
19329
19396
|
if (!picked) {
|
|
19330
19397
|
throw new NotFoundError("Application", selected);
|
|
19331
19398
|
}
|
|
19332
|
-
|
|
19333
|
-
reused = true;
|
|
19334
|
-
setProjectConfig({
|
|
19335
|
-
applicationId: picked.applicationId,
|
|
19336
|
-
name: picked.name,
|
|
19337
|
-
organizationId: profile.organizationId,
|
|
19338
|
-
linkedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
19339
|
-
});
|
|
19340
|
-
emitEvent2({
|
|
19341
|
-
event: "app_reused",
|
|
19342
|
-
applicationId: picked.applicationId,
|
|
19343
|
-
name: picked.name,
|
|
19344
|
-
via: "interactive"
|
|
19345
|
-
});
|
|
19399
|
+
reuse(picked, "interactive");
|
|
19346
19400
|
}
|
|
19347
19401
|
}
|
|
19348
19402
|
}
|
|
@@ -19370,7 +19424,7 @@ function registerUpCommand(program2) {
|
|
|
19370
19424
|
} catch (err) {
|
|
19371
19425
|
if (isEntitlementError(err)) {
|
|
19372
19426
|
const message = err instanceof Error ? err.message : "Plan upgrade required";
|
|
19373
|
-
if (isJsonMode() || shouldSkipConfirmation()) {
|
|
19427
|
+
if (isJsonMode() || isNonInteractiveMode() || shouldSkipConfirmation()) {
|
|
19374
19428
|
await emitNeedsUpgrade(client, err, options.plan, "tarout up");
|
|
19375
19429
|
exit(ExitCode.PERMISSION_DENIED);
|
|
19376
19430
|
}
|
|
@@ -19484,7 +19538,7 @@ function registerUpCommand(program2) {
|
|
|
19484
19538
|
} catch (err) {
|
|
19485
19539
|
if (isEntitlementError(err)) {
|
|
19486
19540
|
const message = err instanceof Error ? err.message : "Plan upgrade required";
|
|
19487
|
-
if (isJsonMode() || shouldSkipConfirmation()) {
|
|
19541
|
+
if (isJsonMode() || isNonInteractiveMode() || shouldSkipConfirmation()) {
|
|
19488
19542
|
await emitNeedsUpgrade(
|
|
19489
19543
|
getApiClient(),
|
|
19490
19544
|
err,
|