vona-cli-set-api 1.1.127 → 1.1.129
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/cli/templates/tools/crud/boilerplate/src/bean/ssrMenu.{{resourceName}}.ts_ +1 -0
- package/cli/templates/tools/crudBasic/boilerplate/src/controller/{{resourceName}}.ts_ +13 -4
- package/cli/templates/tools/crudBasic/boilerplate/test/{{resourceName}}.test.ts_ +22 -3
- package/cli/templates/tools/crudStart/boilerplate/src/controller/{{resourceName}}.ts_ +13 -4
- package/cli/templates/tools/crudStart/boilerplate/test/{{resourceName}}.test.ts_ +22 -3
- package/dist/index.js +6 -9
- package/dist-toolsIsolate/test.js +50 -21
- package/package.json +2 -2
|
@@ -4,6 +4,8 @@ import type { IDecoratorControllerOptions } from 'vona-module-a-web';
|
|
|
4
4
|
|
|
5
5
|
import { BeanBase } from 'vona';
|
|
6
6
|
import { Api, Resource, v } from 'vona-module-a-openapiutils';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { Passport } from 'vona-module-a-user';
|
|
7
9
|
import { Arg, Controller, Web } from 'vona-module-a-web';
|
|
8
10
|
|
|
9
11
|
import type { Model<%=argv.resourceNameCapitalize%> } from '../model/<%=argv.resourceName%>.ts';
|
|
@@ -21,29 +23,36 @@ export interface IControllerOptions<%=argv.resourceNameCapitalize%> extends IDec
|
|
|
21
23
|
export class Controller<%=argv.resourceNameCapitalize%> extends BeanBase {
|
|
22
24
|
@Web.post()
|
|
23
25
|
@Api.body(v.tableIdentity())
|
|
26
|
+
@Passport.systemAdmin()
|
|
24
27
|
async create(@Arg.body() <%=argv.resourceName%>: Dto<%=argv.resourceNameCapitalize%>Create): Promise<TableIdentity> {
|
|
25
28
|
return (await this.scope.service.<%=argv.resourceName%>.create(<%=argv.resourceName%>)).id;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
@Web.get()
|
|
29
32
|
@Api.body(Dto<%=argv.resourceNameCapitalize%>SelectRes)
|
|
33
|
+
@Passport.systemAdmin()
|
|
30
34
|
async select(@Arg.filter(Dto<%=argv.resourceNameCapitalize%>SelectReq) params: IQueryParams<Model<%=argv.resourceNameCapitalize%>>): Promise<Dto<%=argv.resourceNameCapitalize%>SelectRes> {
|
|
31
35
|
return await this.scope.service.<%=argv.resourceName%>.select(params);
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
@Web.get(':id')
|
|
35
39
|
@Api.body(v.optional(), v.object(Dto<%=argv.resourceNameCapitalize%>View))
|
|
40
|
+
@Passport.systemAdmin()
|
|
36
41
|
async view(@Arg.param('id', v.tableIdentity()) id: TableIdentity): Promise<Dto<%=argv.resourceNameCapitalize%>View | undefined> {
|
|
37
42
|
return await this.scope.service.<%=argv.resourceName%>.view(id);
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
@Web.patch(':id')
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
@Api.body(z.null())
|
|
47
|
+
@Passport.systemAdmin()
|
|
48
|
+
async update(@Arg.param('id', v.tableIdentity()) id: TableIdentity, @Arg.body() <%=argv.resourceName%>: Dto<%=argv.resourceNameCapitalize%>Update): Promise<void> {
|
|
49
|
+
await this.scope.service.<%=argv.resourceName%>.update(id, <%=argv.resourceName%>);
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
@Web.delete(':id')
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
@Api.body(z.null())
|
|
54
|
+
@Passport.systemAdmin()
|
|
55
|
+
async delete(@Arg.param('id', v.tableIdentity()) id: TableIdentity): Promise<void> {
|
|
56
|
+
await this.scope.service.<%=argv.resourceName%>.delete(id);
|
|
48
57
|
}
|
|
49
58
|
}
|
|
@@ -15,7 +15,24 @@ describe('<%=argv.resourceName%>.test.ts', () => {
|
|
|
15
15
|
name: '__TomNew__',
|
|
16
16
|
description: 'This is a test',
|
|
17
17
|
};
|
|
18
|
-
//
|
|
18
|
+
// role-less authenticated users cannot access generated admin actions
|
|
19
|
+
await app.bean.passport.signinMock();
|
|
20
|
+
try {
|
|
21
|
+
app.bean.passport.current!.roles = [];
|
|
22
|
+
const actions = ['create', 'select', 'view', 'update', 'delete'];
|
|
23
|
+
const permissions = await Promise.all(
|
|
24
|
+
actions.map(action =>
|
|
25
|
+
app.bean.permission.retrievePermissionAction(
|
|
26
|
+
'<%=argv.moduleInfo.relativeName%>:<%=argv.resourceName%>',
|
|
27
|
+
action,
|
|
28
|
+
),
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
assert.deepEqual(permissions, actions.map(() => false));
|
|
32
|
+
} finally {
|
|
33
|
+
await app.bean.passport.signout();
|
|
34
|
+
}
|
|
35
|
+
// login as system admin
|
|
19
36
|
await app.bean.passport.signinMock();
|
|
20
37
|
// create
|
|
21
38
|
const <%=argv.resourceName%>Id = await app.bean.executor.performAction('post', '<%=argv.moduleActionPathRaw%>', { body: data });
|
|
@@ -24,15 +41,17 @@ describe('<%=argv.resourceName%>.test.ts', () => {
|
|
|
24
41
|
const selectRes: Dto<%=argv.resourceNameCapitalize%>SelectRes = await app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>');
|
|
25
42
|
assert.equal(selectRes.list.findIndex(item => item.name === data.name) > -1, true);
|
|
26
43
|
// update
|
|
27
|
-
await app.bean.executor.performAction('patch', '<%=argv.moduleActionPathRaw%>/:id', {
|
|
44
|
+
const updateRes = await app.bean.executor.performAction('patch', '<%=argv.moduleActionPathRaw%>/:id', {
|
|
28
45
|
params: { id: <%=argv.resourceName%>Id },
|
|
29
46
|
body: dataUpdate,
|
|
30
47
|
});
|
|
48
|
+
assert.equal(updateRes, null);
|
|
31
49
|
// findOne
|
|
32
50
|
let <%=argv.resourceName%>: Entity<%=argv.resourceNameCapitalize%> = await app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>/:id', { params: { id: <%=argv.resourceName%>Id } });
|
|
33
51
|
assert.equal(<%=argv.resourceName%>.name, dataUpdate.name);
|
|
34
52
|
// delete
|
|
35
|
-
await app.bean.executor.performAction('delete', '<%=argv.moduleActionPathRaw%>/:id', { params: { id: <%=argv.resourceName%>.id } });
|
|
53
|
+
const deleteRes = await app.bean.executor.performAction('delete', '<%=argv.moduleActionPathRaw%>/:id', { params: { id: <%=argv.resourceName%>.id } });
|
|
54
|
+
assert.equal(deleteRes, null);
|
|
36
55
|
// findOne
|
|
37
56
|
<%=argv.resourceName%> = await app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>/:id', { params: { id: <%=argv.resourceName%>.id } });
|
|
38
57
|
assert.equal(<%=argv.resourceName%>, undefined);
|
|
@@ -4,6 +4,8 @@ import type { IDecoratorControllerOptions } from 'vona-module-a-web';
|
|
|
4
4
|
|
|
5
5
|
import { BeanBase } from 'vona';
|
|
6
6
|
import { Api, Resource, v } from 'vona-module-a-openapiutils';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { Passport } from 'vona-module-a-user';
|
|
7
9
|
import { Arg, Controller, Web } from 'vona-module-a-web';
|
|
8
10
|
|
|
9
11
|
import type { Model<%=argv.resourceNameCapitalize%> } from '../model/<%=argv.resourceName%>.ts';
|
|
@@ -21,29 +23,36 @@ export interface IControllerOptions<%=argv.resourceNameCapitalize%> extends IDec
|
|
|
21
23
|
export class Controller<%=argv.resourceNameCapitalize%> extends BeanBase {
|
|
22
24
|
@Web.post()
|
|
23
25
|
@Api.body(v.tableIdentity())
|
|
26
|
+
@Passport.systemAdmin()
|
|
24
27
|
async create(@Arg.body() <%=argv.resourceName%>: Dto<%=argv.resourceNameCapitalize%>Create): Promise<TableIdentity> {
|
|
25
28
|
return (await this.scope.service.<%=argv.resourceName%>.create(<%=argv.resourceName%>)).id;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
@Web.get()
|
|
29
32
|
@Api.body(Dto<%=argv.resourceNameCapitalize%>SelectRes)
|
|
33
|
+
@Passport.systemAdmin()
|
|
30
34
|
async select(@Arg.filter(Dto<%=argv.resourceNameCapitalize%>SelectReq) params: IQueryParams<Model<%=argv.resourceNameCapitalize%>>): Promise<Dto<%=argv.resourceNameCapitalize%>SelectRes> {
|
|
31
35
|
return await this.scope.service.<%=argv.resourceName%>.select(params);
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
@Web.get(':id')
|
|
35
39
|
@Api.body(v.optional(), v.object(Dto<%=argv.resourceNameCapitalize%>View))
|
|
40
|
+
@Passport.systemAdmin()
|
|
36
41
|
async view(@Arg.param('id', v.tableIdentity()) id: TableIdentity): Promise<Dto<%=argv.resourceNameCapitalize%>View | undefined> {
|
|
37
42
|
return await this.scope.service.<%=argv.resourceName%>.view(id);
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
@Web.patch(':id')
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
@Api.body(z.null())
|
|
47
|
+
@Passport.systemAdmin()
|
|
48
|
+
async update(@Arg.param('id', v.tableIdentity()) id: TableIdentity, @Arg.body() <%=argv.resourceName%>: Dto<%=argv.resourceNameCapitalize%>Update): Promise<void> {
|
|
49
|
+
await this.scope.service.<%=argv.resourceName%>.update(id, <%=argv.resourceName%>);
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
@Web.delete(':id')
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
@Api.body(z.null())
|
|
54
|
+
@Passport.systemAdmin()
|
|
55
|
+
async delete(@Arg.param('id', v.tableIdentity()) id: TableIdentity): Promise<void> {
|
|
56
|
+
await this.scope.service.<%=argv.resourceName%>.delete(id);
|
|
48
57
|
}
|
|
49
58
|
}
|
|
@@ -15,7 +15,24 @@ describe('<%=argv.resourceName%>.test.ts', () => {
|
|
|
15
15
|
name: '__TomNew__',
|
|
16
16
|
description: 'This is a test',
|
|
17
17
|
};
|
|
18
|
-
//
|
|
18
|
+
// role-less authenticated users cannot access generated admin actions
|
|
19
|
+
await app.bean.passport.signinMock();
|
|
20
|
+
try {
|
|
21
|
+
app.bean.passport.current!.roles = [];
|
|
22
|
+
const actions = ['create', 'select', 'view', 'update', 'delete'];
|
|
23
|
+
const permissions = await Promise.all(
|
|
24
|
+
actions.map(action =>
|
|
25
|
+
app.bean.permission.retrievePermissionAction(
|
|
26
|
+
'<%=argv.moduleInfo.relativeName%>:<%=argv.resourceName%>',
|
|
27
|
+
action,
|
|
28
|
+
),
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
assert.deepEqual(permissions, actions.map(() => false));
|
|
32
|
+
} finally {
|
|
33
|
+
await app.bean.passport.signout();
|
|
34
|
+
}
|
|
35
|
+
// login as system admin
|
|
19
36
|
await app.bean.passport.signinMock();
|
|
20
37
|
// create
|
|
21
38
|
const <%=argv.resourceName%>Id = await app.bean.executor.performAction('post', '<%=argv.moduleActionPathRaw%>', { body: data });
|
|
@@ -24,15 +41,17 @@ describe('<%=argv.resourceName%>.test.ts', () => {
|
|
|
24
41
|
const selectRes: Dto<%=argv.resourceNameCapitalize%>SelectRes = await app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>');
|
|
25
42
|
assert.equal(selectRes.list.findIndex(item => item.name === data.name) > -1, true);
|
|
26
43
|
// update
|
|
27
|
-
await app.bean.executor.performAction('patch', '<%=argv.moduleActionPathRaw%>/:id', {
|
|
44
|
+
const updateRes = await app.bean.executor.performAction('patch', '<%=argv.moduleActionPathRaw%>/:id', {
|
|
28
45
|
params: { id: <%=argv.resourceName%>Id },
|
|
29
46
|
body: dataUpdate,
|
|
30
47
|
});
|
|
48
|
+
assert.equal(updateRes, null);
|
|
31
49
|
// findOne
|
|
32
50
|
let <%=argv.resourceName%>: Entity<%=argv.resourceNameCapitalize%> = await app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>/:id', { params: { id: <%=argv.resourceName%>Id } });
|
|
33
51
|
assert.equal(<%=argv.resourceName%>.name, dataUpdate.name);
|
|
34
52
|
// delete
|
|
35
|
-
await app.bean.executor.performAction('delete', '<%=argv.moduleActionPathRaw%>/:id', { params: { id: <%=argv.resourceName%>.id } });
|
|
53
|
+
const deleteRes = await app.bean.executor.performAction('delete', '<%=argv.moduleActionPathRaw%>/:id', { params: { id: <%=argv.resourceName%>.id } });
|
|
54
|
+
assert.equal(deleteRes, null);
|
|
36
55
|
// findOne
|
|
37
56
|
<%=argv.resourceName%> = await app.bean.executor.performAction('get', '<%=argv.moduleActionPathRaw%>/:id', { params: { id: <%=argv.resourceName%>.id } });
|
|
38
57
|
assert.equal(<%=argv.resourceName%>, undefined);
|
package/dist/index.js
CHANGED
|
@@ -991,15 +991,12 @@ class CliBinTest extends BeanCliBase {
|
|
|
991
991
|
}
|
|
992
992
|
args = args.concat([getImportEsm(), testFile, projectPath, (!!argv.coverage).toString(), patterns.join(',')]);
|
|
993
993
|
// args = args.concat(['--experimental-transform-types', getImportEsm(), testFile, projectPath, (!!argv.coverage).toString(), patterns.join(',')]);
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
cwd: projectPath
|
|
1001
|
-
}
|
|
1002
|
-
});
|
|
994
|
+
await this.helper.spawnExe({
|
|
995
|
+
cmd: 'node',
|
|
996
|
+
args,
|
|
997
|
+
options: {
|
|
998
|
+
cwd: projectPath
|
|
999
|
+
}
|
|
1003
1000
|
});
|
|
1004
1001
|
}
|
|
1005
1002
|
async _outputCoverageReportViewer(projectPath) {
|
|
@@ -59,9 +59,27 @@ async function testRun(projectPath, coverage, patterns) {
|
|
|
59
59
|
];
|
|
60
60
|
// app
|
|
61
61
|
const app = await createGeneralApp(projectPath);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
let testError;
|
|
63
|
+
let closeError;
|
|
64
|
+
let closePromise;
|
|
65
|
+
const closeApplication = async () => {
|
|
66
|
+
const [_, error] = await catchError(() => app.close());
|
|
67
|
+
closeError = error;
|
|
68
|
+
// handles
|
|
69
|
+
if (process.env.TEST_WHYISNODERUNNING === 'true') {
|
|
70
|
+
await sleep(2000);
|
|
71
|
+
const handles = process._getActiveHandles();
|
|
72
|
+
if (handles.length > 3) {
|
|
73
|
+
whyIsNodeRunning();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const closeApplicationOnce = () => {
|
|
78
|
+
return (closePromise ??= closeApplication());
|
|
79
|
+
};
|
|
80
|
+
try {
|
|
81
|
+
// concurrency
|
|
82
|
+
const concurrency = await prepareConcurrency(app);
|
|
65
83
|
const testStream = run({
|
|
66
84
|
isolation: 'none',
|
|
67
85
|
concurrency,
|
|
@@ -76,27 +94,12 @@ async function testRun(projectPath, coverage, patterns) {
|
|
|
76
94
|
.on('test:coverage', data => {
|
|
77
95
|
outputCoverageReport(data.summary.totals);
|
|
78
96
|
})
|
|
79
|
-
.on('test:
|
|
80
|
-
resolve(undefined);
|
|
81
|
-
})
|
|
82
|
-
.on('test:pass', async (t) => {
|
|
97
|
+
.on('test:pass', t => {
|
|
83
98
|
if (t.name === '---done---') {
|
|
84
|
-
|
|
85
|
-
return app.close();
|
|
86
|
-
});
|
|
87
|
-
if (err) {
|
|
88
|
-
console.error(err);
|
|
89
|
-
}
|
|
90
|
-
// handles
|
|
91
|
-
if (process.env.TEST_WHYISNODERUNNING === 'true') {
|
|
92
|
-
await sleep(2000);
|
|
93
|
-
const handles = process._getActiveHandles();
|
|
94
|
-
if (handles.length > 3) {
|
|
95
|
-
whyIsNodeRunning();
|
|
96
|
-
}
|
|
97
|
-
}
|
|
99
|
+
void closeApplicationOnce();
|
|
98
100
|
}
|
|
99
101
|
});
|
|
102
|
+
const summaryPromise = waitForTestSummary(testStream);
|
|
100
103
|
if (coverage) {
|
|
101
104
|
const reporterDir = path.join(projectPath, 'coverage');
|
|
102
105
|
fse.ensureDirSync(reporterDir);
|
|
@@ -106,6 +109,32 @@ async function testRun(projectPath, coverage, patterns) {
|
|
|
106
109
|
else {
|
|
107
110
|
testStream.compose(spec).pipe(process.stdout);
|
|
108
111
|
}
|
|
112
|
+
const summarySuccess = await summaryPromise;
|
|
113
|
+
if (!summarySuccess) {
|
|
114
|
+
throw new Error('node:test reported failed tests');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
testError = error;
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
await closeApplicationOnce();
|
|
122
|
+
}
|
|
123
|
+
if (testError) {
|
|
124
|
+
if (closeError)
|
|
125
|
+
console.error(closeError);
|
|
126
|
+
throw toError(testError);
|
|
127
|
+
}
|
|
128
|
+
if (closeError) {
|
|
129
|
+
throw toError(closeError);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function toError(error) {
|
|
133
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
134
|
+
}
|
|
135
|
+
function waitForTestSummary(testStream) {
|
|
136
|
+
return new Promise((resolve, reject) => {
|
|
137
|
+
testStream.once('test:summary', summary => resolve(summary.success)).once('error', reject);
|
|
109
138
|
});
|
|
110
139
|
}
|
|
111
140
|
async function prepareConcurrency(app) {
|
package/package.json
CHANGED