@apso/cli 0.15.0 → 0.15.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.
@@ -13,11 +13,11 @@ const VALID_KEYS = [
13
13
  "telemetryDisabled",
14
14
  "defaultWorkspace",
15
15
  ];
16
- const BOOLEAN_KEYS = [
16
+ const BOOLEAN_KEYS = new Set([
17
17
  "verbose",
18
18
  "noColor",
19
19
  "telemetryDisabled",
20
- ];
20
+ ]);
21
21
  function parseBoolean(value) {
22
22
  const lower = value.toLowerCase();
23
23
  if (lower === "true" || lower === "1")
@@ -62,7 +62,7 @@ class Config extends base_command_1.default {
62
62
  if (!VALID_KEYS.includes(key)) {
63
63
  this.error(`Unknown key: ${key}\nValid keys: ${VALID_KEYS.join(", ")}`);
64
64
  }
65
- if (BOOLEAN_KEYS.includes(key)) {
65
+ if (BOOLEAN_KEYS.has(key)) {
66
66
  const boolVal = parseBoolean(value);
67
67
  if (boolVal === null) {
68
68
  this.error(`Invalid boolean value: ${value}. Use true/false or 1/0.`);
@@ -8,6 +8,7 @@ export default class Dev extends BaseCommand {
8
8
  build: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
9
9
  };
10
10
  private children;
11
+ private cleanup;
11
12
  run(): Promise<void>;
12
13
  /**
13
14
  * TypeScript: run `npm run dev` which reads DATABASE_TYPE from .env.
@@ -11,6 +11,11 @@ class Dev extends base_command_1.default {
11
11
  constructor() {
12
12
  super(...arguments);
13
13
  this.children = [];
14
+ this.cleanup = () => {
15
+ for (const child of this.children) {
16
+ child.kill("SIGTERM");
17
+ }
18
+ };
14
19
  }
15
20
  async run() {
16
21
  const { flags } = await this.parse(Dev);
@@ -22,13 +27,8 @@ class Dev extends base_command_1.default {
22
27
  this.log(`Detected language: ${language}`);
23
28
  this.log(`Database type: ${databaseType}`);
24
29
  // Set up signal forwarding for clean shutdown
25
- const cleanup = () => {
26
- for (const child of this.children) {
27
- child.kill("SIGTERM");
28
- }
29
- };
30
- process.on("SIGINT", cleanup);
31
- process.on("SIGTERM", cleanup);
30
+ process.on("SIGINT", this.cleanup);
31
+ process.on("SIGTERM", this.cleanup);
32
32
  try {
33
33
  switch (language) {
34
34
  case "typescript":
@@ -45,8 +45,8 @@ class Dev extends base_command_1.default {
45
45
  }
46
46
  }
47
47
  finally {
48
- process.removeListener("SIGINT", cleanup);
49
- process.removeListener("SIGTERM", cleanup);
48
+ process.removeListener("SIGINT", this.cleanup);
49
+ process.removeListener("SIGTERM", this.cleanup);
50
50
  }
51
51
  }
52
52
  /**
@@ -124,6 +124,7 @@ class Dev extends base_command_1.default {
124
124
  return "typescript";
125
125
  }
126
126
  try {
127
+ // eslint-disable-next-line unicorn/prefer-json-parse-buffer -- TS types JSON.parse as string-only
127
128
  const content = fs.readFileSync(apsorcPath, "utf-8");
128
129
  const config = JSON.parse(content);
129
130
  return config.language || "typescript";
@@ -24,12 +24,7 @@ class Init extends base_command_1.default {
24
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
25
  }
26
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
- }
27
+ await (isAuthenticated ? this.runAuthenticated(flags) : this.runOffline(flags));
33
28
  }
34
29
  async runAuthenticated(flags) {
35
30
  // Ask: create new or clone existing
@@ -44,12 +39,7 @@ class Init extends base_command_1.default {
44
39
  ],
45
40
  },
46
41
  ]);
47
- if (action === "clone") {
48
- await this.cloneExisting();
49
- }
50
- else {
51
- await this.createNew(flags);
52
- }
42
+ await (action === "clone" ? this.cloneExisting() : this.createNew(flags));
53
43
  }
54
44
  async cloneExisting() {
55
45
  // Select workspace
@@ -154,7 +154,7 @@ class Login extends base_command_1.default {
154
154
  });
155
155
  this.log(`Logged in as ${profile.email}`);
156
156
  }
157
- catch (error) {
157
+ catch {
158
158
  config_1.credentials.clear();
159
159
  this.error("Invalid token. Please check your token and try again.");
160
160
  }
@@ -4,7 +4,9 @@ const tslib_1 = require("tslib");
4
4
  const core_1 = require("@oclif/core");
5
5
  const fs = tslib_1.__importStar(require("fs"));
6
6
  const path = tslib_1.__importStar(require("path"));
7
+ // eslint-disable-next-line node/no-missing-import -- eslint-plugin-node cannot resolve package "exports" subpaths
7
8
  const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
9
+ // eslint-disable-next-line node/no-missing-import -- eslint-plugin-node cannot resolve package "exports" subpaths
8
10
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
9
11
  const zod_1 = require("zod");
10
12
  const base_command_1 = tslib_1.__importDefault(require("../../lib/base-command"));
@@ -233,7 +235,7 @@ class McpServe extends base_command_1.default {
233
235
  .boolean()
234
236
  .optional()
235
237
  .describe("Skip code formatting after generation (default: false)"),
236
- }, async ({ language, skip_format }) => {
238
+ }, async ({ language }) => {
237
239
  try {
238
240
  const configPath = (0, apsorc_parser_1.findConfigPath)();
239
241
  if (!configPath) {
@@ -301,12 +303,14 @@ class McpServe extends base_command_1.default {
301
303
  const enumFiles = await generator.generateEnums(entities, lowerCaseApiType);
302
304
  for (const file of enumFiles) {
303
305
  const fullPath = path.join(autogenPath, file.path);
306
+ // eslint-disable-next-line no-await-in-loop
304
307
  await (0, file_system_1.createFile)(fullPath, file.content);
305
308
  filesGenerated.push(file.path);
306
309
  }
307
310
  // Generate per-entity files
308
311
  for (const entity of entities) {
309
312
  const entityRelationships = relationshipMap[entity.name] || [];
313
+ // eslint-disable-next-line no-await-in-loop
310
314
  const allFiles = await Promise.all([
311
315
  generator.generateEntity({
312
316
  entity,
@@ -344,6 +348,7 @@ class McpServe extends base_command_1.default {
344
348
  for (const files of allFiles) {
345
349
  for (const file of files) {
346
350
  const fullPath = path.join(autogenPath, file.path);
351
+ // eslint-disable-next-line no-await-in-loop
347
352
  await (0, file_system_1.createFile)(fullPath, file.content);
348
353
  filesGenerated.push(file.path);
349
354
  }
@@ -353,6 +358,7 @@ class McpServe extends base_command_1.default {
353
358
  const guardFiles = await generator.generateGuards(entities, auth);
354
359
  for (const file of guardFiles) {
355
360
  const fullPath = path.join(autogenPath, file.path);
361
+ // eslint-disable-next-line no-await-in-loop
356
362
  await (0, file_system_1.createFile)(fullPath, file.content);
357
363
  filesGenerated.push(file.path);
358
364
  }
@@ -360,6 +366,7 @@ class McpServe extends base_command_1.default {
360
366
  const indexFiles = await generator.generateIndexModule(entities, lowerCaseApiType);
361
367
  for (const file of indexFiles) {
362
368
  const fullPath = path.join(autogenPath, file.path);
369
+ // eslint-disable-next-line no-await-in-loop
363
370
  await (0, file_system_1.createFile)(fullPath, file.content);
364
371
  filesGenerated.push(file.path);
365
372
  }
@@ -75,6 +75,7 @@ class Migrate extends base_command_1.default {
75
75
  });
76
76
  for (const file of migrationFiles) {
77
77
  const fullPath = path.join(process.cwd(), file.path);
78
+ // eslint-disable-next-line no-await-in-loop
78
79
  await (0, file_system_1.createFile)(fullPath, file.content);
79
80
  this.log(`Generated migration file: ${file.path}`);
80
81
  }
@@ -8,9 +8,9 @@ const apsorc_parser_1 = require("../../lib/apsorc-parser");
8
8
  const schema_convert_1 = require("../../lib/utils/schema-convert");
9
9
  // Inline ANSI color helpers (no dependency needed)
10
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;
11
+ const green = (s) => useColor() ? `\u001B[32m${s}\u001B[0m` : s;
12
+ const red = (s) => useColor() ? `\u001B[31m${s}\u001B[0m` : s;
13
+ const yellow = (s) => useColor() ? `\u001B[33m${s}\u001B[0m` : s;
14
14
  class SchemaDiff extends base_command_1.default {
15
15
  async run() {
16
16
  // Auth guard
@@ -40,7 +40,7 @@ function checkPkStrategy(ctx) {
40
40
  }
41
41
  const content = fs.readFileSync(entityPath, "utf-8");
42
42
  // The correct pattern is @PrimaryGeneratedColumn('uuid')
43
- const hasPrimaryGenerated = /PrimaryGeneratedColumn\s*\(\s*['"]uuid['"]\s*\)/.test(content);
43
+ const hasPrimaryGenerated = /PrimaryGeneratedColumn\s*\(\s*["']uuid["']\s*\)/.test(content);
44
44
  // Wrong patterns: @PrimaryColumn() or @PrimaryColumn({ type: 'uuid' }) without generation
45
45
  const hasPrimaryColumnOnly = /PrimaryColumn\s*\(/.test(content) && !hasPrimaryGenerated;
46
46
  if (hasPrimaryColumnOnly) {
@@ -87,7 +87,7 @@ function checkUnusedImports(ctx) {
87
87
  const lines = content.split("\n");
88
88
  // Find import lines from typeorm
89
89
  for (const line of lines) {
90
- const importMatch = line.match(/import\s*\{([^}]+)\}\s*from\s*['"]typeorm['"]/);
90
+ const importMatch = line.match(/import\s*{([^}]+)}\s*from\s*["']typeorm["']/);
91
91
  if (!importMatch)
92
92
  continue;
93
93
  const imports = importMatch[1]
@@ -285,7 +285,7 @@ class GoGenerator extends base_1.BaseGenerator {
285
285
  }
286
286
  const timestamp = new Date()
287
287
  .toISOString()
288
- .replace(/[-:T]/g, "")
288
+ .replace(/[:T-]/g, "")
289
289
  .slice(0, 14);
290
290
  const name = migrationName || "auto";
291
291
  const filename = `${timestamp}_${name}.sql`;
@@ -346,9 +346,6 @@ ${downStatements}
346
346
  entities: entitiesData,
347
347
  });
348
348
  // Generate model registry for AutoMigrate support
349
- const modelImports = entities
350
- .map((e) => `\t// ${e.name} model`)
351
- .join("\n");
352
349
  const modelRefs = entities
353
350
  .map((e) => `\t\t&${(0, casing_1.pascalCase)(e.name)}{},`)
354
351
  .join("\n");
@@ -213,7 +213,7 @@ class PythonGenerator extends base_1.BaseGenerator {
213
213
  }
214
214
  const timestamp = new Date()
215
215
  .toISOString()
216
- .replace(/[-:T]/g, "")
216
+ .replace(/[:T-]/g, "")
217
217
  .slice(0, 14);
218
218
  const revision = timestamp;
219
219
  const name = migrationName || "auto";
@@ -12,6 +12,7 @@
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.resetSandbox = exports.applyMigration = exports.runMigrationSandbox = void 0;
14
14
  const tslib_1 = require("tslib");
15
+ /* eslint-disable new-cap -- TypeORM decorator factories (Entity, Column, ...) are applied programmatically */
15
16
  const fs = tslib_1.__importStar(require("fs"));
16
17
  const path = tslib_1.__importStar(require("path"));
17
18
  const paths_1 = require("../config/paths");
@@ -59,7 +60,7 @@ async function resetPGliteSingleton() {
59
60
  * between runs.
60
61
  */
61
62
  function buildEntityClasses(entities, relationshipMap = {}) {
62
- var _a, _b;
63
+ var _a, _b, _c;
63
64
  clearTypeOrmMetadata();
64
65
  const typeorm = require("typeorm");
65
66
  const classes = [];
@@ -110,7 +111,7 @@ function buildEntityClasses(entities, relationshipMap = {}) {
110
111
  if (field.type === "enum" && field.values) {
111
112
  colOpts.enum = field.values;
112
113
  }
113
- if (field.length)
114
+ if (((_c = field.length) !== null && _c !== void 0 ? _c : 0) > 0)
114
115
  colOpts.length = field.length;
115
116
  if (field.precision)
116
117
  colOpts.precision = field.precision;
@@ -253,10 +254,10 @@ async function createPGliteDataSource(entityClasses) {
253
254
  const pgliteModule = await Promise.resolve().then(() => tslib_1.__importStar(require("typeorm-pglite")));
254
255
  PGliteDriver = pgliteModule.PGliteDriver;
255
256
  }
256
- catch (e) {
257
+ catch (error) {
257
258
  throw new Error("Migration sandbox requires typeorm-pglite and @electric-sql/pglite.\n" +
258
259
  "Install them with: npm install typeorm @electric-sql/pglite typeorm-pglite\n" +
259
- `Original error: ${e instanceof Error ? e.message : e}`);
260
+ `Original error: ${error instanceof Error ? error.message : error}`);
260
261
  }
261
262
  // Create a driver. typeorm-pglite uses a singleton PGliteInstance
262
263
  // so multiple DataSources share the same in-process database.
@@ -394,7 +395,9 @@ async function runMigrationSandbox(current, projectDir) {
394
395
  try {
395
396
  await resetPGliteSingleton();
396
397
  }
397
- catch { /* ignore */ }
398
+ catch {
399
+ /* ignore */
400
+ }
398
401
  return {
399
402
  needed: true,
400
403
  success: false,
@@ -71,11 +71,4 @@ export declare function writeSnapshot(schema: ApsorcSnapshot, projectDir?: strin
71
71
  * Delete the snapshot and sandbox directory contents (for --reset).
72
72
  */
73
73
  export declare function resetSnapshot(projectDir?: string): void;
74
- /**
75
- * Detect whether the current schema differs from the stored snapshot.
76
- *
77
- * Compares entity names, field definitions, and relationships using
78
- * deterministic JSON serialization. Returns true if any change is detected,
79
- * or if no snapshot exists (first run).
80
- */
81
74
  export declare function hasChanges(current: ComparableSchema, snapshot: ApsorcSnapshot | null): boolean;
@@ -27,6 +27,7 @@ function readSnapshot(projectDir) {
27
27
  return null;
28
28
  }
29
29
  try {
30
+ // eslint-disable-next-line unicorn/prefer-json-parse-buffer -- TS types JSON.parse as string-only
30
31
  const content = fs.readFileSync(snapshotPath, "utf8");
31
32
  return JSON.parse(content);
32
33
  }
@@ -62,11 +63,11 @@ exports.resetSnapshot = resetSnapshot;
62
63
  * deterministic JSON serialization. Returns true if any change is detected,
63
64
  * or if no snapshot exists (first run).
64
65
  */
66
+ const normalize = (obj) => JSON.stringify(obj);
65
67
  function hasChanges(current, snapshot) {
66
68
  if (!snapshot) {
67
69
  return true;
68
70
  }
69
- const normalize = (obj) => JSON.stringify(obj);
70
71
  // Compare entity count
71
72
  if (current.entities.length !== snapshot.entities.length) {
72
73
  return true;
@@ -1,6 +1,6 @@
1
1
  <% const {field} = it; %>
2
2
 
3
- @Column({ "type": "bigint"<% if (field.default !== null) {%>, default: <%= field.default %><% } %> })
3
+ @Column({ "type": "bigint", nullable: <%= Boolean(field.nullable) %><% if (field.default !== null) {%>, default: <%= field.default %><% } %> })
4
4
  <% if (field.index ) {%>
5
5
  @Index()
6
6
  <% } %>
@@ -1,6 +1,6 @@
1
1
  <% const {field} = it; %>
2
2
 
3
- @Column({ "type": "decimal", precision: <%= field.precision || 10 %>, scale: <%= field.scale || 2 %><% if (field.default !== null) {%>, default: <%= field.default %><% } %> })
3
+ @Column({ "type": "decimal", precision: <%= field.precision || 10 %>, scale: <%= field.scale || 2 %>, nullable: <%= Boolean(field.nullable) %><% if (field.default !== null) {%>, default: <%= field.default %><% } %> })
4
4
  <% if (field.index ) {%>
5
5
  @Index()
6
6
  <% } %>
@@ -1,6 +1,6 @@
1
1
  <% const {field} = it; %>
2
2
 
3
- @Column({ "type": "float8"<% if (field.default !== null) {%>, default: <%= field.default %><% } %> })
3
+ @Column({ "type": "float8", nullable: <%= Boolean(field.nullable) %><% if (field.default !== null) {%>, default: <%= field.default %><% } %> })
4
4
  <% if (field.index ) {%>
5
5
  @Index()
6
6
  <% } %>
@@ -1,6 +1,6 @@
1
1
  <% const {field} = it; %>
2
2
 
3
- @Column({ "type": "decimal"<% if (field.default !== null) {%>, default: <%= field.default %><% } %> })
3
+ @Column({ "type": "decimal", nullable: <%= Boolean(field.nullable) %><% if (field.default !== null) {%>, default: <%= field.default %><% } %> })
4
4
  <% if (field.index ) {%>
5
5
  @Index()
6
6
  <% } %>
@@ -1,6 +1,6 @@
1
1
  <% const {field} = it; %>
2
2
 
3
- @Column({ "type": "int"<% if (field.default !== null) {%>, default: <%= field.default %><% } %> })
3
+ @Column({ "type": "int", nullable: <%= Boolean(field.nullable) %><% if (field.default !== null) {%>, default: <%= field.default %><% } %> })
4
4
  <% if (field.index ) {%>
5
5
  @Index()
6
6
  <% } %>
@@ -1,6 +1,6 @@
1
1
  <% const {field} = it; %>
2
2
 
3
- @Column({ "type": "money"<% if (field.default !== null) {%>, default: <%= field.default %><% } %> })
3
+ @Column({ "type": "money", nullable: <%= Boolean(field.nullable) %><% if (field.default !== null) {%>, default: <%= field.default %><% } %> })
4
4
  <% if (field.index ) {%>
5
5
  @Index()
6
6
  <% } %>
@@ -1,6 +1,6 @@
1
1
  <% const {field} = it; %>
2
2
 
3
- @Column({ "type": "numeric", precision: <%= field.precision || 10 %>, scale: <%= field.scale || 2 %><% if (field.default !== null) {%>, default: <%= field.default %><% } %> })
3
+ @Column({ "type": "numeric", precision: <%= field.precision || 10 %>, scale: <%= field.scale || 2 %>, nullable: <%= Boolean(field.nullable) %><% if (field.default !== null) {%>, default: <%= field.default %><% } %> })
4
4
  <% if (field.index ) {%>
5
5
  @Index()
6
6
  <% } %>
@@ -1,6 +1,6 @@
1
1
  <% const {field} = it; %>
2
2
 
3
- @Column({ "type": "real"<% if (field.default !== null) {%>, default: <%= field.default %><% } %> })
3
+ @Column({ "type": "real", nullable: <%= Boolean(field.nullable) %><% if (field.default !== null) {%>, default: <%= field.default %><% } %> })
4
4
  <% if (field.index ) {%>
5
5
  @Index()
6
6
  <% } %>
@@ -1,6 +1,6 @@
1
1
  <% const {field} = it; %>
2
2
 
3
- @Column({ "type": "smallint"<% if (field.default !== null) {%>, default: <%= field.default %><% } %> })
3
+ @Column({ "type": "smallint", nullable: <%= Boolean(field.nullable) %><% if (field.default !== null) {%>, default: <%= field.default %><% } %> })
4
4
  <% if (field.index ) {%>
5
5
  @Index()
6
6
  <% } %>
@@ -147,7 +147,7 @@ exports.serviceSchemaToApsorc = serviceSchemaToApsorc;
147
147
  */
148
148
  function sortKeys(value) {
149
149
  if (Array.isArray(value)) {
150
- return value.map(sortKeys);
150
+ return value.map((item) => sortKeys(item));
151
151
  }
152
152
  if (value !== null && typeof value === "object") {
153
153
  const sorted = {};
@@ -39,7 +39,8 @@ async function createSpinner(text) {
39
39
  const config = config_1.globalConfig.read();
40
40
  try {
41
41
  // Dynamic import to keep ora optional at startup
42
- const ora = (await Promise.resolve().then(() => __importStar(require("ora")))).default;
42
+ const oraModule = await Promise.resolve().then(() => __importStar(require("ora")));
43
+ const ora = oraModule.default;
43
44
  const spinner = ora({
44
45
  text,
45
46
  color: config.noColor ? undefined : "cyan",
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@apso/cli",
3
- "version": "0.15.0",
3
+ "version": "0.15.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@apso/cli",
9
- "version": "0.15.0",
10
- "license": "MIT",
9
+ "version": "0.15.1",
10
+ "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@electric-sql/pglite": "^0.2.17",
13
13
  "@modelcontextprotocol/sdk": "^1.29.0",
@@ -22,7 +22,8 @@
22
22
  "rc": "^1.2.8",
23
23
  "shelljs": "^0.8.5",
24
24
  "typeorm": "^0.3.20",
25
- "typeorm-pglite": "^0.3.4"
25
+ "typeorm-pglite": "^0.3.4",
26
+ "zod": "^4.4.3"
26
27
  },
27
28
  "bin": {
28
29
  "apso": "bin/run"
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.15.0",
2
+ "version": "0.15.1",
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.15.0",
3
+ "version": "0.15.1",
4
4
  "mcpName": "io.github.apsoai/apso",
5
5
  "description": "Apso CLI",
6
6
  "author": "Apso by Mavric - @mavric",
@@ -37,7 +37,8 @@
37
37
  "rc": "^1.2.8",
38
38
  "shelljs": "^0.8.5",
39
39
  "typeorm": "^0.3.20",
40
- "typeorm-pglite": "^0.3.4"
40
+ "typeorm-pglite": "^0.3.4",
41
+ "zod": "^4.4.3"
41
42
  },
42
43
  "devDependencies": {
43
44
  "@jest/globals": "^29.5.0",