@apso/cli 0.18.0 → 0.20.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/commands/deploy.d.ts +12 -0
- package/dist/commands/deploy.js +80 -3
- package/dist/commands/generate.js +55 -16
- package/dist/commands/github/connect.d.ts +19 -0
- package/dist/commands/github/connect.js +95 -0
- package/dist/commands/init.js +5 -3
- package/dist/commands/link.js +3 -2
- package/dist/commands/mcp/serve.js +15 -0
- package/dist/lib/api/client.d.ts +9 -0
- package/dist/lib/api/client.js +60 -1
- package/dist/lib/api/services.d.ts +45 -90
- package/dist/lib/api/services.js +259 -126
- package/dist/lib/apsorc-parser.d.ts +7 -0
- package/dist/lib/apsorc-parser.js +4 -0
- package/dist/lib/biome/biome.json +21 -0
- package/dist/lib/config/types.js +4 -1
- package/dist/lib/generators/typescript.js +2 -1
- package/dist/lib/git.d.ts +23 -0
- package/dist/lib/git.js +108 -0
- package/dist/lib/templates/guards/guards.module.eta +18 -12
- package/dist/lib/templates/rest/module-rest.eta +4 -5
- package/dist/lib/types/entity.d.ts +21 -0
- package/dist/lib/types/generator.d.ts +10 -0
- package/dist/lib/upgrade.d.ts +19 -0
- package/dist/lib/upgrade.js +86 -0
- package/npm-shrinkwrap.json +158 -2
- package/oclif.manifest.json +56 -1
- package/package.json +3 -2
|
@@ -20,6 +20,11 @@ export type ApsorcType = {
|
|
|
20
20
|
* its own `emitEvents: false`.
|
|
21
21
|
*/
|
|
22
22
|
emitEvents?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Top-level default for HTTP controller generation. When false, no entity
|
|
25
|
+
* gets a generated controller unless it opts back in with `http: true`.
|
|
26
|
+
*/
|
|
27
|
+
http?: boolean;
|
|
23
28
|
};
|
|
24
29
|
type ParsedApsorcData = {
|
|
25
30
|
entities: Entity[];
|
|
@@ -34,6 +39,8 @@ type ParsedApsorc = {
|
|
|
34
39
|
language?: TargetLanguage;
|
|
35
40
|
/** Top-level default for the DomainEvent ("emitEvents") feature. */
|
|
36
41
|
emitEvents?: boolean;
|
|
42
|
+
/** Top-level default for HTTP controller generation. */
|
|
43
|
+
http?: boolean;
|
|
37
44
|
};
|
|
38
45
|
export declare const parseApsorcV1: (apsorc: ApsorcType) => ParsedApsorcData;
|
|
39
46
|
export declare const parseApsorcV2: (apsorc: ApsorcType) => ParsedApsorcData;
|
|
@@ -37,6 +37,7 @@ const parseRc = () => {
|
|
|
37
37
|
const auth = apsoConfig.auth;
|
|
38
38
|
const language = apsoConfig.language;
|
|
39
39
|
const emitEvents = apsoConfig.emitEvents;
|
|
40
|
+
const http = apsoConfig.http;
|
|
40
41
|
return {
|
|
41
42
|
rootFolder,
|
|
42
43
|
apiType,
|
|
@@ -46,6 +47,7 @@ const parseRc = () => {
|
|
|
46
47
|
auth,
|
|
47
48
|
language,
|
|
48
49
|
emitEvents,
|
|
50
|
+
http,
|
|
49
51
|
};
|
|
50
52
|
};
|
|
51
53
|
const parseApsorc = () => {
|
|
@@ -66,6 +68,7 @@ const parseApsorc = () => {
|
|
|
66
68
|
auth: apsoConfig.auth,
|
|
67
69
|
language: apsoConfig.language,
|
|
68
70
|
emitEvents: apsoConfig.emitEvents,
|
|
71
|
+
http: apsoConfig.http,
|
|
69
72
|
...(0, exports.parseApsorcV1)(apsoConfig),
|
|
70
73
|
};
|
|
71
74
|
if (debug) {
|
|
@@ -83,6 +86,7 @@ const parseApsorc = () => {
|
|
|
83
86
|
auth: apsoConfig.auth,
|
|
84
87
|
language: apsoConfig.language,
|
|
85
88
|
emitEvents: apsoConfig.emitEvents,
|
|
89
|
+
http: apsoConfig.http,
|
|
86
90
|
...(() => {
|
|
87
91
|
const relStart = perf_hooks_1.performance.now();
|
|
88
92
|
const parsed = (0, exports.parseApsorcV2)(apsoConfig);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
|
3
|
+
"formatter": {
|
|
4
|
+
"enabled": true,
|
|
5
|
+
"indentStyle": "space",
|
|
6
|
+
"indentWidth": 2,
|
|
7
|
+
"lineWidth": 100
|
|
8
|
+
},
|
|
9
|
+
"javascript": {
|
|
10
|
+
"parser": {
|
|
11
|
+
"unsafeParameterDecoratorsEnabled": true
|
|
12
|
+
},
|
|
13
|
+
"formatter": {
|
|
14
|
+
"quoteStyle": "double",
|
|
15
|
+
"semicolons": "always"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"linter": {
|
|
19
|
+
"enabled": false
|
|
20
|
+
}
|
|
21
|
+
}
|
package/dist/lib/config/types.js
CHANGED
|
@@ -11,7 +11,10 @@ exports.CONFIG_ENV_VARS = exports.DEFAULT_GLOBAL_CONFIG = void 0;
|
|
|
11
11
|
* Default values for global configuration
|
|
12
12
|
*/
|
|
13
13
|
exports.DEFAULT_GLOBAL_CONFIG = {
|
|
14
|
-
|
|
14
|
+
// The CLI talks to the BFF (the app's /api/* routes), not the internal
|
|
15
|
+
// backend on api.apso.dev. Override for staging with
|
|
16
|
+
// APSO_API_URL=https://app.staging.apso.cloud.
|
|
17
|
+
apiUrl: "https://app.apso.cloud",
|
|
15
18
|
webUrl: "https://app.apso.cloud",
|
|
16
19
|
verbose: false,
|
|
17
20
|
noColor: false,
|
|
@@ -249,7 +249,7 @@ class TypeScriptGenerator extends base_1.BaseGenerator {
|
|
|
249
249
|
];
|
|
250
250
|
}
|
|
251
251
|
async generateModule(options) {
|
|
252
|
-
const { entity, apiType } = options;
|
|
252
|
+
const { entity, apiType, includeController = true } = options;
|
|
253
253
|
const { name: entityName } = entity;
|
|
254
254
|
const moduleName = `${entityName}Module`;
|
|
255
255
|
const svcName = `${entityName}Service`;
|
|
@@ -261,6 +261,7 @@ class TypeScriptGenerator extends base_1.BaseGenerator {
|
|
|
261
261
|
ctrlName,
|
|
262
262
|
resolverName,
|
|
263
263
|
entityName,
|
|
264
|
+
includeController,
|
|
264
265
|
};
|
|
265
266
|
const templatePath = apiType === "graphql"
|
|
266
267
|
? "./graphql/gql-module-graphql"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local git helpers for the deploy flow.
|
|
3
|
+
*
|
|
4
|
+
* The CLI pushes generated code to GitHub with the user's own local git (their
|
|
5
|
+
* credentials / credential helper) — not through apso.ai. The platform only
|
|
6
|
+
* needs the repo *connected* to the service so the provisioner can clone it at
|
|
7
|
+
* deploy time.
|
|
8
|
+
*/
|
|
9
|
+
export declare function gitAvailable(): boolean;
|
|
10
|
+
export declare function isGitRepo(cwd: string): boolean;
|
|
11
|
+
export declare function initRepo(cwd: string): void;
|
|
12
|
+
export declare function getOriginUrl(cwd: string): string | null;
|
|
13
|
+
/** Parse `owner/repo` from an https or ssh GitHub remote URL. */
|
|
14
|
+
export declare function parseRepoFullName(url: string | null): string | null;
|
|
15
|
+
/** The repo the local project is already wired to, if any (owner/repo). */
|
|
16
|
+
export declare function localRepoFullName(cwd: string): string | null;
|
|
17
|
+
export declare function setOrigin(cwd: string, repoUrl: string): void;
|
|
18
|
+
export declare function hasUncommittedChanges(cwd: string): boolean;
|
|
19
|
+
/** Stage everything and commit, unless there is nothing to commit. */
|
|
20
|
+
export declare function commitAll(cwd: string, message: string): boolean;
|
|
21
|
+
/** Push the current HEAD to origin/<branch>, setting upstream. */
|
|
22
|
+
export declare function pushHead(cwd: string, branch: string): void;
|
|
23
|
+
export declare function httpsUrlFor(repoFullName: string): string;
|
package/dist/lib/git.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Local git helpers for the deploy flow.
|
|
4
|
+
*
|
|
5
|
+
* The CLI pushes generated code to GitHub with the user's own local git (their
|
|
6
|
+
* credentials / credential helper) — not through apso.ai. The platform only
|
|
7
|
+
* needs the repo *connected* to the service so the provisioner can clone it at
|
|
8
|
+
* deploy time.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.httpsUrlFor = exports.pushHead = exports.commitAll = exports.hasUncommittedChanges = exports.setOrigin = exports.localRepoFullName = exports.parseRepoFullName = exports.getOriginUrl = exports.initRepo = exports.isGitRepo = exports.gitAvailable = void 0;
|
|
12
|
+
const tslib_1 = require("tslib");
|
|
13
|
+
const child_process_1 = require("child_process");
|
|
14
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
15
|
+
const path = tslib_1.__importStar(require("path"));
|
|
16
|
+
function run(cmd, cwd) {
|
|
17
|
+
return (0, child_process_1.execSync)(cmd, {
|
|
18
|
+
cwd,
|
|
19
|
+
encoding: "utf8",
|
|
20
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
21
|
+
})
|
|
22
|
+
.toString()
|
|
23
|
+
.trim();
|
|
24
|
+
}
|
|
25
|
+
function gitAvailable() {
|
|
26
|
+
try {
|
|
27
|
+
(0, child_process_1.execSync)("git --version", { stdio: "ignore" });
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.gitAvailable = gitAvailable;
|
|
35
|
+
function isGitRepo(cwd) {
|
|
36
|
+
return fs.existsSync(path.join(cwd, ".git"));
|
|
37
|
+
}
|
|
38
|
+
exports.isGitRepo = isGitRepo;
|
|
39
|
+
function initRepo(cwd) {
|
|
40
|
+
if (!isGitRepo(cwd)) {
|
|
41
|
+
run("git init", cwd);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.initRepo = initRepo;
|
|
45
|
+
function getOriginUrl(cwd) {
|
|
46
|
+
try {
|
|
47
|
+
return run("git remote get-url origin", cwd);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.getOriginUrl = getOriginUrl;
|
|
54
|
+
/** Parse `owner/repo` from an https or ssh GitHub remote URL. */
|
|
55
|
+
function parseRepoFullName(url) {
|
|
56
|
+
if (!url)
|
|
57
|
+
return null;
|
|
58
|
+
const m = url.match(/github\.com[:/]([^/]+)\/(.+?)(?:\.git)?\/?$/i);
|
|
59
|
+
return m ? `${m[1]}/${m[2]}` : null;
|
|
60
|
+
}
|
|
61
|
+
exports.parseRepoFullName = parseRepoFullName;
|
|
62
|
+
/** The repo the local project is already wired to, if any (owner/repo). */
|
|
63
|
+
function localRepoFullName(cwd) {
|
|
64
|
+
return parseRepoFullName(getOriginUrl(cwd));
|
|
65
|
+
}
|
|
66
|
+
exports.localRepoFullName = localRepoFullName;
|
|
67
|
+
function setOrigin(cwd, repoUrl) {
|
|
68
|
+
if (getOriginUrl(cwd)) {
|
|
69
|
+
run(`git remote set-url origin ${repoUrl}`, cwd);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
run(`git remote add origin ${repoUrl}`, cwd);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
exports.setOrigin = setOrigin;
|
|
76
|
+
function hasUncommittedChanges(cwd) {
|
|
77
|
+
return run("git status --porcelain", cwd).length > 0;
|
|
78
|
+
}
|
|
79
|
+
exports.hasUncommittedChanges = hasUncommittedChanges;
|
|
80
|
+
/** Stage everything and commit, unless there is nothing to commit. */
|
|
81
|
+
function commitAll(cwd, message) {
|
|
82
|
+
run("git add -A", cwd);
|
|
83
|
+
if (!hasUncommittedChanges(cwd)) {
|
|
84
|
+
// Something may still be staged with no worktree diff; check the index.
|
|
85
|
+
const staged = (() => {
|
|
86
|
+
try {
|
|
87
|
+
return run("git diff --cached --name-only", cwd).length > 0;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
})();
|
|
93
|
+
if (!staged)
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
run(`git commit -m ${JSON.stringify(message)}`, cwd);
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
exports.commitAll = commitAll;
|
|
100
|
+
/** Push the current HEAD to origin/<branch>, setting upstream. */
|
|
101
|
+
function pushHead(cwd, branch) {
|
|
102
|
+
run(`git push -u origin HEAD:${branch}`, cwd);
|
|
103
|
+
}
|
|
104
|
+
exports.pushHead = pushHead;
|
|
105
|
+
function httpsUrlFor(repoFullName) {
|
|
106
|
+
return `https://github.com/${repoFullName}.git`;
|
|
107
|
+
}
|
|
108
|
+
exports.httpsUrlFor = httpsUrlFor;
|
|
@@ -37,9 +37,11 @@ import { <%= it.authConfig.accountUserEntity %> } from '../<%= it.authConfig.acc
|
|
|
37
37
|
*
|
|
38
38
|
* Usage:
|
|
39
39
|
* 1. Import GuardsModule in your AppModule
|
|
40
|
-
* 2. Guards are
|
|
41
|
-
*
|
|
42
|
-
*
|
|
40
|
+
* 2. Guards are ENABLED GLOBALLY BY DEFAULT (secure by default) because this
|
|
41
|
+
* schema declares an auth model / scoped entities. Requests are authenticated
|
|
42
|
+
* and tenant-scoped unless a route opts out with @Public().
|
|
43
|
+
* 3. Services with no auth model generate no guards and rely on the API Gateway
|
|
44
|
+
* API key at the edge.
|
|
43
45
|
*
|
|
44
46
|
<% if (it.authConfig) { %>
|
|
45
47
|
* AuthGuard:
|
|
@@ -88,18 +90,22 @@ import { <%= it.authConfig.accountUserEntity %> } from '../<%= it.authConfig.acc
|
|
|
88
90
|
<% if (it.scopedEntities && it.scopedEntities.length > 0) { %>
|
|
89
91
|
ScopeGuard,
|
|
90
92
|
<% } %>
|
|
91
|
-
//
|
|
93
|
+
// Global guards are ENABLED BY DEFAULT (secure by default) whenever the
|
|
94
|
+
// schema declares an auth model / scoped entities. Services with NO auth
|
|
95
|
+
// config generate no guards at all and are protected at the edge by the
|
|
96
|
+
// API Gateway API key — so this does not conflict with that setup. Use the
|
|
97
|
+
// @Public() decorator to opt an individual route out of authentication.
|
|
92
98
|
<% if (it.authConfig) { %>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
{
|
|
100
|
+
provide: APP_GUARD,
|
|
101
|
+
useClass: AuthGuard,
|
|
102
|
+
},
|
|
97
103
|
<% } %>
|
|
98
104
|
<% if (it.scopedEntities && it.scopedEntities.length > 0) { %>
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
{
|
|
106
|
+
provide: APP_GUARD,
|
|
107
|
+
useClass: ScopeGuard,
|
|
108
|
+
},
|
|
103
109
|
<% } %>
|
|
104
110
|
],
|
|
105
111
|
exports: [
|
|
@@ -4,13 +4,12 @@ import { Module } from "@nestjs/common";
|
|
|
4
4
|
import {<%= it.entityName %>} from './<%= it.entityName%>.entity'
|
|
5
5
|
import { TypeOrmModule } from "@nestjs/typeorm";
|
|
6
6
|
import {<%= it.svcName %>} from "./<%= it.entityName %>.service";
|
|
7
|
-
import {<%= it.ctrlName %>} from "./<%= it.entityName %>.controller";
|
|
8
|
-
|
|
7
|
+
<% if (it.includeController) { %>import {<%= it.ctrlName %>} from "./<%= it.entityName %>.controller";
|
|
8
|
+
<% } %>
|
|
9
9
|
@Module({
|
|
10
10
|
imports: [TypeOrmModule.forFeature([<%= it.entityName %>])],
|
|
11
11
|
providers: [<%= it.svcName %>],
|
|
12
12
|
exports: [<%= it.svcName %>],
|
|
13
|
-
controllers: [<%= it.ctrlName %>],
|
|
14
|
-
})
|
|
13
|
+
<% if (it.includeController) { %> controllers: [<%= it.ctrlName %>],
|
|
14
|
+
<% } %>})
|
|
15
15
|
export class <%= it.moduleName %> {}
|
|
16
|
-
|
|
@@ -55,5 +55,26 @@ export interface Entity {
|
|
|
55
55
|
* an individual entity can opt out with `emitEvents: false`.
|
|
56
56
|
*/
|
|
57
57
|
emitEvents?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Whether to generate the HTTP controller for this entity. Defaults to true.
|
|
60
|
+
* When false, the generator still emits the entity, service, DTOs, and a module
|
|
61
|
+
* (with `TypeOrmModule.forFeature` + the service), but omits the controller —
|
|
62
|
+
* leaving the HTTP surface to a hand-written controller without a route
|
|
63
|
+
* collision with the generated CRUD.
|
|
64
|
+
*
|
|
65
|
+
* Per-framework behavior (the flag is generic, but only matters where a
|
|
66
|
+
* generated route can't be cleanly overridden):
|
|
67
|
+
* - **NestJS** and **Gin (Go)**: actually suppresses the controller/route —
|
|
68
|
+
* these frameworks can't override a generated route (Nest controllers aren't
|
|
69
|
+
* DI-swappable; Gin panics on duplicate paths). Implemented for NestJS;
|
|
70
|
+
* Go is tracked separately.
|
|
71
|
+
* - **FastAPI**: effectively a **no-op** — FastAPI matches routes in
|
|
72
|
+
* registration order, so an app overrides by registering its router ahead of
|
|
73
|
+
* the generated `autogen_router` (no suppression needed).
|
|
74
|
+
*
|
|
75
|
+
* Resolution: the effective value is `entity.http ?? <global http> ?? true`, so
|
|
76
|
+
* controllers are on by default and opted out per entity (or globally).
|
|
77
|
+
*/
|
|
78
|
+
http?: boolean;
|
|
58
79
|
associations?: Association[];
|
|
59
80
|
}
|
|
@@ -109,6 +109,11 @@ export interface GeneratorConfig {
|
|
|
109
109
|
* Effective per-entity value is `entity.emitEvents ?? emitEvents ?? false`.
|
|
110
110
|
*/
|
|
111
111
|
emitEvents?: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* Top-level default for HTTP controller generation.
|
|
114
|
+
* Effective per-entity value is `entity.http ?? http ?? true`.
|
|
115
|
+
*/
|
|
116
|
+
http?: boolean;
|
|
112
117
|
/**
|
|
113
118
|
* Language-specific configuration options
|
|
114
119
|
*/
|
|
@@ -134,6 +139,11 @@ export interface EntityGenerationOptions {
|
|
|
134
139
|
* API type being generated
|
|
135
140
|
*/
|
|
136
141
|
apiType: string;
|
|
142
|
+
/**
|
|
143
|
+
* Whether the entity's HTTP controller is generated/wired. Defaults to true.
|
|
144
|
+
* When false, the module omits the controller (see Entity.http).
|
|
145
|
+
*/
|
|
146
|
+
includeController?: boolean;
|
|
137
147
|
}
|
|
138
148
|
/**
|
|
139
149
|
* Options for generating controllers/routers
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upgrade-on-limit flow.
|
|
3
|
+
*
|
|
4
|
+
* When the platform rejects an action because the workspace hit a plan limit
|
|
5
|
+
* (e.g. the free-tier service cap), we surface a friendly prompt that opens the
|
|
6
|
+
* upgrade page in the browser and retries once the user is done — mirroring the
|
|
7
|
+
* "you've hit a limit, upgrade?" flow developers expect from modern CLIs.
|
|
8
|
+
*/
|
|
9
|
+
import { ApiClientError } from "./api/client";
|
|
10
|
+
/**
|
|
11
|
+
* Prompt the user to upgrade after a limit error, open the upgrade page, and
|
|
12
|
+
* wait for them to finish. Returns true if the caller should retry the action.
|
|
13
|
+
*/
|
|
14
|
+
export declare function promptUpgrade(error: ApiClientError): Promise<boolean>;
|
|
15
|
+
/**
|
|
16
|
+
* Run an action; if it fails with an upgradeable plan-limit error, offer to
|
|
17
|
+
* upgrade and retry once.
|
|
18
|
+
*/
|
|
19
|
+
export declare function withUpgradeRetry<T>(action: () => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Upgrade-on-limit flow.
|
|
4
|
+
*
|
|
5
|
+
* When the platform rejects an action because the workspace hit a plan limit
|
|
6
|
+
* (e.g. the free-tier service cap), we surface a friendly prompt that opens the
|
|
7
|
+
* upgrade page in the browser and retries once the user is done — mirroring the
|
|
8
|
+
* "you've hit a limit, upgrade?" flow developers expect from modern CLIs.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.withUpgradeRetry = exports.promptUpgrade = void 0;
|
|
12
|
+
const tslib_1 = require("tslib");
|
|
13
|
+
const child_process_1 = require("child_process");
|
|
14
|
+
const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
|
|
15
|
+
const client_1 = require("./api/client");
|
|
16
|
+
const config_1 = require("./config");
|
|
17
|
+
/**
|
|
18
|
+
* Open a URL in the default browser (best-effort).
|
|
19
|
+
*/
|
|
20
|
+
function openBrowser(url) {
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
const platform = process.platform;
|
|
23
|
+
const command = platform === "darwin"
|
|
24
|
+
? `open "${url}"`
|
|
25
|
+
: platform === "win32"
|
|
26
|
+
? `start "" "${url}"`
|
|
27
|
+
: `xdg-open "${url}"`;
|
|
28
|
+
(0, child_process_1.exec)(command, () => resolve());
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Prompt the user to upgrade after a limit error, open the upgrade page, and
|
|
33
|
+
* wait for them to finish. Returns true if the caller should retry the action.
|
|
34
|
+
*/
|
|
35
|
+
async function promptUpgrade(error) {
|
|
36
|
+
const config = config_1.globalConfig.read();
|
|
37
|
+
const upgradeUrl = `${config.webUrl}${error.upgradeUrl || "/billing/upgrade"}`;
|
|
38
|
+
console.log("");
|
|
39
|
+
console.log(` ⚠ ${error.message || "You've reached a plan limit."}`);
|
|
40
|
+
console.log("");
|
|
41
|
+
const { proceed } = await inquirer_1.default.prompt([
|
|
42
|
+
{
|
|
43
|
+
type: "confirm",
|
|
44
|
+
name: "proceed",
|
|
45
|
+
message: "Open the upgrade page in your browser?",
|
|
46
|
+
default: true,
|
|
47
|
+
},
|
|
48
|
+
]);
|
|
49
|
+
if (!proceed) {
|
|
50
|
+
console.log(` You can upgrade any time at: ${upgradeUrl}`);
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
console.log("");
|
|
54
|
+
console.log(` Opening ${upgradeUrl}`);
|
|
55
|
+
console.log(" If the browser doesn't open, visit the URL above.");
|
|
56
|
+
await openBrowser(upgradeUrl);
|
|
57
|
+
const { done } = await inquirer_1.default.prompt([
|
|
58
|
+
{
|
|
59
|
+
type: "confirm",
|
|
60
|
+
name: "done",
|
|
61
|
+
message: "Press enter once your upgrade is complete to retry",
|
|
62
|
+
default: true,
|
|
63
|
+
},
|
|
64
|
+
]);
|
|
65
|
+
return done;
|
|
66
|
+
}
|
|
67
|
+
exports.promptUpgrade = promptUpgrade;
|
|
68
|
+
/**
|
|
69
|
+
* Run an action; if it fails with an upgradeable plan-limit error, offer to
|
|
70
|
+
* upgrade and retry once.
|
|
71
|
+
*/
|
|
72
|
+
async function withUpgradeRetry(action) {
|
|
73
|
+
try {
|
|
74
|
+
return await action();
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error instanceof client_1.ApiClientError && error.isLimitError()) {
|
|
78
|
+
const retry = await promptUpgrade(error);
|
|
79
|
+
if (retry) {
|
|
80
|
+
return action();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
exports.withUpgradeRetry = withUpgradeRetry;
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apso/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@apso/cli",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.20.0",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
|
+
"@biomejs/biome": "^1.9.4",
|
|
12
13
|
"@electric-sql/pglite": "^0.2.17",
|
|
13
14
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
14
15
|
"@oclif/core": "^2",
|
|
@@ -706,6 +707,161 @@
|
|
|
706
707
|
"dev": true,
|
|
707
708
|
"peer": true
|
|
708
709
|
},
|
|
710
|
+
"node_modules/@biomejs/biome": {
|
|
711
|
+
"version": "1.9.4",
|
|
712
|
+
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz",
|
|
713
|
+
"integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==",
|
|
714
|
+
"hasInstallScript": true,
|
|
715
|
+
"license": "MIT OR Apache-2.0",
|
|
716
|
+
"bin": {
|
|
717
|
+
"biome": "bin/biome"
|
|
718
|
+
},
|
|
719
|
+
"engines": {
|
|
720
|
+
"node": ">=14.21.3"
|
|
721
|
+
},
|
|
722
|
+
"funding": {
|
|
723
|
+
"type": "opencollective",
|
|
724
|
+
"url": "https://opencollective.com/biome"
|
|
725
|
+
},
|
|
726
|
+
"optionalDependencies": {
|
|
727
|
+
"@biomejs/cli-darwin-arm64": "1.9.4",
|
|
728
|
+
"@biomejs/cli-darwin-x64": "1.9.4",
|
|
729
|
+
"@biomejs/cli-linux-arm64": "1.9.4",
|
|
730
|
+
"@biomejs/cli-linux-arm64-musl": "1.9.4",
|
|
731
|
+
"@biomejs/cli-linux-x64": "1.9.4",
|
|
732
|
+
"@biomejs/cli-linux-x64-musl": "1.9.4",
|
|
733
|
+
"@biomejs/cli-win32-arm64": "1.9.4",
|
|
734
|
+
"@biomejs/cli-win32-x64": "1.9.4"
|
|
735
|
+
}
|
|
736
|
+
},
|
|
737
|
+
"node_modules/@biomejs/cli-darwin-arm64": {
|
|
738
|
+
"version": "1.9.4",
|
|
739
|
+
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz",
|
|
740
|
+
"integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==",
|
|
741
|
+
"cpu": [
|
|
742
|
+
"arm64"
|
|
743
|
+
],
|
|
744
|
+
"license": "MIT OR Apache-2.0",
|
|
745
|
+
"optional": true,
|
|
746
|
+
"os": [
|
|
747
|
+
"darwin"
|
|
748
|
+
],
|
|
749
|
+
"engines": {
|
|
750
|
+
"node": ">=14.21.3"
|
|
751
|
+
}
|
|
752
|
+
},
|
|
753
|
+
"node_modules/@biomejs/cli-darwin-x64": {
|
|
754
|
+
"version": "1.9.4",
|
|
755
|
+
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz",
|
|
756
|
+
"integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==",
|
|
757
|
+
"cpu": [
|
|
758
|
+
"x64"
|
|
759
|
+
],
|
|
760
|
+
"license": "MIT OR Apache-2.0",
|
|
761
|
+
"optional": true,
|
|
762
|
+
"os": [
|
|
763
|
+
"darwin"
|
|
764
|
+
],
|
|
765
|
+
"engines": {
|
|
766
|
+
"node": ">=14.21.3"
|
|
767
|
+
}
|
|
768
|
+
},
|
|
769
|
+
"node_modules/@biomejs/cli-linux-arm64": {
|
|
770
|
+
"version": "1.9.4",
|
|
771
|
+
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz",
|
|
772
|
+
"integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==",
|
|
773
|
+
"cpu": [
|
|
774
|
+
"arm64"
|
|
775
|
+
],
|
|
776
|
+
"license": "MIT OR Apache-2.0",
|
|
777
|
+
"optional": true,
|
|
778
|
+
"os": [
|
|
779
|
+
"linux"
|
|
780
|
+
],
|
|
781
|
+
"engines": {
|
|
782
|
+
"node": ">=14.21.3"
|
|
783
|
+
}
|
|
784
|
+
},
|
|
785
|
+
"node_modules/@biomejs/cli-linux-arm64-musl": {
|
|
786
|
+
"version": "1.9.4",
|
|
787
|
+
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz",
|
|
788
|
+
"integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==",
|
|
789
|
+
"cpu": [
|
|
790
|
+
"arm64"
|
|
791
|
+
],
|
|
792
|
+
"license": "MIT OR Apache-2.0",
|
|
793
|
+
"optional": true,
|
|
794
|
+
"os": [
|
|
795
|
+
"linux"
|
|
796
|
+
],
|
|
797
|
+
"engines": {
|
|
798
|
+
"node": ">=14.21.3"
|
|
799
|
+
}
|
|
800
|
+
},
|
|
801
|
+
"node_modules/@biomejs/cli-linux-x64": {
|
|
802
|
+
"version": "1.9.4",
|
|
803
|
+
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz",
|
|
804
|
+
"integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==",
|
|
805
|
+
"cpu": [
|
|
806
|
+
"x64"
|
|
807
|
+
],
|
|
808
|
+
"license": "MIT OR Apache-2.0",
|
|
809
|
+
"optional": true,
|
|
810
|
+
"os": [
|
|
811
|
+
"linux"
|
|
812
|
+
],
|
|
813
|
+
"engines": {
|
|
814
|
+
"node": ">=14.21.3"
|
|
815
|
+
}
|
|
816
|
+
},
|
|
817
|
+
"node_modules/@biomejs/cli-linux-x64-musl": {
|
|
818
|
+
"version": "1.9.4",
|
|
819
|
+
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz",
|
|
820
|
+
"integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==",
|
|
821
|
+
"cpu": [
|
|
822
|
+
"x64"
|
|
823
|
+
],
|
|
824
|
+
"license": "MIT OR Apache-2.0",
|
|
825
|
+
"optional": true,
|
|
826
|
+
"os": [
|
|
827
|
+
"linux"
|
|
828
|
+
],
|
|
829
|
+
"engines": {
|
|
830
|
+
"node": ">=14.21.3"
|
|
831
|
+
}
|
|
832
|
+
},
|
|
833
|
+
"node_modules/@biomejs/cli-win32-arm64": {
|
|
834
|
+
"version": "1.9.4",
|
|
835
|
+
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz",
|
|
836
|
+
"integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==",
|
|
837
|
+
"cpu": [
|
|
838
|
+
"arm64"
|
|
839
|
+
],
|
|
840
|
+
"license": "MIT OR Apache-2.0",
|
|
841
|
+
"optional": true,
|
|
842
|
+
"os": [
|
|
843
|
+
"win32"
|
|
844
|
+
],
|
|
845
|
+
"engines": {
|
|
846
|
+
"node": ">=14.21.3"
|
|
847
|
+
}
|
|
848
|
+
},
|
|
849
|
+
"node_modules/@biomejs/cli-win32-x64": {
|
|
850
|
+
"version": "1.9.4",
|
|
851
|
+
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz",
|
|
852
|
+
"integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==",
|
|
853
|
+
"cpu": [
|
|
854
|
+
"x64"
|
|
855
|
+
],
|
|
856
|
+
"license": "MIT OR Apache-2.0",
|
|
857
|
+
"optional": true,
|
|
858
|
+
"os": [
|
|
859
|
+
"win32"
|
|
860
|
+
],
|
|
861
|
+
"engines": {
|
|
862
|
+
"node": ">=14.21.3"
|
|
863
|
+
}
|
|
864
|
+
},
|
|
709
865
|
"node_modules/@cspotcode/source-map-support": {
|
|
710
866
|
"version": "0.8.1",
|
|
711
867
|
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|