@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.
- 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,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,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
|
+
}
|