@apso/cli 0.8.6 → 0.10.2
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/README.md +221 -1653
- package/dist/commands/config.d.ts +13 -0
- package/dist/commands/config.js +114 -0
- package/dist/commands/deploy.d.ts +11 -0
- package/dist/commands/deploy.js +141 -0
- package/dist/commands/dev.d.ts +15 -0
- package/dist/commands/dev.js +95 -0
- package/dist/commands/generate.d.ts +12 -0
- package/dist/commands/generate.js +179 -0
- package/dist/commands/init.d.ts +16 -0
- package/dist/commands/init.js +313 -0
- package/dist/commands/link.d.ts +11 -0
- package/dist/commands/link.js +139 -0
- package/dist/commands/login.d.ts +22 -0
- package/dist/commands/login.js +345 -0
- package/dist/commands/logout.d.ts +9 -0
- package/dist/commands/logout.js +38 -0
- package/dist/commands/logs.d.ts +10 -0
- package/dist/commands/logs.js +64 -0
- package/dist/commands/mcp/serve.d.ts +11 -0
- package/dist/commands/mcp/serve.js +818 -0
- package/dist/commands/migrate.d.ts +11 -0
- package/dist/commands/migrate.js +137 -0
- package/dist/commands/open.d.ts +10 -0
- package/dist/commands/open.js +79 -0
- package/dist/commands/projects.d.ts +10 -0
- package/dist/commands/projects.js +106 -0
- package/dist/commands/schema/diff.d.ts +7 -0
- package/dist/commands/schema/diff.js +63 -0
- package/dist/commands/schema/pull.d.ts +9 -0
- package/dist/commands/schema/pull.js +81 -0
- package/dist/commands/schema/push.d.ts +9 -0
- package/dist/commands/schema/push.js +97 -0
- package/dist/commands/schema/validate.d.ts +9 -0
- package/dist/commands/schema/validate.js +90 -0
- package/dist/commands/server/new.d.ts +2 -19
- package/dist/commands/server/new.js +6 -192
- package/dist/commands/server/scaffold.d.ts +2 -25
- package/dist/commands/server/scaffold.js +6 -312
- package/dist/commands/status.d.ts +7 -0
- package/dist/commands/status.js +53 -0
- package/dist/commands/unlink.d.ts +9 -0
- package/dist/commands/unlink.js +47 -0
- package/dist/commands/whoami.d.ts +10 -0
- package/dist/commands/whoami.js +97 -0
- package/dist/lib/api/client.js +1 -1
- package/dist/lib/api/services.js +3 -3
- package/dist/lib/api/types.d.ts +9 -2
- package/dist/lib/apsorc-parser.d.ts +0 -19
- package/dist/lib/apsorc-parser.js +2 -73
- package/dist/lib/guards.d.ts +3 -20
- package/dist/lib/guards.js +1 -113
- package/dist/lib/index.d.ts +1 -9
- package/dist/lib/index.js +1 -18
- package/dist/lib/migrate/entity-generator.d.ts +14 -0
- package/dist/lib/migrate/entity-generator.js +7 -0
- package/dist/lib/migrate/sandbox.d.ts +42 -0
- package/dist/lib/migrate/sandbox.js +337 -0
- package/dist/lib/migrate/snapshot.d.ts +80 -0
- package/dist/lib/migrate/snapshot.js +100 -0
- package/dist/lib/templates/python/models/model-col-datetime.eta +1 -1
- package/dist/lib/templates/python/models/model-col-string.eta +1 -1
- package/dist/lib/templates/python/models/model-col-text.eta +1 -1
- package/dist/lib/templates/python/models/model-col-varchar.eta +1 -1
- package/dist/lib/utils/field.d.ts +12 -0
- package/dist/lib/utils/field.js +80 -1
- package/dist/lib/utils/schema-convert.d.ts +71 -0
- package/dist/lib/utils/schema-convert.js +169 -0
- package/dist/lib/utils/spinner.d.ts +19 -0
- package/dist/lib/utils/spinner.js +87 -0
- package/dist/lib/utils/template.d.ts +12 -0
- package/dist/lib/utils/template.js +49 -0
- package/npm-shrinkwrap.json +18118 -0
- package/oclif.manifest.json +547 -41
- package/package.json +26 -5
- package/dist/lib/controller.d.ts +0 -10
- package/dist/lib/controller.js +0 -99
- package/dist/lib/dto.d.ts +0 -16
- package/dist/lib/dto.js +0 -92
- package/dist/lib/entity.d.ts +0 -19
- package/dist/lib/entity.js +0 -56
- package/dist/lib/enums.d.ts +0 -6
- package/dist/lib/enums.js +0 -28
- package/dist/lib/gql-dto.d.ts +0 -3
- package/dist/lib/gql-dto.js +0 -39
- package/dist/lib/index-module.d.ts +0 -13
- package/dist/lib/index-module.js +0 -26
- package/dist/lib/module.d.ts +0 -13
- package/dist/lib/module.js +0 -42
- package/dist/lib/service.d.ts +0 -2
- package/dist/lib/service.js +0 -61
|
@@ -0,0 +1,313 @@
|
|
|
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 fs = tslib_1.__importStar(require("fs"));
|
|
6
|
+
const path = tslib_1.__importStar(require("path"));
|
|
7
|
+
const shelljs_1 = tslib_1.__importDefault(require("shelljs"));
|
|
8
|
+
const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
|
|
9
|
+
const base_command_1 = tslib_1.__importDefault(require("../lib/base-command"));
|
|
10
|
+
const config_1 = require("../lib/config");
|
|
11
|
+
const services_1 = require("../lib/api/services");
|
|
12
|
+
const template_1 = require("../lib/utils/template");
|
|
13
|
+
class Init extends base_command_1.default {
|
|
14
|
+
async run() {
|
|
15
|
+
const { flags } = await this.parse(Init);
|
|
16
|
+
// Check for git
|
|
17
|
+
if (!shelljs_1.default.which("git")) {
|
|
18
|
+
this.error("git is required but not found. Please install git and try again.");
|
|
19
|
+
}
|
|
20
|
+
// Detect existing project
|
|
21
|
+
const cwd = process.cwd();
|
|
22
|
+
if (fs.existsSync(path.join(cwd, ".apsorc")) ||
|
|
23
|
+
fs.existsSync(path.join(cwd, ".apso", "link.json"))) {
|
|
24
|
+
this.error("An Apso project already exists in this directory. Run commands from a different directory or remove .apsorc / .apso/link.json first.");
|
|
25
|
+
}
|
|
26
|
+
const isAuthenticated = !flags["skip-platform"] && config_1.credentials.isValid();
|
|
27
|
+
if (isAuthenticated) {
|
|
28
|
+
await this.runAuthenticated(flags);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
await this.runOffline(flags);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async runAuthenticated(flags) {
|
|
35
|
+
// Ask: create new or clone existing
|
|
36
|
+
const { action } = await inquirer_1.default.prompt([
|
|
37
|
+
{
|
|
38
|
+
type: "list",
|
|
39
|
+
name: "action",
|
|
40
|
+
message: "What would you like to do?",
|
|
41
|
+
choices: [
|
|
42
|
+
{ name: "Create a new project", value: "create" },
|
|
43
|
+
{ name: "Clone an existing project", value: "clone" },
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
]);
|
|
47
|
+
if (action === "clone") {
|
|
48
|
+
await this.cloneExisting();
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
await this.createNew(flags);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async cloneExisting() {
|
|
55
|
+
// Select workspace
|
|
56
|
+
const workspaces = await services_1.workspacesApi.list();
|
|
57
|
+
if (workspaces.length === 0) {
|
|
58
|
+
this.error("No workspaces found. Create a workspace at https://app.apso.ai first.");
|
|
59
|
+
}
|
|
60
|
+
const { workspace } = await inquirer_1.default.prompt([
|
|
61
|
+
{
|
|
62
|
+
type: "list",
|
|
63
|
+
name: "workspace",
|
|
64
|
+
message: "Select a workspace:",
|
|
65
|
+
choices: workspaces.map((ws) => ({
|
|
66
|
+
name: `${ws.name} (${ws.slug})`,
|
|
67
|
+
value: ws,
|
|
68
|
+
})),
|
|
69
|
+
},
|
|
70
|
+
]);
|
|
71
|
+
// Select service
|
|
72
|
+
const servicesResponse = await services_1.servicesApi.list(workspace.slug);
|
|
73
|
+
if (servicesResponse.data.length === 0) {
|
|
74
|
+
this.error(`No services found in workspace "${workspace.name}". Create one first or choose "Create a new project".`);
|
|
75
|
+
}
|
|
76
|
+
const { service } = await inquirer_1.default.prompt([
|
|
77
|
+
{
|
|
78
|
+
type: "list",
|
|
79
|
+
name: "service",
|
|
80
|
+
message: "Select a service to clone:",
|
|
81
|
+
choices: servicesResponse.data.map((svc) => ({
|
|
82
|
+
name: `${svc.name} (${svc.slug})`,
|
|
83
|
+
value: svc,
|
|
84
|
+
})),
|
|
85
|
+
},
|
|
86
|
+
]);
|
|
87
|
+
const projectPath = path.join(process.cwd(), service.slug);
|
|
88
|
+
// Validate the resolved path stays within cwd
|
|
89
|
+
const resolvedPath = path.resolve(projectPath);
|
|
90
|
+
if (!resolvedPath.startsWith(path.resolve(process.cwd()))) {
|
|
91
|
+
this.error("Invalid service slug: path traversal detected");
|
|
92
|
+
}
|
|
93
|
+
if (service.githubRepo) {
|
|
94
|
+
// Clone from GitHub
|
|
95
|
+
this.log(`Cloning from ${service.githubRepo}...`);
|
|
96
|
+
const branch = service.githubBranch || "main";
|
|
97
|
+
const cloneResult = shelljs_1.default.exec(`git clone --branch="${branch}" "${service.githubRepo}" "${projectPath}"`, { silent: true });
|
|
98
|
+
if (cloneResult.code !== 0) {
|
|
99
|
+
this.error(`Failed to clone repository: ${cloneResult.stderr}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
// No GitHub repo, clone template instead
|
|
104
|
+
this.log("No GitHub repository linked to this service. Cloning default template...");
|
|
105
|
+
(0, template_1.cloneTemplate)(projectPath, "typescript", this.log.bind(this));
|
|
106
|
+
(0, template_1.initGitRepo)(projectPath);
|
|
107
|
+
}
|
|
108
|
+
// Write project link
|
|
109
|
+
const link = {
|
|
110
|
+
workspaceId: workspace.id,
|
|
111
|
+
workspaceSlug: workspace.slug,
|
|
112
|
+
serviceId: service.id,
|
|
113
|
+
serviceSlug: service.slug,
|
|
114
|
+
githubRepo: service.githubRepo,
|
|
115
|
+
githubBranch: service.githubBranch,
|
|
116
|
+
linkedAt: new Date().toISOString(),
|
|
117
|
+
};
|
|
118
|
+
config_1.projectLink.write(link, projectPath);
|
|
119
|
+
this.log(`\nProject cloned to ${projectPath}`);
|
|
120
|
+
this.log(`Linked to workspace "${workspace.name}" / service "${service.name}"`);
|
|
121
|
+
}
|
|
122
|
+
async createNew(flags) {
|
|
123
|
+
// Prompt for name if not provided
|
|
124
|
+
let projectName = flags.name;
|
|
125
|
+
if (!projectName) {
|
|
126
|
+
const response = await inquirer_1.default.prompt([
|
|
127
|
+
{
|
|
128
|
+
type: "input",
|
|
129
|
+
name: "projectName",
|
|
130
|
+
message: "Enter your project name:",
|
|
131
|
+
validate: (input) => {
|
|
132
|
+
if (!input.trim()) {
|
|
133
|
+
return "Project name is required";
|
|
134
|
+
}
|
|
135
|
+
if (!template_1.PROJECT_NAME_PATTERN.test(input)) {
|
|
136
|
+
return "Project name must start with a letter and contain only letters, numbers, hyphens, and underscores";
|
|
137
|
+
}
|
|
138
|
+
return true;
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
]);
|
|
142
|
+
projectName = response.projectName;
|
|
143
|
+
}
|
|
144
|
+
if (!projectName || !template_1.PROJECT_NAME_PATTERN.test(projectName)) {
|
|
145
|
+
this.error("Project name must start with a letter and contain only letters, numbers, hyphens, and underscores");
|
|
146
|
+
}
|
|
147
|
+
// Prompt for language if not provided
|
|
148
|
+
let language = flags.language;
|
|
149
|
+
if (!language) {
|
|
150
|
+
const response = await inquirer_1.default.prompt([
|
|
151
|
+
{
|
|
152
|
+
type: "list",
|
|
153
|
+
name: "language",
|
|
154
|
+
message: "Select your target language:",
|
|
155
|
+
choices: [
|
|
156
|
+
{ name: "TypeScript", value: "typescript" },
|
|
157
|
+
{ name: "Python", value: "python" },
|
|
158
|
+
{ name: "Go", value: "go" },
|
|
159
|
+
],
|
|
160
|
+
default: "typescript",
|
|
161
|
+
},
|
|
162
|
+
]);
|
|
163
|
+
language = response.language;
|
|
164
|
+
}
|
|
165
|
+
const projectPath = path.join(process.cwd(), projectName);
|
|
166
|
+
const resolvedPath = path.resolve(projectPath);
|
|
167
|
+
if (!resolvedPath.startsWith(path.resolve(process.cwd()))) {
|
|
168
|
+
this.error("Invalid project name: path traversal detected");
|
|
169
|
+
}
|
|
170
|
+
// Select workspace
|
|
171
|
+
const workspaces = await services_1.workspacesApi.list();
|
|
172
|
+
if (workspaces.length === 0) {
|
|
173
|
+
this.error("No workspaces found. Create a workspace at https://app.apso.ai first.");
|
|
174
|
+
}
|
|
175
|
+
const { workspace } = await inquirer_1.default.prompt([
|
|
176
|
+
{
|
|
177
|
+
type: "list",
|
|
178
|
+
name: "workspace",
|
|
179
|
+
message: "Select a workspace:",
|
|
180
|
+
choices: workspaces.map((ws) => ({
|
|
181
|
+
name: `${ws.name} (${ws.slug})`,
|
|
182
|
+
value: ws,
|
|
183
|
+
})),
|
|
184
|
+
},
|
|
185
|
+
]);
|
|
186
|
+
// Create service on platform
|
|
187
|
+
this.log(`Creating service "${projectName}" in workspace "${workspace.name}"...`);
|
|
188
|
+
const service = await services_1.servicesApi.create(workspace.slug, {
|
|
189
|
+
name: projectName,
|
|
190
|
+
});
|
|
191
|
+
// Clone template
|
|
192
|
+
(0, template_1.cloneTemplate)(projectPath, language, this.log.bind(this));
|
|
193
|
+
(0, template_1.initGitRepo)(projectPath);
|
|
194
|
+
// Write project link
|
|
195
|
+
const link = {
|
|
196
|
+
workspaceId: workspace.id,
|
|
197
|
+
workspaceSlug: workspace.slug,
|
|
198
|
+
serviceId: service.id,
|
|
199
|
+
serviceSlug: service.slug,
|
|
200
|
+
linkedAt: new Date().toISOString(),
|
|
201
|
+
};
|
|
202
|
+
config_1.projectLink.write(link, projectPath);
|
|
203
|
+
await this.postSetup(projectPath, language);
|
|
204
|
+
this.log(`\nProject created at ${projectPath}`);
|
|
205
|
+
this.log(`Language: ${language}`);
|
|
206
|
+
this.log(`Linked to workspace "${workspace.name}" / service "${service.name}"`);
|
|
207
|
+
}
|
|
208
|
+
async runOffline(flags) {
|
|
209
|
+
// Prompt for name if not provided
|
|
210
|
+
let projectName = flags.name;
|
|
211
|
+
if (!projectName) {
|
|
212
|
+
const response = await inquirer_1.default.prompt([
|
|
213
|
+
{
|
|
214
|
+
type: "input",
|
|
215
|
+
name: "projectName",
|
|
216
|
+
message: "Enter your project name:",
|
|
217
|
+
validate: (input) => {
|
|
218
|
+
if (!input.trim()) {
|
|
219
|
+
return "Project name is required";
|
|
220
|
+
}
|
|
221
|
+
if (!template_1.PROJECT_NAME_PATTERN.test(input)) {
|
|
222
|
+
return "Project name must start with a letter and contain only letters, numbers, hyphens, and underscores";
|
|
223
|
+
}
|
|
224
|
+
return true;
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
]);
|
|
228
|
+
projectName = response.projectName;
|
|
229
|
+
}
|
|
230
|
+
if (!projectName || !template_1.PROJECT_NAME_PATTERN.test(projectName)) {
|
|
231
|
+
this.error("Project name must start with a letter and contain only letters, numbers, hyphens, and underscores");
|
|
232
|
+
}
|
|
233
|
+
// Prompt for language if not provided
|
|
234
|
+
let language = flags.language;
|
|
235
|
+
if (!language) {
|
|
236
|
+
const response = await inquirer_1.default.prompt([
|
|
237
|
+
{
|
|
238
|
+
type: "list",
|
|
239
|
+
name: "language",
|
|
240
|
+
message: "Select your target language:",
|
|
241
|
+
choices: [
|
|
242
|
+
{ name: "TypeScript", value: "typescript" },
|
|
243
|
+
{ name: "Python", value: "python" },
|
|
244
|
+
{ name: "Go", value: "go" },
|
|
245
|
+
],
|
|
246
|
+
default: "typescript",
|
|
247
|
+
},
|
|
248
|
+
]);
|
|
249
|
+
language = response.language;
|
|
250
|
+
}
|
|
251
|
+
const projectPath = path.join(process.cwd(), projectName);
|
|
252
|
+
const resolvedPath = path.resolve(projectPath);
|
|
253
|
+
if (!resolvedPath.startsWith(path.resolve(process.cwd()))) {
|
|
254
|
+
this.error("Invalid project name: path traversal detected");
|
|
255
|
+
}
|
|
256
|
+
// Clone template and init git
|
|
257
|
+
(0, template_1.cloneTemplate)(projectPath, language, this.log.bind(this));
|
|
258
|
+
(0, template_1.initGitRepo)(projectPath);
|
|
259
|
+
await this.postSetup(projectPath, language);
|
|
260
|
+
this.log(`\nProject created at ${projectPath}`);
|
|
261
|
+
this.log(`Language: ${language}`);
|
|
262
|
+
this.log('Run "apso login" to link this project to the Apso platform.');
|
|
263
|
+
}
|
|
264
|
+
async postSetup(projectPath, language) {
|
|
265
|
+
switch (language) {
|
|
266
|
+
case "typescript": {
|
|
267
|
+
if (shelljs_1.default.which("npm")) {
|
|
268
|
+
this.log("Installing modules...");
|
|
269
|
+
await this.runNpmCommand([
|
|
270
|
+
"install",
|
|
271
|
+
"--force",
|
|
272
|
+
"--prefix",
|
|
273
|
+
projectPath,
|
|
274
|
+
]);
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
this.log("Warning: npm not found. Skipping module installation.");
|
|
278
|
+
}
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
case "python": {
|
|
282
|
+
this.log("Python project created. Run 'pip install -e .[dev]' to install dependencies.");
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
case "go": {
|
|
286
|
+
this.log("Go project created. Run 'go mod tidy' to install dependencies.");
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
Init.description = "Create a new Apso project or clone an existing one";
|
|
293
|
+
Init.examples = [
|
|
294
|
+
`$ apso init`,
|
|
295
|
+
`$ apso init --name my-app --language typescript`,
|
|
296
|
+
`$ apso init --name my-app --language python --skip-platform`,
|
|
297
|
+
];
|
|
298
|
+
Init.flags = {
|
|
299
|
+
name: core_1.Flags.string({
|
|
300
|
+
char: "n",
|
|
301
|
+
description: "Project name",
|
|
302
|
+
}),
|
|
303
|
+
language: core_1.Flags.string({
|
|
304
|
+
char: "l",
|
|
305
|
+
description: "Target language (typescript, python, go)",
|
|
306
|
+
options: ["typescript", "python", "go"],
|
|
307
|
+
}),
|
|
308
|
+
"skip-platform": core_1.Flags.boolean({
|
|
309
|
+
description: "Skip platform linking (offline mode)",
|
|
310
|
+
default: false,
|
|
311
|
+
}),
|
|
312
|
+
};
|
|
313
|
+
exports.default = Init;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import BaseCommand from "../lib/base-command";
|
|
2
|
+
export default class Link extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
workspace: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
7
|
+
service: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
8
|
+
force: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
};
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
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 fs = tslib_1.__importStar(require("fs"));
|
|
6
|
+
const path = tslib_1.__importStar(require("path"));
|
|
7
|
+
const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
|
|
8
|
+
const base_command_1 = tslib_1.__importDefault(require("../lib/base-command"));
|
|
9
|
+
const config_1 = require("../lib/config");
|
|
10
|
+
const services_1 = require("../lib/api/services");
|
|
11
|
+
class Link extends base_command_1.default {
|
|
12
|
+
async run() {
|
|
13
|
+
const { flags } = await this.parse(Link);
|
|
14
|
+
// Auth guard
|
|
15
|
+
if (!config_1.credentials.isValid()) {
|
|
16
|
+
this.error("Not logged in. Run 'apso login' first.");
|
|
17
|
+
}
|
|
18
|
+
// Check .apsorc exists
|
|
19
|
+
const apsorcPath = path.join(process.cwd(), ".apsorc");
|
|
20
|
+
if (!fs.existsSync(apsorcPath)) {
|
|
21
|
+
this.error("No .apsorc found in current directory. Run 'apso init' first or navigate to an Apso project.");
|
|
22
|
+
}
|
|
23
|
+
// Check if already linked
|
|
24
|
+
if (config_1.projectLink.exists() && !flags.force) {
|
|
25
|
+
const existing = config_1.projectLink.read();
|
|
26
|
+
const { confirm } = await inquirer_1.default.prompt([
|
|
27
|
+
{
|
|
28
|
+
type: "confirm",
|
|
29
|
+
name: "confirm",
|
|
30
|
+
message: `Already linked to ${existing === null || existing === void 0 ? void 0 : existing.workspaceSlug}/${existing === null || existing === void 0 ? void 0 : existing.serviceSlug}. Overwrite?`,
|
|
31
|
+
default: false,
|
|
32
|
+
},
|
|
33
|
+
]);
|
|
34
|
+
if (!confirm) {
|
|
35
|
+
this.log("Link unchanged.");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Determine workspace
|
|
40
|
+
let workspace;
|
|
41
|
+
if (flags.workspace) {
|
|
42
|
+
workspace = await services_1.workspacesApi.get(flags.workspace);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
const workspaces = await services_1.workspacesApi.list();
|
|
46
|
+
if (workspaces.length === 0) {
|
|
47
|
+
this.error("No workspaces found. Create a workspace at https://app.apso.ai first.");
|
|
48
|
+
}
|
|
49
|
+
const response = await inquirer_1.default.prompt([
|
|
50
|
+
{
|
|
51
|
+
type: "list",
|
|
52
|
+
name: "workspace",
|
|
53
|
+
message: "Select a workspace:",
|
|
54
|
+
choices: workspaces.map((ws) => ({
|
|
55
|
+
name: `${ws.name} (${ws.slug})`,
|
|
56
|
+
value: ws,
|
|
57
|
+
})),
|
|
58
|
+
},
|
|
59
|
+
]);
|
|
60
|
+
workspace = response.workspace;
|
|
61
|
+
}
|
|
62
|
+
// Determine service
|
|
63
|
+
let service;
|
|
64
|
+
if (flags.service) {
|
|
65
|
+
service = await services_1.servicesApi.get(workspace.slug, flags.service);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
const servicesResponse = await services_1.servicesApi.list(workspace.slug);
|
|
69
|
+
const services = servicesResponse.data;
|
|
70
|
+
const choices = [
|
|
71
|
+
...services.map((svc) => ({
|
|
72
|
+
name: `${svc.name} (${svc.slug})`,
|
|
73
|
+
value: svc,
|
|
74
|
+
})),
|
|
75
|
+
{ name: "+ Create new service", value: null },
|
|
76
|
+
];
|
|
77
|
+
const { selectedService } = await inquirer_1.default.prompt([
|
|
78
|
+
{
|
|
79
|
+
type: "list",
|
|
80
|
+
name: "selectedService",
|
|
81
|
+
message: "Select a service:",
|
|
82
|
+
choices,
|
|
83
|
+
},
|
|
84
|
+
]);
|
|
85
|
+
if (selectedService) {
|
|
86
|
+
service = selectedService;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
// Create new service
|
|
90
|
+
const { serviceName } = await inquirer_1.default.prompt([
|
|
91
|
+
{
|
|
92
|
+
type: "input",
|
|
93
|
+
name: "serviceName",
|
|
94
|
+
message: "Enter a name for the new service:",
|
|
95
|
+
validate: (input) => input.trim() ? true : "Service name is required",
|
|
96
|
+
},
|
|
97
|
+
]);
|
|
98
|
+
this.log(`Creating service "${serviceName}"...`);
|
|
99
|
+
service = await services_1.servicesApi.create(workspace.slug, {
|
|
100
|
+
name: serviceName,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Write link
|
|
105
|
+
const link = {
|
|
106
|
+
workspaceId: workspace.id,
|
|
107
|
+
workspaceSlug: workspace.slug,
|
|
108
|
+
serviceId: service.id,
|
|
109
|
+
serviceSlug: service.slug,
|
|
110
|
+
githubRepo: service.githubRepo,
|
|
111
|
+
githubBranch: service.githubBranch,
|
|
112
|
+
linkedAt: new Date().toISOString(),
|
|
113
|
+
};
|
|
114
|
+
config_1.projectLink.write(link);
|
|
115
|
+
this.log(`Linked to workspace "${workspace.name}" / service "${service.name}"`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
Link.description = "Link this project to a platform service";
|
|
119
|
+
Link.examples = [
|
|
120
|
+
`$ apso link`,
|
|
121
|
+
`$ apso link --workspace my-team --service my-api`,
|
|
122
|
+
`$ apso link --force`,
|
|
123
|
+
];
|
|
124
|
+
Link.flags = {
|
|
125
|
+
workspace: core_1.Flags.string({
|
|
126
|
+
char: "w",
|
|
127
|
+
description: "Workspace slug",
|
|
128
|
+
}),
|
|
129
|
+
service: core_1.Flags.string({
|
|
130
|
+
char: "s",
|
|
131
|
+
description: "Service slug",
|
|
132
|
+
}),
|
|
133
|
+
force: core_1.Flags.boolean({
|
|
134
|
+
char: "f",
|
|
135
|
+
description: "Overwrite existing link without confirmation",
|
|
136
|
+
default: false,
|
|
137
|
+
}),
|
|
138
|
+
};
|
|
139
|
+
exports.default = Link;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import BaseCommand from "../lib/base-command";
|
|
2
|
+
export default class Login extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
help: import("@oclif/core/lib/interfaces").BooleanFlag<void>;
|
|
7
|
+
token: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
8
|
+
};
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Login using a direct API token (for CI/CD)
|
|
12
|
+
*/
|
|
13
|
+
private loginWithToken;
|
|
14
|
+
/**
|
|
15
|
+
* Login using browser-based OAuth flow
|
|
16
|
+
*/
|
|
17
|
+
private loginWithBrowser;
|
|
18
|
+
/**
|
|
19
|
+
* Find an available port for the callback server
|
|
20
|
+
*/
|
|
21
|
+
private findAvailablePort;
|
|
22
|
+
}
|