@apso/cli 0.33.1 → 0.35.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.
- package/dist/lib/templates/entities/entity.eta +1 -1
- package/dist/lib/templates/graphql/gql-dto-graphql.eta +1 -1
- package/dist/lib/templates/guards/scope.guard.eta +1 -1
- package/dist/lib/templates/rest/controller-rest-spec.eta +1 -1
- package/dist/lib/templates/rest/controller-rest.eta +1 -1
- package/dist/lib/templates/rest/service-rest.eta +3 -3
- package/dist/lib/utils/relationships/parse.d.ts +8 -3
- package/dist/lib/utils/relationships/parse.js +72 -6
- package/dist/lib/utils/template.d.ts +8 -0
- package/dist/lib/utils/template.js +17 -4
- package/npm-shrinkwrap.json +2 -2
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
|
@@ -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 '@
|
|
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 '@
|
|
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 @
|
|
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 '@
|
|
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 '@
|
|
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 "@
|
|
6
|
-
import { QueryOptions, CrudRequest } from '@
|
|
7
|
-
import { ParsedRequestParams } from '@
|
|
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'
|
|
@@ -10,10 +10,15 @@ import { ApsorcRelationship, Relationship, RelationshipMap, RelationshipForTempl
|
|
|
10
10
|
export declare const parseOneToMany: (relationship: ApsorcRelationship) => RelationshipMap;
|
|
11
11
|
/**
|
|
12
12
|
* Parses a ManyToOne relationship definition.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* Bidirectional by default, mirroring parseOneToMany with from/to swapped:
|
|
14
|
+
* the target entity gets the inverse OneToMany collection (and with it the
|
|
15
|
+
* controller join allowlist), so `Authors?join=books` works no matter which
|
|
16
|
+
* direction the .apsorc declared the relationship in.
|
|
17
|
+
* Set `bi_directional: false` to keep the old FK-only unidirectional shape.
|
|
18
|
+
* Self-referencing relationships stay unidirectional (both sides would
|
|
19
|
+
* collide on the same RelationshipMap key).
|
|
15
20
|
* @param relationship The .apsorc relationship definition.
|
|
16
|
-
* @returns A RelationshipMap
|
|
21
|
+
* @returns A RelationshipMap with entries for both sides (or just 'from' when unidirectional).
|
|
17
22
|
*/
|
|
18
23
|
export declare const parseManytoOne: (relationship: ApsorcRelationship) => RelationshipMap;
|
|
19
24
|
/**
|
|
@@ -39,20 +39,52 @@ const parseOneToMany = (relationship) => {
|
|
|
39
39
|
exports.parseOneToMany = parseOneToMany;
|
|
40
40
|
/**
|
|
41
41
|
* Parses a ManyToOne relationship definition.
|
|
42
|
-
*
|
|
43
|
-
*
|
|
42
|
+
* Bidirectional by default, mirroring parseOneToMany with from/to swapped:
|
|
43
|
+
* the target entity gets the inverse OneToMany collection (and with it the
|
|
44
|
+
* controller join allowlist), so `Authors?join=books` works no matter which
|
|
45
|
+
* direction the .apsorc declared the relationship in.
|
|
46
|
+
* Set `bi_directional: false` to keep the old FK-only unidirectional shape.
|
|
47
|
+
* Self-referencing relationships stay unidirectional (both sides would
|
|
48
|
+
* collide on the same RelationshipMap key).
|
|
44
49
|
* @param relationship The .apsorc relationship definition.
|
|
45
|
-
* @returns A RelationshipMap
|
|
50
|
+
* @returns A RelationshipMap with entries for both sides (or just 'from' when unidirectional).
|
|
46
51
|
*/
|
|
47
52
|
const parseManytoOne = (relationship) => {
|
|
53
|
+
const unidirectional = relationship.bi_directional === false ||
|
|
54
|
+
relationship.from === relationship.to;
|
|
55
|
+
if (unidirectional) {
|
|
56
|
+
return {
|
|
57
|
+
[relationship.from]: [
|
|
58
|
+
{
|
|
59
|
+
name: relationship.to,
|
|
60
|
+
type: "ManyToOne",
|
|
61
|
+
referenceName: relationship.to_name || null,
|
|
62
|
+
nullable: relationship.nullable || false,
|
|
63
|
+
index: relationship.index || false,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
48
68
|
return {
|
|
49
69
|
[relationship.from]: [
|
|
50
70
|
{
|
|
51
71
|
name: relationship.to,
|
|
52
72
|
type: "ManyToOne",
|
|
53
|
-
referenceName: relationship.to_name || null,
|
|
54
73
|
nullable: relationship.nullable || false,
|
|
74
|
+
biDirectional: true,
|
|
55
75
|
index: relationship.index || false,
|
|
76
|
+
cascadeDelete: relationship.cascadeDelete || false,
|
|
77
|
+
referenceName: relationship.to_name || relationship.to,
|
|
78
|
+
inverseReferenceName: relationship.from,
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
[relationship.to]: [
|
|
82
|
+
{
|
|
83
|
+
name: relationship.from,
|
|
84
|
+
type: "OneToMany",
|
|
85
|
+
biDirectional: true,
|
|
86
|
+
referenceName: relationship.from,
|
|
87
|
+
inverseReferenceName: relationship.to_name || relationship.to,
|
|
56
88
|
},
|
|
57
89
|
],
|
|
58
90
|
};
|
|
@@ -264,18 +296,52 @@ const parseRelationship = (relationship, allRelationships) => {
|
|
|
264
296
|
],
|
|
265
297
|
};
|
|
266
298
|
}
|
|
267
|
-
case "ManyToOne":
|
|
299
|
+
case "ManyToOne": {
|
|
300
|
+
// Bidirectional by default, mirroring the OneToMany case with the roles
|
|
301
|
+
// swapped: the target entity gets the inverse OneToMany collection (and
|
|
302
|
+
// with it the controller join allowlist), so the collection-side embed
|
|
303
|
+
// works no matter which direction .apsorc declared the relationship in.
|
|
304
|
+
// `bi_directional: false` keeps the old FK-only shape; self-references
|
|
305
|
+
// stay unidirectional (both sides would collide on the same map key).
|
|
306
|
+
const manySideRefName = relationship.to_name || relationship.to;
|
|
307
|
+
if (relationship.bi_directional === false ||
|
|
308
|
+
relationship.from === relationship.to) {
|
|
309
|
+
return {
|
|
310
|
+
[relationship.from]: [
|
|
311
|
+
{
|
|
312
|
+
name: relationship.to,
|
|
313
|
+
type: "ManyToOne",
|
|
314
|
+
referenceName: manySideRefName,
|
|
315
|
+
nullable: relationship.nullable || false,
|
|
316
|
+
index: relationship.index || false,
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
};
|
|
320
|
+
}
|
|
268
321
|
return {
|
|
269
322
|
[relationship.from]: [
|
|
270
323
|
{
|
|
271
324
|
name: relationship.to,
|
|
272
325
|
type: "ManyToOne",
|
|
273
|
-
referenceName: relationship.to_name || relationship.to,
|
|
274
326
|
nullable: relationship.nullable || false,
|
|
327
|
+
biDirectional: true,
|
|
275
328
|
index: relationship.index || false,
|
|
329
|
+
cascadeDelete: relationship.cascadeDelete || false,
|
|
330
|
+
referenceName: manySideRefName,
|
|
331
|
+
inverseReferenceName: relationship.from,
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
[relationship.to]: [
|
|
335
|
+
{
|
|
336
|
+
name: relationship.from,
|
|
337
|
+
type: "OneToMany",
|
|
338
|
+
biDirectional: true,
|
|
339
|
+
referenceName: relationship.from,
|
|
340
|
+
inverseReferenceName: manySideRefName,
|
|
276
341
|
},
|
|
277
342
|
],
|
|
278
343
|
};
|
|
344
|
+
}
|
|
279
345
|
case "OneToOne": {
|
|
280
346
|
const inverseOnetoOneDef = allRelationships.find((def) => def.from === relationship.to &&
|
|
281
347
|
def.to === relationship.from &&
|
|
@@ -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
|
-
|
|
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=
|
|
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
|
|
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);
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apso/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.35.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@apso/cli",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.35.0",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@biomejs/biome": "^1.9.4",
|
package/oclif.manifest.json
CHANGED