cabloy 5.1.141 → 5.1.142
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 +2 -2
- package/CHANGELOG.md +21 -0
- package/cabloy-docs/frontend/form-guide.md +47 -0
- package/cabloy-docs/frontend/markdown-guide.md +50 -17
- package/cabloy-docs/frontend/route-alias-guide.md +20 -0
- package/cabloy-docs/frontend/scripts.md +9 -11
- package/cabloy-docs/frontend/zova-form-under-the-hood.md +25 -0
- package/cabloy-docs/fullstack/parallel-worktree-environment.md +1 -1
- package/cabloy-docs/fullstack/quickstart.md +5 -4
- package/cabloy-docs/reference/repo-scripts.md +29 -42
- package/e2e/config/playwright.config.ts +34 -0
- package/e2e/scripts/e2e.ts +2 -27
- package/e2e/scripts/runE2e.ts +62 -0
- package/e2e/scripts/runE2eArgs.test.ts +75 -0
- package/e2e/scripts/runE2eArgs.ts +158 -0
- package/e2e/specs/{a-commerce/commerce.spec.ts → a-commerce.spec.ts} +361 -30
- package/e2e/specs/{cabloy-basic/account.spec.ts → account.spec.ts} +1 -2
- package/oxlint.config.ts +3 -0
- package/package.json +3 -9
- package/scripts/upgrade.ts +63 -68
- package/vona/packages-utils/lint/package.json +1 -1
- package/vona/packages-utils/lint/src/oxc/lint.ts +1 -1
- package/vona/packages-utils/lint/src/oxc/lintVue.ts +1 -1
- package/vona/pnpm-lock.yaml +41 -41
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/refundRecoveryView.tsx +7 -2
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/order.ts +31 -15
- package/vona/src/suite/a-home/modules/home-user/package.json +1 -0
- package/vona/src/suite/a-home/modules/home-user/src/service/account.ts +15 -2
- package/vona/src/suite/a-home/modules/home-user/test/accountActivation.test.ts +34 -4
- package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/package.json +2 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/src/.metadata/index.ts +26 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/src/bean/imageScene.markdown.ts +13 -0
- package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
- package/vona/src/suite-vendor/a-pay/modules/a-pay/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/providerOperation.ts +23 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/refundOperation.ts +7 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/package.json +5 -5
- package/zova/packages-utils/zova-jsx/package.json +2 -2
- package/zova/packages-zova/zova/package.json +3 -3
- package/zova/packages-zova/zova-core/package.json +1 -1
- package/zova/packages-zova/zova-core/src/bean/resource/locale/type.ts +1 -0
- package/zova/pnpm-lock.yaml +63 -18
- package/zova/src/front/config/config/config.cabloyBasicWeb.ts +3 -1
- package/zova/src/front/config/config/config.cabloyCommerce.ts +4 -2
- package/zova/src/suite/a-commerce/modules/commerce-member/src/page/address/controller.tsx +64 -63
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +6 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/component/tableCellActionRefund/controller.tsx +413 -134
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/en-us.ts +76 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/zh-cn.ts +67 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/order.ts +1 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/cart/controller.tsx +1 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/checkout/controller.tsx +7 -7
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/order/controller.tsx +46 -9
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx +50 -30
- package/zova/src/suite/cabloy-basic/modules/basic-captcha/src/component/formFieldCaptcha/controller.tsx +6 -2
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/component/formFieldImage/controller.tsx +9 -15
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/package.json +10 -2
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/.metadata/index.ts +11 -2
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/.metadata/locales.ts +7 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/formFieldMarkdown/controller.tsx +723 -5
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/formFieldMarkdown/render.tsx +461 -2
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/config/locale/en-us.ts +59 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/config/locale/zh-cn.ts +59 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/index.ts +1 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/lib/codeBlockLanguages.ts +42 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/lib/richTextContentStyle.ts +20 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/test/lib/codeBlockLanguages.test.ts +25 -0
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +1 -0
- package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
- package/zova/src/suite-vendor/a-pay/modules/a-pay/package.json +1 -1
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/component/paymentNextAction/controller.tsx +26 -12
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/cli/openapi.config.ts +1 -1
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/package.json +1 -1
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/api/payMockMockPayment.ts +26 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/apiSchema/payMockMockPayment.ts +8 -1
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/model/payMockPayment.ts +13 -1
- package/zova/src/suite-vendor/a-pay/package.json +3 -3
- package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/formField/controller.tsx +2 -0
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/types/formField.ts +7 -6
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/package.json +3 -3
- package/e2e/config/playwright.basic.config.ts +0 -3
- package/e2e/config/playwright.commerce.config.ts +0 -3
- package/e2e/config/playwright.shared.config.ts +0 -38
- package/e2e/scripts/testE2eClean.ts +0 -56
- /package/e2e/specs/{cabloy-basic/basic.spec.ts → cabloy-basic.spec.ts} +0 -0
package/scripts/upgrade.ts
CHANGED
|
@@ -59,14 +59,17 @@ const OVERWRITE_DIRS_CABLOY_BASIC: string[] = [
|
|
|
59
59
|
'zova/src/suite/a-devui',
|
|
60
60
|
];
|
|
61
61
|
|
|
62
|
-
const FRAMEWORK_E2E_DIRS_CABLOY_BASIC: string[] = [
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
'e2e/specs/cabloy-basic',
|
|
66
|
-
'e2e/specs/
|
|
62
|
+
const FRAMEWORK_E2E_DIRS_CABLOY_BASIC: string[] = ['e2e/config', 'e2e/scripts'];
|
|
63
|
+
|
|
64
|
+
const FRAMEWORK_E2E_FILES_CABLOY_BASIC: string[] = [
|
|
65
|
+
'e2e/specs/cabloy-basic.spec.ts',
|
|
66
|
+
'e2e/specs/account.spec.ts',
|
|
67
|
+
'e2e/specs/a-commerce.spec.ts',
|
|
67
68
|
];
|
|
68
69
|
|
|
69
|
-
const FRAMEWORK_E2E_SCRIPT_NAMES_CABLOY_BASIC: string[] = [
|
|
70
|
+
const FRAMEWORK_E2E_SCRIPT_NAMES_CABLOY_BASIC: string[] = ['test:e2e', 'test:e2e:fast'];
|
|
71
|
+
|
|
72
|
+
const FRAMEWORK_E2E_LEGACY_SCRIPT_NAMES_CABLOY_BASIC: string[] = [
|
|
70
73
|
'test:e2e:basic',
|
|
71
74
|
'test:e2e:basic:web',
|
|
72
75
|
'test:e2e:basic:admin',
|
|
@@ -75,15 +78,11 @@ const FRAMEWORK_E2E_SCRIPT_NAMES_CABLOY_BASIC: string[] = [
|
|
|
75
78
|
'test:e2e:commerce:web',
|
|
76
79
|
'test:e2e:commerce:admin',
|
|
77
80
|
'test:e2e:commerce:clean',
|
|
81
|
+
'test:e2e:basic:flow',
|
|
78
82
|
];
|
|
79
83
|
|
|
80
84
|
const FRAMEWORK_E2E_DEV_DEPENDENCY_CABLOY_BASIC = '@playwright/test';
|
|
81
85
|
|
|
82
|
-
const OBSOLETE_FRAMEWORK_E2E_SCRIPTS_CABLOY_BASIC: Record<string, string> = {
|
|
83
|
-
'test:e2e:basic:flow':
|
|
84
|
-
'playwright test --config e2e/config/playwright.basic.config.ts --grep ATP-BASIC-FLOW-01',
|
|
85
|
-
};
|
|
86
|
-
|
|
87
86
|
const MERGE_DIRS: string[] = [
|
|
88
87
|
// Claude project assets
|
|
89
88
|
'.claude/commands',
|
|
@@ -231,24 +230,6 @@ async function extractTarball(tarballPath: string, targetDir: string): Promise<v
|
|
|
231
230
|
}
|
|
232
231
|
}
|
|
233
232
|
|
|
234
|
-
function needsBasicE2eReconciliation(): boolean {
|
|
235
|
-
if (!isCabloyBasic()) return false;
|
|
236
|
-
|
|
237
|
-
for (const dir of FRAMEWORK_E2E_DIRS_CABLOY_BASIC) {
|
|
238
|
-
if (!existsSync(resolve(ROOT_DIR, dir))) return true;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const packageJson = readPackageJson(resolve(ROOT_DIR, 'package.json'));
|
|
242
|
-
for (const name of FRAMEWORK_E2E_SCRIPT_NAMES_CABLOY_BASIC) {
|
|
243
|
-
const script = packageJson.scripts?.[name];
|
|
244
|
-
if (!script?.includes('e2e/')) return true;
|
|
245
|
-
}
|
|
246
|
-
for (const [name, value] of Object.entries(OBSOLETE_FRAMEWORK_E2E_SCRIPTS_CABLOY_BASIC)) {
|
|
247
|
-
if (packageJson.scripts?.[name] === value) return true;
|
|
248
|
-
}
|
|
249
|
-
return !packageJson.devDependencies?.[FRAMEWORK_E2E_DEV_DEPENDENCY_CABLOY_BASIC];
|
|
250
|
-
}
|
|
251
|
-
|
|
252
233
|
function mergeFrameworkE2eAssets(dryRun?: boolean): void {
|
|
253
234
|
if (!isCabloyBasic()) return;
|
|
254
235
|
|
|
@@ -264,6 +245,38 @@ function mergeFrameworkE2eAssets(dryRun?: boolean): void {
|
|
|
264
245
|
}
|
|
265
246
|
copyDirectory(src, dest);
|
|
266
247
|
}
|
|
248
|
+
|
|
249
|
+
for (const file of FRAMEWORK_E2E_FILES_CABLOY_BASIC) {
|
|
250
|
+
const src = resolve(TEMP_DIR, file);
|
|
251
|
+
const dest = resolve(ROOT_DIR, file);
|
|
252
|
+
if (!existsSync(src)) {
|
|
253
|
+
throw new Error(`Expected framework E2E file in package: ${file}`);
|
|
254
|
+
}
|
|
255
|
+
if (dryRun) {
|
|
256
|
+
log(` [dry-run] Overwrite framework E2E file: ${file}`);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
260
|
+
cpSync(src, dest);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const legacyFrameworkFiles: string[] = [
|
|
264
|
+
'e2e/config/playwright.basic.config.ts',
|
|
265
|
+
'e2e/config/playwright.commerce.config.ts',
|
|
266
|
+
'e2e/config/playwright.shared.config.ts',
|
|
267
|
+
'e2e/specs/cabloy-basic/basic.spec.ts',
|
|
268
|
+
'e2e/specs/cabloy-basic/account.spec.ts',
|
|
269
|
+
'e2e/specs/a-commerce/commerce.spec.ts',
|
|
270
|
+
];
|
|
271
|
+
for (const file of legacyFrameworkFiles) {
|
|
272
|
+
const dest = resolve(ROOT_DIR, file);
|
|
273
|
+
if (!existsSync(dest)) continue;
|
|
274
|
+
if (dryRun) {
|
|
275
|
+
log(` [dry-run] Remove legacy framework E2E file: ${file}`);
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
rmSync(dest, { force: true });
|
|
279
|
+
}
|
|
267
280
|
}
|
|
268
281
|
|
|
269
282
|
function reconcileFrameworkE2ePackageJson(dryRun?: boolean): void {
|
|
@@ -324,18 +337,11 @@ function reconcileFrameworkE2ePackageJson(dryRun?: boolean): void {
|
|
|
324
337
|
}
|
|
325
338
|
}
|
|
326
339
|
|
|
327
|
-
for (const
|
|
328
|
-
|
|
329
|
-
if (!currentValue) continue;
|
|
330
|
-
if (currentValue !== value) {
|
|
331
|
-
log(
|
|
332
|
-
` Retain customized obsolete framework E2E script package.json scripts.${name}; remove it manually if no longer needed.`,
|
|
333
|
-
);
|
|
334
|
-
continue;
|
|
335
|
-
}
|
|
340
|
+
for (const name of FRAMEWORK_E2E_LEGACY_SCRIPT_NAMES_CABLOY_BASIC) {
|
|
341
|
+
if (!projectPackage.scripts?.[name]) continue;
|
|
336
342
|
changed = true;
|
|
337
343
|
if (dryRun) {
|
|
338
|
-
log(` [dry-run] Remove
|
|
344
|
+
log(` [dry-run] Remove legacy framework E2E script package.json scripts.${name}`);
|
|
339
345
|
} else {
|
|
340
346
|
delete projectPackage.scripts?.[name];
|
|
341
347
|
}
|
|
@@ -513,41 +519,30 @@ async function main(): Promise<void> {
|
|
|
513
519
|
|
|
514
520
|
const latestPackageInfo = await fetchLatestPackageInfo();
|
|
515
521
|
const currentVersion = readVersionMarker();
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
log(`Current project Cabloy version: ${currentVersion}`);
|
|
520
|
-
} else {
|
|
521
|
-
log(
|
|
522
|
-
`Warning: Missing ${VERSION_MARKER_FILE}, continuing with upgrade for backward compatibility.`,
|
|
522
|
+
if (!currentVersion) {
|
|
523
|
+
throw new Error(
|
|
524
|
+
`Missing ${VERSION_MARKER_FILE}; this project is not a supported Cabloy upgrade target.`,
|
|
523
525
|
);
|
|
524
526
|
}
|
|
527
|
+
|
|
528
|
+
log(`Current project Cabloy version: ${currentVersion}`);
|
|
525
529
|
log(`Latest Cabloy version: ${latestPackageInfo.version}`);
|
|
526
530
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
if (
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
`Cabloy is already up to date (current: ${currentVersion}), but the Cabloy Basic E2E baseline is incomplete. Repairing it in this upgrade; no additional upgrade command is required.\n`,
|
|
533
|
-
);
|
|
534
|
-
} else if (!dryRun) {
|
|
535
|
-
log(`Cabloy is already up to date (current: ${currentVersion}). Skipping upgrade.`);
|
|
536
|
-
return;
|
|
537
|
-
} else {
|
|
538
|
-
log(
|
|
539
|
-
`Cabloy is already up to date (current: ${currentVersion}). Continuing dry-run to show overwrite plan.\n`,
|
|
540
|
-
);
|
|
541
|
-
}
|
|
542
|
-
} else if (comparison > 0) {
|
|
543
|
-
throw new Error(
|
|
544
|
-
`Project Cabloy version ${currentVersion} is newer than npm latest ${latestPackageInfo.version}. Aborting upgrade.`,
|
|
545
|
-
);
|
|
546
|
-
} else {
|
|
547
|
-
log(`Upgrading Cabloy from ${currentVersion} to ${latestPackageInfo.version}...\n`);
|
|
531
|
+
const comparison = compareVersions(currentVersion, latestPackageInfo.version);
|
|
532
|
+
if (comparison === 0) {
|
|
533
|
+
if (!dryRun) {
|
|
534
|
+
log(`Cabloy is already up to date (current: ${currentVersion}). Skipping upgrade.`);
|
|
535
|
+
return;
|
|
548
536
|
}
|
|
537
|
+
log(
|
|
538
|
+
`Cabloy is already up to date (current: ${currentVersion}). Continuing dry-run to show overwrite plan.\n`,
|
|
539
|
+
);
|
|
540
|
+
} else if (comparison > 0) {
|
|
541
|
+
throw new Error(
|
|
542
|
+
`Project Cabloy version ${currentVersion} is newer than npm latest ${latestPackageInfo.version}. Aborting upgrade.`,
|
|
543
|
+
);
|
|
549
544
|
} else {
|
|
550
|
-
log(`Upgrading Cabloy to ${latestPackageInfo.version}...\n`);
|
|
545
|
+
log(`Upgrading Cabloy from ${currentVersion} to ${latestPackageInfo.version}...\n`);
|
|
551
546
|
}
|
|
552
547
|
|
|
553
548
|
try {
|
|
@@ -452,7 +452,7 @@ const _configDefault: OxlintConfig = {
|
|
|
452
452
|
propElementValues: 'always',
|
|
453
453
|
},
|
|
454
454
|
],
|
|
455
|
-
'@stylistic/jsx-curly-newline': '
|
|
455
|
+
'@stylistic/jsx-curly-newline': 'off',
|
|
456
456
|
'@stylistic/jsx-curly-spacing': ['error', 'never'],
|
|
457
457
|
'@stylistic/jsx-equals-spacing': 'error',
|
|
458
458
|
'@stylistic/jsx-first-prop-new-line': 'error',
|
|
@@ -450,7 +450,7 @@ const _configDefault: OxlintConfig = {
|
|
|
450
450
|
propElementValues: 'always',
|
|
451
451
|
},
|
|
452
452
|
],
|
|
453
|
-
'@stylistic/jsx-curly-newline': '
|
|
453
|
+
'@stylistic/jsx-curly-newline': 'off',
|
|
454
454
|
'@stylistic/jsx-curly-spacing': ['error', 'never'],
|
|
455
455
|
'@stylistic/jsx-equals-spacing': 'error',
|
|
456
456
|
'@stylistic/jsx-first-prop-new-line': 'error',
|
package/vona/pnpm-lock.yaml
CHANGED
|
@@ -564,8 +564,8 @@ importers:
|
|
|
564
564
|
specifier: npm:@cabloy/zod@4.3.8
|
|
565
565
|
version: '@cabloy/zod@4.3.8'
|
|
566
566
|
zova:
|
|
567
|
-
specifier: ^5.1.
|
|
568
|
-
version: 5.1.
|
|
567
|
+
specifier: ^5.1.151
|
|
568
|
+
version: 5.1.151(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3))
|
|
569
569
|
zova-jsx:
|
|
570
570
|
specifier: ^1.1.86
|
|
571
571
|
version: 1.1.86(typescript@5.9.3)
|
|
@@ -597,8 +597,8 @@ importers:
|
|
|
597
597
|
specifier: ^5.1.50
|
|
598
598
|
version: 5.1.50
|
|
599
599
|
zova-module-a-pay:
|
|
600
|
-
specifier: ^5.0.
|
|
601
|
-
version: 5.0.
|
|
600
|
+
specifier: ^5.0.6
|
|
601
|
+
version: 5.0.6
|
|
602
602
|
zova-module-a-router:
|
|
603
603
|
specifier: ^5.1.34
|
|
604
604
|
version: 5.1.34
|
|
@@ -615,8 +615,8 @@ importers:
|
|
|
615
615
|
specifier: ^5.1.38
|
|
616
616
|
version: 5.1.38(vue@3.5.39(typescript@5.9.3))
|
|
617
617
|
zova-module-pay-mock:
|
|
618
|
-
specifier: ^5.0.
|
|
619
|
-
version: 5.0.
|
|
618
|
+
specifier: ^5.0.4
|
|
619
|
+
version: 5.0.4
|
|
620
620
|
zova-module-rest-resource:
|
|
621
621
|
specifier: ^5.1.45
|
|
622
622
|
version: 5.1.45
|
|
@@ -642,8 +642,8 @@ importers:
|
|
|
642
642
|
specifier: npm:@cabloy/zod@4.3.8
|
|
643
643
|
version: '@cabloy/zod@4.3.8'
|
|
644
644
|
zova:
|
|
645
|
-
specifier: ^5.1.
|
|
646
|
-
version: 5.1.
|
|
645
|
+
specifier: ^5.1.151
|
|
646
|
+
version: 5.1.151(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3))
|
|
647
647
|
zova-jsx:
|
|
648
648
|
specifier: ^1.1.86
|
|
649
649
|
version: 1.1.86(typescript@5.9.3)
|
|
@@ -675,8 +675,8 @@ importers:
|
|
|
675
675
|
specifier: ^5.1.50
|
|
676
676
|
version: 5.1.50
|
|
677
677
|
zova-module-a-pay:
|
|
678
|
-
specifier: ^5.0.
|
|
679
|
-
version: 5.0.
|
|
678
|
+
specifier: ^5.0.6
|
|
679
|
+
version: 5.0.6
|
|
680
680
|
zova-module-a-router:
|
|
681
681
|
specifier: ^5.1.34
|
|
682
682
|
version: 5.1.34
|
|
@@ -693,8 +693,8 @@ importers:
|
|
|
693
693
|
specifier: ^5.1.38
|
|
694
694
|
version: 5.1.38(vue@3.5.39(typescript@5.9.3))
|
|
695
695
|
zova-module-pay-mock:
|
|
696
|
-
specifier: ^5.0.
|
|
697
|
-
version: 5.0.
|
|
696
|
+
specifier: ^5.0.4
|
|
697
|
+
version: 5.0.4
|
|
698
698
|
zova-module-rest-resource:
|
|
699
699
|
specifier: ^5.1.45
|
|
700
700
|
version: 5.1.45
|
|
@@ -720,8 +720,8 @@ importers:
|
|
|
720
720
|
specifier: npm:@cabloy/zod@4.3.8
|
|
721
721
|
version: '@cabloy/zod@4.3.8'
|
|
722
722
|
zova:
|
|
723
|
-
specifier: ^5.1.
|
|
724
|
-
version: 5.1.
|
|
723
|
+
specifier: ^5.1.151
|
|
724
|
+
version: 5.1.151(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3))
|
|
725
725
|
zova-jsx:
|
|
726
726
|
specifier: ^1.1.86
|
|
727
727
|
version: 1.1.86(typescript@5.9.3)
|
|
@@ -753,8 +753,8 @@ importers:
|
|
|
753
753
|
specifier: ^5.1.50
|
|
754
754
|
version: 5.1.50
|
|
755
755
|
zova-module-a-pay:
|
|
756
|
-
specifier: ^5.0.
|
|
757
|
-
version: 5.0.
|
|
756
|
+
specifier: ^5.0.6
|
|
757
|
+
version: 5.0.6
|
|
758
758
|
zova-module-a-router:
|
|
759
759
|
specifier: ^5.1.34
|
|
760
760
|
version: 5.1.34
|
|
@@ -771,8 +771,8 @@ importers:
|
|
|
771
771
|
specifier: ^5.1.38
|
|
772
772
|
version: 5.1.38(vue@3.5.39(typescript@5.9.3))
|
|
773
773
|
zova-module-pay-mock:
|
|
774
|
-
specifier: ^5.0.
|
|
775
|
-
version: 5.0.
|
|
774
|
+
specifier: ^5.0.4
|
|
775
|
+
version: 5.0.4
|
|
776
776
|
zova-module-rest-resource:
|
|
777
777
|
specifier: ^5.1.45
|
|
778
778
|
version: 5.1.45
|
|
@@ -798,8 +798,8 @@ importers:
|
|
|
798
798
|
specifier: npm:@cabloy/zod@4.3.8
|
|
799
799
|
version: '@cabloy/zod@4.3.8'
|
|
800
800
|
zova:
|
|
801
|
-
specifier: ^5.1.
|
|
802
|
-
version: 5.1.
|
|
801
|
+
specifier: ^5.1.151
|
|
802
|
+
version: 5.1.151(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3))
|
|
803
803
|
zova-jsx:
|
|
804
804
|
specifier: ^1.1.86
|
|
805
805
|
version: 1.1.86(typescript@5.9.3)
|
|
@@ -831,8 +831,8 @@ importers:
|
|
|
831
831
|
specifier: ^5.1.50
|
|
832
832
|
version: 5.1.50
|
|
833
833
|
zova-module-a-pay:
|
|
834
|
-
specifier: ^5.0.
|
|
835
|
-
version: 5.0.
|
|
834
|
+
specifier: ^5.0.6
|
|
835
|
+
version: 5.0.6
|
|
836
836
|
zova-module-a-router:
|
|
837
837
|
specifier: ^5.1.34
|
|
838
838
|
version: 5.1.34
|
|
@@ -849,8 +849,8 @@ importers:
|
|
|
849
849
|
specifier: ^5.1.38
|
|
850
850
|
version: 5.1.38(vue@3.5.39(typescript@5.9.3))
|
|
851
851
|
zova-module-pay-mock:
|
|
852
|
-
specifier: ^5.0.
|
|
853
|
-
version: 5.0.
|
|
852
|
+
specifier: ^5.0.4
|
|
853
|
+
version: 5.0.4
|
|
854
854
|
zova-module-rest-resource:
|
|
855
855
|
specifier: ^5.1.45
|
|
856
856
|
version: 5.1.45
|
|
@@ -8994,8 +8994,8 @@ packages:
|
|
|
8994
8994
|
zova-module-a-openapi@5.1.50:
|
|
8995
8995
|
resolution: {integrity: sha512-ZrYFZaVARpBf5ByfCtCPD4z/2ByAZn42XsKUJZuqWcvVrccY7v3kaiXoCrfQAz9aDar+wqwXOo548eAHz4wzqg==}
|
|
8996
8996
|
|
|
8997
|
-
zova-module-a-pay@5.0.
|
|
8998
|
-
resolution: {integrity: sha512
|
|
8997
|
+
zova-module-a-pay@5.0.6:
|
|
8998
|
+
resolution: {integrity: sha512-k1MXVsaN3zCgFDLQkUaUoN9W8Z02TWTsFr/h1A8ljUBAQccFiQusmt1035qvLYHidrHX64fC4R5F8SuIDLNf3w==}
|
|
8999
8999
|
|
|
9000
9000
|
zova-module-a-router@5.1.34:
|
|
9001
9001
|
resolution: {integrity: sha512-6XAia5GVmdJqUcwGW9r2vJ2JomnU0QMHK7y7QQoniUZ0JqDj7SWFbSYXd6WDlvLU7Kd5yt4WfyzE7GQVb/HcnA==}
|
|
@@ -9012,8 +9012,8 @@ packages:
|
|
|
9012
9012
|
zova-module-a-ssrhmr@5.1.23:
|
|
9013
9013
|
resolution: {integrity: sha512-BiwI/lodmJgWU5whwI4iEd3ZTbWCFIxG/V0YOejHaPqk85viFAVn9xlURNdpo3A9yFKRZDZja4IA8WuO6i0QAA==}
|
|
9014
9014
|
|
|
9015
|
-
zova-module-a-ssrserver@5.1.
|
|
9016
|
-
resolution: {integrity: sha512-
|
|
9015
|
+
zova-module-a-ssrserver@5.1.26:
|
|
9016
|
+
resolution: {integrity: sha512-1NK1hG1ruwGTvSgCGQ2fWCulTHi7PSOm8ZWVkrEQAhPnMs992IOTapKCXMl6mlGchnylfUfWRbpZ6OmzSGDICw==}
|
|
9017
9017
|
|
|
9018
9018
|
zova-module-a-style@5.1.35:
|
|
9019
9019
|
resolution: {integrity: sha512-i73I3wFvdQonj3l4fkovkKBZwSK4s6D4vL3AVfVvFJ4eIboKH2yw0hCILKRzLoESNQ0T+lNS6ozeimvtA4/6DQ==}
|
|
@@ -9027,17 +9027,17 @@ packages:
|
|
|
9027
9027
|
zova-module-a-zova@5.1.101:
|
|
9028
9028
|
resolution: {integrity: sha512-htiX0YX6kytmTiiiVuTH5lVcM5doff/AYIbiIxcoOU955q6vxrKNT4TQSGZiaAGY9cb45kr4n75nLunGxxaOPQ==}
|
|
9029
9029
|
|
|
9030
|
-
zova-module-pay-mock@5.0.
|
|
9031
|
-
resolution: {integrity: sha512-
|
|
9030
|
+
zova-module-pay-mock@5.0.4:
|
|
9031
|
+
resolution: {integrity: sha512-olXNzUK7wuNThHiE8ND/8wu9eFhMODI7l/Tcdh7/fFe5EWRhPL37HCj8gjYy1gHcuFQdN3+gim3Zqmp7JG5nFw==}
|
|
9032
9032
|
|
|
9033
9033
|
zova-module-rest-resource@5.1.45:
|
|
9034
9034
|
resolution: {integrity: sha512-QqzqBaeObbnnVdfhQZLFaU6/gSaL9BJzmzgKR6d/GkucDWezDlfISyoPstp+NhkBs1VPR4yk5nhKVG9jRlx/6w==}
|
|
9035
9035
|
|
|
9036
|
-
zova-suite-a-zova@5.1.
|
|
9037
|
-
resolution: {integrity: sha512-
|
|
9036
|
+
zova-suite-a-zova@5.1.150:
|
|
9037
|
+
resolution: {integrity: sha512-UZ/yRFWylL/MFKDvxlFYBZ6vb+WEW6NsOZSXztSfmOc8jevuZCy0I1lwgIZppR7wNWiBMdIt/jq7CqTHYhvMjw==}
|
|
9038
9038
|
|
|
9039
|
-
zova@5.1.
|
|
9040
|
-
resolution: {integrity: sha512-
|
|
9039
|
+
zova@5.1.151:
|
|
9040
|
+
resolution: {integrity: sha512-ORpFNcMYRmoWtIGIGGswPpeLgrYiax9prStB3Em0umUFGxVLPg9Yt0R1/psFlFgcC9WionitSz4WBhjvcra8Xw==}
|
|
9041
9041
|
|
|
9042
9042
|
zwitch@1.0.5:
|
|
9043
9043
|
resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==}
|
|
@@ -15401,7 +15401,7 @@ snapshots:
|
|
|
15401
15401
|
openapi3-ts: 4.6.0
|
|
15402
15402
|
typestyle: 2.4.0
|
|
15403
15403
|
|
|
15404
|
-
zova-module-a-pay@5.0.
|
|
15404
|
+
zova-module-a-pay@5.0.6: {}
|
|
15405
15405
|
|
|
15406
15406
|
zova-module-a-router@5.1.34: {}
|
|
15407
15407
|
|
|
@@ -15419,7 +15419,7 @@ snapshots:
|
|
|
15419
15419
|
'@cabloy/socket': link:packages-utils/socket
|
|
15420
15420
|
debounce: 3.0.0
|
|
15421
15421
|
|
|
15422
|
-
zova-module-a-ssrserver@5.1.
|
|
15422
|
+
zova-module-a-ssrserver@5.1.26:
|
|
15423
15423
|
dependencies:
|
|
15424
15424
|
ms: 2.1.3
|
|
15425
15425
|
|
|
@@ -15458,11 +15458,11 @@ snapshots:
|
|
|
15458
15458
|
- typescript
|
|
15459
15459
|
- vue
|
|
15460
15460
|
|
|
15461
|
-
zova-module-pay-mock@5.0.
|
|
15461
|
+
zova-module-pay-mock@5.0.4: {}
|
|
15462
15462
|
|
|
15463
15463
|
zova-module-rest-resource@5.1.45: {}
|
|
15464
15464
|
|
|
15465
|
-
zova-suite-a-zova@5.1.
|
|
15465
|
+
zova-suite-a-zova@5.1.150(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3)):
|
|
15466
15466
|
dependencies:
|
|
15467
15467
|
zova-module-a-api: 5.1.22
|
|
15468
15468
|
zova-module-a-app: 5.1.26
|
|
@@ -15484,7 +15484,7 @@ snapshots:
|
|
|
15484
15484
|
zova-module-a-routertabs: 5.1.34
|
|
15485
15485
|
zova-module-a-ssr: 5.1.37
|
|
15486
15486
|
zova-module-a-ssrhmr: 5.1.23
|
|
15487
|
-
zova-module-a-ssrserver: 5.1.
|
|
15487
|
+
zova-module-a-ssrserver: 5.1.26
|
|
15488
15488
|
zova-module-a-style: 5.1.35
|
|
15489
15489
|
zova-module-a-table: 5.1.38(vue@3.5.39(typescript@5.9.3))
|
|
15490
15490
|
zova-module-a-zod: 5.1.37
|
|
@@ -15496,10 +15496,10 @@ snapshots:
|
|
|
15496
15496
|
- typescript
|
|
15497
15497
|
- vue
|
|
15498
15498
|
|
|
15499
|
-
zova@5.1.
|
|
15499
|
+
zova@5.1.151(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3)):
|
|
15500
15500
|
dependencies:
|
|
15501
15501
|
zova-core: 5.1.86(patch_hash=144450bd7a567a6067e5857c0cf4c780981b7e90bc48745c09e1696a9b6f1b97)(typescript@5.9.3)
|
|
15502
|
-
zova-suite-a-zova: 5.1.
|
|
15502
|
+
zova-suite-a-zova: 5.1.150(typescript@5.9.3)(vue@3.5.39(typescript@5.9.3))
|
|
15503
15503
|
transitivePeerDependencies:
|
|
15504
15504
|
- '@vue/composition-api'
|
|
15505
15505
|
- debug
|
|
@@ -62,8 +62,13 @@ export class DtoRefundRecoveryView {
|
|
|
62
62
|
@Api.field(v.optional())
|
|
63
63
|
finalizedAt?: Date;
|
|
64
64
|
|
|
65
|
-
@Api.field(z.enum(['none', 'reconcile_only', 'query_only', 'retry_same_key']))
|
|
66
|
-
recoveryDisposition:
|
|
65
|
+
@Api.field(z.enum(['none', 'await_webhook', 'reconcile_only', 'query_only', 'retry_same_key']))
|
|
66
|
+
recoveryDisposition:
|
|
67
|
+
| 'none'
|
|
68
|
+
| 'await_webhook'
|
|
69
|
+
| 'reconcile_only'
|
|
70
|
+
| 'query_only'
|
|
71
|
+
| 'retry_same_key';
|
|
67
72
|
|
|
68
73
|
@Api.field(v.required())
|
|
69
74
|
recoveryMessage: string;
|
|
@@ -880,6 +880,11 @@ export class ServiceOrder extends BeanBase {
|
|
|
880
880
|
};
|
|
881
881
|
}
|
|
882
882
|
|
|
883
|
+
private _providerSupportsRefundQuery(providerName: string, clientName: string) {
|
|
884
|
+
const { provider } = this.bean.payProvider.resolveByName(providerName, clientName);
|
|
885
|
+
return typeof provider.queryRefund === 'function';
|
|
886
|
+
}
|
|
887
|
+
|
|
883
888
|
private _refundRecoveryView(
|
|
884
889
|
linked: Awaited<ReturnType<typeof this.refundRecoveryLinkage>>,
|
|
885
890
|
): DtoRefundRecoveryView {
|
|
@@ -893,7 +898,14 @@ export class ServiceOrder extends BeanBase {
|
|
|
893
898
|
hasUnresolvedReconciliation,
|
|
894
899
|
} = linked;
|
|
895
900
|
const canQuery = Boolean(
|
|
896
|
-
refundOperation.providerRefundId &&
|
|
901
|
+
refundOperation.providerRefundId &&
|
|
902
|
+
providerOperation.state !== 'succeeded' &&
|
|
903
|
+
this._providerSupportsRefundQuery(paymentSession.providerName, paymentSession.clientName),
|
|
904
|
+
);
|
|
905
|
+
const canAwaitWebhook = Boolean(
|
|
906
|
+
refundOperation.providerRefundId &&
|
|
907
|
+
providerOperation.state !== 'succeeded' &&
|
|
908
|
+
!this._providerSupportsRefundQuery(paymentSession.providerName, paymentSession.clientName),
|
|
897
909
|
);
|
|
898
910
|
const canRetry =
|
|
899
911
|
!refundOperation.providerRefundId &&
|
|
@@ -909,21 +921,25 @@ export class ServiceOrder extends BeanBase {
|
|
|
909
921
|
providerOperation.errorCode === 'refund_submission_outcome_unknown' &&
|
|
910
922
|
Boolean(providerOperation.submittedAt) &&
|
|
911
923
|
!hasUnresolvedReconciliation;
|
|
912
|
-
const recoveryDisposition =
|
|
913
|
-
? '
|
|
914
|
-
:
|
|
915
|
-
? '
|
|
916
|
-
:
|
|
917
|
-
? '
|
|
918
|
-
:
|
|
924
|
+
const recoveryDisposition = canAwaitWebhook
|
|
925
|
+
? 'await_webhook'
|
|
926
|
+
: canQuery
|
|
927
|
+
? 'query_only'
|
|
928
|
+
: canRetry
|
|
929
|
+
? 'retry_same_key'
|
|
930
|
+
: canReconcile
|
|
931
|
+
? 'reconcile_only'
|
|
932
|
+
: 'none';
|
|
919
933
|
const recoveryMessage =
|
|
920
|
-
recoveryDisposition === '
|
|
921
|
-
? 'A
|
|
922
|
-
: recoveryDisposition === '
|
|
923
|
-
? '
|
|
924
|
-
: recoveryDisposition === '
|
|
925
|
-
? '
|
|
926
|
-
:
|
|
934
|
+
recoveryDisposition === 'await_webhook'
|
|
935
|
+
? 'A provider refund identifier is available. Wait for the verified provider webhook; do not resubmit or reconcile by querying.'
|
|
936
|
+
: recoveryDisposition === 'query_only'
|
|
937
|
+
? 'A verified provider refund identifier is available. Reconcile without resubmitting.'
|
|
938
|
+
: recoveryDisposition === 'reconcile_only'
|
|
939
|
+
? 'Provider refund outcome is unknown. Record reconciliation before considering a retry.'
|
|
940
|
+
: recoveryDisposition === 'retry_same_key'
|
|
941
|
+
? 'Reconciliation was unresolved. A single acknowledged retry may reuse the original provider request.'
|
|
942
|
+
: 'No provider refund submission is available. Do not create or manually declare a replacement refund.';
|
|
927
943
|
return {
|
|
928
944
|
orderId: order.id,
|
|
929
945
|
refundRequestId: refundRequest.id,
|
|
@@ -217,7 +217,11 @@ export class ServiceAccount extends BeanBase {
|
|
|
217
217
|
const link = this._getActivationLinkWithToken(consumerLink, rawToken);
|
|
218
218
|
await this.bean.mail.send({
|
|
219
219
|
to: currentUser.email,
|
|
220
|
-
subject: this.scope.locale.ActivationEmailSubject(),
|
|
220
|
+
subject: replaceTemplate(this.scope.locale.ActivationEmailSubject(), {
|
|
221
|
+
userName: currentUser.name,
|
|
222
|
+
link,
|
|
223
|
+
siteName: this.ctx.instance.title || this.app.meta.env.APP_TITLE,
|
|
224
|
+
}),
|
|
221
225
|
text: replaceTemplate(this.scope.locale.ActivationEmailBody(), {
|
|
222
226
|
userName: currentUser.name,
|
|
223
227
|
link,
|
|
@@ -559,12 +563,21 @@ export class ServiceAccount extends BeanBase {
|
|
|
559
563
|
|
|
560
564
|
private _getActivationLink(consumerUrl: string): URL {
|
|
561
565
|
const url = this._getConsumerLink(consumerUrl, 'activation');
|
|
562
|
-
if (url.pathname
|
|
566
|
+
if (!this._isActivationConsumerPath(url.pathname)) {
|
|
563
567
|
this.app.throw(503, 'account activation consumer URL is invalid');
|
|
564
568
|
}
|
|
565
569
|
return url;
|
|
566
570
|
}
|
|
567
571
|
|
|
572
|
+
private _isActivationConsumerPath(pathname: string) {
|
|
573
|
+
if (pathname === activationConsumerPath) return true;
|
|
574
|
+
const sites = this.app.scope('a-ssr').service.ssr.getSitesEnabled();
|
|
575
|
+
return sites.some(site => {
|
|
576
|
+
const publicPath = String(site.beanOptions.options?.publicPath ?? '');
|
|
577
|
+
return publicPath && pathname === `/${publicPath}${activationConsumerPath}`;
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
|
|
568
581
|
private _getPasswordSetLink(consumerUrl: string): URL {
|
|
569
582
|
return this._getConsumerLink(consumerUrl, 'password-set');
|
|
570
583
|
}
|
|
@@ -5,6 +5,7 @@ import { acquireTestLock, app } from 'vona-mock';
|
|
|
5
5
|
|
|
6
6
|
const activationConsumePath = '/home/user/account/activation/consume';
|
|
7
7
|
const activationConsumerUrl = 'https://account.example.test/home/user/activation';
|
|
8
|
+
const activationConsumerUrlAdmin = 'https://account.example.test/admin/home/user/activation';
|
|
8
9
|
|
|
9
10
|
describe('accountActivation.test.ts', { concurrency: false, sequential: true }, () => {
|
|
10
11
|
const releases: Array<() => void> = [];
|
|
@@ -73,6 +74,26 @@ describe('accountActivation.test.ts', { concurrency: false, sequential: true },
|
|
|
73
74
|
}
|
|
74
75
|
});
|
|
75
76
|
|
|
77
|
+
it('preserves an enabled SSR site public path in activation links', async () => {
|
|
78
|
+
let fixture: IFixture | undefined;
|
|
79
|
+
let restoreSites: (() => void) | undefined;
|
|
80
|
+
try {
|
|
81
|
+
fixture = await createFixture();
|
|
82
|
+
restoreSites = configureAdminSsrSite();
|
|
83
|
+
const issued = await issueActivationLink(fixture, activationConsumerUrlAdmin);
|
|
84
|
+
assert.equal(issued.path, '/admin/home/user/activation');
|
|
85
|
+
assert.deepEqual(await getActivationState(getDigest(issued.token)), {
|
|
86
|
+
purpose: 'account-activation',
|
|
87
|
+
userId: fixture.userId,
|
|
88
|
+
consumerPath: '/home/user/activation',
|
|
89
|
+
email: fixture.email,
|
|
90
|
+
});
|
|
91
|
+
} finally {
|
|
92
|
+
restoreSites?.();
|
|
93
|
+
if (fixture) await removeFixture(fixture);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
76
97
|
it('issues an activation link through registration and activates the registered user', async () => {
|
|
77
98
|
let registration: IRegistration | undefined;
|
|
78
99
|
try {
|
|
@@ -175,6 +196,17 @@ interface IRegistration extends IFixture {
|
|
|
175
196
|
path: string;
|
|
176
197
|
}
|
|
177
198
|
|
|
199
|
+
function configureAdminSsrSite() {
|
|
200
|
+
const service = app.scope('a-ssr').service.ssr;
|
|
201
|
+
const getSitesEnabled = service.getSitesEnabled;
|
|
202
|
+
service.getSitesEnabled = (() => [
|
|
203
|
+
{ beanOptions: { options: { publicPath: 'admin' } } },
|
|
204
|
+
]) as typeof service.getSitesEnabled;
|
|
205
|
+
return () => {
|
|
206
|
+
service.getSitesEnabled = getSitesEnabled;
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
178
210
|
async function configureActivation() {
|
|
179
211
|
return await app.bean.executor.mockCtx(async () => {
|
|
180
212
|
const options =
|
|
@@ -288,7 +320,7 @@ async function removeFixture(fixture: IFixture) {
|
|
|
288
320
|
});
|
|
289
321
|
}
|
|
290
322
|
|
|
291
|
-
async function issueActivationLink(fixture: IFixture) {
|
|
323
|
+
async function issueActivationLink(fixture: IFixture, consumerUrl = activationConsumerUrl) {
|
|
292
324
|
let text: unknown;
|
|
293
325
|
const mail = app.bean.mail;
|
|
294
326
|
const send = mail.send;
|
|
@@ -297,9 +329,7 @@ async function issueActivationLink(fixture: IFixture) {
|
|
|
297
329
|
};
|
|
298
330
|
try {
|
|
299
331
|
await app.bean.executor.mockCtx(async () => {
|
|
300
|
-
await app
|
|
301
|
-
.scope('home-user')
|
|
302
|
-
.service.account.issueActivationLink(fixture.userId, activationConsumerUrl);
|
|
332
|
+
await app.scope('home-user').service.account.issueActivationLink(fixture.userId, consumerUrl);
|
|
303
333
|
});
|
|
304
334
|
} finally {
|
|
305
335
|
mail.send = send;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vona-module-a-markdown",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Vona Module"
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"title": "a-markdown",
|
|
59
59
|
"vonaModule": {
|
|
60
60
|
"dependencies": {
|
|
61
|
+
"a-image": "5.0.0",
|
|
61
62
|
"a-vona": "5.0.0"
|
|
62
63
|
}
|
|
63
64
|
}
|