@apso/cli 0.32.0 → 0.34.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.
@@ -287,7 +287,14 @@ async function apiRequest(path, options = {}) {
287
287
  }
288
288
  // Handle server errors with retry
289
289
  if (response.status >= 500) {
290
- lastError = new ApiClientError(errorData.message || "Server error", response.status, errorData.details);
290
+ // The BFF returns its message in `.error`; the backend uses `.message`.
291
+ const errObj = errorData;
292
+ const serverMsg = errorData.message ||
293
+ (typeof errObj.error === "string"
294
+ ? errObj.error
295
+ : undefined) ||
296
+ "Server error";
297
+ lastError = new ApiClientError(serverMsg, response.status, errorData.details);
291
298
  await sleep(getBackoffDelay(attempt));
292
299
  continue;
293
300
  }
@@ -138,5 +138,16 @@ export declare const codeApi: {
138
138
  bucket?: string;
139
139
  key?: string;
140
140
  }>;
141
+ /**
142
+ * Record S3 metadata after the presigned PUT completes. Without this the
143
+ * service row's s3_code_key stays null and a server-side push fails with
144
+ * "No S3 code found". Mirrors the browser upload flow (upload-url -> PUT ->
145
+ * finalize).
146
+ */
147
+ finalize(serviceId: string): Promise<{
148
+ success: boolean;
149
+ version?: string;
150
+ size?: number;
151
+ }>;
141
152
  };
142
153
  export {};
@@ -377,4 +377,13 @@ exports.codeApi = {
377
377
  async getUploadUrl(serviceId) {
378
378
  return client_1.api.get(`/api/services/${serviceId}/code/upload-url`);
379
379
  },
380
+ /**
381
+ * Record S3 metadata after the presigned PUT completes. Without this the
382
+ * service row's s3_code_key stays null and a server-side push fails with
383
+ * "No S3 code found". Mirrors the browser upload flow (upload-url -> PUT ->
384
+ * finalize).
385
+ */
386
+ async finalize(serviceId) {
387
+ return client_1.api.post(`/api/services/${serviceId}/code/finalize`, {});
388
+ },
380
389
  };
@@ -1,6 +1,6 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="node" />
3
- /** Zip the project source into a buffer, skipping build/vcs/dependency dirs. */
3
+ /** Zip the project source into a buffer, skipping build/vcs/dependency/secret files. */
4
4
  export declare function zipProject(cwd: string): Buffer;
5
5
  /**
6
6
  * Server-side push: upload the project zip to S3 and have the platform push it
@@ -15,12 +15,28 @@ const EXCLUDE_DIRS = new Set([
15
15
  ".apso",
16
16
  "coverage",
17
17
  ]);
18
- /** Zip the project source into a buffer, skipping build/vcs/dependency dirs. */
18
+ /**
19
+ * Never push local secrets to the repo. `.env` and `.env.*` (e.g. `.env.local`)
20
+ * routinely hold DB passwords and session secrets; `.env.example` is the one
21
+ * env file that is meant to be committed. The backend injects real production
22
+ * env at runtime, so these are never needed in the pushed code.
23
+ */
24
+ function isSecretEnvFile(basename) {
25
+ if (basename === ".env.example" || basename === ".env.sample")
26
+ return false;
27
+ return basename === ".env" || basename.startsWith(".env.");
28
+ }
29
+ /** Zip the project source into a buffer, skipping build/vcs/dependency/secret files. */
19
30
  function zipProject(cwd) {
20
31
  const zip = new adm_zip_1.default();
21
32
  zip.addLocalFolder(cwd, "", (filename) => {
22
33
  const parts = filename.split(path.sep);
23
- return !parts.some((p) => EXCLUDE_DIRS.has(p));
34
+ if (parts.some((p) => EXCLUDE_DIRS.has(p)))
35
+ return false;
36
+ const base = parts[parts.length - 1];
37
+ if (isSecretEnvFile(base))
38
+ return false;
39
+ return true;
24
40
  });
25
41
  return zip.toBuffer();
26
42
  }
@@ -41,6 +57,11 @@ async function serverSidePush(opts) {
41
57
  if (!res.ok) {
42
58
  throw new Error(`Code upload failed (${res.status}). ${await res.text().catch(() => "")}`);
43
59
  }
60
+ // Record the S3 metadata (bucket/key/version) on the service row. The
61
+ // presigned PUT lands the zip in S3 but does NOT persist where it went; the
62
+ // backend push reads s3_code_key from the DB, so without this it fails with
63
+ // "No S3 code found". Same three-step flow the browser upload uses.
64
+ await services_1.codeApi.finalize(opts.serviceId);
44
65
  await services_1.githubApi.push(opts.serviceId, opts.connectionId, {
45
66
  branch: opts.branch,
46
67
  message: opts.message,
@@ -37,7 +37,7 @@ import {
37
37
  import { JSONResolver } from 'graphql-scalars';
38
38
  <% } %>
39
39
  import { Type } from 'class-transformer';
40
- import { CrudValidationGroups } from '@nestjsx/crud';
40
+ import { CrudValidationGroups } from '@apso/crud';
41
41
  <% if (it.importEnums) { %>
42
42
  import * as enums from '../enums';
43
43
  <% } %>
@@ -21,7 +21,7 @@ import { Type } from 'class-transformer';
21
21
 
22
22
  import { ObjectType, Field, Int, GraphQLISODateTime, ID } from '@nestjs/graphql';
23
23
  import { JSONResolver } from 'graphql-scalars';
24
- import { CrudValidationGroups } from '@nestjsx/crud';
24
+ import { CrudValidationGroups } from '@apso/crud';
25
25
  <% if (it.importEnums) { %>
26
26
  import * as enums from '../../enums';
27
27
  <% } %>
@@ -293,7 +293,7 @@ export class ScopeGuard implements CanActivate {
293
293
  if (scope.direct) {
294
294
  const scopeValue = this.getScopeValue(request, scope.contextKey);
295
295
  if (scopeValue) {
296
- // Add filter for @nestjsx/crud
296
+ // Add filter for @apso/crud
297
297
  request.query[`filter.${scope.field}`] = `$eq:${scopeValue}`;
298
298
  }
299
299
  }
@@ -4,7 +4,7 @@ import { Test, TestingModule } from '@nestjs/testing';
4
4
  import {<%= it.entityName %>, <%= it.entityName %>Create} from './dtos/<%= it.entityName%>.dto'
5
5
  import {<%= it.ctrlName %>} from "./<%= it.entityName %>.controller";
6
6
  import {<%= it.svcName %>} from "./<%= it.entityName %>.service";
7
- import { CrudRequest } from '@nestjsx/crud';
7
+ import { CrudRequest } from '@apso/crud';
8
8
 
9
9
  const baseRequest: CrudRequest = {
10
10
  parsed: {
@@ -1,7 +1,7 @@
1
1
  <%~ includeFile('./header.eta') %>
2
2
 
3
3
  import { Controller, Body } from "@nestjs/common";
4
- import { Crud, CrudController, Override, ParsedRequest, CrudRequest, ParsedBody, CreateManyDto } from '@nestjsx/crud';
4
+ import { Crud, CrudController, Override, ParsedRequest, CrudRequest, ParsedBody, CreateManyDto } from '@apso/crud';
5
5
  import { ApiTags, ApiResponse, ApiOperation, ApiBody } from '@nestjs/swagger';
6
6
  import {<%= it.entityName %>, <%= it.entityName %>Create, <%= it.entityName %>Update} from './dtos/<%= it.entityName%>.dto'
7
7
  import {<%= it.svcName %>} from './<%= it.entityName %>.service';
@@ -2,9 +2,9 @@
2
2
 
3
3
  import { Injectable, NotFoundException } from "@nestjs/common";
4
4
  import { InjectRepository } from "@nestjs/typeorm";
5
- import { TypeOrmCrudService } from "@nestjsx/crud-typeorm";
6
- import { QueryOptions, CrudRequest } from '@nestjsx/crud';
7
- import { ParsedRequestParams } from '@nestjsx/crud-request';
5
+ import { TypeOrmCrudService } from "@apso/crud-typeorm";
6
+ import { QueryOptions, CrudRequest } from '@apso/crud';
7
+ import { ParsedRequestParams } from '@apso/crud-request';
8
8
  import { Repository, DeepPartial } from 'typeorm';
9
9
 
10
10
  import {<%= it.entityName %>} from './<%= it.entityName%>.entity'
@@ -1,5 +1,13 @@
1
1
  import { TargetLanguage } from "../types";
2
2
  export declare const TEMPLATE_REPOS: Record<TargetLanguage, string>;
3
+ /**
4
+ * Git ref (tag or branch) to clone for each template. Pinning to a tag makes
5
+ * the CLI-version-to-template pairing explicit instead of a date cutoff on
6
+ * template main. v2.0.0 is the first TS template revision on published
7
+ * @apso/crud ^1.0.1 (dual-dialect: nestjsx + PostgREST); the Python and Go
8
+ * template repos are untagged and don't carry the dialect work.
9
+ */
10
+ export declare const TEMPLATE_REFS: Record<TargetLanguage, string>;
3
11
  export declare const PROJECT_NAME_PATTERN: RegExp;
4
12
  /**
5
13
  * Clone a template repository into the given project path.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.initGitRepo = exports.ensureEnvFile = exports.cloneTemplate = exports.PROJECT_NAME_PATTERN = exports.TEMPLATE_REPOS = void 0;
3
+ exports.initGitRepo = exports.ensureEnvFile = exports.cloneTemplate = exports.PROJECT_NAME_PATTERN = exports.TEMPLATE_REFS = exports.TEMPLATE_REPOS = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const fs = tslib_1.__importStar(require("fs"));
6
6
  const path = tslib_1.__importStar(require("path"));
@@ -10,6 +10,18 @@ exports.TEMPLATE_REPOS = {
10
10
  python: "https://github.com/apsoai/service-template-python.git",
11
11
  go: "https://github.com/apsoai/service-template-go.git",
12
12
  };
13
+ /**
14
+ * Git ref (tag or branch) to clone for each template. Pinning to a tag makes
15
+ * the CLI-version-to-template pairing explicit instead of a date cutoff on
16
+ * template main. v2.0.0 is the first TS template revision on published
17
+ * @apso/crud ^1.0.1 (dual-dialect: nestjsx + PostgREST); the Python and Go
18
+ * template repos are untagged and don't carry the dialect work.
19
+ */
20
+ exports.TEMPLATE_REFS = {
21
+ typescript: "v2.0.0",
22
+ python: "main",
23
+ go: "main",
24
+ };
13
25
  exports.PROJECT_NAME_PATTERN = /^[A-Za-z][\w-]*$/;
14
26
  /**
15
27
  * Clone a template repository into the given project path.
@@ -17,7 +29,8 @@ exports.PROJECT_NAME_PATTERN = /^[A-Za-z][\w-]*$/;
17
29
  */
18
30
  function cloneTemplate(projectPath, language, log) {
19
31
  const repoUrl = exports.TEMPLATE_REPOS[language];
20
- log(`Cloning ${language} service template...`);
32
+ const templateRef = exports.TEMPLATE_REFS[language];
33
+ log(`Cloning ${language} service template (${templateRef})...`);
21
34
  if (fs.existsSync(projectPath)) {
22
35
  throw new Error(`Directory already exists: ${projectPath}`);
23
36
  }
@@ -27,12 +40,12 @@ function cloneTemplate(projectPath, language, log) {
27
40
  catch (error) {
28
41
  throw new Error(`Failed to create directory: ${error.message}`);
29
42
  }
30
- const cloneResult = shelljs_1.default.exec(`git clone --depth=1 --branch=main "${repoUrl}" "${projectPath}"`, { silent: true });
43
+ const cloneResult = shelljs_1.default.exec(`git clone --depth=1 --branch="${templateRef}" "${repoUrl}" "${projectPath}"`, { silent: true });
31
44
  if (cloneResult.code !== 0) {
32
45
  shelljs_1.default.rm("-rf", projectPath);
33
46
  throw new Error(`Failed to clone the template repository from GitHub.\n` +
34
47
  `Error Output:\n${cloneResult.stderr}\n\n` +
35
- `Please check your network connection and ensure the repository exists at ${repoUrl}`);
48
+ `Please check your network connection and ensure ref "${templateRef}" exists at ${repoUrl}`);
36
49
  }
37
50
  shelljs_1.default.rm("-rf", path.join(projectPath, ".git"));
38
51
  ensureEnvFile(projectPath, log);
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@apso/cli",
3
- "version": "0.32.0",
3
+ "version": "0.34.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@apso/cli",
9
- "version": "0.32.0",
9
+ "version": "0.34.0",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@biomejs/biome": "^1.9.4",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.32.0",
2
+ "version": "0.34.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.32.0",
3
+ "version": "0.34.0",
4
4
  "mcpName": "io.github.apsoai/apso",
5
5
  "description": "Apso CLI",
6
6
  "author": "Apso by Mavric - @mavric",