@apso/cli 0.8.6 → 0.10.1

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,11 @@
1
+ import BaseCommand from "../lib/base-command";
2
+ export default class Migrate extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ apply: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
7
+ reset: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
8
+ sql: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
9
+ };
10
+ run(): Promise<void>;
11
+ }
@@ -0,0 +1,137 @@
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 base_command_1 = tslib_1.__importDefault(require("../lib/base-command"));
6
+ const apsorc_parser_1 = require("../lib/apsorc-parser");
7
+ const snapshot_1 = require("../lib/migrate/snapshot");
8
+ const sandbox_1 = require("../lib/migrate/sandbox");
9
+ class Migrate extends base_command_1.default {
10
+ async run() {
11
+ const { flags } = await this.parse(Migrate);
12
+ // Handle reset
13
+ if (flags.reset) {
14
+ (0, sandbox_1.resetSandbox)();
15
+ this.log("Sandbox reset. Snapshot and PGlite data cleared.");
16
+ return;
17
+ }
18
+ // Parse .apsorc
19
+ let parsed;
20
+ try {
21
+ parsed = (0, apsorc_parser_1.parseApsorc)();
22
+ }
23
+ catch (error) {
24
+ const msg = error instanceof Error ? error.message : String(error);
25
+ this.error(`Failed to parse .apsorc: ${msg}`);
26
+ }
27
+ // Quick check against snapshot
28
+ const snapshot = (0, snapshot_1.readSnapshot)();
29
+ const currentComparable = {
30
+ entities: parsed.entities.map((e) => {
31
+ var _a;
32
+ return ({
33
+ name: e.name,
34
+ fields: (_a = e.fields) === null || _a === void 0 ? void 0 : _a.map((f) => {
35
+ var _a;
36
+ return ({
37
+ name: f.name,
38
+ type: f.type,
39
+ nullable: f.nullable,
40
+ unique: f.unique,
41
+ default: (_a = f.default) !== null && _a !== void 0 ? _a : undefined,
42
+ index: f.index,
43
+ });
44
+ }),
45
+ primaryKeyType: e.primaryKeyType,
46
+ created_at: e.created_at,
47
+ updated_at: e.updated_at,
48
+ scopeBy: e.scopeBy,
49
+ });
50
+ }),
51
+ };
52
+ if (!(0, snapshot_1.hasChanges)(currentComparable, snapshot)) {
53
+ if (!flags.sql) {
54
+ this.log("Schema unchanged. No migration needed.");
55
+ }
56
+ return;
57
+ }
58
+ if (!flags.sql) {
59
+ this.log("Schema changes detected. Running migration sandbox...");
60
+ }
61
+ // Run the sandbox
62
+ const result = await (0, sandbox_1.runMigrationSandbox)({ entities: parsed.entities, relationshipMap: parsed.relationshipMap });
63
+ if (!result.needed) {
64
+ if (!flags.sql) {
65
+ this.log("No database-level changes detected.");
66
+ }
67
+ return;
68
+ }
69
+ // Output results
70
+ if (flags.sql) {
71
+ // Raw SQL output mode
72
+ for (const sql of result.upSql) {
73
+ this.log(sql + ";");
74
+ }
75
+ return;
76
+ }
77
+ if (result.success) {
78
+ this.log("");
79
+ this.log("Migration SQL (up):");
80
+ this.log("---");
81
+ for (const sql of result.upSql) {
82
+ this.log(` ${sql};`);
83
+ }
84
+ this.log("---");
85
+ this.log("");
86
+ this.log(`${result.upSql.length} statement(s) generated and tested.`);
87
+ this.log("Migration validated against local PGlite.");
88
+ if (flags.apply) {
89
+ (0, sandbox_1.applyMigration)({
90
+ entities: parsed.entities,
91
+ relationshipMap: parsed.relationshipMap,
92
+ });
93
+ this.log("Snapshot updated.");
94
+ }
95
+ else {
96
+ this.log("Run with --apply to update the snapshot.");
97
+ }
98
+ }
99
+ else {
100
+ this.log("");
101
+ this.log("Migration FAILED.");
102
+ if (result.error) {
103
+ this.log(`Error: ${result.error}`);
104
+ }
105
+ if (result.upSql.length > 0) {
106
+ this.log("");
107
+ this.log("Attempted SQL:");
108
+ for (const sql of result.upSql) {
109
+ this.log(` ${sql};`);
110
+ }
111
+ }
112
+ this.exit(1);
113
+ }
114
+ }
115
+ }
116
+ Migrate.description = "Detect schema changes, generate migration SQL, and test against a local PGlite sandbox";
117
+ Migrate.examples = [
118
+ `$ apso migrate`,
119
+ `$ apso migrate --apply`,
120
+ `$ apso migrate --reset`,
121
+ `$ apso migrate --sql`,
122
+ ];
123
+ Migrate.flags = {
124
+ apply: core_1.Flags.boolean({
125
+ description: "Apply the migration: update the local snapshot after a successful test",
126
+ default: false,
127
+ }),
128
+ reset: core_1.Flags.boolean({
129
+ description: "Reset the sandbox (clear snapshot and PGlite data)",
130
+ default: false,
131
+ }),
132
+ sql: core_1.Flags.boolean({
133
+ description: "Output raw SQL only (for piping to files)",
134
+ default: false,
135
+ }),
136
+ };
137
+ exports.default = Migrate;
@@ -0,0 +1,10 @@
1
+ import BaseCommand from "../lib/base-command";
2
+ export default class Open extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ endpoint: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
7
+ dashboard: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
8
+ };
9
+ run(): Promise<void>;
10
+ }
@@ -0,0 +1,79 @@
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 child_process_1 = require("child_process");
6
+ const os = tslib_1.__importStar(require("os"));
7
+ const base_command_1 = tslib_1.__importDefault(require("../lib/base-command"));
8
+ const config_1 = require("../lib/config");
9
+ const config_2 = require("../lib/config");
10
+ const services_1 = require("../lib/api/services");
11
+ class Open extends base_command_1.default {
12
+ async run() {
13
+ const { flags } = await this.parse(Open);
14
+ // Auth guard
15
+ if (!config_1.credentials.isValid()) {
16
+ this.error("Not logged in. Run 'apso login' first.");
17
+ }
18
+ // Link guard
19
+ const link = config_1.projectLink.read();
20
+ if (!link) {
21
+ this.error("Project not linked. Run 'apso link' first.");
22
+ }
23
+ const config = config_2.globalConfig.read();
24
+ if (flags.endpoint) {
25
+ // Open the live API endpoint
26
+ const service = await services_1.servicesApi.get(link.workspaceSlug, link.serviceSlug);
27
+ if (!service.endpoint) {
28
+ this.error("Service has no endpoint. Deploy first with 'apso deploy'.");
29
+ }
30
+ this.log(`Opening endpoint: ${service.endpoint}`);
31
+ openUrl(service.endpoint);
32
+ }
33
+ else {
34
+ // Open the dashboard (default)
35
+ const dashboardUrl = `${config.webUrl}/workspaces/${link.workspaceSlug}/services/${link.serviceSlug}`;
36
+ this.log(`Opening dashboard: ${dashboardUrl}`);
37
+ openUrl(dashboardUrl);
38
+ }
39
+ }
40
+ }
41
+ Open.description = "Open the service dashboard or endpoint in browser";
42
+ Open.examples = [
43
+ `$ apso open`,
44
+ `$ apso open --endpoint`,
45
+ `$ apso open --dashboard`,
46
+ ];
47
+ Open.flags = {
48
+ endpoint: core_1.Flags.boolean({
49
+ char: "e",
50
+ description: "Open the service API endpoint",
51
+ default: false,
52
+ }),
53
+ dashboard: core_1.Flags.boolean({
54
+ char: "d",
55
+ description: "Open the service dashboard (default)",
56
+ default: false,
57
+ }),
58
+ };
59
+ exports.default = Open;
60
+ function openUrl(url) {
61
+ const platform = os.platform();
62
+ let command;
63
+ switch (platform) {
64
+ case "darwin":
65
+ command = `open "${url}"`;
66
+ break;
67
+ case "win32":
68
+ command = `start "" "${url}"`;
69
+ break;
70
+ default:
71
+ command = `xdg-open "${url}"`;
72
+ break;
73
+ }
74
+ (0, child_process_1.exec)(command, (error) => {
75
+ if (error) {
76
+ // Fail silently -- the URL was already printed
77
+ }
78
+ });
79
+ }
@@ -0,0 +1,10 @@
1
+ import BaseCommand from "../lib/base-command";
2
+ export default class Projects 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
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
8
+ };
9
+ run(): Promise<void>;
10
+ }
@@ -0,0 +1,106 @@
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
+ class Projects extends base_command_1.default {
10
+ async run() {
11
+ const { flags } = await this.parse(Projects);
12
+ // Auth guard
13
+ if (!config_1.credentials.isValid()) {
14
+ this.error("Not logged in. Run 'apso login' first.");
15
+ }
16
+ // Determine workspace
17
+ let workspaceSlug;
18
+ if (flags.workspace) {
19
+ workspaceSlug = flags.workspace;
20
+ }
21
+ else {
22
+ // Try from existing link
23
+ const link = config_1.projectLink.read();
24
+ if (link) {
25
+ workspaceSlug = link.workspaceSlug;
26
+ }
27
+ else {
28
+ // Prompt
29
+ const workspaces = await services_1.workspacesApi.list();
30
+ if (workspaces.length === 0) {
31
+ this.error("No workspaces found. Create a workspace at https://app.apso.ai first.");
32
+ }
33
+ const response = await inquirer_1.default.prompt([
34
+ {
35
+ type: "list",
36
+ name: "workspace",
37
+ message: "Select a workspace:",
38
+ choices: workspaces.map((ws) => ({
39
+ name: `${ws.name} (${ws.slug})`,
40
+ value: ws,
41
+ })),
42
+ },
43
+ ]);
44
+ workspaceSlug = response.workspace.slug;
45
+ }
46
+ }
47
+ // Fetch services
48
+ const servicesResponse = await services_1.servicesApi.list(workspaceSlug);
49
+ const services = servicesResponse.data;
50
+ if (services.length === 0) {
51
+ this.log(`No services found in workspace "${workspaceSlug}".`);
52
+ return;
53
+ }
54
+ // Get currently-linked service for highlighting
55
+ const link = config_1.projectLink.read();
56
+ const linkedServiceId = link === null || link === void 0 ? void 0 : link.serviceId;
57
+ if (flags.json) {
58
+ this.log(JSON.stringify(services, null, 2));
59
+ return;
60
+ }
61
+ // Table output
62
+ this.log(`Services in workspace "${workspaceSlug}":\n`);
63
+ this.log(padRight("NAME", 24) +
64
+ padRight("SLUG", 20) +
65
+ padRight("STATUS", 12) +
66
+ padRight("LAST DEPLOYED", 22) +
67
+ "REPO");
68
+ this.log("-".repeat(90));
69
+ for (const svc of services) {
70
+ const isLinked = svc.id === linkedServiceId;
71
+ const marker = isLinked ? " *" : "";
72
+ const deployed = svc.lastDeployedAt
73
+ ? new Date(svc.lastDeployedAt).toLocaleDateString()
74
+ : "-";
75
+ const repo = svc.githubRepo || "-";
76
+ this.log(padRight(`${svc.name}${marker}`, 24) +
77
+ padRight(svc.slug, 20) +
78
+ padRight(svc.status, 12) +
79
+ padRight(deployed, 22) +
80
+ repo);
81
+ }
82
+ if (linkedServiceId) {
83
+ this.log("\n* = currently linked");
84
+ }
85
+ }
86
+ }
87
+ Projects.description = "List projects (services) in a workspace";
88
+ Projects.examples = [
89
+ `$ apso projects`,
90
+ `$ apso projects --workspace my-team`,
91
+ `$ apso projects --json`,
92
+ ];
93
+ Projects.flags = {
94
+ workspace: core_1.Flags.string({
95
+ char: "w",
96
+ description: "Workspace slug",
97
+ }),
98
+ json: core_1.Flags.boolean({
99
+ description: "Output as JSON",
100
+ default: false,
101
+ }),
102
+ };
103
+ exports.default = Projects;
104
+ function padRight(str, len) {
105
+ return str.length >= len ? str + " " : str + " ".repeat(len - str.length);
106
+ }
@@ -0,0 +1,7 @@
1
+ import BaseCommand from "../../lib/base-command";
2
+ export default class SchemaDiff extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {};
6
+ run(): Promise<void>;
7
+ }
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const base_command_1 = tslib_1.__importDefault(require("../../lib/base-command"));
5
+ const config_1 = require("../../lib/config");
6
+ const services_1 = require("../../lib/api/services");
7
+ const apsorc_parser_1 = require("../../lib/apsorc-parser");
8
+ const schema_convert_1 = require("../../lib/utils/schema-convert");
9
+ // Inline ANSI color helpers (no dependency needed)
10
+ const useColor = () => !config_1.globalConfig.read().noColor;
11
+ const green = (s) => useColor() ? `\x1b[32m${s}\x1b[0m` : s;
12
+ const red = (s) => useColor() ? `\x1b[31m${s}\x1b[0m` : s;
13
+ const yellow = (s) => useColor() ? `\x1b[33m${s}\x1b[0m` : s;
14
+ class SchemaDiff extends base_command_1.default {
15
+ async run() {
16
+ // Auth guard
17
+ if (!config_1.credentials.isValid()) {
18
+ this.error("Not logged in. Run 'apso login' first.");
19
+ }
20
+ // Link guard
21
+ const link = config_1.projectLink.read();
22
+ if (!link) {
23
+ this.error("Project not linked. Run 'apso link' first.");
24
+ }
25
+ // Parse local schema
26
+ let parsed;
27
+ try {
28
+ parsed = (0, apsorc_parser_1.parseApsorc)();
29
+ }
30
+ catch (error) {
31
+ const msg = error instanceof Error ? error.message : String(error);
32
+ this.error(`Failed to parse .apsorc: ${msg}`);
33
+ }
34
+ const localSchema = (0, schema_convert_1.apsorcToServiceSchema)(parsed);
35
+ // Get diff from platform
36
+ const diff = await services_1.schemaApi.diff(link.workspaceSlug, link.serviceSlug, localSchema);
37
+ if (!diff.hasChanges) {
38
+ this.log("Local and remote schemas are in sync.");
39
+ return;
40
+ }
41
+ this.log("Schema differences:\n");
42
+ if (diff.added.length > 0) {
43
+ for (const item of diff.added) {
44
+ this.log(green(` + ${item}`));
45
+ }
46
+ }
47
+ if (diff.removed.length > 0) {
48
+ for (const item of diff.removed) {
49
+ this.log(red(` - ${item}`));
50
+ }
51
+ }
52
+ if (diff.modified.length > 0) {
53
+ for (const item of diff.modified) {
54
+ this.log(yellow(` ~ ${item}`));
55
+ }
56
+ }
57
+ this.log(`\n${green(String(diff.added.length) + " added")}, ${red(String(diff.removed.length) + " removed")}, ${yellow(String(diff.modified.length) + " modified")}`);
58
+ }
59
+ }
60
+ SchemaDiff.description = "Show diff between local and remote schema";
61
+ SchemaDiff.examples = [`$ apso schema diff`];
62
+ SchemaDiff.flags = {};
63
+ exports.default = SchemaDiff;
@@ -0,0 +1,9 @@
1
+ import BaseCommand from "../../lib/base-command";
2
+ export default class SchemaPull extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ yes: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
7
+ };
8
+ run(): Promise<void>;
9
+ }
@@ -0,0 +1,81 @@
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 inquirer_1 = tslib_1.__importDefault(require("inquirer"));
7
+ const base_command_1 = tslib_1.__importDefault(require("../../lib/base-command"));
8
+ const config_1 = require("../../lib/config");
9
+ const services_1 = require("../../lib/api/services");
10
+ const apsorc_parser_1 = require("../../lib/apsorc-parser");
11
+ const schema_convert_1 = require("../../lib/utils/schema-convert");
12
+ class SchemaPull extends base_command_1.default {
13
+ async run() {
14
+ const { flags } = await this.parse(SchemaPull);
15
+ // Auth guard
16
+ if (!config_1.credentials.isValid()) {
17
+ this.error("Not logged in. Run 'apso login' first.");
18
+ }
19
+ // Link guard
20
+ const link = config_1.projectLink.read();
21
+ if (!link) {
22
+ this.error("Project not linked. Run 'apso link' first.");
23
+ }
24
+ // Fetch remote schema
25
+ const remoteSchema = await services_1.schemaApi.get(link.workspaceSlug, link.serviceSlug);
26
+ // Convert to .apsorc format
27
+ const apsorcData = (0, schema_convert_1.serviceSchemaToApsorc)(remoteSchema);
28
+ // Find local .apsorc path
29
+ const configPath = (0, apsorc_parser_1.findConfigPath)();
30
+ if (!configPath) {
31
+ this.error("No .apsorc file found. Run 'apso init' first.");
32
+ }
33
+ // Show what will change
34
+ const entityNames = apsorcData.entities.map((e) => e.name).join(", ");
35
+ this.log(`Remote schema: ${apsorcData.entities.length} entities (${entityNames})`);
36
+ this.log(`Relationships: ${apsorcData.relationships.length}`);
37
+ this.log(`Target file: ${configPath}`);
38
+ // Confirm
39
+ if (!flags.yes) {
40
+ const { confirm } = await inquirer_1.default.prompt([
41
+ {
42
+ type: "confirm",
43
+ name: "confirm",
44
+ message: "Overwrite local .apsorc with remote schema?",
45
+ default: false,
46
+ },
47
+ ]);
48
+ if (!confirm) {
49
+ this.log("Pull cancelled.");
50
+ return;
51
+ }
52
+ }
53
+ // Backup existing .apsorc
54
+ const backupPath = (0, config_1.createBackup)(configPath);
55
+ this.log(`Backup created: ${backupPath}`);
56
+ // Write updated .apsorc
57
+ fs.writeFileSync(configPath, JSON.stringify(apsorcData, null, 2), "utf-8");
58
+ // Update sync metadata
59
+ const remoteHash = (0, schema_convert_1.computeSchemaHash)(remoteSchema.entities);
60
+ const localHash = (0, schema_convert_1.computeSchemaHash)(apsorcData);
61
+ config_1.projectLink.updateSyncMetadata({
62
+ lastSyncedAt: new Date().toISOString(),
63
+ localSchemaHash: localHash,
64
+ remoteSchemaHash: remoteHash,
65
+ });
66
+ this.log("Schema pulled successfully.");
67
+ }
68
+ }
69
+ SchemaPull.description = "Pull remote schema to local .apsorc";
70
+ SchemaPull.examples = [
71
+ `$ apso schema pull`,
72
+ `$ apso schema pull --yes`,
73
+ ];
74
+ SchemaPull.flags = {
75
+ yes: core_1.Flags.boolean({
76
+ char: "y",
77
+ description: "Skip confirmation prompt",
78
+ default: false,
79
+ }),
80
+ };
81
+ exports.default = SchemaPull;
@@ -0,0 +1,9 @@
1
+ import BaseCommand from "../../lib/base-command";
2
+ export default class SchemaPush extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ yes: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
7
+ };
8
+ run(): Promise<void>;
9
+ }
@@ -0,0 +1,97 @@
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
+ const apsorc_parser_1 = require("../../lib/apsorc-parser");
10
+ const schema_convert_1 = require("../../lib/utils/schema-convert");
11
+ class SchemaPush extends base_command_1.default {
12
+ async run() {
13
+ const { flags } = await this.parse(SchemaPush);
14
+ // Auth guard
15
+ if (!config_1.credentials.isValid()) {
16
+ this.error("Not logged in. Run 'apso login' first.");
17
+ }
18
+ // Link guard
19
+ const link = config_1.projectLink.read();
20
+ if (!link) {
21
+ this.error("Project not linked. Run 'apso link' first.");
22
+ }
23
+ // Parse local schema
24
+ let parsed;
25
+ try {
26
+ parsed = (0, apsorc_parser_1.parseApsorc)();
27
+ }
28
+ catch (error) {
29
+ const msg = error instanceof Error ? error.message : String(error);
30
+ this.error(`Failed to parse .apsorc: ${msg}`);
31
+ }
32
+ const localSchema = (0, schema_convert_1.apsorcToServiceSchema)(parsed);
33
+ // Check diff first
34
+ const diff = await services_1.schemaApi.diff(link.workspaceSlug, link.serviceSlug, localSchema);
35
+ if (!diff.hasChanges) {
36
+ this.log("Already in sync. Nothing to push.");
37
+ return;
38
+ }
39
+ // Show changes
40
+ this.log("Changes to push:\n");
41
+ if (diff.added.length > 0) {
42
+ for (const item of diff.added) {
43
+ this.log(` + ${item}`);
44
+ }
45
+ }
46
+ if (diff.removed.length > 0) {
47
+ for (const item of diff.removed) {
48
+ this.log(` - ${item}`);
49
+ }
50
+ }
51
+ if (diff.modified.length > 0) {
52
+ for (const item of diff.modified) {
53
+ this.log(` ~ ${item}`);
54
+ }
55
+ }
56
+ this.log(`\n${diff.added.length} added, ${diff.removed.length} removed, ${diff.modified.length} modified`);
57
+ // Confirm
58
+ if (!flags.yes) {
59
+ const { confirm } = await inquirer_1.default.prompt([
60
+ {
61
+ type: "confirm",
62
+ name: "confirm",
63
+ message: "Push these changes to the platform?",
64
+ default: true,
65
+ },
66
+ ]);
67
+ if (!confirm) {
68
+ this.log("Push cancelled.");
69
+ return;
70
+ }
71
+ }
72
+ // Push
73
+ const updatedSchema = await services_1.schemaApi.update(link.workspaceSlug, link.serviceSlug, localSchema);
74
+ // Update sync metadata
75
+ const localHash = (0, schema_convert_1.computeSchemaHash)(localSchema);
76
+ const remoteHash = (0, schema_convert_1.computeSchemaHash)(updatedSchema.entities);
77
+ config_1.projectLink.updateSyncMetadata({
78
+ lastSyncedAt: new Date().toISOString(),
79
+ localSchemaHash: localHash,
80
+ remoteSchemaHash: remoteHash,
81
+ });
82
+ this.log("Schema pushed successfully.");
83
+ }
84
+ }
85
+ SchemaPush.description = "Push local schema to the platform";
86
+ SchemaPush.examples = [
87
+ `$ apso schema push`,
88
+ `$ apso schema push --yes`,
89
+ ];
90
+ SchemaPush.flags = {
91
+ yes: core_1.Flags.boolean({
92
+ char: "y",
93
+ description: "Skip confirmation prompt",
94
+ default: false,
95
+ }),
96
+ };
97
+ exports.default = SchemaPush;
@@ -0,0 +1,9 @@
1
+ import BaseCommand from "../../lib/base-command";
2
+ export default class SchemaValidate extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ local: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
7
+ };
8
+ run(): Promise<void>;
9
+ }