@apso/cli 0.20.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/hooks/init/telemetry.d.ts +7 -0
- package/dist/hooks/init/telemetry.js +14 -0
- package/dist/hooks/postrun/telemetry.d.ts +8 -0
- package/dist/hooks/postrun/telemetry.js +18 -0
- 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/base-command.d.ts +8 -0
- package/dist/lib/base-command.js +21 -0
- package/dist/lib/config/manager.js +9 -0
- package/dist/lib/config/types.d.ts +7 -0
- package/dist/lib/git.d.ts +2 -0
- package/dist/lib/git.js +7 -2
- package/dist/lib/telemetry/telemetry.d.ts +21 -0
- package/dist/lib/telemetry/telemetry.js +162 -0
- package/dist/lib/templates/go/header.eta +4 -3
- package/dist/lib/templates/header.eta +5 -7
- package/dist/lib/templates/python/header.eta +4 -4
- package/npm-shrinkwrap.json +433 -12
- package/oclif.manifest.json +23 -1
- package/package.json +7 -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("");
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const telemetry_1 = require("../../lib/telemetry/telemetry");
|
|
4
|
+
/**
|
|
5
|
+
* Runs before any command. Initializes PostHog + Sentry and records that a
|
|
6
|
+
* command was invoked. Best-effort: never blocks or fails the command.
|
|
7
|
+
*/
|
|
8
|
+
const hook = async function (opts) {
|
|
9
|
+
(0, telemetry_1.initTelemetry)();
|
|
10
|
+
if (opts.id) {
|
|
11
|
+
(0, telemetry_1.track)("cli_command_started", { command: opts.id });
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
exports.default = hook;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Hook } from "@oclif/core";
|
|
2
|
+
/**
|
|
3
|
+
* Runs after a command completes successfully. Records completion + duration,
|
|
4
|
+
* then flushes both clients before the short-lived CLI process exits (posthog
|
|
5
|
+
* batches, so an explicit flush is required or events are lost).
|
|
6
|
+
*/
|
|
7
|
+
declare const hook: Hook<"postrun">;
|
|
8
|
+
export default hook;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const telemetry_1 = require("../../lib/telemetry/telemetry");
|
|
4
|
+
/**
|
|
5
|
+
* Runs after a command completes successfully. Records completion + duration,
|
|
6
|
+
* then flushes both clients before the short-lived CLI process exits (posthog
|
|
7
|
+
* batches, so an explicit flush is required or events are lost).
|
|
8
|
+
*/
|
|
9
|
+
const hook = async function (opts) {
|
|
10
|
+
var _a;
|
|
11
|
+
(0, telemetry_1.track)("cli_command_completed", {
|
|
12
|
+
command: (_a = opts.Command) === null || _a === void 0 ? void 0 : _a.id,
|
|
13
|
+
duration_ms: (0, telemetry_1.elapsedMs)(),
|
|
14
|
+
success: true,
|
|
15
|
+
});
|
|
16
|
+
await (0, telemetry_1.shutdownTelemetry)();
|
|
17
|
+
};
|
|
18
|
+
exports.default = hook;
|
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) {
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { Command } from "@oclif/core";
|
|
2
2
|
export default abstract class BaseCommand extends Command {
|
|
3
|
+
/**
|
|
4
|
+
* Report failures to Sentry + PostHog before oclif exits. The postrun hook
|
|
5
|
+
* does not fire on error, so the flush has to happen here.
|
|
6
|
+
*/
|
|
7
|
+
catch(err: Error & {
|
|
8
|
+
exitCode?: number;
|
|
9
|
+
code?: string;
|
|
10
|
+
}): Promise<any>;
|
|
3
11
|
runCommand(command: string, args: string[], silent?: boolean): Promise<void>;
|
|
4
12
|
runNpmCommand(args: string[], silent?: boolean): Promise<void>;
|
|
5
13
|
}
|
package/dist/lib/base-command.js
CHANGED
|
@@ -4,7 +4,28 @@ const tslib_1 = require("tslib");
|
|
|
4
4
|
const core_1 = require("@oclif/core");
|
|
5
5
|
const child_process_1 = require("child_process");
|
|
6
6
|
const os_1 = tslib_1.__importDefault(require("os"));
|
|
7
|
+
const telemetry_1 = require("./telemetry/telemetry");
|
|
7
8
|
class BaseCommand extends core_1.Command {
|
|
9
|
+
/**
|
|
10
|
+
* Report failures to Sentry + PostHog before oclif exits. The postrun hook
|
|
11
|
+
* does not fire on error, so the flush has to happen here.
|
|
12
|
+
*/
|
|
13
|
+
async catch(err) {
|
|
14
|
+
try {
|
|
15
|
+
(0, telemetry_1.captureException)(err, { command: this.id });
|
|
16
|
+
(0, telemetry_1.track)("cli_command_failed", {
|
|
17
|
+
command: this.id,
|
|
18
|
+
duration_ms: (0, telemetry_1.elapsedMs)(),
|
|
19
|
+
error: err === null || err === void 0 ? void 0 : err.message,
|
|
20
|
+
error_code: err === null || err === void 0 ? void 0 : err.code,
|
|
21
|
+
});
|
|
22
|
+
await (0, telemetry_1.shutdownTelemetry)();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// never mask the original error with a telemetry failure
|
|
26
|
+
}
|
|
27
|
+
return super.catch(err);
|
|
28
|
+
}
|
|
8
29
|
async runCommand(command, args, silent = false) {
|
|
9
30
|
return new Promise((resolve, reject) => {
|
|
10
31
|
const isWindows = os_1.default.platform() === "win32";
|
|
@@ -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/dist/lib/git.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export declare function setOrigin(cwd: string, repoUrl: string): void;
|
|
|
18
18
|
export declare function hasUncommittedChanges(cwd: string): boolean;
|
|
19
19
|
/** Stage everything and commit, unless there is nothing to commit. */
|
|
20
20
|
export declare function commitAll(cwd: string, message: string): boolean;
|
|
21
|
+
/** Attribution trailer appended to every commit the CLI creates. */
|
|
22
|
+
export declare const APSO_COMMIT_TRAILER = "Generated with Apso (https://apso.ai)";
|
|
21
23
|
/** Push the current HEAD to origin/<branch>, setting upstream. */
|
|
22
24
|
export declare function pushHead(cwd: string, branch: string): void;
|
|
23
25
|
export declare function httpsUrlFor(repoFullName: string): string;
|
package/dist/lib/git.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* deploy time.
|
|
9
9
|
*/
|
|
10
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;
|
|
11
|
+
exports.httpsUrlFor = exports.pushHead = exports.APSO_COMMIT_TRAILER = exports.commitAll = exports.hasUncommittedChanges = exports.setOrigin = exports.localRepoFullName = exports.parseRepoFullName = exports.getOriginUrl = exports.initRepo = exports.isGitRepo = exports.gitAvailable = void 0;
|
|
12
12
|
const tslib_1 = require("tslib");
|
|
13
13
|
const child_process_1 = require("child_process");
|
|
14
14
|
const fs = tslib_1.__importStar(require("fs"));
|
|
@@ -93,10 +93,15 @@ function commitAll(cwd, message) {
|
|
|
93
93
|
if (!staged)
|
|
94
94
|
return false;
|
|
95
95
|
}
|
|
96
|
-
|
|
96
|
+
// Second -m adds a trailer paragraph attributing the code to Apso. Mirrors
|
|
97
|
+
// the "Co-Authored-By" convention: a machine-visible footprint on every
|
|
98
|
+
// commit the CLI makes.
|
|
99
|
+
run(`git commit -m ${JSON.stringify(message)} -m ${JSON.stringify(exports.APSO_COMMIT_TRAILER)}`, cwd);
|
|
97
100
|
return true;
|
|
98
101
|
}
|
|
99
102
|
exports.commitAll = commitAll;
|
|
103
|
+
/** Attribution trailer appended to every commit the CLI creates. */
|
|
104
|
+
exports.APSO_COMMIT_TRAILER = "Generated with Apso (https://apso.ai)";
|
|
100
105
|
/** Push the current HEAD to origin/<branch>, setting upstream. */
|
|
101
106
|
function pushHead(cwd, branch) {
|
|
102
107
|
run(`git push -u origin HEAD:${branch}`, cwd);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
declare function isDisabled(telemetryDisabled: boolean): boolean;
|
|
2
|
+
/** Stable, non-PII fallback id for logged-out users (no username, no MAC). */
|
|
3
|
+
declare function anonMachineId(): string;
|
|
4
|
+
/**
|
|
5
|
+
* Initialize telemetry. Call once at process start (init hook). Safe to call
|
|
6
|
+
* when disabled — it just no-ops everything downstream.
|
|
7
|
+
*/
|
|
8
|
+
export declare function initTelemetry(): void;
|
|
9
|
+
/** Emit a PostHog event. No-op when disabled. */
|
|
10
|
+
export declare function track(event: string, properties?: Record<string, unknown>): void;
|
|
11
|
+
/** Report an exception to Sentry. No-op when disabled. */
|
|
12
|
+
export declare function captureException(err: unknown, context?: Record<string, unknown>): void;
|
|
13
|
+
/** Milliseconds since the process (CLI invocation) started. */
|
|
14
|
+
export declare function elapsedMs(): number;
|
|
15
|
+
/** Flush + close both clients. Call before the process exits. */
|
|
16
|
+
export declare function shutdownTelemetry(): Promise<void>;
|
|
17
|
+
export declare const __testing: {
|
|
18
|
+
isDisabled: typeof isDisabled;
|
|
19
|
+
anonMachineId: typeof anonMachineId;
|
|
20
|
+
};
|
|
21
|
+
export {};
|