cabloy 5.1.125 → 5.1.127
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/.claude/skills/cabloy-backend-scaffold/SKILL.md +1 -1
- package/.claude/skills/cabloy-backend-scaffold/evals/evals.json +6 -0
- package/.claude/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +13 -4
- package/.claude/skills/cabloy-workflow/SKILL.md +4 -2
- package/.claude/skills/cabloy-workflow/evals/evals.json +14 -2
- package/.claude/skills/cabloy-worktree-environment/SKILL.md +206 -0
- package/.claude/skills/cabloy-worktree-environment/evals/evals.json +77 -0
- package/.github/workflows/vona-test-pg.yml +3 -0
- package/.github/workflows/vona-test-sqlite3.yml +3 -0
- package/CHANGELOG.md +36 -0
- package/CLAUDE.md +1 -1
- package/cabloy-docs/.vitepress/config.mjs +1 -0
- package/cabloy-docs/backend/backend-contract-emission-specimen.md +2 -2
- package/cabloy-docs/backend/dto-guide.md +10 -7
- package/cabloy-docs/backend/dto-infer-generation.md +127 -7
- package/cabloy-docs/backend/error-guide.md +17 -0
- package/cabloy-docs/backend/foundation.md +1 -1
- package/cabloy-docs/backend/introduction.md +1 -0
- package/cabloy-docs/backend/module-dependencies.md +104 -0
- package/cabloy-docs/backend/serialization-guide.md +6 -0
- package/cabloy-docs/fullstack/parallel-worktree-environment.md +72 -34
- package/cabloy-docs/fullstack/suites-and-modules.md +1 -0
- package/cabloy-docs/reference/package-map.md +1 -0
- package/e2e/specs/a-commerce/commerce.spec.ts +224 -4
- package/package.json +2 -1
- package/scripts/init.ts +7 -12
- package/scripts/initAppName.test.ts +72 -0
- package/scripts/initAppName.ts +50 -0
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- 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/bean/resource/error/errorClass.ts +21 -4
- package/vona/packages-vona/vona-core/src/lib/bean/resource/error/errorObject.ts +1 -0
- package/vona/packages-vona/vona-core/src/lib/bean/type.ts +3 -1
- package/vona/packages-vona/vona-core/src/types/interface/module.ts +6 -1
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/pnpm-lock.yaml +27 -147
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/index.ts +36 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/en-us.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/zh-cn.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/categorySelectResItem.tsx +3 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/categoryView.tsx +3 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/entity/category.tsx +21 -2
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/model/category.ts +11 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/service/category.ts +7 -2
- package/vona/src/suite/a-commerce/modules/commerce-catalog/test/categoryPresentation.test.ts +218 -0
- package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressMineCreate.tsx +6 -27
- package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressMineItem.tsx +6 -31
- package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressMineUpdate.tsx +6 -2
- package/vona/src/suite/a-commerce/modules/commerce-member/src/lib/addressMine.ts +16 -0
- package/vona/src/suite/a-commerce/modules/commerce-member/src/service/address.ts +4 -17
- package/vona/src/suite/a-commerce/modules/commerce-member/test/addressOwnership.test.ts +63 -19
- package/vona/src/suite/a-commerce/modules/commerce-payment/package.json +2 -1
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/payScene.commerceOrder.ts +11 -2
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/service/commercePayScene.ts +26 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/test/paymentAttempt.test.ts +19 -2
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/config/locale/en-us.ts +5 -2
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/config/locale/zh-cn.ts +5 -2
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponTemplateCreate.tsx +26 -35
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponTemplateUpdate.tsx +6 -1
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/entity/couponTemplate.tsx +40 -4
- package/vona/src/suite/a-commerce/modules/commerce-promotion/test/couponTemplateFormLayout.test.ts +85 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +1 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/controller/order.ts +3 -5
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderSummary.tsx +7 -40
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/refundResult.tsx +3 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/shipmentView.tsx +6 -15
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/lib/order.ts +9 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/order.ts +123 -12
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/order.test.ts +25 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/paymentOutcome.test.ts +19 -2
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/refundLifecycle.test.ts +111 -3
- package/vona/src/suite/a-training/modules/training-record/src/dto/detailRecordSubjectResItem.tsx +6 -1
- package/vona/src/suite/a-training/modules/training-record/src/dto/recordSelectResItem.tsx +3 -1
- package/vona/src/suite/a-training/modules/training-record/src/dto/recordView.tsx +4 -1
- package/vona/src/suite/a-training/modules/training-record/test/record.test.ts +45 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/detailRecordResItem.tsx +6 -1
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentSummary.tsx +5 -23
- package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +37 -5
- 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/.metadata/index.ts +29 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/meta.index.ts +3 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/meta.redlock.ts +3 -1
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/meta.version.ts +7 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/queue.outboxDispatch.ts +9 -4
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/schedule.providerOperationDispatch.ts +11 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/outboxEvent.tsx +11 -4
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/providerOperation.tsx +12 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/webhookInbox.tsx +6 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/outbox.ts +12 -5
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/paymentSession.ts +1 -37
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/providerOperation.ts +355 -1
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/refundOperation.ts +226 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/webhook.ts +148 -50
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/types/payScene.ts +2 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/.metadata/index.ts +39 -22
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/bean/payProvider.mock.ts +42 -13
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/controller/mockPayment.ts +11 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/dto/mockRefundComplete.tsx +13 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/dto/mockRefundReceipt.tsx +16 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/service/payMock.ts +56 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/package.json +2 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/bean/payProvider.paypal.ts +117 -8
- 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/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- 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/lib/dto/dtoGet.ts +5 -5
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/src/suite/a-commerce/modules/commerce-member/src/api/openapi/schemas.ts +58 -12
- package/zova/src/suite/a-commerce/modules/commerce-member/src/api/openapi/types.ts +673 -66
- package/zova/src/suite/a-commerce/modules/commerce-promotion/src/api/openapi/types.ts +55 -15
- package/zova/src/suite/a-commerce/modules/commerce-trade/cli/openapi.config.ts +1 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/commerceTradeOrder.ts +15 -17
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/schemas.ts +12 -8
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +116 -30
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/apiSchema/commerceTradeOrder.ts +3 -3
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/component/tableCellActionRefund/controller.tsx +1 -8
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/order.ts +5 -9
- package/zova/src/suite/a-training/modules/training-student/src/.metadata/index.ts +1 -1
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/schemas.ts +301 -17
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +5461 -1788
- package/zova/src/suite/cabloy-basic/modules/basic-select/src/bean/tableCell.select.tsx +12 -6
- package/zova/src/suite/cabloy-basic/modules/basic-select/src/component/formFieldSelect/controller.tsx +6 -3
|
@@ -535,7 +535,9 @@ test(
|
|
|
535
535
|
await orderCard.getByRole('button', { name: 'View', exact: true }).click();
|
|
536
536
|
await expect(page).toHaveURL(new RegExp(`/commerce/order/${checkout.orderId}(?:/|$)`));
|
|
537
537
|
await historyDetailResponse;
|
|
538
|
-
await expect(
|
|
538
|
+
await expect(
|
|
539
|
+
page.getByRole('heading', { name: `Order #${checkout.orderId}`, level: 1 }),
|
|
540
|
+
).toBeVisible();
|
|
539
541
|
await expect(page.getByText('1 × $45.99 = $45.99')).toBeVisible();
|
|
540
542
|
await expect(page.getByRole('alert')).toHaveCount(0);
|
|
541
543
|
expect(pageErrors).toEqual([]);
|
|
@@ -705,11 +707,39 @@ test(
|
|
|
705
707
|
const executeResponse = waitForApiResponse(
|
|
706
708
|
adminPage,
|
|
707
709
|
'POST',
|
|
708
|
-
`/api/commerce/trade/order/${checkout.orderId}/
|
|
710
|
+
`/api/commerce/trade/order/${checkout.orderId}/executeRefund`,
|
|
709
711
|
);
|
|
710
712
|
await orderRow.getByRole('button', { name: 'Execute refund', exact: true }).click();
|
|
711
|
-
|
|
712
|
-
|
|
713
|
+
const executeResponseValue = await executeResponse;
|
|
714
|
+
expect(executeResponseValue.ok()).toBeTruthy();
|
|
715
|
+
const executeResult = (await executeResponseValue.json()).data;
|
|
716
|
+
const authorization = executeResponseValue.request().headers()['authorization'];
|
|
717
|
+
expect(authorization).toMatch(/^Bearer /);
|
|
718
|
+
const authHeaders = { Authorization: authorization };
|
|
719
|
+
expectTableIdentity(executeResult.refundAttemptId);
|
|
720
|
+
expectTableIdentity(executeResult.refundOperationId);
|
|
721
|
+
expect([
|
|
722
|
+
executeResult.orderState,
|
|
723
|
+
executeResult.refundState,
|
|
724
|
+
executeResult.refundAttemptState,
|
|
725
|
+
]).toEqual(['refund_approved', 'approved', 'created']);
|
|
726
|
+
const completeRefundResponse = await adminPage.request.post(
|
|
727
|
+
`/api/pay/mock/payment-session/refund-operation/${executeResult.refundOperationId}/complete`,
|
|
728
|
+
{ headers: authHeaders, data: { outcome: 'succeeded' } },
|
|
729
|
+
);
|
|
730
|
+
expect(completeRefundResponse.ok()).toBeTruthy();
|
|
731
|
+
await expect
|
|
732
|
+
.poll(
|
|
733
|
+
async () => {
|
|
734
|
+
await adminPage.reload({ waitUntil: 'load' });
|
|
735
|
+
const currentOrderRow = await getAdminOrderRow(adminPage, checkout.orderId);
|
|
736
|
+
return (await currentOrderRow.textContent()) ?? '';
|
|
737
|
+
},
|
|
738
|
+
{ timeout: 40_000 },
|
|
739
|
+
)
|
|
740
|
+
.toContain('refunded');
|
|
741
|
+
const refundedOrderRow = await getAdminOrderRow(adminPage, checkout.orderId);
|
|
742
|
+
await expect(refundedOrderRow).toContainText('refunded');
|
|
713
743
|
expect(adminPageErrors).toEqual([]);
|
|
714
744
|
} finally {
|
|
715
745
|
await adminContext.close().catch(() => {});
|
|
@@ -796,6 +826,196 @@ test(
|
|
|
796
826
|
},
|
|
797
827
|
);
|
|
798
828
|
|
|
829
|
+
test(
|
|
830
|
+
'ATP-SPC-01: Coupon Template renders semantic Admin field controls',
|
|
831
|
+
{ tag: ['@admin', '@flow', '@fia'] },
|
|
832
|
+
async ({ browser }) => {
|
|
833
|
+
test.setTimeout(60_000);
|
|
834
|
+
const adminContext = await browser.newContext();
|
|
835
|
+
const adminPage = await adminContext.newPage();
|
|
836
|
+
const adminPageErrors = collectPageErrors(adminPage);
|
|
837
|
+
try {
|
|
838
|
+
await adminPage.setViewportSize({ width: 1440, height: 900 });
|
|
839
|
+
await login(adminPage, '/commerce-admin/', 'admin', '123456', 'commerceAdmin');
|
|
840
|
+
await adminPage.goto(
|
|
841
|
+
'/commerce-admin/rest/resource/commerce-promotion%3AcouponTemplate/create',
|
|
842
|
+
{
|
|
843
|
+
waitUntil: 'load',
|
|
844
|
+
},
|
|
845
|
+
);
|
|
846
|
+
await expect(adminPage).toHaveURL(
|
|
847
|
+
/\/commerce-admin\/rest\/resource\/commerce-promotion(?:%3A|:|%253A)couponTemplate\/create(?:[/?#]|$)/,
|
|
848
|
+
);
|
|
849
|
+
|
|
850
|
+
for (const groupName of [
|
|
851
|
+
'Basic Information',
|
|
852
|
+
'Discount Policy',
|
|
853
|
+
'Validity Window',
|
|
854
|
+
'Usage Limits',
|
|
855
|
+
]) {
|
|
856
|
+
await expect(adminPage.getByRole('group', { name: groupName })).toBeVisible();
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
const state = adminPage
|
|
860
|
+
.getByRole('group', { name: 'Template State *' })
|
|
861
|
+
.getByRole('combobox');
|
|
862
|
+
await expect(state).toBeVisible();
|
|
863
|
+
await state.selectOption({ label: 'Active' });
|
|
864
|
+
await expect(state).toHaveValue('active');
|
|
865
|
+
|
|
866
|
+
const discountInput = adminPage
|
|
867
|
+
.getByRole('group', { name: 'Fixed Discount *' })
|
|
868
|
+
.getByRole('textbox');
|
|
869
|
+
const minSpendInput = adminPage
|
|
870
|
+
.getByRole('group', { name: 'Minimum Spend *' })
|
|
871
|
+
.getByRole('textbox');
|
|
872
|
+
await discountInput.fill('12.34');
|
|
873
|
+
await minSpendInput.fill('45.67');
|
|
874
|
+
await expect(discountInput).toHaveValue('12.34');
|
|
875
|
+
await expect(minSpendInput).toHaveValue('45.67');
|
|
876
|
+
await expect(
|
|
877
|
+
adminPage.getByRole('group', { name: 'Valid From *' }).locator('input[type="date"]'),
|
|
878
|
+
).toBeVisible();
|
|
879
|
+
await expect(
|
|
880
|
+
adminPage.getByRole('group', { name: 'Valid Until *' }).locator('input[type="date"]'),
|
|
881
|
+
).toBeVisible();
|
|
882
|
+
|
|
883
|
+
await adminPage
|
|
884
|
+
.getByRole('group', { name: 'Name *' })
|
|
885
|
+
.getByRole('textbox')
|
|
886
|
+
.fill('E2E Coupon Template');
|
|
887
|
+
await adminPage.getByRole('group', { name: 'Currency *' }).getByRole('textbox').fill('USD');
|
|
888
|
+
await adminPage
|
|
889
|
+
.getByRole('group', { name: 'Valid From *' })
|
|
890
|
+
.getByRole('textbox')
|
|
891
|
+
.fill('2026-08-04');
|
|
892
|
+
await adminPage
|
|
893
|
+
.getByRole('group', { name: 'Valid Until *' })
|
|
894
|
+
.getByRole('textbox')
|
|
895
|
+
.fill('2026-12-31');
|
|
896
|
+
await adminPage
|
|
897
|
+
.getByRole('group', { name: 'Total Issue Limit' })
|
|
898
|
+
.getByRole('textbox')
|
|
899
|
+
.fill('10');
|
|
900
|
+
await adminPage
|
|
901
|
+
.getByRole('group', { name: 'Total Usage Limit' })
|
|
902
|
+
.getByRole('textbox')
|
|
903
|
+
.fill('10');
|
|
904
|
+
await adminPage
|
|
905
|
+
.getByRole('group', { name: 'Per-customer Issue Limit' })
|
|
906
|
+
.getByRole('textbox')
|
|
907
|
+
.fill('1');
|
|
908
|
+
|
|
909
|
+
await expect(adminPage.getByRole('button', { name: 'Submit', exact: true })).toBeVisible();
|
|
910
|
+
await expect(adminPage.getByRole('button', { name: 'Back', exact: true })).toBeVisible();
|
|
911
|
+
expect(adminPageErrors).toEqual([]);
|
|
912
|
+
} finally {
|
|
913
|
+
await adminContext.close().catch(() => {});
|
|
914
|
+
}
|
|
915
|
+
},
|
|
916
|
+
);
|
|
917
|
+
|
|
918
|
+
test(
|
|
919
|
+
'ATP-SPC-02: Category renders semantic Admin relation and publication controls',
|
|
920
|
+
{ tag: ['@admin', '@flow', '@category'] },
|
|
921
|
+
async ({ browser }, testInfo) => {
|
|
922
|
+
test.setTimeout(60_000);
|
|
923
|
+
const suffix = `${testInfo.workerIndex}-${testInfo.parallelIndex ?? testInfo.retry}-${Date.now()}`;
|
|
924
|
+
const parentName = `E2E Category Parent ${suffix}`;
|
|
925
|
+
const childName = `E2E Category Child ${suffix}`;
|
|
926
|
+
const adminContext = await browser.newContext();
|
|
927
|
+
const adminPage = await adminContext.newPage();
|
|
928
|
+
const adminPageErrors = collectPageErrors(adminPage);
|
|
929
|
+
const categoryCreatePath = '/commerce-admin/rest/resource/commerce-catalog%3Acategory/create';
|
|
930
|
+
const categoryListPath = '/commerce-admin/rest/resource/commerce-catalog%3Acategory';
|
|
931
|
+
const categoryActionPath = '/api/commerce/catalog/category';
|
|
932
|
+
let parentId: number | string | undefined;
|
|
933
|
+
let childId: number | string | undefined;
|
|
934
|
+
let headers: { Authorization: string } | undefined;
|
|
935
|
+
try {
|
|
936
|
+
await adminPage.setViewportSize({ width: 1440, height: 900 });
|
|
937
|
+
await login(adminPage, '/commerce-admin/', 'admin', '123456', 'commerceAdmin');
|
|
938
|
+
const accessToken = (await adminContext.cookies()).find(
|
|
939
|
+
cookie => cookie.name === 'token',
|
|
940
|
+
)?.value;
|
|
941
|
+
expect(accessToken).toBeTruthy();
|
|
942
|
+
headers = { Authorization: `Bearer ${accessToken}` };
|
|
943
|
+
|
|
944
|
+
const parentCreateResponse = await adminPage.request.post(categoryActionPath, {
|
|
945
|
+
data: { name: parentName, published: false },
|
|
946
|
+
headers,
|
|
947
|
+
});
|
|
948
|
+
expect(parentCreateResponse.ok()).toBeTruthy();
|
|
949
|
+
parentId = (await parentCreateResponse.json()).data;
|
|
950
|
+
expectTableIdentity(parentId);
|
|
951
|
+
|
|
952
|
+
await adminPage.goto(categoryCreatePath, { waitUntil: 'load' });
|
|
953
|
+
const parentPublication = adminPage
|
|
954
|
+
.getByRole('group', { name: 'Published' })
|
|
955
|
+
.getByRole('combobox');
|
|
956
|
+
await expect(parentPublication).toBeVisible();
|
|
957
|
+
await expect(parentPublication.locator('option')).toHaveText([
|
|
958
|
+
'',
|
|
959
|
+
'Unpublished',
|
|
960
|
+
'Published',
|
|
961
|
+
]);
|
|
962
|
+
await parentPublication.selectOption({ label: 'Unpublished' });
|
|
963
|
+
await expect(parentPublication).toHaveValue('false');
|
|
964
|
+
|
|
965
|
+
const parentPicker = adminPage
|
|
966
|
+
.getByRole('group', { name: 'Parent category' })
|
|
967
|
+
.getByRole('combobox');
|
|
968
|
+
await expect(parentPicker).toBeVisible();
|
|
969
|
+
await parentPicker.selectOption({ label: parentName });
|
|
970
|
+
const parentValue = await parentPicker.inputValue();
|
|
971
|
+
expect(parentValue).toBe(String(parentId));
|
|
972
|
+
const childPublication = adminPage
|
|
973
|
+
.getByRole('group', { name: 'Published' })
|
|
974
|
+
.getByRole('combobox');
|
|
975
|
+
await childPublication.selectOption({ label: 'Published' });
|
|
976
|
+
await expect(childPublication).toHaveValue('true');
|
|
977
|
+
|
|
978
|
+
const childCreateResponse = await adminPage.request.post(categoryActionPath, {
|
|
979
|
+
data: { name: childName, parentId: parentValue, published: true },
|
|
980
|
+
headers,
|
|
981
|
+
});
|
|
982
|
+
expect(childCreateResponse.ok()).toBeTruthy();
|
|
983
|
+
childId = (await childCreateResponse.json()).data;
|
|
984
|
+
expectTableIdentity(childId);
|
|
985
|
+
|
|
986
|
+
await adminPage.goto(categoryListPath, { waitUntil: 'load' });
|
|
987
|
+
const childRow = adminPage.getByRole('row', { name: new RegExp(childName) });
|
|
988
|
+
await expect(childRow).toBeVisible();
|
|
989
|
+
await expect(childRow.getByText(parentName, { exact: true })).toBeVisible();
|
|
990
|
+
await expect(childRow.getByText('Published', { exact: true })).toBeVisible();
|
|
991
|
+
|
|
992
|
+
await adminPage.goto(`${categoryListPath}/${childId}`, { waitUntil: 'load' });
|
|
993
|
+
await expect(
|
|
994
|
+
adminPage.getByRole('group', { name: 'Parent category' }).getByRole('textbox'),
|
|
995
|
+
).toHaveValue(parentName);
|
|
996
|
+
await expect(
|
|
997
|
+
adminPage.getByRole('group', { name: 'Published' }).getByRole('textbox'),
|
|
998
|
+
).toHaveValue('Published');
|
|
999
|
+
await expect(adminPage.getByRole('button', { name: 'Submit', exact: true })).toHaveCount(0);
|
|
1000
|
+
expect(adminPageErrors).toEqual([]);
|
|
1001
|
+
} finally {
|
|
1002
|
+
if (childId && headers) {
|
|
1003
|
+
const response = await adminPage.request.delete(`${categoryActionPath}/${childId}`, {
|
|
1004
|
+
headers,
|
|
1005
|
+
});
|
|
1006
|
+
expect(response.ok()).toBeTruthy();
|
|
1007
|
+
}
|
|
1008
|
+
if (parentId && headers) {
|
|
1009
|
+
const response = await adminPage.request.delete(`${categoryActionPath}/${parentId}`, {
|
|
1010
|
+
headers,
|
|
1011
|
+
});
|
|
1012
|
+
expect(response.ok()).toBeTruthy();
|
|
1013
|
+
}
|
|
1014
|
+
await adminContext.close().catch(() => {});
|
|
1015
|
+
}
|
|
1016
|
+
},
|
|
1017
|
+
);
|
|
1018
|
+
|
|
799
1019
|
test(
|
|
800
1020
|
'Commerce Cart: anonymous browser is redirected to login',
|
|
801
1021
|
{ tag: ['@web', '@cart'] },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cabloy",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.127",
|
|
4
4
|
"gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
|
|
5
5
|
"description": "A Node.js fullstack framework",
|
|
6
6
|
"keywords": [
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"scripts": {
|
|
24
24
|
"init": "node scripts/init.ts",
|
|
25
25
|
"init:test-data": "node scripts/initTestData.ts",
|
|
26
|
+
"test:scripts": "node --test scripts/initAppName.test.ts",
|
|
26
27
|
"upgrade": "node scripts/upgrade.ts",
|
|
27
28
|
"upgrade:dry-run": "node scripts/upgrade.ts --dry-run",
|
|
28
29
|
"vona": "node ./vona/packages-cli/cli/src/bin/vona.ts --projectPath=vona",
|
package/scripts/init.ts
CHANGED
|
@@ -9,10 +9,11 @@ import {
|
|
|
9
9
|
rmSync,
|
|
10
10
|
writeFileSync,
|
|
11
11
|
} from 'node:fs';
|
|
12
|
-
import { basename } from 'node:path';
|
|
13
12
|
import { dirname, resolve } from 'node:path';
|
|
14
13
|
import { fileURLToPath } from 'node:url';
|
|
15
14
|
|
|
15
|
+
import { updateBaseAppName } from './initAppName.ts';
|
|
16
|
+
|
|
16
17
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
18
|
const ROOT_DIR = resolve(__dirname, '..');
|
|
18
19
|
const VONA_DIR = resolve(ROOT_DIR, 'vona');
|
|
@@ -141,19 +142,13 @@ function seedVonaZovaRestWorkspaceDependencies(pkgPath: string): void {
|
|
|
141
142
|
console.log('[init] Seeded Vona .zova-rest dependencies');
|
|
142
143
|
}
|
|
143
144
|
|
|
144
|
-
// --- Step 0: Set APP_NAME in .env files ---
|
|
145
|
+
// --- Step 0: Set APP_NAME in base .env files ---
|
|
145
146
|
|
|
146
147
|
function setAppName(): void {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
let content = readFileSync(filePath, 'utf-8');
|
|
152
|
-
content = content.replace(/^APP_NAME.*/m, `APP_NAME = ${projectName}`);
|
|
153
|
-
writeFileSync(filePath, content);
|
|
154
|
-
// eslint-disable-next-line
|
|
155
|
-
console.log(`[init] Set APP_NAME = ${projectName} in ${filePath}`);
|
|
156
|
-
}
|
|
148
|
+
updateBaseAppName(ROOT_DIR);
|
|
149
|
+
// if (updateBaseAppName(ROOT_DIR)) return;
|
|
150
|
+
// eslint-disable-next-line
|
|
151
|
+
// console.log('[init] Linked Git worktree detected; preserving base APP_NAME defaults');
|
|
157
152
|
}
|
|
158
153
|
|
|
159
154
|
// --- Step A: Generate vona/env/.env.prod.local ---
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { basename, resolve } from 'node:path';
|
|
6
|
+
import test from 'node:test';
|
|
7
|
+
|
|
8
|
+
import { isLinkedGitWorktree, updateBaseAppName } from './initAppName.ts';
|
|
9
|
+
|
|
10
|
+
function git(cwd: string, args: string[]): void {
|
|
11
|
+
execFileSync('git', args, { cwd, stdio: 'ignore' });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function writeBaseEnvFiles(rootDir: string, appName: string): void {
|
|
15
|
+
for (const relativePath of ['vona/env/.env', 'zova/env/.env']) {
|
|
16
|
+
const filePath = resolve(rootDir, relativePath);
|
|
17
|
+
mkdirSync(resolve(filePath, '..'), { recursive: true });
|
|
18
|
+
writeFileSync(filePath, `APP_NAME = ${appName}\nOTHER_VALUE = preserved\n`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readBaseEnvFiles(rootDir: string): string[] {
|
|
23
|
+
return ['vona/env/.env', 'zova/env/.env'].map(relativePath =>
|
|
24
|
+
readFileSync(resolve(rootDir, relativePath), 'utf-8'),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
test('classifies project roots before updating base APP_NAME files', { concurrency: false }, () => {
|
|
29
|
+
const tempDir = mkdtempSync(resolve(tmpdir(), 'cabloy-init-app-name-'));
|
|
30
|
+
const primaryDir = resolve(tempDir, 'primary');
|
|
31
|
+
const linkedDir = resolve(tempDir, 'linked');
|
|
32
|
+
try {
|
|
33
|
+
mkdirSync(primaryDir);
|
|
34
|
+
git(primaryDir, ['init']);
|
|
35
|
+
git(primaryDir, ['config', 'user.email', 'test@example.com']);
|
|
36
|
+
git(primaryDir, ['config', 'user.name', 'Cabloy Test']);
|
|
37
|
+
writeBaseEnvFiles(primaryDir, 'original');
|
|
38
|
+
git(primaryDir, ['add', '.']);
|
|
39
|
+
git(primaryDir, ['commit', '-m', 'initial fixture']);
|
|
40
|
+
|
|
41
|
+
assert.equal(isLinkedGitWorktree(primaryDir), false);
|
|
42
|
+
assert.equal(updateBaseAppName(primaryDir), true);
|
|
43
|
+
for (const content of readBaseEnvFiles(primaryDir)) {
|
|
44
|
+
assert.equal(content, `APP_NAME = ${basename(primaryDir)}\nOTHER_VALUE = preserved\n`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
git(primaryDir, ['checkout', '--', '.']);
|
|
48
|
+
git(primaryDir, ['worktree', 'add', '--detach', linkedDir, 'HEAD']);
|
|
49
|
+
const linkedBefore = readBaseEnvFiles(linkedDir);
|
|
50
|
+
assert.equal(isLinkedGitWorktree(linkedDir), true);
|
|
51
|
+
assert.equal(updateBaseAppName(linkedDir), false);
|
|
52
|
+
assert.deepEqual(readBaseEnvFiles(linkedDir), linkedBefore);
|
|
53
|
+
|
|
54
|
+
const nestedProjectDir = resolve(linkedDir, 'created-project');
|
|
55
|
+
writeBaseEnvFiles(nestedProjectDir, 'original');
|
|
56
|
+
assert.equal(isLinkedGitWorktree(nestedProjectDir), false);
|
|
57
|
+
assert.equal(updateBaseAppName(nestedProjectDir), true);
|
|
58
|
+
for (const content of readBaseEnvFiles(nestedProjectDir)) {
|
|
59
|
+
assert.equal(content, `APP_NAME = ${basename(nestedProjectDir)}\nOTHER_VALUE = preserved\n`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const nonGitProjectDir = resolve(tempDir, 'non-git-project');
|
|
63
|
+
writeBaseEnvFiles(nonGitProjectDir, 'original');
|
|
64
|
+
assert.equal(isLinkedGitWorktree(nonGitProjectDir), false);
|
|
65
|
+
assert.equal(updateBaseAppName(nonGitProjectDir), true);
|
|
66
|
+
for (const content of readBaseEnvFiles(nonGitProjectDir)) {
|
|
67
|
+
assert.equal(content, `APP_NAME = ${basename(nonGitProjectDir)}\nOTHER_VALUE = preserved\n`);
|
|
68
|
+
}
|
|
69
|
+
} finally {
|
|
70
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
71
|
+
}
|
|
72
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { basename, resolve } from 'node:path';
|
|
4
|
+
|
|
5
|
+
function gitOutput(rootDir: string, args: string[]): string {
|
|
6
|
+
return execFileSync('git', args, {
|
|
7
|
+
cwd: rootDir,
|
|
8
|
+
encoding: 'utf-8',
|
|
9
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
10
|
+
}).trim();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function isLinkedGitWorktree(rootDir: string): boolean {
|
|
14
|
+
const projectRoot = realpathSync(resolve(rootDir));
|
|
15
|
+
try {
|
|
16
|
+
const gitRoot = realpathSync(
|
|
17
|
+
resolve(gitOutput(projectRoot, ['rev-parse', '--path-format=absolute', '--show-toplevel'])),
|
|
18
|
+
);
|
|
19
|
+
if (gitRoot !== projectRoot) return false;
|
|
20
|
+
|
|
21
|
+
const gitDir = realpathSync(
|
|
22
|
+
resolve(gitOutput(projectRoot, ['rev-parse', '--path-format=absolute', '--git-dir'])),
|
|
23
|
+
);
|
|
24
|
+
const gitCommonDir = realpathSync(
|
|
25
|
+
resolve(gitOutput(projectRoot, ['rev-parse', '--path-format=absolute', '--git-common-dir'])),
|
|
26
|
+
);
|
|
27
|
+
return gitDir !== gitCommonDir;
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function updateBaseAppName(rootDir: string): boolean {
|
|
34
|
+
if (isLinkedGitWorktree(rootDir)) return false;
|
|
35
|
+
|
|
36
|
+
const projectRoot = resolve(rootDir);
|
|
37
|
+
const projectName = basename(projectRoot);
|
|
38
|
+
const envFiles = [resolve(projectRoot, 'vona/env/.env'), resolve(projectRoot, 'zova/env/.env')];
|
|
39
|
+
for (const filePath of envFiles) {
|
|
40
|
+
if (!existsSync(filePath)) continue;
|
|
41
|
+
const content = readFileSync(filePath, 'utf-8').replace(
|
|
42
|
+
/^APP_NAME.*/m,
|
|
43
|
+
`APP_NAME = ${projectName}`,
|
|
44
|
+
);
|
|
45
|
+
writeFileSync(filePath, content);
|
|
46
|
+
// eslint-disable-next-line
|
|
47
|
+
console.log(`[init] Set APP_NAME = ${projectName} in ${filePath}`);
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vona",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.84",
|
|
4
4
|
"gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
|
|
5
5
|
"description": "Vona is an intuitive, elegant and powerful Node.js framework for rapidly developing enterprise applications of any size",
|
|
6
6
|
"keywords": [
|
|
@@ -21,7 +21,7 @@ export class ErrorClass extends BeanSimple {
|
|
|
21
21
|
fail(module, code, ...args) {
|
|
22
22
|
const body = this.parseFail(module, code, ...args);
|
|
23
23
|
|
|
24
|
-
this.ctx.response.status =
|
|
24
|
+
this.ctx.response.status = body.status;
|
|
25
25
|
this.ctx.response.type = 'application/json';
|
|
26
26
|
this.ctx.response.body = { code: body.code, message: body.message }; // body maybe Error
|
|
27
27
|
}
|
|
@@ -32,7 +32,7 @@ export class ErrorClass extends BeanSimple {
|
|
|
32
32
|
const err = new Error();
|
|
33
33
|
err.code = body.code;
|
|
34
34
|
err.message = body.message;
|
|
35
|
-
err.status =
|
|
35
|
+
err.status = body.status;
|
|
36
36
|
throw err;
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -58,9 +58,16 @@ export class ErrorClass extends BeanSimple {
|
|
|
58
58
|
|
|
59
59
|
// convert from enum
|
|
60
60
|
let text;
|
|
61
|
+
let status;
|
|
61
62
|
if (ebError && code && typeof code === 'string') {
|
|
62
63
|
text = code;
|
|
63
|
-
|
|
64
|
+
const declaration = ebError[code];
|
|
65
|
+
if (__isErrorDescriptor(declaration)) {
|
|
66
|
+
code = declaration.code;
|
|
67
|
+
status = declaration.status;
|
|
68
|
+
} else {
|
|
69
|
+
code = declaration;
|
|
70
|
+
}
|
|
64
71
|
}
|
|
65
72
|
|
|
66
73
|
if (code === undefined || code === null || code === '') {
|
|
@@ -75,10 +82,20 @@ export class ErrorClass extends BeanSimple {
|
|
|
75
82
|
}
|
|
76
83
|
|
|
77
84
|
code = __combineErrorCode(module, code);
|
|
78
|
-
|
|
85
|
+
status ??= __calcStatus(code);
|
|
86
|
+
return { code, status, message };
|
|
79
87
|
}
|
|
80
88
|
}
|
|
81
89
|
|
|
90
|
+
function __isErrorDescriptor(value) {
|
|
91
|
+
return (
|
|
92
|
+
value &&
|
|
93
|
+
typeof value === 'object' &&
|
|
94
|
+
typeof value.code === 'number' &&
|
|
95
|
+
typeof value.status === 'number'
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
82
99
|
function __combineErrorCode(module, code) {
|
|
83
100
|
if (typeof code !== 'number' || code <= 1000) return code;
|
|
84
101
|
return module ? `${module}:${code}` : code;
|
|
@@ -29,12 +29,14 @@ export type TypeBeanScopeLocaleKeys = keyof IBeanScopeLocale;
|
|
|
29
29
|
export interface IBeanScopeErrors {}
|
|
30
30
|
export type TypeBeanScopeErrorsKeys = keyof IBeanScopeErrors;
|
|
31
31
|
|
|
32
|
+
type TypeErrorDeclarationCode<T> = T extends { code: infer Code } ? Code : T;
|
|
33
|
+
|
|
32
34
|
export type TypeScopesErrorsHelper<
|
|
33
35
|
ModuleName extends keyof IBeanScopeErrors,
|
|
34
36
|
Errors extends IBeanScopeErrors[ModuleName],
|
|
35
37
|
> = {
|
|
36
38
|
// @ts-ignore: ignore
|
|
37
|
-
[K in keyof Errors as `${ModuleName}:${Errors[K]}`]: K;
|
|
39
|
+
[K in keyof Errors as `${ModuleName}:${TypeErrorDeclarationCode<Errors[K]>}`]: K;
|
|
38
40
|
};
|
|
39
41
|
export type TypeScopesErrorCodes = TypeRecordValues<{
|
|
40
42
|
[ModuleName in keyof IBeanScopeErrors]: keyof TypeScopesErrorsHelper<
|
|
@@ -4,7 +4,12 @@ import type { IModuleMain, IMonkeyModule, IMonkeySystem } from './monkey.ts';
|
|
|
4
4
|
|
|
5
5
|
export type TypeModuleResourceLocales = Record<string, object>;
|
|
6
6
|
export type TypeModuleResourceLocaleModules = Record<string, TypeModuleResourceLocales>;
|
|
7
|
-
export
|
|
7
|
+
export interface IModuleResourceErrorDescriptor {
|
|
8
|
+
code: number;
|
|
9
|
+
status: number;
|
|
10
|
+
}
|
|
11
|
+
export type TypeModuleResourceError = number | IModuleResourceErrorDescriptor;
|
|
12
|
+
export type TypeModuleResourceErrors = Record<string, TypeModuleResourceError>;
|
|
8
13
|
export type TypeModuleResourceErrorModules = Record<string, TypeModuleResourceErrors>;
|
|
9
14
|
export type TypeModuleResourceConfig = (app: VonaApplication) => object | Promise<object>;
|
|
10
15
|
|