@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.
Files changed (91) hide show
  1. package/README.md +221 -1653
  2. package/dist/commands/config.d.ts +13 -0
  3. package/dist/commands/config.js +114 -0
  4. package/dist/commands/deploy.d.ts +11 -0
  5. package/dist/commands/deploy.js +141 -0
  6. package/dist/commands/dev.d.ts +15 -0
  7. package/dist/commands/dev.js +95 -0
  8. package/dist/commands/generate.d.ts +12 -0
  9. package/dist/commands/generate.js +179 -0
  10. package/dist/commands/init.d.ts +16 -0
  11. package/dist/commands/init.js +313 -0
  12. package/dist/commands/link.d.ts +11 -0
  13. package/dist/commands/link.js +139 -0
  14. package/dist/commands/login.d.ts +22 -0
  15. package/dist/commands/login.js +345 -0
  16. package/dist/commands/logout.d.ts +9 -0
  17. package/dist/commands/logout.js +38 -0
  18. package/dist/commands/logs.d.ts +10 -0
  19. package/dist/commands/logs.js +64 -0
  20. package/dist/commands/mcp/serve.d.ts +11 -0
  21. package/dist/commands/mcp/serve.js +818 -0
  22. package/dist/commands/migrate.d.ts +11 -0
  23. package/dist/commands/migrate.js +137 -0
  24. package/dist/commands/open.d.ts +10 -0
  25. package/dist/commands/open.js +79 -0
  26. package/dist/commands/projects.d.ts +10 -0
  27. package/dist/commands/projects.js +106 -0
  28. package/dist/commands/schema/diff.d.ts +7 -0
  29. package/dist/commands/schema/diff.js +63 -0
  30. package/dist/commands/schema/pull.d.ts +9 -0
  31. package/dist/commands/schema/pull.js +81 -0
  32. package/dist/commands/schema/push.d.ts +9 -0
  33. package/dist/commands/schema/push.js +97 -0
  34. package/dist/commands/schema/validate.d.ts +9 -0
  35. package/dist/commands/schema/validate.js +90 -0
  36. package/dist/commands/server/new.d.ts +2 -19
  37. package/dist/commands/server/new.js +6 -192
  38. package/dist/commands/server/scaffold.d.ts +2 -25
  39. package/dist/commands/server/scaffold.js +6 -312
  40. package/dist/commands/status.d.ts +7 -0
  41. package/dist/commands/status.js +53 -0
  42. package/dist/commands/unlink.d.ts +9 -0
  43. package/dist/commands/unlink.js +47 -0
  44. package/dist/commands/whoami.d.ts +10 -0
  45. package/dist/commands/whoami.js +97 -0
  46. package/dist/lib/api/client.js +1 -1
  47. package/dist/lib/api/services.js +3 -3
  48. package/dist/lib/api/types.d.ts +9 -2
  49. package/dist/lib/apsorc-parser.d.ts +0 -19
  50. package/dist/lib/apsorc-parser.js +2 -73
  51. package/dist/lib/guards.d.ts +3 -20
  52. package/dist/lib/guards.js +1 -113
  53. package/dist/lib/index.d.ts +1 -9
  54. package/dist/lib/index.js +1 -18
  55. package/dist/lib/migrate/entity-generator.d.ts +14 -0
  56. package/dist/lib/migrate/entity-generator.js +7 -0
  57. package/dist/lib/migrate/sandbox.d.ts +42 -0
  58. package/dist/lib/migrate/sandbox.js +337 -0
  59. package/dist/lib/migrate/snapshot.d.ts +80 -0
  60. package/dist/lib/migrate/snapshot.js +100 -0
  61. package/dist/lib/templates/python/models/model-col-datetime.eta +1 -1
  62. package/dist/lib/templates/python/models/model-col-string.eta +1 -1
  63. package/dist/lib/templates/python/models/model-col-text.eta +1 -1
  64. package/dist/lib/templates/python/models/model-col-varchar.eta +1 -1
  65. package/dist/lib/utils/field.d.ts +12 -0
  66. package/dist/lib/utils/field.js +80 -1
  67. package/dist/lib/utils/schema-convert.d.ts +71 -0
  68. package/dist/lib/utils/schema-convert.js +169 -0
  69. package/dist/lib/utils/spinner.d.ts +19 -0
  70. package/dist/lib/utils/spinner.js +87 -0
  71. package/dist/lib/utils/template.d.ts +12 -0
  72. package/dist/lib/utils/template.js +49 -0
  73. package/npm-shrinkwrap.json +18118 -0
  74. package/oclif.manifest.json +547 -41
  75. package/package.json +26 -5
  76. package/dist/lib/controller.d.ts +0 -10
  77. package/dist/lib/controller.js +0 -99
  78. package/dist/lib/dto.d.ts +0 -16
  79. package/dist/lib/dto.js +0 -92
  80. package/dist/lib/entity.d.ts +0 -19
  81. package/dist/lib/entity.js +0 -56
  82. package/dist/lib/enums.d.ts +0 -6
  83. package/dist/lib/enums.js +0 -28
  84. package/dist/lib/gql-dto.d.ts +0 -3
  85. package/dist/lib/gql-dto.js +0 -39
  86. package/dist/lib/index-module.d.ts +0 -13
  87. package/dist/lib/index-module.js +0 -26
  88. package/dist/lib/module.d.ts +0 -13
  89. package/dist/lib/module.js +0 -42
  90. package/dist/lib/service.d.ts +0 -2
  91. 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
+ }