@apso/cli 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
+ const path = tslib_1.__importStar(require("path"));
4
5
  const core_1 = require("@oclif/core");
5
6
  const base_command_1 = tslib_1.__importDefault(require("../lib/base-command"));
6
7
  const apsorc_parser_1 = require("../lib/apsorc-parser");
7
- const snapshot_1 = require("../lib/migrate/snapshot");
8
8
  const sandbox_1 = require("../lib/migrate/sandbox");
9
+ const lib_1 = require("../lib");
10
+ const file_system_1 = require("../lib/utils/file-system");
9
11
  class Migrate extends base_command_1.default {
10
12
  async run() {
11
13
  const { flags } = await this.parse(Migrate);
@@ -24,37 +26,6 @@ class Migrate extends base_command_1.default {
24
26
  const msg = error instanceof Error ? error.message : String(error);
25
27
  this.error(`Failed to parse .apsorc: ${msg}`);
26
28
  }
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
29
  if (!flags.sql) {
59
30
  this.log("Schema changes detected. Running migration sandbox...");
60
31
  }
@@ -85,6 +56,29 @@ class Migrate extends base_command_1.default {
85
56
  this.log("");
86
57
  this.log(`${result.upSql.length} statement(s) generated and tested.`);
87
58
  this.log("Migration validated against local PGlite.");
59
+ // Generate language-native migration files for Python and Go
60
+ const language = parsed.language || "typescript";
61
+ if (language !== "typescript") {
62
+ const generatorConfig = {
63
+ language,
64
+ rootFolder: parsed.rootFolder || "src",
65
+ apiType: parsed.apiType || "rest",
66
+ entities: parsed.entities,
67
+ relationshipMap: parsed.relationshipMap,
68
+ };
69
+ const generator = (0, lib_1.createGenerator)(generatorConfig);
70
+ const migrationFiles = await generator.generateMigration({
71
+ entities: parsed.entities,
72
+ relationshipMap: parsed.relationshipMap,
73
+ upSql: result.upSql,
74
+ downSql: result.downSql,
75
+ });
76
+ for (const file of migrationFiles) {
77
+ const fullPath = path.join(process.cwd(), file.path);
78
+ await (0, file_system_1.createFile)(fullPath, file.content);
79
+ this.log(`Generated migration file: ${file.path}`);
80
+ }
81
+ }
88
82
  if (flags.apply) {
89
83
  (0, sandbox_1.applyMigration)({
90
84
  entities: parsed.entities,
@@ -30,7 +30,7 @@ export declare class GoGenerator extends BaseGenerator {
30
30
  generateService(options: ServiceGenerationOptions): Promise<GeneratedFile[]>;
31
31
  generateDto(options: DtoGenerationOptions): Promise<GeneratedFile[]>;
32
32
  generateModule(_options: EntityGenerationOptions): Promise<GeneratedFile[]>;
33
- generateMigration(_options: MigrationGenerationOptions): Promise<GeneratedFile[]>;
33
+ generateMigration(options: MigrationGenerationOptions): Promise<GeneratedFile[]>;
34
34
  generateEnums(entities: Entity[], _apiType: string): Promise<GeneratedFile[]>;
35
35
  generateIndexModule(entities: Entity[], _apiType: string): Promise<GeneratedFile[]>;
36
36
  generateGuards(_entities: Entity[], auth?: AuthConfig): Promise<GeneratedFile[]>;
@@ -278,10 +278,31 @@ class GoGenerator extends base_1.BaseGenerator {
278
278
  // Route registration is handled in the index module
279
279
  return [];
280
280
  }
281
- async generateMigration(_options) {
282
- // Goose handles migrations differently
283
- // This is a placeholder for future migration template generation
284
- return [];
281
+ async generateMigration(options) {
282
+ const { upSql, downSql, migrationName } = options;
283
+ if (!upSql || upSql.length === 0) {
284
+ return [];
285
+ }
286
+ const timestamp = new Date()
287
+ .toISOString()
288
+ .replace(/[-:T]/g, "")
289
+ .slice(0, 14);
290
+ const name = migrationName || "auto";
291
+ const filename = `${timestamp}_${name}.sql`;
292
+ const upStatements = upSql.map((sql) => `${sql};`).join("\n");
293
+ const downStatements = (downSql || []).map((sql) => `${sql};`).join("\n");
294
+ const content = `-- +goose Up
295
+ ${upStatements}
296
+
297
+ -- +goose Down
298
+ ${downStatements}
299
+ `;
300
+ return [
301
+ {
302
+ path: `migrations/${filename}`,
303
+ content,
304
+ },
305
+ ];
285
306
  }
286
307
  async generateEnums(entities, _apiType) {
287
308
  if (entities.length === 0) {
@@ -324,11 +345,32 @@ class GoGenerator extends base_1.BaseGenerator {
324
345
  const content = await this.renderTemplate("./index-module", {
325
346
  entities: entitiesData,
326
347
  });
348
+ // Generate model registry for AutoMigrate support
349
+ const modelImports = entities
350
+ .map((e) => `\t// ${e.name} model`)
351
+ .join("\n");
352
+ const modelRefs = entities
353
+ .map((e) => `\t\t&${(0, casing_1.pascalCase)(e.name)}{},`)
354
+ .join("\n");
355
+ const registryContent = `package models
356
+
357
+ // All returns all registered GORM models for AutoMigrate.
358
+ // This file is auto-generated by the Apso CLI.
359
+ func All() []interface{} {
360
+ \treturn []interface{}{
361
+ ${modelRefs}
362
+ \t}
363
+ }
364
+ `;
327
365
  return [
328
366
  {
329
367
  path: "routes/routes.go",
330
368
  content,
331
369
  },
370
+ {
371
+ path: "models/registry.go",
372
+ content: registryContent,
373
+ },
332
374
  ];
333
375
  }
334
376
  async generateGuards(_entities, auth) {
@@ -14,7 +14,7 @@ export declare class PythonGenerator extends BaseGenerator {
14
14
  generateService(options: ServiceGenerationOptions): Promise<GeneratedFile[]>;
15
15
  generateDto(options: DtoGenerationOptions): Promise<GeneratedFile[]>;
16
16
  generateModule(_options: EntityGenerationOptions): Promise<GeneratedFile[]>;
17
- generateMigration(_options: MigrationGenerationOptions): Promise<GeneratedFile[]>;
17
+ generateMigration(options: MigrationGenerationOptions): Promise<GeneratedFile[]>;
18
18
  generateEnums(entities: Entity[], _apiType: string): Promise<GeneratedFile[]>;
19
19
  generateIndexModule(entities: Entity[], _apiType: string): Promise<GeneratedFile[]>;
20
20
  generateGuards(_entities: Entity[], _auth?: AuthConfig): Promise<GeneratedFile[]>;
@@ -206,10 +206,46 @@ class PythonGenerator extends base_1.BaseGenerator {
206
206
  // The router registration is handled in the index module
207
207
  return [];
208
208
  }
209
- async generateMigration(_options) {
210
- // Alembic handles migrations differently
211
- // This is a placeholder for future migration template generation
212
- return [];
209
+ async generateMigration(options) {
210
+ const { upSql, downSql, migrationName } = options;
211
+ if (!upSql || upSql.length === 0) {
212
+ return [];
213
+ }
214
+ const timestamp = new Date()
215
+ .toISOString()
216
+ .replace(/[-:T]/g, "")
217
+ .slice(0, 14);
218
+ const revision = timestamp;
219
+ const name = migrationName || "auto";
220
+ const filename = `${timestamp}_${name}.py`;
221
+ const upStatements = upSql
222
+ .map((sql) => ` op.execute("""${sql}""")`)
223
+ .join("\n");
224
+ const downStatements = (downSql || [])
225
+ .map((sql) => ` op.execute("""${sql}""")`)
226
+ .join("\n");
227
+ const content = `"""${name}"""
228
+ from alembic import op
229
+
230
+ revision = '${revision}'
231
+ down_revision = None
232
+ branch_labels = None
233
+ depends_on = None
234
+
235
+
236
+ def upgrade() -> None:
237
+ ${upStatements}
238
+
239
+
240
+ def downgrade() -> None:
241
+ ${downStatements || " pass"}
242
+ `;
243
+ return [
244
+ {
245
+ path: `alembic/versions/${filename}`,
246
+ content,
247
+ },
248
+ ];
213
249
  }
214
250
  async generateEnums(entities, _apiType) {
215
251
  if (entities.length === 0) {
@@ -1,11 +1,12 @@
1
1
  <% const {field} = it; %>
2
2
 
3
+ <% if (field.primary ) {%>
4
+ @PrimaryGeneratedColumn('uuid')
5
+ <% } else { %>
3
6
  @Column({ "type": "uuid"<% if (field.default !== null) {%>, default: <%= field.default %><% } %> })
4
7
  <% if (field.index ) {%>
5
8
  @Index()
6
9
  <% } %>
7
- <% if (field.primary ) {%>
8
- @PrimaryColumn()
9
10
  <% } %>
10
11
 
11
- <%= field.name %>: string;
12
+ <%= field.name %>: string;
@@ -58,8 +58,7 @@ export class <%= it.name %> {
58
58
  <% if (it.createPrimaryKey) { %>
59
59
  // Fields
60
60
  <% if (it.primaryKeyType === 'uuid') { %>
61
- @Column({ type: 'uuid' })
62
- @PrimaryColumn()
61
+ @PrimaryGeneratedColumn('uuid')
63
62
  id: string;
64
63
  <% } else if (it.primaryKeyType === 'text') { %>
65
64
  @Column({ type: 'text' })
@@ -173,6 +173,14 @@ export interface MigrationGenerationOptions {
173
173
  * Migration name/version
174
174
  */
175
175
  migrationName?: string;
176
+ /**
177
+ * SQL statements for the up direction (from PGlite sandbox)
178
+ */
179
+ upSql?: string[];
180
+ /**
181
+ * SQL statements for the down direction (from PGlite sandbox)
182
+ */
183
+ downSql?: string[];
176
184
  }
177
185
  /**
178
186
  * Interface that all language-specific generators must implement
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@apso/cli",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@apso/cli",
9
- "version": "0.12.0",
9
+ "version": "0.13.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@electric-sql/pglite": "^0.2.17",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.12.0",
2
+ "version": "0.13.0",
3
3
  "commands": {
4
4
  "config": {
5
5
  "id": "config",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apso/cli",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "mcpName": "io.github.apsoai/apso",
5
5
  "description": "Apso CLI",
6
6
  "author": "Apso by Mavric - @mavric",