@apso/cli 0.47.0 → 0.47.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/commands/deploy.js +1 -1
- package/dist/commands/env/add.js +2 -1
- package/dist/commands/env/list.js +4 -0
- package/dist/commands/open.js +1 -1
- package/dist/commands/promote.js +2 -1
- package/dist/lib/api/client.d.ts +6 -0
- package/dist/lib/api/client.js +21 -3
- package/dist/lib/environments.d.ts +2 -0
- package/dist/lib/environments.js +7 -1
- package/dist/lib/upgrade.d.ts +14 -4
- package/dist/lib/upgrade.js +31 -13
- package/npm-shrinkwrap.json +2 -2
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
package/dist/commands/deploy.js
CHANGED
|
@@ -100,7 +100,7 @@ class Deploy extends base_command_1.default {
|
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
// Trigger build
|
|
103
|
-
const build = await (0, upgrade_1.withUpgradeRetry)(() => services_1.buildApi.trigger(link.workspaceSlug, link.serviceSlug, env));
|
|
103
|
+
const build = await (0, upgrade_1.withUpgradeRetry)(() => services_1.buildApi.trigger(link.workspaceSlug, link.serviceSlug, env), { workspaceSlug: link.workspaceSlug, serviceId: service.id });
|
|
104
104
|
this.log(`Build started: ${build.id}`);
|
|
105
105
|
if (flags["no-wait"]) {
|
|
106
106
|
this.log(`Deploy triggered. Run 'apso status --env ${env}' to check progress.`);
|
package/dist/commands/env/add.js
CHANGED
|
@@ -5,6 +5,7 @@ const core_1 = require("@oclif/core");
|
|
|
5
5
|
const base_command_1 = tslib_1.__importDefault(require("../../lib/base-command"));
|
|
6
6
|
const services_1 = require("../../lib/api/services");
|
|
7
7
|
const environments_1 = require("../../lib/environments");
|
|
8
|
+
const upgrade_1 = require("../../lib/upgrade");
|
|
8
9
|
class EnvAdd extends base_command_1.default {
|
|
9
10
|
async run() {
|
|
10
11
|
const { flags } = await this.parse(EnvAdd);
|
|
@@ -16,7 +17,7 @@ class EnvAdd extends base_command_1.default {
|
|
|
16
17
|
const ok = await (0, environments_1.confirmOrExit)(this, flags.yes, `Add staging to "${service.name}"? This starts the paid staging add-on.`);
|
|
17
18
|
if (!ok)
|
|
18
19
|
return this.log("Cancelled.");
|
|
19
|
-
await services_1.envApi.addStaging(service.id);
|
|
20
|
+
await (0, upgrade_1.withUpgradeRetry)(() => services_1.envApi.addStaging(service.id), (0, environments_1.pageContext)(service));
|
|
20
21
|
this.log(`Staging is being created for "${service.name}". Deploy to it with 'apso deploy --env staging'.`);
|
|
21
22
|
}
|
|
22
23
|
}
|
|
@@ -15,6 +15,10 @@ class EnvList extends base_command_1.default {
|
|
|
15
15
|
this.log(JSON.stringify(envs, null, 2));
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
18
|
+
// Production's row appears with the first deploy; until then say so rather than print nothing.
|
|
19
|
+
if (!envs.some((e) => e.environment === "production")) {
|
|
20
|
+
this.log(`${"production".padEnd(12)}${"not deployed yet".padEnd(22)}-`);
|
|
21
|
+
}
|
|
18
22
|
for (const e of envs) {
|
|
19
23
|
this.log(`${e.environment.padEnd(12)}${(e.buildStatus || e.status || "-").padEnd(22)}${(_a = e.url) !== null && _a !== void 0 ? _a : "-"}`);
|
|
20
24
|
}
|
package/dist/commands/open.js
CHANGED
|
@@ -36,7 +36,7 @@ class Open extends base_command_1.default {
|
|
|
36
36
|
}
|
|
37
37
|
else {
|
|
38
38
|
// Open the dashboard (default)
|
|
39
|
-
const dashboardUrl = `${config.webUrl}
|
|
39
|
+
const dashboardUrl = `${config.webUrl}/${link.workspaceSlug}/services/${link.serviceId}/dashboard`;
|
|
40
40
|
this.log(`Opening dashboard: ${dashboardUrl}`);
|
|
41
41
|
openUrl(dashboardUrl);
|
|
42
42
|
}
|
package/dist/commands/promote.js
CHANGED
|
@@ -6,6 +6,7 @@ const core_1 = require("@oclif/core");
|
|
|
6
6
|
const base_command_1 = tslib_1.__importDefault(require("../lib/base-command"));
|
|
7
7
|
const services_1 = require("../lib/api/services");
|
|
8
8
|
const environments_1 = require("../lib/environments");
|
|
9
|
+
const upgrade_1 = require("../lib/upgrade");
|
|
9
10
|
const spinner_1 = require("../lib/utils/spinner");
|
|
10
11
|
class Promote extends base_command_1.default {
|
|
11
12
|
async run() {
|
|
@@ -23,7 +24,7 @@ class Promote extends base_command_1.default {
|
|
|
23
24
|
const ok = await (0, environments_1.confirmOrExit)(this, flags.yes, `Promote staging to production for "${service.name}"?`);
|
|
24
25
|
if (!ok)
|
|
25
26
|
return this.log("Cancelled.");
|
|
26
|
-
await services_1.envApi.promote(service.id);
|
|
27
|
+
await (0, upgrade_1.withUpgradeRetry)(() => services_1.envApi.promote(service.id), (0, environments_1.pageContext)(service));
|
|
27
28
|
if (flags["no-wait"]) {
|
|
28
29
|
this.log("Promote started. Run 'apso status' to check progress.");
|
|
29
30
|
return;
|
package/dist/lib/api/client.d.ts
CHANGED
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
* - Timeout handling
|
|
10
10
|
*/
|
|
11
11
|
import { ApiRequestOptions } from "./types";
|
|
12
|
+
/**
|
|
13
|
+
* Refusals the user resolves in the web app, and where: "service" is the
|
|
14
|
+
* service's Deployments page (size and staging), "billing" the workspace's
|
|
15
|
+
* Billing page.
|
|
16
|
+
*/
|
|
17
|
+
export declare const APP_FIX_PAGES: Record<string, "service" | "billing">;
|
|
12
18
|
/**
|
|
13
19
|
* Custom error class for API errors
|
|
14
20
|
*/
|
package/dist/lib/api/client.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - Timeout handling
|
|
11
11
|
*/
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
-
exports.api = exports.apiRequest = exports.checkNetworkConnectivity = exports.ApiClientError = void 0;
|
|
13
|
+
exports.api = exports.apiRequest = exports.checkNetworkConnectivity = exports.ApiClientError = exports.APP_FIX_PAGES = void 0;
|
|
14
14
|
const config_1 = require("../config");
|
|
15
15
|
/**
|
|
16
16
|
* Default request timeout in milliseconds
|
|
@@ -24,6 +24,17 @@ const MAX_RETRIES = 3;
|
|
|
24
24
|
* Base delay for exponential backoff in milliseconds
|
|
25
25
|
*/
|
|
26
26
|
const BACKOFF_BASE_DELAY = 1000;
|
|
27
|
+
/**
|
|
28
|
+
* Refusals the user resolves in the web app, and where: "service" is the
|
|
29
|
+
* service's Deployments page (size and staging), "billing" the workspace's
|
|
30
|
+
* Billing page.
|
|
31
|
+
*/
|
|
32
|
+
exports.APP_FIX_PAGES = {
|
|
33
|
+
STAGING_REQUIRES_PAID_SERVICE: "service",
|
|
34
|
+
STAGING_ADDON_REQUIRED: "service",
|
|
35
|
+
STAGING_ADDON_PURCHASE_FAILED: "service",
|
|
36
|
+
SERVICE_SUSPENDED: "billing",
|
|
37
|
+
};
|
|
27
38
|
/**
|
|
28
39
|
* Custom error class for API errors
|
|
29
40
|
*/
|
|
@@ -45,7 +56,9 @@ class ApiClientError extends Error {
|
|
|
45
56
|
* upgrading (e.g. the free-tier service or deploy limit).
|
|
46
57
|
*/
|
|
47
58
|
isLimitError() {
|
|
48
|
-
|
|
59
|
+
if (this.code && this.code in exports.APP_FIX_PAGES)
|
|
60
|
+
return true;
|
|
61
|
+
return ((this.statusCode === 403 || this.statusCode === 402) &&
|
|
49
62
|
(this.code === "SERVICE_LIMIT_REACHED" ||
|
|
50
63
|
this.code === "LIMIT_REACHED" ||
|
|
51
64
|
Boolean(this.upgradeUrl)));
|
|
@@ -306,7 +319,12 @@ async function apiRequest(path, options = {}) {
|
|
|
306
319
|
}
|
|
307
320
|
// Client errors - don't retry
|
|
308
321
|
const limit = extractLimitInfo(errorData);
|
|
309
|
-
|
|
322
|
+
// The app's API routes put their sentence in `.error`; the backend uses `.message`.
|
|
323
|
+
const appError = errorData.error;
|
|
324
|
+
const clientError = new ApiClientError(limit.message ||
|
|
325
|
+
errorData.message ||
|
|
326
|
+
(typeof appError === "string" ? appError : undefined) ||
|
|
327
|
+
"Request failed", response.status, errorData.details);
|
|
310
328
|
clientError.code = limit.code;
|
|
311
329
|
clientError.upgradeUrl = limit.upgradeUrl;
|
|
312
330
|
throw clientError;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { Command } from "@oclif/core";
|
|
2
2
|
import { type ServiceEnv } from "./api/services";
|
|
3
3
|
import type { Service } from "./api/types";
|
|
4
|
+
import type { PageContext } from "./upgrade";
|
|
4
5
|
export declare const envFlag: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
5
6
|
export declare const asEnv: (value: string | undefined) => ServiceEnv;
|
|
6
7
|
export declare function linkedService(cmd: Command): Promise<Service>;
|
|
8
|
+
export declare function pageContext(service: Service): PageContext;
|
|
7
9
|
export declare function confirmOrExit(cmd: Command, yes: boolean, question: string): Promise<boolean>;
|
package/dist/lib/environments.js
CHANGED
|
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.confirmOrExit = exports.linkedService = exports.asEnv = exports.envFlag = void 0;
|
|
26
|
+
exports.confirmOrExit = exports.pageContext = exports.linkedService = exports.asEnv = exports.envFlag = void 0;
|
|
27
27
|
const core_1 = require("@oclif/core");
|
|
28
28
|
const config_1 = require("./config");
|
|
29
29
|
const services_1 = require("./api/services");
|
|
@@ -46,6 +46,12 @@ async function linkedService(cmd) {
|
|
|
46
46
|
return services_1.servicesApi.get(link.workspaceId, link.serviceSlug);
|
|
47
47
|
}
|
|
48
48
|
exports.linkedService = linkedService;
|
|
49
|
+
// Where the linked service lives in the web app, for links to the page that fixes a refusal.
|
|
50
|
+
function pageContext(service) {
|
|
51
|
+
var _a;
|
|
52
|
+
return { workspaceSlug: (_a = config_1.projectLink.read()) === null || _a === void 0 ? void 0 : _a.workspaceSlug, serviceId: service.id };
|
|
53
|
+
}
|
|
54
|
+
exports.pageContext = pageContext;
|
|
49
55
|
// Asks before a step that costs money or replaces something; headless runs must pass --yes.
|
|
50
56
|
async function confirmOrExit(cmd, yes, question) {
|
|
51
57
|
if (yes)
|
package/dist/lib/upgrade.d.ts
CHANGED
|
@@ -7,13 +7,23 @@
|
|
|
7
7
|
* "you've hit a limit, upgrade?" flow developers expect from modern CLIs.
|
|
8
8
|
*/
|
|
9
9
|
import { ApiClientError } from "./api/client";
|
|
10
|
+
/** Where the user is when the refusal happened, so the link can go to the exact page. */
|
|
11
|
+
export interface PageContext {
|
|
12
|
+
workspaceSlug?: string;
|
|
13
|
+
serviceId?: string;
|
|
14
|
+
}
|
|
15
|
+
/** The web app page that resolves this refusal, and how to name it in a prompt. */
|
|
16
|
+
export declare function fixPage(error: ApiClientError, ctx?: PageContext): {
|
|
17
|
+
url: string;
|
|
18
|
+
label: string;
|
|
19
|
+
};
|
|
10
20
|
/**
|
|
11
|
-
* Prompt the user to
|
|
12
|
-
* wait for them to finish. Returns true if the caller should retry
|
|
21
|
+
* Prompt the user to resolve a plan or billing refusal in the web app, open the
|
|
22
|
+
* page, and wait for them to finish. Returns true if the caller should retry.
|
|
13
23
|
*/
|
|
14
|
-
export declare function promptUpgrade(error: ApiClientError): Promise<boolean>;
|
|
24
|
+
export declare function promptUpgrade(error: ApiClientError, ctx?: PageContext): Promise<boolean>;
|
|
15
25
|
/**
|
|
16
26
|
* Run an action; if it fails with an upgradeable plan-limit error, offer to
|
|
17
27
|
* upgrade and retry once.
|
|
18
28
|
*/
|
|
19
|
-
export declare function withUpgradeRetry<T>(action: () => Promise<T
|
|
29
|
+
export declare function withUpgradeRetry<T>(action: () => Promise<T>, ctx?: PageContext): Promise<T>;
|
package/dist/lib/upgrade.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* "you've hit a limit, upgrade?" flow developers expect from modern CLIs.
|
|
9
9
|
*/
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
-
exports.withUpgradeRetry = exports.promptUpgrade = void 0;
|
|
11
|
+
exports.withUpgradeRetry = exports.promptUpgrade = exports.fixPage = void 0;
|
|
12
12
|
const tslib_1 = require("tslib");
|
|
13
13
|
const child_process_1 = require("child_process");
|
|
14
14
|
const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
|
|
@@ -29,13 +29,31 @@ function openBrowser(url) {
|
|
|
29
29
|
(0, child_process_1.exec)(command, () => resolve());
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
|
+
/** The web app page that resolves this refusal, and how to name it in a prompt. */
|
|
33
|
+
function fixPage(error, ctx = {}) {
|
|
34
|
+
const web = config_1.globalConfig.read().webUrl;
|
|
35
|
+
const where = error.code ? client_1.APP_FIX_PAGES[error.code] : undefined;
|
|
36
|
+
if (where === "service" && ctx.workspaceSlug && ctx.serviceId) {
|
|
37
|
+
return {
|
|
38
|
+
url: `${web}/${ctx.workspaceSlug}/services/${ctx.serviceId}/deployments`,
|
|
39
|
+
label: "the service's Deployments page",
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (where === "billing" && ctx.workspaceSlug) {
|
|
43
|
+
return { url: `${web}/${ctx.workspaceSlug}/billing`, label: "Billing" };
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
url: `${web}${error.upgradeUrl || "/billing/upgrade"}`,
|
|
47
|
+
label: "the upgrade page",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
exports.fixPage = fixPage;
|
|
32
51
|
/**
|
|
33
|
-
* Prompt the user to
|
|
34
|
-
* wait for them to finish. Returns true if the caller should retry
|
|
52
|
+
* Prompt the user to resolve a plan or billing refusal in the web app, open the
|
|
53
|
+
* page, and wait for them to finish. Returns true if the caller should retry.
|
|
35
54
|
*/
|
|
36
|
-
async function promptUpgrade(error) {
|
|
37
|
-
const
|
|
38
|
-
const upgradeUrl = `${config.webUrl}${error.upgradeUrl || "/billing/upgrade"}`;
|
|
55
|
+
async function promptUpgrade(error, ctx = {}) {
|
|
56
|
+
const { url: upgradeUrl, label } = fixPage(error, ctx);
|
|
39
57
|
console.log("");
|
|
40
58
|
console.log(` ⚠ ${error.message || "You've reached a plan limit."}`);
|
|
41
59
|
console.log("");
|
|
@@ -43,12 +61,12 @@ async function promptUpgrade(error) {
|
|
|
43
61
|
{
|
|
44
62
|
type: "confirm",
|
|
45
63
|
name: "proceed",
|
|
46
|
-
message:
|
|
64
|
+
message: `Open ${label} in your browser?`,
|
|
47
65
|
default: true,
|
|
48
66
|
},
|
|
49
67
|
]);
|
|
50
68
|
if (!proceed) {
|
|
51
|
-
console.log(` You can
|
|
69
|
+
console.log(` You can do this any time at: ${upgradeUrl}`);
|
|
52
70
|
return false;
|
|
53
71
|
}
|
|
54
72
|
console.log("");
|
|
@@ -59,7 +77,7 @@ async function promptUpgrade(error) {
|
|
|
59
77
|
{
|
|
60
78
|
type: "confirm",
|
|
61
79
|
name: "done",
|
|
62
|
-
message: "Press enter once
|
|
80
|
+
message: "Press enter once you are done there to retry",
|
|
63
81
|
default: true,
|
|
64
82
|
},
|
|
65
83
|
]);
|
|
@@ -70,7 +88,7 @@ exports.promptUpgrade = promptUpgrade;
|
|
|
70
88
|
* Run an action; if it fails with an upgradeable plan-limit error, offer to
|
|
71
89
|
* upgrade and retry once.
|
|
72
90
|
*/
|
|
73
|
-
async function withUpgradeRetry(action) {
|
|
91
|
+
async function withUpgradeRetry(action, ctx = {}) {
|
|
74
92
|
try {
|
|
75
93
|
return await action();
|
|
76
94
|
}
|
|
@@ -79,10 +97,10 @@ async function withUpgradeRetry(action) {
|
|
|
79
97
|
// Headless (agent/CI): never open a browser or prompt — surface the limit
|
|
80
98
|
// as a clear error with the upgrade URL so the caller can act on it.
|
|
81
99
|
if (!(0, interactive_1.isInteractive)()) {
|
|
82
|
-
const
|
|
83
|
-
throw new Error(`${error.message || "Plan limit reached."}
|
|
100
|
+
const { url } = fixPage(error, ctx);
|
|
101
|
+
throw new Error(`${(error.message || "Plan limit reached").replace(/\.?$/, ".")} Resolve it at ${url}`);
|
|
84
102
|
}
|
|
85
|
-
const retry = await promptUpgrade(error);
|
|
103
|
+
const retry = await promptUpgrade(error, ctx);
|
|
86
104
|
if (retry) {
|
|
87
105
|
return action();
|
|
88
106
|
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apso/cli",
|
|
3
|
-
"version": "0.47.
|
|
3
|
+
"version": "0.47.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@apso/cli",
|
|
9
|
-
"version": "0.47.
|
|
9
|
+
"version": "0.47.1",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@apso/schema-tools": "^0.2.0",
|
package/oclif.manifest.json
CHANGED