@apso/cli 0.40.0 → 0.41.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.
@@ -10,6 +10,7 @@ const interactive_1 = require("../lib/utils/interactive");
10
10
  const perf_hooks_1 = require("perf_hooks");
11
11
  const file_system_1 = require("../lib/utils/file-system");
12
12
  const git_hooks_1 = require("../lib/utils/git-hooks");
13
+ const crud_deps_1 = require("../lib/utils/crud-deps");
13
14
  class Generate extends base_command_1.default {
14
15
  async run() {
15
16
  var _a, _b;
@@ -240,6 +241,15 @@ class Generate extends base_command_1.default {
240
241
  else if (generatedFileCount === 0) {
241
242
  console.log("[apso] Skipping formatting (no files were generated)");
242
243
  }
244
+ // Generated TS code imports @apso/crud*. Projects from the v1 template
245
+ // (@nestjsx/crud) don't declare those, and every entity then fails to
246
+ // compile with TS2307. Declare them so the next install picks them up.
247
+ if (language === "typescript" && generatedFileCount > 0) {
248
+ const added = (0, crud_deps_1.ensureCrudDeps)(process.cwd());
249
+ if (added.length > 0) {
250
+ console.log(`[apso] Added ${added.join(", ")} to package.json (the generated code imports them). Run npm install before building.`);
251
+ }
252
+ }
243
253
  const totalBuildTime = perf_hooks_1.performance.now() - totalBuildStart;
244
254
  console.log(`[apso] Finished building all entities in ${totalBuildTime.toFixed(2)} ms`);
245
255
  // Best-effort: install the commit co-author hook so commits that include
@@ -5,6 +5,7 @@ export default class MigrateVerify extends BaseCommand {
5
5
  static flags: {
6
6
  baseline: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
7
7
  migration: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
8
+ "skip-build": import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
8
9
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
9
10
  };
10
11
  run(): Promise<void>;
@@ -8,7 +8,7 @@ const verify_1 = require("../../lib/verify");
8
8
  class MigrateVerify extends base_command_1.default {
9
9
  async run() {
10
10
  const { flags } = await this.parse(MigrateVerify);
11
- const result = await (0, verify_1.verifyMigration)(process.cwd(), path.resolve(flags.baseline), flags.migration, (0, verify_1.cliBinPath)(this.config.root));
11
+ const result = await (0, verify_1.verifyMigration)(process.cwd(), path.resolve(flags.baseline), flags.migration, (0, verify_1.cliBinPath)(this.config.root), flags["skip-build"]);
12
12
  (0, verify_1.report)((s) => this.log(s), result, flags.json, result.ok ? "Migration verified." : undefined);
13
13
  if (!result.ok)
14
14
  this.exit(1);
@@ -27,6 +27,10 @@ MigrateVerify.flags = {
27
27
  description: "The migration file to run",
28
28
  required: true,
29
29
  }),
30
+ "skip-build": core_1.Flags.boolean({
31
+ description: "Use the existing dist/ build instead of building first",
32
+ default: false,
33
+ }),
30
34
  json: core_1.Flags.boolean({
31
35
  description: "Print the result as JSON",
32
36
  default: false,
@@ -3,6 +3,7 @@ export default class TestSmoke extends BaseCommand {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {
6
+ "skip-build": import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
6
7
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
7
8
  };
8
9
  run(): Promise<void>;
@@ -11,7 +11,9 @@ class TestSmoke extends base_command_1.default {
11
11
  const { entities, auth } = (0, apsorc_parser_1.parseApsorc)();
12
12
  // With auth configured every entity route needs a session, so only /health is checked.
13
13
  const names = auth ? [] : entities.map((e) => e.name);
14
- const result = await (0, verify_1.smokeTest)(process.cwd(), names);
14
+ const result = await (0, verify_1.smokeTest)(process.cwd(), names, {
15
+ skipBuild: flags["skip-build"],
16
+ });
15
17
  (0, verify_1.report)((s) => this.log(s), result, flags.json, result.ok ? "Smoke test passed." : undefined);
16
18
  if (!result.ok)
17
19
  this.exit(1);
@@ -20,6 +22,10 @@ class TestSmoke extends base_command_1.default {
20
22
  TestSmoke.description = "Start the service on a local PGlite database and call /health and every entity's list endpoint (TypeScript services)";
21
23
  TestSmoke.examples = [`$ apso test smoke`];
22
24
  TestSmoke.flags = {
25
+ "skip-build": core_1.Flags.boolean({
26
+ description: "Use the existing dist/ build instead of building first",
27
+ default: false,
28
+ }),
23
29
  json: core_1.Flags.boolean({
24
30
  description: "Print the result as JSON",
25
31
  default: false,
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Runtime packages the generated TypeScript code imports (entities, services,
3
+ * controllers, guards). Versions match the pinned service-template-ts tag
4
+ * (see TEMPLATE_REFS in ./template.ts); bump both together.
5
+ */
6
+ export declare const APSO_CRUD_DEPS: Record<string, string>;
7
+ /**
8
+ * Add any missing @apso/crud runtime packages to the project's package.json.
9
+ *
10
+ * Projects created from the v1 template (@nestjsx/crud) don't declare them, so
11
+ * freshly generated code fails `nest build` with TS2307 "Cannot find module
12
+ * '@apso/crud'" on every entity. Existing @nestjsx deps are left alone because
13
+ * hand-written code may still import them.
14
+ *
15
+ * @param {string} projectDir - Directory holding the project's package.json.
16
+ * @returns {string[]} Names added (empty when nothing changed or no package.json).
17
+ */
18
+ export declare function ensureCrudDeps(projectDir: string): string[];
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ensureCrudDeps = exports.APSO_CRUD_DEPS = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const fs = tslib_1.__importStar(require("fs"));
6
+ const path = tslib_1.__importStar(require("path"));
7
+ /**
8
+ * Runtime packages the generated TypeScript code imports (entities, services,
9
+ * controllers, guards). Versions match the pinned service-template-ts tag
10
+ * (see TEMPLATE_REFS in ./template.ts); bump both together.
11
+ */
12
+ exports.APSO_CRUD_DEPS = {
13
+ "@apso/crud": "^1.0.1",
14
+ "@apso/crud-core": "^1.0.1",
15
+ "@apso/crud-request": "^1.0.1",
16
+ "@apso/crud-typeorm": "^1.0.1",
17
+ };
18
+ /**
19
+ * Add any missing @apso/crud runtime packages to the project's package.json.
20
+ *
21
+ * Projects created from the v1 template (@nestjsx/crud) don't declare them, so
22
+ * freshly generated code fails `nest build` with TS2307 "Cannot find module
23
+ * '@apso/crud'" on every entity. Existing @nestjsx deps are left alone because
24
+ * hand-written code may still import them.
25
+ *
26
+ * @param {string} projectDir - Directory holding the project's package.json.
27
+ * @returns {string[]} Names added (empty when nothing changed or no package.json).
28
+ */
29
+ function ensureCrudDeps(projectDir) {
30
+ var _a;
31
+ const pkgPath = path.join(projectDir, "package.json");
32
+ if (!fs.existsSync(pkgPath))
33
+ return [];
34
+ const raw = fs.readFileSync(pkgPath, "utf8");
35
+ const pkg = JSON.parse(raw);
36
+ const declared = { ...pkg.devDependencies, ...pkg.dependencies };
37
+ const missing = Object.keys(exports.APSO_CRUD_DEPS).filter((name) => !declared[name]);
38
+ if (missing.length === 0)
39
+ return [];
40
+ pkg.dependencies = (_a = pkg.dependencies) !== null && _a !== void 0 ? _a : {};
41
+ for (const name of missing)
42
+ pkg.dependencies[name] = exports.APSO_CRUD_DEPS[name];
43
+ pkg.dependencies = Object.fromEntries(Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b)));
44
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + (raw.endsWith("\n") ? "\n" : ""));
45
+ return missing;
46
+ }
47
+ exports.ensureCrudDeps = ensureCrudDeps;
@@ -46,14 +46,17 @@ export interface CheckResult {
46
46
  * Run a migration on a fresh copy of the deployed schema, then check that the
47
47
  * database matches the project's entities (a second generate finds nothing).
48
48
  */
49
- export declare function verifyMigration(projectDir: string, baselineApsorc: string, migrationFile: string, cliBin: string): Promise<CheckResult>;
49
+ export declare function verifyMigration(projectDir: string, baselineApsorc: string, migrationFile: string, cliBin: string, skipBuild?: boolean): Promise<CheckResult>;
50
50
  /**
51
51
  * Start the built service on a PGlite database synced to its entities and call
52
52
  * GET /health and the list endpoint of every entity.
53
53
  * ponytail: list only, no create/read/delete; add when payloads can be built
54
54
  * from required fields and relationships.
55
55
  */
56
- export declare function smokeTest(projectDir: string, entityNames: string[], timeoutMs?: number): Promise<CheckResult>;
56
+ export declare function smokeTest(projectDir: string, entityNames: string[], { skipBuild, timeoutMs }?: {
57
+ skipBuild?: boolean | undefined;
58
+ timeoutMs?: number | undefined;
59
+ }): Promise<CheckResult>;
57
60
  /** The CLI's own entry point, for running `generate` in the baseline copy. */
58
61
  export declare const cliBinPath: (root: string) => string;
59
62
  /** Print a check's steps (or JSON) through the command's logger. */
@@ -137,7 +137,7 @@ ds.initialize()
137
137
  * Run a migration on a fresh copy of the deployed schema, then check that the
138
138
  * database matches the project's entities (a second generate finds nothing).
139
139
  */
140
- async function verifyMigration(projectDir, baselineApsorc, migrationFile, cliBin) {
140
+ async function verifyMigration(projectDir, baselineApsorc, migrationFile, cliBin, skipBuild = false) {
141
141
  const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "apso-pglite-"));
142
142
  const steps = [];
143
143
  const done = (s) => {
@@ -147,7 +147,7 @@ async function verifyMigration(projectDir, baselineApsorc, migrationFile, cliBin
147
147
  try {
148
148
  if (!done(await buildBaseline(projectDir, baselineApsorc, dataDir, cliBin)))
149
149
  return { ok: false, steps };
150
- if (!done(step("build", await build(projectDir))))
150
+ if (!skipBuild && !done(step("build", await build(projectDir))))
151
151
  return { ok: false, steps };
152
152
  const compiled = path
153
153
  .join("dist", path.relative("src", migrationFile))
@@ -173,12 +173,15 @@ const freePort = () => new Promise((resolve, reject) => {
173
173
  });
174
174
  const get = (url) => new Promise((resolve) => {
175
175
  http
176
- .get(url, (res) => {
176
+ .get(url, { timeout: 15000 }, (res) => {
177
177
  let body = "";
178
178
  res.on("data", (d) => {
179
179
  body += d;
180
180
  });
181
181
  res.on("end", () => { var _a; return resolve({ status: (_a = res.statusCode) !== null && _a !== void 0 ? _a : 0, body }); });
182
+ })
183
+ .on("timeout", function () {
184
+ this.destroy(new Error("No response within 15s"));
182
185
  })
183
186
  .on("error", (e) => resolve({ status: 0, body: e.message }));
184
187
  });
@@ -188,7 +191,7 @@ const get = (url) => new Promise((resolve) => {
188
191
  * ponytail: list only, no create/read/delete; add when payloads can be built
189
192
  * from required fields and relationships.
190
193
  */
191
- async function smokeTest(projectDir, entityNames, timeoutMs = 60000) {
194
+ async function smokeTest(projectDir, entityNames, { skipBuild = false, timeoutMs = 60000 } = {}) {
192
195
  var _a, _b;
193
196
  const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "apso-pglite-"));
194
197
  const steps = [];
@@ -198,7 +201,7 @@ async function smokeTest(projectDir, entityNames, timeoutMs = 60000) {
198
201
  };
199
202
  let server;
200
203
  try {
201
- if (!done(step("build", await build(projectDir))))
204
+ if (!skipBuild && !done(step("build", await build(projectDir))))
202
205
  return { ok: false, steps };
203
206
  if (!done(step("schema", await typeorm(projectDir, ["schema:sync"], (0, exports.pgliteEnv)(dataDir)))))
204
207
  return { ok: false, steps };
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@apso/cli",
3
- "version": "0.40.0",
3
+ "version": "0.41.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@apso/cli",
9
- "version": "0.40.0",
9
+ "version": "0.41.1",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@apso/schema-tools": "^0.2.0",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.40.0",
2
+ "version": "0.41.1",
3
3
  "commands": {
4
4
  "config": {
5
5
  "id": "config",
@@ -832,6 +832,12 @@
832
832
  "required": true,
833
833
  "multiple": false
834
834
  },
835
+ "skip-build": {
836
+ "name": "skip-build",
837
+ "type": "boolean",
838
+ "description": "Use the existing dist/ build instead of building first",
839
+ "allowNo": false
840
+ },
835
841
  "json": {
836
842
  "name": "json",
837
843
  "type": "boolean",
@@ -1002,6 +1008,12 @@
1002
1008
  "$ apso test smoke"
1003
1009
  ],
1004
1010
  "flags": {
1011
+ "skip-build": {
1012
+ "name": "skip-build",
1013
+ "type": "boolean",
1014
+ "description": "Use the existing dist/ build instead of building first",
1015
+ "allowNo": false
1016
+ },
1005
1017
  "json": {
1006
1018
  "name": "json",
1007
1019
  "type": "boolean",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apso/cli",
3
- "version": "0.40.0",
3
+ "version": "0.41.1",
4
4
  "mcpName": "io.github.apsoai/apso",
5
5
  "description": "Apso CLI",
6
6
  "author": "Apso by Mavric - @mavric",