cabloy 5.1.215 → 5.1.217
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/.cabloy-version +1 -1
- package/.github/workflows/playwright-e2e.yml +163 -13
- package/.github/workflows/vona-test-sqlite3.yml +77 -7
- package/CHANGELOG.md +22 -0
- package/CONTRIBUTING.md +4 -2
- package/package.json +1 -1
- package/repo-docs/frontend/scripts.md +2 -0
- package/repo-docs/reference/repo-scripts.md +27 -1
- package/repo-e2e/scripts/e2e.test.ts +1 -1
- package/repo-e2e/scripts/managedTest.test.ts +115 -3
- package/repo-e2e/scripts/managedTest.ts +69 -20
- package/repo-e2e/scripts/managedTestImports.test.ts +55 -5
- package/repo-e2e/scripts/runE2e.test.ts +109 -0
- package/repo-e2e/scripts/runE2e.ts +58 -39
- package/repo-e2e/scripts/runE2eArgs.test.ts +16 -1
- package/repo-e2e/scripts/runE2eArgs.ts +5 -0
- package/repo-e2e/specs/a-commerce.spec.ts +10 -2
- package/repo-e2e/specs/home-user-account.spec.ts +4 -0
- package/repo-e2e/specs/markdown.spec.ts +14 -3
- package/scripts/generateOpenapiIsolated.test.ts +5 -1
- package/scripts/runIsolatedTest.test.ts +403 -36
- package/scripts/runIsolatedTest.ts +139 -12
- package/scripts/testBrowserGuardianWindows.test.ts +327 -0
- package/scripts/testClaimCleanup.test.ts +25 -9
- package/scripts/testClaimCleanup.ts +14 -45
- package/scripts/testClaimProcesses.test.ts +81 -2
- package/scripts/testClaimProcesses.ts +46 -10
- package/scripts/testProcessGuardianWindows.test.ts +602 -0
- package/scripts/testProcessGuardianWindows.ts +155 -0
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.build.ts +4 -1
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.tools.masterDetail.ts +4 -4
- package/vona/packages-cli/cli-set-api/test/binBuildEntries.test.ts +159 -0
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/packages-vona/vona-core/package.json +1 -1
- package/vona/packages-vona/vona-core/src/lib/core/testClaim.test.ts +298 -17
- package/vona/packages-vona/vona-core/src/lib/core/testClaim.ts +205 -97
- package/vona/packages-vona/vona-core/src/lib/core/testClaimFilesystem.test.ts +247 -0
- package/vona/packages-vona/vona-core/src/lib/core/testClaimFilesystem.ts +224 -0
- package/vona/packages-vona/vona-core/src/lib/core/testClaimFilesystemWindows.cs +229 -0
- package/vona/packages-vona/vona-core/src/lib/core/testClaimFilesystemWindows.ps1 +20 -0
- package/vona/packages-vona/vona-core/src/lib/core/testClaimFilesystemWindows.ts +200 -0
- package/vona/packages-vona/vona-core/src/lib/core/testProcessWindowsNative.cs +336 -0
- package/vona/packages-vona/vona-core/src/lib/core/testProcessWindowsNative.ps1 +20 -0
- package/vona/packages-vona/vona-core/src/lib/core/testProcessWindowsNative.ts +531 -0
- package/vona/packages-vona/vona-core/src/lib/framework/bootstrap.test.ts +61 -0
- package/vona/packages-vona/vona-core/src/lib/framework/index.ts +1 -0
- package/vona/packages-vona/vona-core/src/lib/framework/processWorker.test.ts +59 -0
- package/vona/packages-vona/vona-core/src/lib/framework/processWorker.ts +8 -5
- package/vona/packages-vona/vona-core/src/lib/framework/useApp.test.ts +84 -0
- package/vona/packages-vona/vona-core/src/lib/framework/useApp.ts +2 -2
- package/vona/packages-vona/vona-core/src/lib/utils/util.ts +1 -1
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/pnpm-lock.yaml +22 -22
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-executor/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-executor/src/bean/bean.executor.ts +2 -1
- package/vona/src/suite-vendor/a-vona/modules/a-logger/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/middlewareSystem.httpLog.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactIsolatedReadyHeader.test.ts +16 -0
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactIsolatedReadyHeader.ts +6 -3
- package/vona/src/suite-vendor/a-vona/modules/a-logger/test/middlewareSystem.httpLog.test.ts +80 -0
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/src/bean/middlewareSystem.health.ts +73 -2
- package/vona/src/suite-vendor/a-vona/modules/a-metrics/test/middlewareSystem.health.test.ts +309 -0
- package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/service/transactionAsyncLocalStorage_.ts +3 -3
- package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-static/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-static/src/bean/middlewareSystem.static.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-static/test/middlewareSystem.static.test.ts +101 -0
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/packages-cli/cli/package.json +2 -2
- package/zova/packages-cli/cli-set-front/package.json +2 -2
- package/zova/packages-utils/zova-vite/package.json +1 -1
- package/zova/packages-utils/zova-vite/src/configUtils.ts +1 -1
- package/zova/packages-utils/zova-vite/test/configUtils.test.ts +55 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/cart.ts +8 -4
- package/zova/src/suite/a-commerce/modules/commerce-trade/test/lib/cartModel.test.ts +71 -0
- /package/vona/packages-cli/cli-set-api/cli/templates/tools/masterDetail/boilerplate/dto/{<%=argv.detailDtoBaseName%>.tsx_ → {{detailDtoBaseName}}.tsx_} +0 -0
- /package/vona/packages-cli/cli-set-api/cli/templates/tools/masterDetail/boilerplate/dto/{<%=argv.detailDtoMutateName%>.tsx_ → {{detailDtoMutateName}}.tsx_} +0 -0
- /package/vona/packages-cli/cli-set-api/cli/templates/tools/masterDetail/boilerplate/dto/{<%=argv.detailDtoResItemName%>.tsx_ → {{detailDtoResItemName}}.tsx_} +0 -0
- /package/vona/packages-cli/cli-set-api/cli/templates/tools/masterDetail/boilerplate/dto/{<%=argv.detailDtoViewName%>.tsx_ → {{detailDtoViewName}}.tsx_} +0 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { createHmac } from 'node:crypto';
|
|
3
|
+
import { EventEmitter } from 'node:events';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
import { registerHooks } from 'node:module';
|
|
6
|
+
import { describe, test } from 'node:test';
|
|
7
|
+
import ts from 'typescript';
|
|
8
|
+
|
|
9
|
+
const healthUrl = new URL('../src/bean/middlewareSystem.health.ts?fixture=health', import.meta.url)
|
|
10
|
+
.href;
|
|
11
|
+
const identityUrl = new URL(
|
|
12
|
+
'../../../../../../packages-vona/vona-core/src/lib/core/testIsolation.ts',
|
|
13
|
+
import.meta.url,
|
|
14
|
+
).href;
|
|
15
|
+
let closes = 0;
|
|
16
|
+
const closeKey = Symbol.for('vona.health.test.close');
|
|
17
|
+
(globalThis as Record<symbol, unknown>)[closeKey] = () => closes++;
|
|
18
|
+
const hooks = registerHooks({
|
|
19
|
+
resolve(specifier, context, nextResolve) {
|
|
20
|
+
if (context.parentURL === healthUrl) {
|
|
21
|
+
const source = {
|
|
22
|
+
'vona': `export class BeanBase {} export { assertTestAppIdentity } from ${JSON.stringify(identityUrl)}; export function closeProcessWorker() { globalThis[Symbol.for('vona.health.test.close')](); }`,
|
|
23
|
+
'vona-module-a-aspect': 'export function MiddlewareSystem() { return () => {}; }',
|
|
24
|
+
}[specifier];
|
|
25
|
+
if (source)
|
|
26
|
+
return { url: `data:text/javascript,${encodeURIComponent(source)}`, shortCircuit: true };
|
|
27
|
+
}
|
|
28
|
+
return nextResolve(specifier, context);
|
|
29
|
+
},
|
|
30
|
+
load(url, context, nextLoad) {
|
|
31
|
+
if (url === healthUrl) {
|
|
32
|
+
return {
|
|
33
|
+
format: 'module',
|
|
34
|
+
source: ts.transpileModule(readFileSync(new URL(url), 'utf8'), {
|
|
35
|
+
compilerOptions: {
|
|
36
|
+
experimentalDecorators: true,
|
|
37
|
+
target: ts.ScriptTarget.ESNext,
|
|
38
|
+
module: ts.ModuleKind.ESNext,
|
|
39
|
+
},
|
|
40
|
+
}).outputText,
|
|
41
|
+
shortCircuit: true,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return nextLoad(url, context);
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
const { MiddlewareSystemHealth } = await import(healthUrl);
|
|
48
|
+
hooks.deregister();
|
|
49
|
+
|
|
50
|
+
const runId = '20261014010203-a1b2c3d4';
|
|
51
|
+
const key = 'a'.repeat(64);
|
|
52
|
+
const nonce = 'b'.repeat(32);
|
|
53
|
+
const payload = `${runId}\nPOST\n/health/test-stop\n${nonce}`;
|
|
54
|
+
const sign = (value: string) => createHmac('sha256', key).update(value).digest('hex');
|
|
55
|
+
|
|
56
|
+
function fixture() {
|
|
57
|
+
const req = { socket: { remoteAddress: '127.0.0.1' }, url: '/health/test-stop' };
|
|
58
|
+
const headers: Record<string, string> = {
|
|
59
|
+
'x-vona-test-stop-nonce': nonce,
|
|
60
|
+
'x-vona-test-stop-authorization': sign(`vona-test-stop-request-v1\n${payload}`),
|
|
61
|
+
};
|
|
62
|
+
const responseHeaders: Record<string, string> = {};
|
|
63
|
+
const ctx = {
|
|
64
|
+
path: '/health/test-stop',
|
|
65
|
+
method: 'POST',
|
|
66
|
+
req,
|
|
67
|
+
res: new EventEmitter(),
|
|
68
|
+
status: 0,
|
|
69
|
+
type: '',
|
|
70
|
+
body: undefined as unknown,
|
|
71
|
+
get: (name: string) => headers[name] ?? '',
|
|
72
|
+
set: (name: string, value: string) => (responseHeaders[name] = value),
|
|
73
|
+
};
|
|
74
|
+
const app = {
|
|
75
|
+
name: `cabtest${runId}`,
|
|
76
|
+
meta: { env: { META_MODE: 'test', VONA_TEST_RUN_ID: runId } },
|
|
77
|
+
config: { server: { workers: 1 } },
|
|
78
|
+
};
|
|
79
|
+
let nexts = 0;
|
|
80
|
+
const healthCalls: string[] = [];
|
|
81
|
+
const middleware = Object.create(MiddlewareSystemHealth.prototype);
|
|
82
|
+
Object.assign(middleware, {
|
|
83
|
+
app,
|
|
84
|
+
ctx,
|
|
85
|
+
$scope: {
|
|
86
|
+
metrics: {
|
|
87
|
+
service: {
|
|
88
|
+
health: {
|
|
89
|
+
async status(path: string) {
|
|
90
|
+
healthCalls.push(path);
|
|
91
|
+
return { code: 200, status: 'ok' };
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
app,
|
|
100
|
+
ctx,
|
|
101
|
+
headers,
|
|
102
|
+
responseHeaders,
|
|
103
|
+
healthCalls,
|
|
104
|
+
execute: () =>
|
|
105
|
+
middleware.execute({}, async () => {
|
|
106
|
+
nexts++;
|
|
107
|
+
}),
|
|
108
|
+
nexts: () => nexts,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function withServerEnv(callback: () => Promise<void>) {
|
|
113
|
+
const names = ['META_MODE', 'VONA_TEST_RUN_ID', 'VONA_TEST_READY_KEY'] as const;
|
|
114
|
+
const old = names.map(name => process.env[name]);
|
|
115
|
+
process.env.META_MODE = 'test';
|
|
116
|
+
process.env.VONA_TEST_RUN_ID = runId;
|
|
117
|
+
process.env.VONA_TEST_READY_KEY = key;
|
|
118
|
+
return callback().finally(() => {
|
|
119
|
+
for (const [index, name] of names.entries()) {
|
|
120
|
+
if (old[index] === undefined) delete process.env[name];
|
|
121
|
+
else process.env[name] = old[index];
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
describe('authenticated managed shutdown', { concurrency: false }, () => {
|
|
127
|
+
test('shutdown acknowledges its distinct domain before close and rejects replay across middleware instances', async () => {
|
|
128
|
+
await withServerEnv(async () => {
|
|
129
|
+
const f = fixture();
|
|
130
|
+
const before = closes;
|
|
131
|
+
await f.execute();
|
|
132
|
+
assert.equal(f.ctx.status, 200);
|
|
133
|
+
assert.deepEqual(f.ctx.body, { status: 'stopping' });
|
|
134
|
+
assert.equal(
|
|
135
|
+
f.responseHeaders['x-vona-test-stop-proof'],
|
|
136
|
+
sign(`vona-test-stop-ack-v1\n${payload}`),
|
|
137
|
+
);
|
|
138
|
+
assert.equal(closes, before);
|
|
139
|
+
const replay = fixture();
|
|
140
|
+
replay.app = f.app;
|
|
141
|
+
// A separate request-scoped middleware with the same application must reject.
|
|
142
|
+
const middleware = Object.create(MiddlewareSystemHealth.prototype);
|
|
143
|
+
Object.assign(middleware, { app: f.app, ctx: replay.ctx });
|
|
144
|
+
await middleware.execute({}, async () => assert.fail('not next'));
|
|
145
|
+
assert.equal(replay.ctx.status, 409);
|
|
146
|
+
f.ctx.res.emit('finish');
|
|
147
|
+
f.ctx.res.emit('finish');
|
|
148
|
+
assert.equal(closes, before + 1);
|
|
149
|
+
assert.deepEqual(f.healthCalls, []);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const invalidCases: [string, (f: ReturnType<typeof fixture>) => void, number][] = [
|
|
154
|
+
[
|
|
155
|
+
'ordinary mode',
|
|
156
|
+
f => {
|
|
157
|
+
f.app.meta.env.META_MODE = 'dev';
|
|
158
|
+
},
|
|
159
|
+
404,
|
|
160
|
+
],
|
|
161
|
+
[
|
|
162
|
+
'missing identity',
|
|
163
|
+
f => {
|
|
164
|
+
f.app.meta.env.VONA_TEST_RUN_ID = '';
|
|
165
|
+
},
|
|
166
|
+
404,
|
|
167
|
+
],
|
|
168
|
+
[
|
|
169
|
+
'invalid date',
|
|
170
|
+
f => {
|
|
171
|
+
f.app.meta.env.VONA_TEST_RUN_ID = '20269999010203-a1b2c3d4';
|
|
172
|
+
},
|
|
173
|
+
404,
|
|
174
|
+
],
|
|
175
|
+
[
|
|
176
|
+
'wrong app identity',
|
|
177
|
+
f => {
|
|
178
|
+
f.app.name = 'ordinary-app';
|
|
179
|
+
},
|
|
180
|
+
404,
|
|
181
|
+
],
|
|
182
|
+
[
|
|
183
|
+
'multiple workers',
|
|
184
|
+
f => {
|
|
185
|
+
f.app.config.server.workers = 2;
|
|
186
|
+
},
|
|
187
|
+
404,
|
|
188
|
+
],
|
|
189
|
+
[
|
|
190
|
+
'non-loopback socket ignoring forwarded IP',
|
|
191
|
+
f => {
|
|
192
|
+
f.ctx.req.socket.remoteAddress = '192.0.2.1';
|
|
193
|
+
f.headers['x-forwarded-for'] = '127.0.0.1';
|
|
194
|
+
},
|
|
195
|
+
404,
|
|
196
|
+
],
|
|
197
|
+
[
|
|
198
|
+
'wrong method',
|
|
199
|
+
f => {
|
|
200
|
+
f.ctx.method = 'GET';
|
|
201
|
+
},
|
|
202
|
+
403,
|
|
203
|
+
],
|
|
204
|
+
[
|
|
205
|
+
'query path',
|
|
206
|
+
f => {
|
|
207
|
+
f.ctx.req.url += '?ignored=yes';
|
|
208
|
+
},
|
|
209
|
+
403,
|
|
210
|
+
],
|
|
211
|
+
[
|
|
212
|
+
'missing nonce',
|
|
213
|
+
f => {
|
|
214
|
+
delete f.headers['x-vona-test-stop-nonce'];
|
|
215
|
+
},
|
|
216
|
+
403,
|
|
217
|
+
],
|
|
218
|
+
[
|
|
219
|
+
'malformed authorization',
|
|
220
|
+
f => {
|
|
221
|
+
f.headers['x-vona-test-stop-authorization'] = 'short';
|
|
222
|
+
},
|
|
223
|
+
403,
|
|
224
|
+
],
|
|
225
|
+
[
|
|
226
|
+
'bad signature',
|
|
227
|
+
f => {
|
|
228
|
+
f.headers['x-vona-test-stop-authorization'] = 'f'.repeat(64);
|
|
229
|
+
},
|
|
230
|
+
403,
|
|
231
|
+
],
|
|
232
|
+
[
|
|
233
|
+
'readiness signature cannot stop',
|
|
234
|
+
f => {
|
|
235
|
+
f.headers['x-vona-test-stop-authorization'] = sign(nonce);
|
|
236
|
+
},
|
|
237
|
+
403,
|
|
238
|
+
],
|
|
239
|
+
[
|
|
240
|
+
'ack signature cannot stop',
|
|
241
|
+
f => {
|
|
242
|
+
f.headers['x-vona-test-stop-authorization'] = sign(`vona-test-stop-ack-v1\n${payload}`);
|
|
243
|
+
},
|
|
244
|
+
403,
|
|
245
|
+
],
|
|
246
|
+
[
|
|
247
|
+
'wrong run signature',
|
|
248
|
+
f => {
|
|
249
|
+
f.headers['x-vona-test-stop-authorization'] = sign(
|
|
250
|
+
`vona-test-stop-request-v1\n${payload.replace(runId, '20261014010203-d4e5f6a7')}`,
|
|
251
|
+
);
|
|
252
|
+
},
|
|
253
|
+
403,
|
|
254
|
+
],
|
|
255
|
+
[
|
|
256
|
+
'different nonce',
|
|
257
|
+
f => {
|
|
258
|
+
f.headers['x-vona-test-stop-nonce'] = 'c'.repeat(32);
|
|
259
|
+
},
|
|
260
|
+
403,
|
|
261
|
+
],
|
|
262
|
+
];
|
|
263
|
+
for (const [name, change, status] of invalidCases) {
|
|
264
|
+
test(`shutdown rejects ${name} without initiating close`, async () => {
|
|
265
|
+
await withServerEnv(async () => {
|
|
266
|
+
const f = fixture();
|
|
267
|
+
const before = closes;
|
|
268
|
+
change(f);
|
|
269
|
+
await f.execute();
|
|
270
|
+
assert.equal(f.ctx.status, status);
|
|
271
|
+
assert.equal(f.ctx.res.listenerCount('finish'), 0);
|
|
272
|
+
f.ctx.res.emit('finish');
|
|
273
|
+
assert.equal(closes, before);
|
|
274
|
+
assert.deepEqual(f.responseHeaders, {});
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
for (const [name, value] of [
|
|
280
|
+
['META_MODE', 'dev'],
|
|
281
|
+
['VONA_TEST_RUN_ID', '20261014010203-d4e5f6a7'],
|
|
282
|
+
['VONA_TEST_READY_KEY', 'not-a-key'],
|
|
283
|
+
] as const) {
|
|
284
|
+
test(`shutdown requires matching server-only ${name}`, async () => {
|
|
285
|
+
await withServerEnv(async () => {
|
|
286
|
+
const f = fixture();
|
|
287
|
+
process.env[name] = value;
|
|
288
|
+
await f.execute();
|
|
289
|
+
assert.equal(f.ctx.status, 404);
|
|
290
|
+
assert.equal(f.ctx.res.listenerCount('finish'), 0);
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
test('existing readiness signing and unrelated health routes remain unchanged', async () => {
|
|
296
|
+
await withServerEnv(async () => {
|
|
297
|
+
const f = fixture();
|
|
298
|
+
f.ctx.path = '/health/ready';
|
|
299
|
+
f.headers['x-vona-test-challenge'] = nonce;
|
|
300
|
+
await f.execute();
|
|
301
|
+
assert.equal(f.responseHeaders['x-vona-test-proof'], sign(nonce));
|
|
302
|
+
assert.deepEqual(f.healthCalls, ['/health/ready']);
|
|
303
|
+
assert.equal(f.ctx.res.listenerCount('finish'), 0);
|
|
304
|
+
f.ctx.path = '/unrelated';
|
|
305
|
+
await f.execute();
|
|
306
|
+
assert.equal(f.nexts(), 1);
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
});
|
package/vona/src/suite-vendor/a-vona/modules/a-orm/src/service/transactionAsyncLocalStorage_.ts
CHANGED
|
@@ -18,9 +18,9 @@ export class ServiceTransactionAsyncLocalStorage extends BeanBase {
|
|
|
18
18
|
return this.transactionStorage.getStore()!;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
async run<RESULT>(fn: FunctionAsync<RESULT
|
|
22
|
-
//
|
|
23
|
-
if (this.transactionState) {
|
|
21
|
+
async run<RESULT>(fn: FunctionAsync<RESULT>, isolate = false): Promise<RESULT> {
|
|
22
|
+
// Delegated calls and datasource switches share state; independent contexts do not.
|
|
23
|
+
if (!isolate && this.transactionState) {
|
|
24
24
|
return fn();
|
|
25
25
|
}
|
|
26
26
|
const transactionState = this.bean._newBean(ServiceTransactionState);
|
|
@@ -161,7 +161,7 @@ async function _getFullPathInner(
|
|
|
161
161
|
): Promise<string | undefined> {
|
|
162
162
|
// trim prefix
|
|
163
163
|
if (options.apiStaticPrefix !== '/') {
|
|
164
|
-
const filePrefix = path.normalize(options.apiStaticPrefix.replace(/^\//, ''));
|
|
164
|
+
const filePrefix = path.posix.normalize(options.apiStaticPrefix.replace(/^\//, ''));
|
|
165
165
|
if (filename.indexOf(filePrefix) !== 0) {
|
|
166
166
|
return;
|
|
167
167
|
// cannot throw error
|
|
@@ -170,7 +170,7 @@ async function _getFullPathInner(
|
|
|
170
170
|
filename = filename.slice(filePrefix.length);
|
|
171
171
|
}
|
|
172
172
|
// check
|
|
173
|
-
const parts = filename.split(
|
|
173
|
+
const parts = filename.split('/');
|
|
174
174
|
const wordFirst = parts.shift();
|
|
175
175
|
// public
|
|
176
176
|
if (wordFirst === 'public') {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { registerHooks } from 'node:module';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { test } from 'node:test';
|
|
6
|
+
import ts from 'typescript';
|
|
7
|
+
|
|
8
|
+
for (const platform of ['posix', 'win32'] as const) {
|
|
9
|
+
const middlewareUrl = new URL(
|
|
10
|
+
`../src/bean/middlewareSystem.static.ts?fixture=static-${platform}`,
|
|
11
|
+
import.meta.url,
|
|
12
|
+
).href;
|
|
13
|
+
const hooks = registerHooks({
|
|
14
|
+
resolve(specifier, context, nextResolve) {
|
|
15
|
+
if (context.parentURL === middlewareUrl) {
|
|
16
|
+
const source = {
|
|
17
|
+
'vona': 'export class BeanBase {} export function compose() {}',
|
|
18
|
+
'vona-module-a-aspect': 'export function MiddlewareSystem() { return () => {}; }',
|
|
19
|
+
'node:path': `import {posix,win32} from 'node:path'; export default {...${platform},posix,win32};`,
|
|
20
|
+
'../.metadata/this.ts': "export const __ThisModule__ = 'a-static';",
|
|
21
|
+
}[specifier];
|
|
22
|
+
if (source)
|
|
23
|
+
return { url: `data:text/javascript,${encodeURIComponent(source)}`, shortCircuit: true };
|
|
24
|
+
}
|
|
25
|
+
return nextResolve(specifier, context);
|
|
26
|
+
},
|
|
27
|
+
load(url, context, nextLoad) {
|
|
28
|
+
if (url === middlewareUrl) {
|
|
29
|
+
return {
|
|
30
|
+
format: 'module',
|
|
31
|
+
source: ts.transpileModule(
|
|
32
|
+
`${readFileSync(new URL(url), 'utf8')}\nexport { getFullPath };`,
|
|
33
|
+
{
|
|
34
|
+
compilerOptions: {
|
|
35
|
+
experimentalDecorators: true,
|
|
36
|
+
target: ts.ScriptTarget.ESNext,
|
|
37
|
+
module: ts.ModuleKind.ESNext,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
).outputText,
|
|
41
|
+
shortCircuit: true,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return nextLoad(url, context);
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
let getFullPath;
|
|
48
|
+
try {
|
|
49
|
+
({ getFullPath } = await import(middlewareUrl));
|
|
50
|
+
} finally {
|
|
51
|
+
hooks.deregister();
|
|
52
|
+
}
|
|
53
|
+
const filesystem = path[platform];
|
|
54
|
+
const root = platform === 'win32' ? 'D:\\app\\public' : '/app/public';
|
|
55
|
+
const assetRoot = platform === 'win32' ? 'D:\\modules' : '/modules';
|
|
56
|
+
const ctx = {
|
|
57
|
+
app: {
|
|
58
|
+
scope: () => ({ event: { resolvePath: { emit: (data, fallback) => fallback(data) } } }),
|
|
59
|
+
meta: { modules: { 'home-index': {} } },
|
|
60
|
+
util: {
|
|
61
|
+
getAssetPathPhysical: (module: string, scene: string, filename: string) =>
|
|
62
|
+
filesystem.join(assetRoot, module, scene, filename),
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
const options = { apiStaticPrefix: '/api/static/' };
|
|
67
|
+
|
|
68
|
+
test(`${platform}: static URL separators resolve public uploads and module assets to native paths`, async () => {
|
|
69
|
+
for (const separator of ['/', '\\']) {
|
|
70
|
+
const publicUrl = 'api/static/public/1/image-native/default/avatar.jpg'.replaceAll(
|
|
71
|
+
'/',
|
|
72
|
+
separator,
|
|
73
|
+
);
|
|
74
|
+
assert.equal(
|
|
75
|
+
await getFullPath(ctx, root, publicUrl, options),
|
|
76
|
+
filesystem.join(root, '1', 'image-native', 'default', 'avatar.jpg'),
|
|
77
|
+
);
|
|
78
|
+
const moduleUrl = 'api/static/home/index/img/vona.png'.replaceAll('/', separator);
|
|
79
|
+
assert.equal(
|
|
80
|
+
await getFullPath(ctx, root, moduleUrl, options),
|
|
81
|
+
filesystem.join(assetRoot, 'home-index', 'static', 'img', 'vona.png'),
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test(`${platform}: static resolution preserves root-prefix configuration and rejects unknown routes`, async () => {
|
|
87
|
+
assert.equal(
|
|
88
|
+
await getFullPath(ctx, root, 'public/avatar.jpg', { apiStaticPrefix: '/' }),
|
|
89
|
+
filesystem.join(root, 'avatar.jpg'),
|
|
90
|
+
);
|
|
91
|
+
assert.equal(await getFullPath(ctx, root, 'other/public/avatar.jpg', options), undefined);
|
|
92
|
+
assert.equal(
|
|
93
|
+
await getFullPath(ctx, root, 'api/static/unknown/module/file.jpg', options),
|
|
94
|
+
undefined,
|
|
95
|
+
);
|
|
96
|
+
await assert.rejects(
|
|
97
|
+
getFullPath(ctx, root, 'api/static/public/../../outside.jpg', options),
|
|
98
|
+
/unsafe filename/,
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zova-cli",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.114",
|
|
4
4
|
"gitHead": "6f675a8cc46d596142c591c28a40cc4d82fcc6cc",
|
|
5
5
|
"description": "zova cli",
|
|
6
6
|
"keywords": [
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"@cabloy/process-helper": "^3.1.8",
|
|
45
45
|
"fs-extra": "^11.3.5",
|
|
46
46
|
"semver": "^7.6.2",
|
|
47
|
-
"zova-cli-set-front": "^1.2.
|
|
47
|
+
"zova-cli-set-front": "^1.2.112"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"clean-package": "^2.2.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zova-cli-set-front",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.112",
|
|
4
4
|
"gitHead": "6f675a8cc46d596142c591c28a40cc4d82fcc6cc",
|
|
5
5
|
"description": "zova cli-set-front",
|
|
6
6
|
"keywords": [
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"vite": "^8.0.14",
|
|
71
71
|
"yaml": "^2.8.3",
|
|
72
72
|
"zova-openapi": "^1.1.17",
|
|
73
|
-
"zova-vite": "^1.1.
|
|
73
|
+
"zova-vite": "^1.1.48"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
76
|
"clean-package": "^2.2.0",
|
|
@@ -275,7 +275,7 @@ export function createConfigUtils(
|
|
|
275
275
|
for (const moduleName in modules) {
|
|
276
276
|
const module = modules[moduleName];
|
|
277
277
|
//
|
|
278
|
-
let test = module.root;
|
|
278
|
+
let test = module.root.replace(/\\/g, '/');
|
|
279
279
|
let index = test.indexOf(appDir);
|
|
280
280
|
if (index > -1) {
|
|
281
281
|
test = test.substring(index + appDir.length);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { registerHooks } from 'node:module';
|
|
3
|
+
import { test } from 'node:test';
|
|
4
|
+
|
|
5
|
+
const configUrl = new URL('../src/configUtils.ts?fixture=chunk-paths', import.meta.url).href;
|
|
6
|
+
const hooks = registerHooks({
|
|
7
|
+
resolve(specifier, context, nextResolve) {
|
|
8
|
+
if (context.parentURL === configUrl) {
|
|
9
|
+
const source = {
|
|
10
|
+
'@cabloy/module-glob': `export async function glob({projectPath}) {
|
|
11
|
+
const separator = projectPath.includes('\\\\') ? '\\\\' : '/';
|
|
12
|
+
const appDir = projectPath.replaceAll('/', separator);
|
|
13
|
+
return {modules:{'a-form':{root:appDir+separator+'src/suite-vendor/a-zova/modules/a-form'.replaceAll('/',separator),package:{}},
|
|
14
|
+
'a-external':{root:appDir+separator+'node_modules/zova-module-a-external'.replaceAll('/',separator),package:{}}}};
|
|
15
|
+
}`,
|
|
16
|
+
'@cabloy/dotenv':
|
|
17
|
+
"export function loadEnvs() { throw Error('Environment loading is not part of chunk grouping'); }",
|
|
18
|
+
}[specifier];
|
|
19
|
+
if (source)
|
|
20
|
+
return { url: `data:text/javascript,${encodeURIComponent(source)}`, shortCircuit: true };
|
|
21
|
+
}
|
|
22
|
+
return nextResolve(specifier, context);
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
let createConfigUtils;
|
|
26
|
+
try {
|
|
27
|
+
({ createConfigUtils } = await import(configUrl));
|
|
28
|
+
} finally {
|
|
29
|
+
hooks.deregister();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
for (const appDir of ['/checkout/zova', 'D:\\checkout\\zova', 'D:\\checkout/zova']) {
|
|
33
|
+
test(`module chunk groups match normalized Vite IDs for ${appDir}`, async () => {
|
|
34
|
+
const config = createConfigUtils(
|
|
35
|
+
{ flavor: 'cabloyBasicAdmin', mode: 'production', appMode: 'ssr' },
|
|
36
|
+
{ appDir },
|
|
37
|
+
);
|
|
38
|
+
await config.loadModulesMeta();
|
|
39
|
+
const groups = config.codeSplittingGroups();
|
|
40
|
+
const form = groups.find(group => group.name === 'a-form');
|
|
41
|
+
assert.equal(form.test, '/src/suite-vendor/a-zova/modules/a-form/');
|
|
42
|
+
assert.ok(
|
|
43
|
+
`${appDir.replaceAll('\\', '/')}/src/suite-vendor/a-zova/modules/a-form/src/index.ts`.includes(
|
|
44
|
+
form.test,
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
const external = groups.find(group => group.name === 'a-external');
|
|
48
|
+
assert.equal(external.test, '/zova-module-a-external/');
|
|
49
|
+
assert.ok(
|
|
50
|
+
`${appDir.replaceAll('\\', '/')}/node_modules/zova-module-a-external/src/index.ts`.includes(
|
|
51
|
+
external.test,
|
|
52
|
+
),
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -29,7 +29,8 @@ export class ModelCart extends BeanModelBase {
|
|
|
29
29
|
mutationFn: async (body: ApiApiCommerceTradeCartaddItemRequestBody) => {
|
|
30
30
|
return await this.scope.api.commerceTradeCart.addItem(body);
|
|
31
31
|
},
|
|
32
|
-
onSuccess: async
|
|
32
|
+
onSuccess: async cart => {
|
|
33
|
+
this.$setQueryData(['current'], cart);
|
|
33
34
|
await this.$invalidateQueries({ queryKey: ['current'] });
|
|
34
35
|
},
|
|
35
36
|
});
|
|
@@ -47,7 +48,8 @@ export class ModelCart extends BeanModelBase {
|
|
|
47
48
|
}) => {
|
|
48
49
|
return await this.scope.api.commerceTradeCart.updateItem(body, { params: { id } });
|
|
49
50
|
},
|
|
50
|
-
onSuccess: async
|
|
51
|
+
onSuccess: async cart => {
|
|
52
|
+
this.$setQueryData(['current'], cart);
|
|
51
53
|
await this.$invalidateQueries({ queryKey: ['current'] });
|
|
52
54
|
},
|
|
53
55
|
});
|
|
@@ -59,7 +61,8 @@ export class ModelCart extends BeanModelBase {
|
|
|
59
61
|
mutationFn: async (id: string) => {
|
|
60
62
|
return await this.scope.api.commerceTradeCart.deleteItem({ params: { id } });
|
|
61
63
|
},
|
|
62
|
-
onSuccess: async
|
|
64
|
+
onSuccess: async cart => {
|
|
65
|
+
this.$setQueryData(['current'], cart);
|
|
63
66
|
await this.$invalidateQueries({ queryKey: ['current'] });
|
|
64
67
|
},
|
|
65
68
|
});
|
|
@@ -93,7 +96,8 @@ export class ModelCart extends BeanModelBase {
|
|
|
93
96
|
mutationFn: async () => {
|
|
94
97
|
return await this.scope.api.commerceTradeCart.clear();
|
|
95
98
|
},
|
|
96
|
-
onSuccess: async
|
|
99
|
+
onSuccess: async cart => {
|
|
100
|
+
this.$setQueryData(['current'], cart);
|
|
97
101
|
await this.$invalidateQueries({ queryKey: ['current'] });
|
|
98
102
|
},
|
|
99
103
|
});
|