@apso/cli 0.21.0 → 0.22.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.js +1 -1
- package/dist/commands/init.js +2 -2
- package/dist/commands/link.js +3 -3
- package/dist/commands/login.js +3 -3
- package/dist/commands/projects.js +17 -5
- package/dist/commands/status.js +1 -1
- package/dist/commands/use.d.ts +14 -0
- package/dist/commands/use.js +86 -0
- package/dist/commands/whoami.js +6 -4
- package/dist/lib/api/client.js +3 -1
- package/dist/lib/api/services.d.ts +4 -4
- package/dist/lib/api/services.js +16 -7
- package/dist/lib/config/manager.js +9 -0
- package/dist/lib/config/types.d.ts +7 -0
- package/npm-shrinkwrap.json +2 -2
- package/oclif.manifest.json +23 -1
- package/package.json +1 -1
package/dist/commands/deploy.js
CHANGED
|
@@ -23,7 +23,7 @@ class Deploy extends base_command_1.default {
|
|
|
23
23
|
this.error("Project not linked. Run 'apso link' first.");
|
|
24
24
|
}
|
|
25
25
|
// Fetch service
|
|
26
|
-
const service = await services_1.servicesApi.get(link.
|
|
26
|
+
const service = await services_1.servicesApi.get(link.workspaceId, link.serviceSlug);
|
|
27
27
|
// Run migration sandbox (unless skipped)
|
|
28
28
|
let migrationCount = 0;
|
|
29
29
|
if (!flags["skip-migrate"]) {
|
package/dist/commands/init.js
CHANGED
|
@@ -60,7 +60,7 @@ class Init extends base_command_1.default {
|
|
|
60
60
|
},
|
|
61
61
|
]);
|
|
62
62
|
// Select service
|
|
63
|
-
const servicesResponse = await services_1.servicesApi.list(workspace.
|
|
63
|
+
const servicesResponse = await services_1.servicesApi.list(workspace.id);
|
|
64
64
|
if (servicesResponse.data.length === 0) {
|
|
65
65
|
this.error(`No services found in workspace "${workspace.name}". Create one first or choose "Create a new project".`);
|
|
66
66
|
}
|
|
@@ -177,7 +177,7 @@ class Init extends base_command_1.default {
|
|
|
177
177
|
// Create service on platform
|
|
178
178
|
this.log(`Creating service "${projectName}" in workspace "${workspace.name}"...`);
|
|
179
179
|
const serviceName = projectName;
|
|
180
|
-
const service = await (0, upgrade_1.withUpgradeRetry)(() => services_1.servicesApi.create(workspace.
|
|
180
|
+
const service = await (0, upgrade_1.withUpgradeRetry)(() => services_1.servicesApi.create(workspace.id, {
|
|
181
181
|
name: serviceName,
|
|
182
182
|
}));
|
|
183
183
|
// Clone template
|
package/dist/commands/link.js
CHANGED
|
@@ -63,10 +63,10 @@ class Link extends base_command_1.default {
|
|
|
63
63
|
// Determine service
|
|
64
64
|
let service;
|
|
65
65
|
if (flags.service) {
|
|
66
|
-
service = await services_1.servicesApi.get(workspace.
|
|
66
|
+
service = await services_1.servicesApi.get(workspace.id, flags.service);
|
|
67
67
|
}
|
|
68
68
|
else {
|
|
69
|
-
const servicesResponse = await services_1.servicesApi.list(workspace.
|
|
69
|
+
const servicesResponse = await services_1.servicesApi.list(workspace.id);
|
|
70
70
|
const services = servicesResponse.data;
|
|
71
71
|
const choices = [
|
|
72
72
|
...services.map((svc) => ({
|
|
@@ -97,7 +97,7 @@ class Link extends base_command_1.default {
|
|
|
97
97
|
},
|
|
98
98
|
]);
|
|
99
99
|
this.log(`Creating service "${serviceName}"...`);
|
|
100
|
-
service = await (0, upgrade_1.withUpgradeRetry)(() => services_1.servicesApi.create(workspace.
|
|
100
|
+
service = await (0, upgrade_1.withUpgradeRetry)(() => services_1.servicesApi.create(workspace.id, {
|
|
101
101
|
name: serviceName,
|
|
102
102
|
}));
|
|
103
103
|
}
|
package/dist/commands/login.js
CHANGED
|
@@ -298,9 +298,9 @@ class Login extends base_command_1.default {
|
|
|
298
298
|
this.log(`Successfully logged in as ${tokenResponse.user.email}`);
|
|
299
299
|
this.log("");
|
|
300
300
|
this.log("Next steps:");
|
|
301
|
-
this.log(" apso
|
|
302
|
-
this.log(" apso
|
|
303
|
-
this.log(" apso
|
|
301
|
+
this.log(" apso use - Select your workspace");
|
|
302
|
+
this.log(" apso whoami - View your account + workspace");
|
|
303
|
+
this.log(" apso projects - List services in a workspace");
|
|
304
304
|
}
|
|
305
305
|
catch (error) {
|
|
306
306
|
if (error instanceof Error) {
|
|
@@ -13,17 +13,28 @@ class Projects extends base_command_1.default {
|
|
|
13
13
|
if (!config_1.credentials.isValid()) {
|
|
14
14
|
this.error("Not logged in. Run 'apso login' first.");
|
|
15
15
|
}
|
|
16
|
-
// Determine workspace
|
|
16
|
+
// Determine workspace. The BFF scopes services by the NUMERIC workspace id,
|
|
17
|
+
// so resolve slugs to an id before listing.
|
|
18
|
+
let workspaceId;
|
|
17
19
|
let workspaceSlug;
|
|
18
20
|
if (flags.workspace) {
|
|
19
|
-
|
|
21
|
+
const ws = await services_1.workspacesApi.get(flags.workspace);
|
|
22
|
+
workspaceId = String(ws.id);
|
|
23
|
+
workspaceSlug = ws.slug;
|
|
20
24
|
}
|
|
21
25
|
else {
|
|
22
|
-
//
|
|
26
|
+
// Prefer an existing link, then the active workspace set via `apso use`,
|
|
27
|
+
// and only prompt as a last resort.
|
|
23
28
|
const link = config_1.projectLink.read();
|
|
29
|
+
const active = config_1.globalConfig.read();
|
|
24
30
|
if (link) {
|
|
31
|
+
workspaceId = String(link.workspaceId);
|
|
25
32
|
workspaceSlug = link.workspaceSlug;
|
|
26
33
|
}
|
|
34
|
+
else if (active.activeWorkspaceId) {
|
|
35
|
+
workspaceId = active.activeWorkspaceId;
|
|
36
|
+
workspaceSlug = active.activeWorkspaceSlug || active.activeWorkspaceId;
|
|
37
|
+
}
|
|
27
38
|
else {
|
|
28
39
|
// Prompt
|
|
29
40
|
const workspaces = await services_1.workspacesApi.list();
|
|
@@ -41,11 +52,12 @@ class Projects extends base_command_1.default {
|
|
|
41
52
|
})),
|
|
42
53
|
},
|
|
43
54
|
]);
|
|
55
|
+
workspaceId = String(response.workspace.id);
|
|
44
56
|
workspaceSlug = response.workspace.slug;
|
|
45
57
|
}
|
|
46
58
|
}
|
|
47
|
-
// Fetch services
|
|
48
|
-
const servicesResponse = await services_1.servicesApi.list(
|
|
59
|
+
// Fetch services (scoped by numeric workspace id)
|
|
60
|
+
const servicesResponse = await services_1.servicesApi.list(workspaceId);
|
|
49
61
|
const services = servicesResponse.data;
|
|
50
62
|
if (services.length === 0) {
|
|
51
63
|
this.log(`No services found in workspace "${workspaceSlug}".`);
|
package/dist/commands/status.js
CHANGED
|
@@ -16,7 +16,7 @@ class Status extends base_command_1.default {
|
|
|
16
16
|
this.error("Project not linked. Run 'apso link' first.");
|
|
17
17
|
}
|
|
18
18
|
// Fetch service
|
|
19
|
-
const service = await services_1.servicesApi.get(link.
|
|
19
|
+
const service = await services_1.servicesApi.get(link.workspaceId, link.serviceSlug);
|
|
20
20
|
this.log(`Service: ${service.name} (${service.slug})`);
|
|
21
21
|
this.log(`Workspace: ${link.workspaceSlug}`);
|
|
22
22
|
this.log(`Status: ${service.status}`);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import BaseCommand from "../lib/base-command";
|
|
2
|
+
/**
|
|
3
|
+
* Select the active workspace for subsequent CLI commands. With no argument it
|
|
4
|
+
* shows an interactive picker; pass a slug/name to set it directly. The choice
|
|
5
|
+
* persists to ~/.apso/config.json and scopes every workspace-aware command.
|
|
6
|
+
*/
|
|
7
|
+
export default class Use extends BaseCommand {
|
|
8
|
+
static description: string;
|
|
9
|
+
static examples: string[];
|
|
10
|
+
static args: {
|
|
11
|
+
workspace: import("@oclif/core/lib/interfaces/parser").Arg<string | undefined, Record<string, unknown>>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const core_1 = require("@oclif/core");
|
|
5
|
+
const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
|
|
6
|
+
const base_command_1 = tslib_1.__importDefault(require("../lib/base-command"));
|
|
7
|
+
const config_1 = require("../lib/config");
|
|
8
|
+
const services_1 = require("../lib/api/services");
|
|
9
|
+
/**
|
|
10
|
+
* Select the active workspace for subsequent CLI commands. With no argument it
|
|
11
|
+
* shows an interactive picker; pass a slug/name to set it directly. The choice
|
|
12
|
+
* persists to ~/.apso/config.json and scopes every workspace-aware command.
|
|
13
|
+
*/
|
|
14
|
+
class Use extends base_command_1.default {
|
|
15
|
+
async run() {
|
|
16
|
+
const { args } = await this.parse(Use);
|
|
17
|
+
if (!config_1.credentials.isValid()) {
|
|
18
|
+
this.error("Not logged in. Run 'apso login' first.");
|
|
19
|
+
}
|
|
20
|
+
const workspaces = await services_1.workspacesApi.list();
|
|
21
|
+
if (workspaces.length === 0) {
|
|
22
|
+
this.error("No workspaces found. Create one at https://app.apso.cloud first.");
|
|
23
|
+
}
|
|
24
|
+
let ws;
|
|
25
|
+
if (args.workspace) {
|
|
26
|
+
ws = workspaces.find((w) => w.slug === args.workspace || w.name === args.workspace);
|
|
27
|
+
if (!ws) {
|
|
28
|
+
this.error(`Workspace not found: ${args.workspace}\nRun 'apso use' to pick from a list.`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else if (workspaces.length === 1) {
|
|
32
|
+
ws = workspaces[0];
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
const answer = await inquirer_1.default.prompt([
|
|
36
|
+
{
|
|
37
|
+
type: "list",
|
|
38
|
+
name: "workspace",
|
|
39
|
+
message: "Select your workspace:",
|
|
40
|
+
choices: workspaces.map((w) => ({
|
|
41
|
+
name: `${w.name} (${w.slug})`,
|
|
42
|
+
value: w,
|
|
43
|
+
})),
|
|
44
|
+
},
|
|
45
|
+
]);
|
|
46
|
+
ws = answer.workspace;
|
|
47
|
+
}
|
|
48
|
+
config_1.globalConfig.write({
|
|
49
|
+
activeWorkspaceId: String(ws.id),
|
|
50
|
+
activeWorkspaceSlug: ws.slug,
|
|
51
|
+
activeWorkspaceName: ws.name,
|
|
52
|
+
});
|
|
53
|
+
this.log("");
|
|
54
|
+
this.log(` Now using workspace: ${ws.name} (${ws.slug})`);
|
|
55
|
+
// Show what's in the workspace so the next step is obvious.
|
|
56
|
+
try {
|
|
57
|
+
const services = (await services_1.servicesApi.list(ws.id)).data;
|
|
58
|
+
this.log("");
|
|
59
|
+
if (services.length > 0) {
|
|
60
|
+
this.log(" Services:");
|
|
61
|
+
for (const s of services) {
|
|
62
|
+
this.log(` - ${s.name} (${s.slug})`);
|
|
63
|
+
}
|
|
64
|
+
this.log("");
|
|
65
|
+
this.log(" Run 'apso link' inside a project to work with one.");
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
this.log(" No services yet.");
|
|
69
|
+
this.log(" Run 'apso init' to scaffold one, then 'apso deploy' to ship it.");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// listing is a convenience; never fail `use` over it
|
|
74
|
+
}
|
|
75
|
+
this.log("");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
Use.description = "Select the active workspace for CLI commands";
|
|
79
|
+
Use.examples = ["$ apso use", "$ apso use my-workspace"];
|
|
80
|
+
Use.args = {
|
|
81
|
+
workspace: core_1.Args.string({
|
|
82
|
+
description: "Workspace slug or name (skips the picker)",
|
|
83
|
+
required: false,
|
|
84
|
+
}),
|
|
85
|
+
};
|
|
86
|
+
exports.default = Use;
|
package/dist/commands/whoami.js
CHANGED
|
@@ -71,12 +71,14 @@ class Whoami extends base_command_1.default {
|
|
|
71
71
|
this.log(` Workspace: ${link.workspaceSlug}`);
|
|
72
72
|
this.log(` Service: ${link.serviceSlug}`);
|
|
73
73
|
}
|
|
74
|
-
else if (config.
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
else if (config.activeWorkspaceName || config.activeWorkspaceSlug) {
|
|
75
|
+
const name = config.activeWorkspaceName || config.activeWorkspaceSlug;
|
|
76
|
+
const slug = config.activeWorkspaceSlug;
|
|
77
|
+
this.log(` Workspace: ${name}${slug ? ` (${slug})` : ""}`);
|
|
78
|
+
this.log(" Service: None (run 'apso link' in a project to set)");
|
|
77
79
|
}
|
|
78
80
|
else {
|
|
79
|
-
this.log(" Workspace: None (run 'apso use
|
|
81
|
+
this.log(" Workspace: None (run 'apso use' to select one)");
|
|
80
82
|
this.log(" Service: None");
|
|
81
83
|
}
|
|
82
84
|
this.log("");
|
package/dist/lib/api/client.js
CHANGED
|
@@ -225,7 +225,9 @@ async function apiRequest(path, options = {}) {
|
|
|
225
225
|
// Resolve the active workspace id from, in order: an explicit header the
|
|
226
226
|
// caller passed, the APSO_WORKSPACE_ID env override, or the linked project.
|
|
227
227
|
if (!requestHeaders["X-Workspace-Id"]) {
|
|
228
|
-
const activeWorkspaceId = process.env.APSO_WORKSPACE_ID ||
|
|
228
|
+
const activeWorkspaceId = process.env.APSO_WORKSPACE_ID ||
|
|
229
|
+
((_a = config_1.projectLink.read()) === null || _a === void 0 ? void 0 : _a.workspaceId) ||
|
|
230
|
+
config_1.globalConfig.read().activeWorkspaceId;
|
|
229
231
|
if (activeWorkspaceId) {
|
|
230
232
|
requestHeaders["X-Workspace-Id"] = String(activeWorkspaceId);
|
|
231
233
|
}
|
|
@@ -37,15 +37,15 @@ export declare const workspacesApi: {
|
|
|
37
37
|
* Services API
|
|
38
38
|
*/
|
|
39
39
|
export declare const servicesApi: {
|
|
40
|
-
list(
|
|
40
|
+
list(workspaceId?: string | number, options?: {
|
|
41
41
|
status?: string;
|
|
42
42
|
page?: number;
|
|
43
43
|
pageSize?: number;
|
|
44
44
|
}): Promise<PaginatedResponse<Service>>;
|
|
45
45
|
getById(id: string): Promise<Service>;
|
|
46
|
-
/** Resolve (
|
|
47
|
-
get(
|
|
48
|
-
create(
|
|
46
|
+
/** Resolve (workspaceId, serviceSlug) to a service via the workspace-scoped list. */
|
|
47
|
+
get(workspaceId: string, serviceSlug: string): Promise<Service>;
|
|
48
|
+
create(workspaceId: string, data: {
|
|
49
49
|
name: string;
|
|
50
50
|
slug?: string;
|
|
51
51
|
description?: string;
|
package/dist/lib/api/services.js
CHANGED
|
@@ -128,30 +128,39 @@ exports.workspacesApi = {
|
|
|
128
128
|
* Services API
|
|
129
129
|
*/
|
|
130
130
|
exports.servicesApi = {
|
|
131
|
-
async list(
|
|
131
|
+
async list(workspaceId, options) {
|
|
132
132
|
const params = {};
|
|
133
133
|
if (options === null || options === void 0 ? void 0 : options.pageSize)
|
|
134
134
|
params.limit = options.pageSize;
|
|
135
135
|
if ((options === null || options === void 0 ? void 0 : options.page) && (options === null || options === void 0 ? void 0 : options.pageSize)) {
|
|
136
136
|
params.offset = (options.page - 1) * options.pageSize;
|
|
137
137
|
}
|
|
138
|
-
|
|
138
|
+
// The BFF scopes /api/services by the NUMERIC workspace id in the
|
|
139
|
+
// X-Workspace-Id header (not the slug). Pass it explicitly so the call
|
|
140
|
+
// works even before a project is linked.
|
|
141
|
+
const headers = workspaceId
|
|
142
|
+
? { "X-Workspace-Id": String(workspaceId) }
|
|
143
|
+
: undefined;
|
|
144
|
+
const raw = await client_1.api.get("/api/services", { params, headers });
|
|
139
145
|
return adaptPaginated(raw, adaptService);
|
|
140
146
|
},
|
|
141
147
|
async getById(id) {
|
|
142
148
|
const raw = await client_1.api.get(`/api/services/${id}`);
|
|
143
149
|
return adaptService(raw);
|
|
144
150
|
},
|
|
145
|
-
/** Resolve (
|
|
146
|
-
async get(
|
|
147
|
-
const list = await this.list();
|
|
151
|
+
/** Resolve (workspaceId, serviceSlug) to a service via the workspace-scoped list. */
|
|
152
|
+
async get(workspaceId, serviceSlug) {
|
|
153
|
+
const list = await this.list(workspaceId);
|
|
148
154
|
const match = list.data.find((s) => s.slug === serviceSlug);
|
|
149
155
|
if (!match)
|
|
150
156
|
throw new Error(`Service not found: ${serviceSlug}`);
|
|
151
157
|
return match;
|
|
152
158
|
},
|
|
153
|
-
async create(
|
|
154
|
-
const
|
|
159
|
+
async create(workspaceId, data) {
|
|
160
|
+
const headers = workspaceId
|
|
161
|
+
? { "X-Workspace-Id": String(workspaceId) }
|
|
162
|
+
: undefined;
|
|
163
|
+
const raw = await client_1.api.post("/api/services", { name: data.name }, { headers });
|
|
155
164
|
return adaptService(raw);
|
|
156
165
|
},
|
|
157
166
|
async update(_workspaceSlug, serviceSlugOrId, data) {
|
|
@@ -72,6 +72,15 @@ exports.globalConfig = {
|
|
|
72
72
|
if (fileConfig) {
|
|
73
73
|
Object.assign(config, fileConfig);
|
|
74
74
|
}
|
|
75
|
+
// Auto-migrate legacy apso.ai hosts written by an older CLI. Without this a
|
|
76
|
+
// saved config keeps pointing login/API at the retired app.apso.ai/
|
|
77
|
+
// api.apso.ai instead of the current apso.cloud defaults.
|
|
78
|
+
if (config.apiUrl && /(?:app|api)\.apso\.ai\b/.test(config.apiUrl)) {
|
|
79
|
+
config.apiUrl = types_1.DEFAULT_GLOBAL_CONFIG.apiUrl;
|
|
80
|
+
}
|
|
81
|
+
if (config.webUrl && /(?:app|api)\.apso\.ai\b/.test(config.webUrl)) {
|
|
82
|
+
config.webUrl = types_1.DEFAULT_GLOBAL_CONFIG.webUrl;
|
|
83
|
+
}
|
|
75
84
|
// Override with environment variables
|
|
76
85
|
if (process.env.APSO_API_URL) {
|
|
77
86
|
config.apiUrl = process.env.APSO_API_URL;
|
|
@@ -96,6 +96,13 @@ export interface GlobalConfigFile {
|
|
|
96
96
|
* Default workspace slug to use
|
|
97
97
|
*/
|
|
98
98
|
defaultWorkspace?: string;
|
|
99
|
+
/**
|
|
100
|
+
* Active workspace, set via `apso use`. The numeric id scopes API calls
|
|
101
|
+
* (X-Workspace-Id); slug/name are for display.
|
|
102
|
+
*/
|
|
103
|
+
activeWorkspaceId?: string;
|
|
104
|
+
activeWorkspaceSlug?: string;
|
|
105
|
+
activeWorkspaceName?: string;
|
|
99
106
|
/**
|
|
100
107
|
* Platform API base URL
|
|
101
108
|
*/
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apso/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.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.22.0",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@biomejs/biome": "^1.9.4",
|
package/oclif.manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.22.0",
|
|
3
3
|
"commands": {
|
|
4
4
|
"config": {
|
|
5
5
|
"id": "config",
|
|
@@ -501,6 +501,28 @@
|
|
|
501
501
|
},
|
|
502
502
|
"args": {}
|
|
503
503
|
},
|
|
504
|
+
"use": {
|
|
505
|
+
"id": "use",
|
|
506
|
+
"description": "Select the active workspace for CLI commands",
|
|
507
|
+
"strict": true,
|
|
508
|
+
"pluginName": "@apso/cli",
|
|
509
|
+
"pluginAlias": "@apso/cli",
|
|
510
|
+
"pluginType": "core",
|
|
511
|
+
"aliases": [],
|
|
512
|
+
"hiddenAliases": [],
|
|
513
|
+
"examples": [
|
|
514
|
+
"$ apso use",
|
|
515
|
+
"$ apso use my-workspace"
|
|
516
|
+
],
|
|
517
|
+
"flags": {},
|
|
518
|
+
"args": {
|
|
519
|
+
"workspace": {
|
|
520
|
+
"name": "workspace",
|
|
521
|
+
"description": "Workspace slug or name (skips the picker)",
|
|
522
|
+
"required": false
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
},
|
|
504
526
|
"whoami": {
|
|
505
527
|
"id": "whoami",
|
|
506
528
|
"description": "Display information about the current user and context",
|