@tarout/cli 0.6.0 → 0.7.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 +140 -110
- 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.0",
|
|
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,
|
|
@@ -7564,20 +7568,50 @@ function extractEntitlementKeyFromError(err) {
|
|
|
7564
7568
|
const m = msg.match(/Plan limit reached for ([\w.]+)/i);
|
|
7565
7569
|
return m?.[1];
|
|
7566
7570
|
}
|
|
7571
|
+
function buildRemedyOptions(remedy, requestedPlan, catalog) {
|
|
7572
|
+
if (remedy.kind === "addon" || remedy.kind === "plan_quantity") {
|
|
7573
|
+
const upgradePlan = nextPlanForRequested(requestedPlan);
|
|
7574
|
+
const planDef = (catalog?.plans ?? []).find(
|
|
7575
|
+
(p) => (p.planKey ?? p.key) === upgradePlan
|
|
7576
|
+
);
|
|
7577
|
+
return [
|
|
7578
|
+
{
|
|
7579
|
+
action: remedy.kind === "addon" ? "buy_addon" : "add_app_slot",
|
|
7580
|
+
label: remedy.kind === "addon" ? `Buy just the ${remedy.targetName ?? remedy.targetKey}` : "Add one more app slot",
|
|
7581
|
+
command: remedy.command
|
|
7582
|
+
},
|
|
7583
|
+
{
|
|
7584
|
+
action: "upgrade_plan",
|
|
7585
|
+
label: `Upgrade to ${planDef?.name ?? upgradePlan}`,
|
|
7586
|
+
command: `tarout billing upgrade ${upgradePlan} --wait`
|
|
7587
|
+
}
|
|
7588
|
+
];
|
|
7589
|
+
}
|
|
7590
|
+
return [
|
|
7591
|
+
{
|
|
7592
|
+
action: "upgrade_plan",
|
|
7593
|
+
label: `Upgrade to ${remedy.targetName ?? remedy.targetKey}`,
|
|
7594
|
+
command: remedy.command
|
|
7595
|
+
}
|
|
7596
|
+
];
|
|
7597
|
+
}
|
|
7567
7598
|
async function emitNeedsUpgrade(client, err, requestedPlan, retryCommand) {
|
|
7568
7599
|
const message = err instanceof Error ? err.message : "Plan upgrade required";
|
|
7569
7600
|
const failedKey = extractEntitlementKeyFromError(err);
|
|
7570
7601
|
const catalog = await fetchCatalogSafely(client);
|
|
7571
7602
|
const remedy = resolveEntitlementRemedy(failedKey, catalog, { requestedPlan });
|
|
7603
|
+
const options = buildRemedyOptions(remedy, requestedPlan, catalog);
|
|
7604
|
+
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
7605
|
outputError("NEEDS_UPGRADE", message, {
|
|
7573
7606
|
failedEntitlementKey: failedKey,
|
|
7574
7607
|
remedyKind: remedy.kind,
|
|
7575
|
-
// `suggestedPlan` retained for back-compat
|
|
7576
|
-
//
|
|
7608
|
+
// `suggestedPlan`/`nextCommand` retained for back-compat (the recommended
|
|
7609
|
+
// targeted action); `options` is the authoritative choice list.
|
|
7577
7610
|
suggestedPlan: remedy.targetKey,
|
|
7578
7611
|
suggestedTarget: remedy.targetKey,
|
|
7579
7612
|
nextCommand: remedy.command,
|
|
7580
|
-
|
|
7613
|
+
options,
|
|
7614
|
+
hint
|
|
7581
7615
|
});
|
|
7582
7616
|
}
|
|
7583
7617
|
async function listFreeDatabasesSafely(client) {
|
|
@@ -8656,6 +8690,12 @@ function registerDeployCommands(program2) {
|
|
|
8656
8690
|
).option(
|
|
8657
8691
|
"--reuse-storage <ref>",
|
|
8658
8692
|
"Reuse an existing storage bucket in this project: <id>, <name>, or 'auto' (exactly one match)"
|
|
8693
|
+
).option(
|
|
8694
|
+
"--new-app",
|
|
8695
|
+
"Create a new app instead of being prompted to reuse an existing one"
|
|
8696
|
+
).option(
|
|
8697
|
+
"--name <name>",
|
|
8698
|
+
"Name for a newly created app (defaults to the directory name)"
|
|
8659
8699
|
).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
8700
|
"--framework-preset <preset>",
|
|
8661
8701
|
"Framework preset override (e.g. nextjs, vite, astro)"
|
|
@@ -8744,7 +8784,7 @@ function registerDeployCommands(program2) {
|
|
|
8744
8784
|
} catch (err) {
|
|
8745
8785
|
if (isEntitlementError(err)) {
|
|
8746
8786
|
const message = err instanceof Error ? err.message : "Plan upgrade required";
|
|
8747
|
-
if (isJsonMode() || shouldSkipConfirmation()) {
|
|
8787
|
+
if (isJsonMode() || isNonInteractiveMode() || shouldSkipConfirmation()) {
|
|
8748
8788
|
await emitNeedsUpgrade(
|
|
8749
8789
|
getApiClient(),
|
|
8750
8790
|
err,
|
|
@@ -19207,6 +19247,9 @@ function registerUpCommand(program2) {
|
|
|
19207
19247
|
).option("--token <token>", "API token for this run").option("--name <name>", "Application name (defaults to directory name)").option("--plan <plan>", "App hosting plan: free, shared, or dedicated", "free").option("--source <source>", "Source: upload (default) or github", "upload").option(
|
|
19208
19248
|
"--app <ref>",
|
|
19209
19249
|
"Deploy to an existing app by id or name (skips create-or-pick prompt; 'auto' picks the lone match)"
|
|
19250
|
+
).option(
|
|
19251
|
+
"--new-app",
|
|
19252
|
+
"Create a new app instead of being prompted to reuse an existing one"
|
|
19210
19253
|
).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
19254
|
"--database <type>",
|
|
19212
19255
|
"Provision and attach a database: none, postgres, or mysql (defaults to auto-detected)"
|
|
@@ -19268,9 +19311,7 @@ function registerUpCommand(program2) {
|
|
|
19268
19311
|
}
|
|
19269
19312
|
return allApps;
|
|
19270
19313
|
};
|
|
19271
|
-
|
|
19272
|
-
const apps = await loadApps();
|
|
19273
|
-
const picked = resolveAppRef(apps, options.app);
|
|
19314
|
+
const reuse = (picked, via) => {
|
|
19274
19315
|
app = picked;
|
|
19275
19316
|
reused = true;
|
|
19276
19317
|
setProjectConfig({
|
|
@@ -19283,45 +19324,47 @@ function registerUpCommand(program2) {
|
|
|
19283
19324
|
event: "app_reused",
|
|
19284
19325
|
applicationId: picked.applicationId,
|
|
19285
19326
|
name: picked.name,
|
|
19286
|
-
via
|
|
19327
|
+
via
|
|
19287
19328
|
});
|
|
19288
|
-
}
|
|
19329
|
+
};
|
|
19330
|
+
if (options.app) {
|
|
19289
19331
|
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()) {
|
|
19332
|
+
reuse(resolveAppRef(apps, options.app), "--app");
|
|
19333
|
+
} else if (!options.newApp) {
|
|
19303
19334
|
const apps = await loadApps();
|
|
19304
|
-
|
|
19335
|
+
const linkedApp = linked ? findApp3(apps, linked.applicationId) ?? findApp3(apps, linked.name) : void 0;
|
|
19336
|
+
if (shouldSkipConfirmation()) {
|
|
19337
|
+
if (linkedApp) reuse(linkedApp, "linked");
|
|
19338
|
+
} else if (apps.length > 0) {
|
|
19305
19339
|
const createValue = "__create__";
|
|
19306
|
-
|
|
19307
|
-
|
|
19308
|
-
|
|
19309
|
-
|
|
19340
|
+
const orderedApps = linkedApp ? [
|
|
19341
|
+
linkedApp,
|
|
19342
|
+
...apps.filter(
|
|
19343
|
+
(a) => a.applicationId !== linkedApp.applicationId
|
|
19344
|
+
)
|
|
19345
|
+
] : apps;
|
|
19310
19346
|
const selected = await select(
|
|
19311
|
-
"
|
|
19347
|
+
"Create a new app or reuse an existing one?",
|
|
19312
19348
|
[
|
|
19313
19349
|
{
|
|
19314
19350
|
name: `Create a new app${options.name ? ` named "${options.name}"` : ""}`,
|
|
19315
19351
|
value: createValue
|
|
19316
19352
|
},
|
|
19317
|
-
...
|
|
19318
|
-
name:
|
|
19353
|
+
...orderedApps.map((existing) => ({
|
|
19354
|
+
name: `Reuse ${existing.name} ${colors.dim(`(${existing.applicationId.slice(0, 8)})`)}${linkedApp && existing.applicationId === linkedApp.applicationId ? colors.dim(" \u2014 linked") : ""}`,
|
|
19319
19355
|
value: existing.applicationId
|
|
19320
19356
|
}))
|
|
19321
19357
|
],
|
|
19322
19358
|
{
|
|
19323
|
-
field: "
|
|
19324
|
-
flag: "--app <id|name
|
|
19359
|
+
field: "deploy_app",
|
|
19360
|
+
flag: "--new-app to create a new app, or --app <id|name> to reuse an existing one",
|
|
19361
|
+
context: {
|
|
19362
|
+
linkedApp: linkedApp ? { id: linkedApp.applicationId, name: linkedApp.name } : null,
|
|
19363
|
+
apps: orderedApps.map((a) => ({
|
|
19364
|
+
id: a.applicationId,
|
|
19365
|
+
name: a.name
|
|
19366
|
+
}))
|
|
19367
|
+
}
|
|
19325
19368
|
}
|
|
19326
19369
|
);
|
|
19327
19370
|
if (selected !== createValue) {
|
|
@@ -19329,20 +19372,7 @@ function registerUpCommand(program2) {
|
|
|
19329
19372
|
if (!picked) {
|
|
19330
19373
|
throw new NotFoundError("Application", selected);
|
|
19331
19374
|
}
|
|
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
|
-
});
|
|
19375
|
+
reuse(picked, "interactive");
|
|
19346
19376
|
}
|
|
19347
19377
|
}
|
|
19348
19378
|
}
|
|
@@ -19370,7 +19400,7 @@ function registerUpCommand(program2) {
|
|
|
19370
19400
|
} catch (err) {
|
|
19371
19401
|
if (isEntitlementError(err)) {
|
|
19372
19402
|
const message = err instanceof Error ? err.message : "Plan upgrade required";
|
|
19373
|
-
if (isJsonMode() || shouldSkipConfirmation()) {
|
|
19403
|
+
if (isJsonMode() || isNonInteractiveMode() || shouldSkipConfirmation()) {
|
|
19374
19404
|
await emitNeedsUpgrade(client, err, options.plan, "tarout up");
|
|
19375
19405
|
exit(ExitCode.PERMISSION_DENIED);
|
|
19376
19406
|
}
|
|
@@ -19484,7 +19514,7 @@ function registerUpCommand(program2) {
|
|
|
19484
19514
|
} catch (err) {
|
|
19485
19515
|
if (isEntitlementError(err)) {
|
|
19486
19516
|
const message = err instanceof Error ? err.message : "Plan upgrade required";
|
|
19487
|
-
if (isJsonMode() || shouldSkipConfirmation()) {
|
|
19517
|
+
if (isJsonMode() || isNonInteractiveMode() || shouldSkipConfirmation()) {
|
|
19488
19518
|
await emitNeedsUpgrade(
|
|
19489
19519
|
getApiClient(),
|
|
19490
19520
|
err,
|