cabloy 5.1.108 → 5.1.110

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.
Files changed (192) hide show
  1. package/.cabloy-version +1 -1
  2. package/.gitignore +5 -2
  3. package/CHANGELOG.md +43 -0
  4. package/cabloy-docs/.vitepress/config.mjs +1 -0
  5. package/cabloy-docs/ai/playbook-module-removal.md +1 -1
  6. package/cabloy-docs/ai/virtual-decorator-guidance.md +9 -9
  7. package/cabloy-docs/backend/websocket-call-flow.md +1 -1
  8. package/cabloy-docs/backend/websocket-guide.md +1 -1
  9. package/cabloy-docs/frontend/fetch-interceptor-guide.md +8 -8
  10. package/cabloy-docs/frontend/form-guide.md +3 -1
  11. package/cabloy-docs/frontend/form-layout-guide.md +322 -0
  12. package/cabloy-docs/frontend/introduction.md +1 -0
  13. package/cabloy-docs/frontend/resource-entry-page-deep-dive.md +2 -0
  14. package/cabloy-docs/frontend/router-tabs-layout-integration.md +2 -1
  15. package/cabloy-docs/frontend/scripts.md +56 -1
  16. package/cabloy-docs/frontend/ssr-build-deploy-guide.md +1 -1
  17. package/cabloy-docs/frontend/ssr-overview.md +6 -0
  18. package/cabloy-docs/frontend/ssr-review-checklist.md +1 -1
  19. package/cabloy-docs/frontend/ssr-troubleshooting-guide.md +9 -9
  20. package/cabloy-docs/frontend/table-resource-crud-cookbook.md +33 -1
  21. package/cabloy-docs/frontend/zova-form-source-reading-map.md +36 -4
  22. package/cabloy-docs/frontend/zova-form-under-the-hood.md +13 -0
  23. package/cabloy-docs/fullstack/framework-performance.md +3 -3
  24. package/cabloy-docs/fullstack/quickstart.md +14 -0
  25. package/cabloy-docs/reference/repo-scripts.md +79 -0
  26. package/e2e/config/playwright.basic.config.ts +3 -0
  27. package/e2e/config/playwright.commerce.config.ts +3 -0
  28. package/e2e/config/playwright.shared.config.ts +38 -0
  29. package/e2e/scripts/e2e.ts +33 -0
  30. package/e2e/scripts/startE2eVona.ts +38 -0
  31. package/e2e/scripts/testE2eClean.ts +56 -0
  32. package/e2e/specs/a-basic/basic.spec.ts +271 -0
  33. package/e2e/specs/a-commerce/commerce.spec.ts +61 -0
  34. package/package.json +17 -1
  35. package/scripts/upgrade.ts +182 -7
  36. package/test-results/.last-run.json +4 -0
  37. package/vona/packages-vona/vona/package.json +1 -1
  38. package/vona/pnpm-lock.yaml +304 -45
  39. package/vona/src/suite/a-commerce/modules/commerce-catalog/package.json +52 -0
  40. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/index.ts +29 -0
  41. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/this.ts +2 -0
  42. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/index.ts +1 -0
  43. package/vona/src/suite/a-commerce/modules/commerce-catalog/tsconfig.build.json +11 -0
  44. package/vona/src/suite/a-commerce/modules/commerce-catalog/tsconfig.json +7 -0
  45. package/vona/src/suite/a-commerce/modules/commerce-member/package.json +52 -0
  46. package/vona/src/suite/a-commerce/modules/commerce-member/src/.metadata/index.ts +29 -0
  47. package/vona/src/suite/a-commerce/modules/commerce-member/src/.metadata/this.ts +2 -0
  48. package/vona/src/suite/a-commerce/modules/commerce-member/src/index.ts +1 -0
  49. package/vona/src/suite/a-commerce/modules/commerce-member/tsconfig.build.json +11 -0
  50. package/vona/src/suite/a-commerce/modules/commerce-member/tsconfig.json +7 -0
  51. package/vona/src/suite/a-commerce/modules/commerce-payment/package.json +52 -0
  52. package/vona/src/suite/a-commerce/modules/commerce-payment/src/.metadata/index.ts +29 -0
  53. package/vona/src/suite/a-commerce/modules/commerce-payment/src/.metadata/this.ts +2 -0
  54. package/vona/src/suite/a-commerce/modules/commerce-payment/src/index.ts +1 -0
  55. package/vona/src/suite/a-commerce/modules/commerce-payment/tsconfig.build.json +11 -0
  56. package/vona/src/suite/a-commerce/modules/commerce-payment/tsconfig.json +7 -0
  57. package/vona/src/suite/a-commerce/modules/commerce-promotion/package.json +52 -0
  58. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/.metadata/index.ts +29 -0
  59. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/.metadata/this.ts +2 -0
  60. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/index.ts +1 -0
  61. package/vona/src/suite/a-commerce/modules/commerce-promotion/tsconfig.build.json +11 -0
  62. package/vona/src/suite/a-commerce/modules/commerce-promotion/tsconfig.json +7 -0
  63. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/package.json +53 -0
  64. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/.metadata/index.ts +81 -0
  65. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/.metadata/this.ts +2 -0
  66. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/bean/ssrMenu.home.ts +21 -0
  67. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/bean/ssrSite.commerceAdmin.ts +34 -0
  68. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/index.ts +1 -0
  69. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/tsconfig.build.json +11 -0
  70. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/tsconfig.json +7 -0
  71. package/vona/src/suite/a-commerce/modules/commerce-siteweb/package.json +53 -0
  72. package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/.metadata/index.ts +81 -0
  73. package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/.metadata/this.ts +2 -0
  74. package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/bean/ssrMenu.home.ts +20 -0
  75. package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/bean/ssrSite.commerce.ts +34 -0
  76. package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/index.ts +1 -0
  77. package/vona/src/suite/a-commerce/modules/commerce-siteweb/tsconfig.build.json +11 -0
  78. package/vona/src/suite/a-commerce/modules/commerce-siteweb/tsconfig.json +7 -0
  79. package/vona/src/suite/a-commerce/modules/commerce-trade/package.json +52 -0
  80. package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +29 -0
  81. package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/this.ts +2 -0
  82. package/vona/src/suite/a-commerce/modules/commerce-trade/src/index.ts +1 -0
  83. package/vona/src/suite/a-commerce/modules/commerce-trade/tsconfig.build.json +11 -0
  84. package/vona/src/suite/a-commerce/modules/commerce-trade/tsconfig.json +7 -0
  85. package/vona/src/suite/a-commerce/package.json +18 -0
  86. package/vona/src/suite/a-commerce/tsconfig.base.json +4 -0
  87. package/vona/src/suite/a-commerce/tsconfig.json +28 -0
  88. package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +2 -0
  89. package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +2 -0
  90. package/vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx +50 -1
  91. package/vona/src/suite/a-training/modules/training-student/src/dto/studentSelectResItem.tsx +21 -1
  92. package/vona/src/suite/a-training/modules/training-student/src/dto/studentUpdate.tsx +47 -1
  93. package/vona/src/suite/a-training/modules/training-student/src/dto/studentView.tsx +47 -1
  94. package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +61 -2
  95. package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
  96. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/bean/bean.database.ts +1 -1
  97. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/bean/schedule.softDeletionPrune.ts +1 -1
  98. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/bean.model/bean.model_meta.ts +1 -1
  99. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoAggregate.ts +1 -1
  100. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoCreate.ts +1 -1
  101. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoGroup.ts +1 -1
  102. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoSelectAndCount.ts +1 -1
  103. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoUpdate.ts +1 -1
  104. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/modelCacheBase.ts +1 -1
  105. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/relations.ts +1 -1
  106. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/relationsDynamic.ts +1 -1
  107. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/relationsMutate.ts +1 -1
  108. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/relationsStatic.ts +1 -1
  109. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/service/database.ts +1 -1
  110. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/service/databaseDialectBase_.ts +10 -3
  111. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/service/db_.ts +1 -1
  112. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  113. package/zova/env/.env.cabloyCommerce +24 -0
  114. package/zova/env/.env.cabloyCommerceAdmin +19 -0
  115. package/zova/package.original.json +10 -1
  116. package/zova/packages-zova/zova/package.json +2 -2
  117. package/zova/pnpm-lock.yaml +145 -31
  118. package/zova/src/front/config/config/config.cabloyCommerce.ts +16 -0
  119. package/zova/src/front/config/config/config.cabloyCommerceAdmin.ts +13 -0
  120. package/zova/src/suite/a-commerce/modules/commerce-catalog/package.json +52 -0
  121. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/index.ts +26 -0
  122. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/this.ts +2 -0
  123. package/zova/src/suite/a-commerce/modules/commerce-catalog/src/index.ts +1 -0
  124. package/zova/src/suite/a-commerce/modules/commerce-catalog/tsconfig.build.json +13 -0
  125. package/zova/src/suite/a-commerce/modules/commerce-catalog/tsconfig.json +5 -0
  126. package/zova/src/suite/a-commerce/modules/commerce-member/package.json +52 -0
  127. package/zova/src/suite/a-commerce/modules/commerce-member/src/.metadata/index.ts +26 -0
  128. package/zova/src/suite/a-commerce/modules/commerce-member/src/.metadata/this.ts +2 -0
  129. package/zova/src/suite/a-commerce/modules/commerce-member/src/index.ts +1 -0
  130. package/zova/src/suite/a-commerce/modules/commerce-member/tsconfig.build.json +13 -0
  131. package/zova/src/suite/a-commerce/modules/commerce-member/tsconfig.json +5 -0
  132. package/zova/src/suite/a-commerce/modules/commerce-payment/package.json +52 -0
  133. package/zova/src/suite/a-commerce/modules/commerce-payment/src/.metadata/index.ts +26 -0
  134. package/zova/src/suite/a-commerce/modules/commerce-payment/src/.metadata/this.ts +2 -0
  135. package/zova/src/suite/a-commerce/modules/commerce-payment/src/index.ts +1 -0
  136. package/zova/src/suite/a-commerce/modules/commerce-payment/tsconfig.build.json +13 -0
  137. package/zova/src/suite/a-commerce/modules/commerce-payment/tsconfig.json +5 -0
  138. package/zova/src/suite/a-commerce/modules/commerce-promotion/package.json +52 -0
  139. package/zova/src/suite/a-commerce/modules/commerce-promotion/src/.metadata/index.ts +26 -0
  140. package/zova/src/suite/a-commerce/modules/commerce-promotion/src/.metadata/this.ts +2 -0
  141. package/zova/src/suite/a-commerce/modules/commerce-promotion/src/index.ts +1 -0
  142. package/zova/src/suite/a-commerce/modules/commerce-promotion/tsconfig.build.json +13 -0
  143. package/zova/src/suite/a-commerce/modules/commerce-promotion/tsconfig.json +5 -0
  144. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/package.json +52 -0
  145. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/.metadata/index.ts +26 -0
  146. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/.metadata/this.ts +2 -0
  147. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/index.ts +1 -0
  148. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/tsconfig.build.json +13 -0
  149. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/tsconfig.json +5 -0
  150. package/zova/src/suite/a-commerce/modules/commerce-siteweb/package.json +52 -0
  151. package/zova/src/suite/a-commerce/modules/commerce-siteweb/src/.metadata/index.ts +26 -0
  152. package/zova/src/suite/a-commerce/modules/commerce-siteweb/src/.metadata/this.ts +2 -0
  153. package/zova/src/suite/a-commerce/modules/commerce-siteweb/src/index.ts +1 -0
  154. package/zova/src/suite/a-commerce/modules/commerce-siteweb/tsconfig.build.json +13 -0
  155. package/zova/src/suite/a-commerce/modules/commerce-siteweb/tsconfig.json +5 -0
  156. package/zova/src/suite/a-commerce/modules/commerce-trade/package.json +52 -0
  157. package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +26 -0
  158. package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/this.ts +2 -0
  159. package/zova/src/suite/a-commerce/modules/commerce-trade/src/index.ts +1 -0
  160. package/zova/src/suite/a-commerce/modules/commerce-trade/tsconfig.build.json +13 -0
  161. package/zova/src/suite/a-commerce/modules/commerce-trade/tsconfig.json +5 -0
  162. package/zova/src/suite/a-commerce/package.json +18 -0
  163. package/zova/src/suite/a-commerce/tsconfig.base.json +4 -0
  164. package/zova/src/suite/a-commerce/tsconfig.json +4 -0
  165. package/zova/src/suite/cabloy-basic/modules/basic-form/src/.metadata/component/blockFormLayout.ts +31 -0
  166. package/zova/src/suite/cabloy-basic/modules/basic-form/src/.metadata/index.ts +13 -0
  167. package/zova/src/suite/cabloy-basic/modules/basic-form/src/component/blockFormLayout/controller.tsx +188 -0
  168. package/zova/src/suite/cabloy-basic/modules/basic-page/src/.metadata/component/blockFilterActions.ts +31 -0
  169. package/zova/src/suite/cabloy-basic/modules/basic-page/src/.metadata/index.ts +13 -0
  170. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockFilter/controller.tsx +61 -31
  171. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockFilterActions/controller.tsx +51 -0
  172. package/zova/src/suite/cabloy-basic/modules/basic-page/src/types/page.ts +10 -0
  173. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockForm/controller.tsx +9 -2
  174. package/zova/src/suite-vendor/a-zova/modules/a-app/package.json +1 -1
  175. package/zova/src/suite-vendor/a-zova/modules/a-app/src/component/app/controller.tsx +6 -0
  176. package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +5 -3
  177. package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/form/controller.tsx +44 -3
  178. package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/form/render.tsx +22 -4
  179. package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/formField/controller.tsx +3 -2
  180. package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/formLayout.ts +203 -0
  181. package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/index.ts +1 -0
  182. package/zova/src/suite-vendor/a-zova/modules/a-form/src/types/formField.ts +7 -4
  183. package/zova/src/suite-vendor/a-zova/modules/a-form/src/types/formLayout.ts +63 -0
  184. package/zova/src/suite-vendor/a-zova/modules/a-form/src/types/index.ts +1 -0
  185. package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
  186. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/action.ts +1 -1
  187. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/resource/formLayout.ts +56 -0
  188. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/resource/index.ts +1 -0
  189. package/zova/src/suite-vendor/a-zova/modules/a-ssr/package.json +1 -1
  190. package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/lib/ssr.ts +19 -1
  191. package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/monkey.ts +5 -0
  192. package/zova/src/suite-vendor/a-zova/package.json +5 -5
@@ -0,0 +1,271 @@
1
+ import type { Locator, Page } from '@playwright/test';
2
+
3
+ import { expect, test } from '@playwright/test';
4
+
5
+ const studentResourceUrl =
6
+ /\/admin\/rest\/resource\/training-student(?:%3A|:|%253A)student(?:[/?#]|$)/;
7
+
8
+ interface IFieldGeometry {
9
+ container: {
10
+ display: string;
11
+ flexWrap: string;
12
+ left: number;
13
+ right: number;
14
+ };
15
+ field: {
16
+ top: number;
17
+ left: number;
18
+ right: number;
19
+ bottom: number;
20
+ };
21
+ }
22
+
23
+ function collectPageErrors(page: Page) {
24
+ const errors: Error[] = [];
25
+ page.on('pageerror', error => {
26
+ errors.push(error);
27
+ });
28
+ return errors;
29
+ }
30
+
31
+ async function loginAsAdmin(page: Page) {
32
+ await page.goto('/admin/', { waitUntil: 'load' });
33
+ if (page.url().includes('/admin/login')) {
34
+ await page.getByPlaceholder('Your Username').fill('admin');
35
+ await page.getByPlaceholder('Your Password').fill('123456');
36
+ await expect(page.getByPlaceholder('Please input captcha')).not.toHaveValue('');
37
+ await page.getByRole('button', { name: 'Login', exact: true }).click();
38
+ await expect(page).not.toHaveURL(/\/admin\/login(?:\?|$)/);
39
+ }
40
+
41
+ await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'admin');
42
+ }
43
+
44
+ function waitForStudentSelect(page: Page) {
45
+ return page.waitForResponse(response => {
46
+ const url = new URL(response.url());
47
+ return (
48
+ response.request().method() === 'GET' &&
49
+ response.ok() &&
50
+ url.pathname === '/api/training/student' &&
51
+ !response.request().headers()['x-vona-openapi-schema']
52
+ );
53
+ });
54
+ }
55
+
56
+ async function getFieldGeometry(locator: Locator): Promise<IFieldGeometry> {
57
+ return locator.evaluate(element => {
58
+ const field = element.closest('label')?.parentElement;
59
+ const container = field?.parentElement;
60
+ if (!field || !container) throw new Error('Could not resolve the filter field layout');
61
+
62
+ const fieldRect = field.getBoundingClientRect();
63
+ const containerRect = container.getBoundingClientRect();
64
+ const containerStyle = getComputedStyle(container);
65
+ return {
66
+ container: {
67
+ display: containerStyle.display,
68
+ flexWrap: containerStyle.flexWrap,
69
+ left: containerRect.left,
70
+ right: containerRect.right,
71
+ },
72
+ field: {
73
+ top: fieldRect.top,
74
+ left: fieldRect.left,
75
+ right: fieldRect.right,
76
+ bottom: fieldRect.bottom,
77
+ },
78
+ };
79
+ });
80
+ }
81
+
82
+ async function getFlowGeometry(name: Locator, level: Locator, createdAtStart: Locator) {
83
+ return Promise.all([
84
+ getFieldGeometry(name),
85
+ getFieldGeometry(level),
86
+ getFieldGeometry(createdAtStart),
87
+ ]);
88
+ }
89
+
90
+ function assertVisualOrder(geometry: IFieldGeometry[]) {
91
+ for (let index = 1; index < geometry.length; index++) {
92
+ const previous = geometry[index - 1].field;
93
+ const current = geometry[index].field;
94
+ expect(
95
+ current.top > previous.top + 2 ||
96
+ (Math.abs(current.top - previous.top) <= 2 && current.left > previous.left),
97
+ ).toBeTruthy();
98
+ }
99
+ }
100
+
101
+ test(
102
+ 'ATP-BASIC-SSR-01: anonymous Web HTML hydrates through the default site',
103
+ { tag: ['@web', '@smoke'] },
104
+ async ({ page, request }) => {
105
+ const response = await request.get('/');
106
+ expect(response.ok()).toBeTruthy();
107
+ expect((await response.text()).toLowerCase()).not.toContain('data-zova-hydrated');
108
+
109
+ const pageErrors = collectPageErrors(page);
110
+ const documentResponse = await page.goto('/', { waitUntil: 'load' });
111
+ expect(documentResponse?.ok()).toBeTruthy();
112
+ await expect(page).toHaveURL(/\/$/);
113
+ await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'web');
114
+ await expect(page.getByText('Web: en-us')).toBeVisible();
115
+ await expect(page.getByText('Dashboard')).toHaveCount(0);
116
+ await expect(page.locator('body')).toBeVisible();
117
+ await expect(page).not.toHaveTitle(/error/i);
118
+ expect(pageErrors).toEqual([]);
119
+ },
120
+ );
121
+
122
+ test(
123
+ 'ATP-BASIC-SSR-02: Admin waits for nested hydration before ready',
124
+ { tag: ['@admin', '@smoke'] },
125
+ async ({ page, request }) => {
126
+ const response = await request.get('/admin/', { maxRedirects: 0 });
127
+ expect(response.status()).toBe(302);
128
+ const loginPath = response.headers().location;
129
+ expect(loginPath).toMatch(/^\/admin\/login(?:\?|$)/);
130
+
131
+ const loginResponse = await request.get(loginPath!);
132
+ expect(loginResponse.ok()).toBeTruthy();
133
+ expect((await loginResponse.text()).toLowerCase()).not.toContain('data-zova-hydrated');
134
+
135
+ let releaseRequest: (() => void) | undefined;
136
+ const requestHeld = new Promise<void>(resolve => {
137
+ releaseRequest = resolve;
138
+ });
139
+ let firstRequest = true;
140
+ let requestObserved: () => void;
141
+ const requestBlocked = new Promise<void>(resolve => {
142
+ requestObserved = resolve;
143
+ });
144
+
145
+ await page.route(/\/admin\/assets\/a-form-[^/]+\.js(?:\?.*)?$/, async route => {
146
+ if (!firstRequest) {
147
+ await route.continue();
148
+ return;
149
+ }
150
+ firstRequest = false;
151
+ requestObserved();
152
+ await requestHeld;
153
+ await route.continue();
154
+ });
155
+
156
+ const pageErrors = collectPageErrors(page);
157
+ const navigation = page.goto('/admin/', { waitUntil: 'load' });
158
+ try {
159
+ await requestBlocked;
160
+ await expect(page).toHaveURL(/\/admin\/login(?:\?|$)/);
161
+ await expect(page.getByRole('heading', { name: 'Login' })).toBeVisible();
162
+ await expect(page.locator('html')).not.toHaveAttribute('data-zova-hydrated');
163
+ } finally {
164
+ releaseRequest?.();
165
+ }
166
+
167
+ const documentResponse = await navigation;
168
+ expect(documentResponse?.ok()).toBeTruthy();
169
+ await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'admin');
170
+ await expect(page.getByRole('heading', { name: 'Login' })).toBeVisible();
171
+ await expect(page.getByText('Dashboard')).toHaveCount(0);
172
+ await expect(page.locator('body')).toBeVisible();
173
+ expect(pageErrors).toEqual([]);
174
+ await page.unroute(/\/admin\/assets\/a-form-[^/]+\.js(?:\?.*)?$/);
175
+ },
176
+ );
177
+
178
+ test(
179
+ 'ATP-BASIC-FLOW-01: Training Student flow filter wraps and submits queries',
180
+ { tag: ['@admin', '@flow'] },
181
+ async ({ page }) => {
182
+ await page.setViewportSize({ width: 1440, height: 900 });
183
+ const pageErrors = collectPageErrors(page);
184
+
185
+ await loginAsAdmin(page);
186
+
187
+ const initialSelect = waitForStudentSelect(page);
188
+ await page.getByRole('link', { name: 'Student', exact: true }).click();
189
+ await expect(page).toHaveURL(studentResourceUrl);
190
+ await initialSelect;
191
+
192
+ const name = page.getByLabel('Student Name');
193
+ const level = page.getByRole('combobox', { name: 'Training Stage' });
194
+ const createdAt = page.locator('label').filter({ hasText: /^Created At/ });
195
+ const dates = createdAt.locator('input[type="date"]');
196
+ const createdAtStart = dates.nth(0);
197
+ const createdAtEnd = dates.nth(1);
198
+ const search = page.getByRole('button', { name: 'Search', exact: true });
199
+ const reset = page.getByRole('button', { name: 'Reset', exact: true });
200
+
201
+ await expect(name).toBeVisible();
202
+ await expect(level).toBeVisible();
203
+ await expect(dates).toHaveCount(2);
204
+ await expect(search).toBeVisible();
205
+ await expect(reset).toBeVisible();
206
+
207
+ const wideGeometry = await getFlowGeometry(name, level, createdAtStart);
208
+ expect(wideGeometry[0].container.display).toBe('flex');
209
+ expect(wideGeometry[0].container.flexWrap).toBe('wrap');
210
+ expect(wideGeometry[1].container).toEqual(wideGeometry[0].container);
211
+ expect(wideGeometry[2].container).toEqual(wideGeometry[0].container);
212
+ expect(Math.abs(wideGeometry[0].field.top - wideGeometry[1].field.top)).toBeLessThanOrEqual(2);
213
+ expect(Math.abs(wideGeometry[1].field.top - wideGeometry[2].field.top)).toBeLessThanOrEqual(2);
214
+ assertVisualOrder(wideGeometry);
215
+ for (const geometry of wideGeometry) {
216
+ expect(geometry.field.right).toBeLessThanOrEqual(geometry.container.right + 1);
217
+ }
218
+
219
+ await page.setViewportSize({ width: 700, height: 900 });
220
+ await expect
221
+ .poll(
222
+ async () =>
223
+ new Set(
224
+ (await getFlowGeometry(name, level, createdAtStart)).map(item =>
225
+ Math.round(item.field.top),
226
+ ),
227
+ ).size,
228
+ )
229
+ .toBeGreaterThan(1);
230
+
231
+ const narrowGeometry = await getFlowGeometry(name, level, createdAtStart);
232
+ assertVisualOrder(narrowGeometry);
233
+ const viewport = page.viewportSize()!;
234
+ for (const geometry of narrowGeometry) {
235
+ expect(geometry.field.left).toBeGreaterThanOrEqual(-1);
236
+ expect(geometry.field.right).toBeLessThanOrEqual(viewport.width + 1);
237
+ }
238
+ const createdAtEndBox = await createdAtEnd.boundingBox();
239
+ expect(createdAtEndBox).not.toBeNull();
240
+ expect(createdAtEndBox!.x + createdAtEndBox!.width).toBeLessThanOrEqual(viewport.width + 1);
241
+ await expect
242
+ .poll(() =>
243
+ page.evaluate(
244
+ () => document.documentElement.scrollWidth - document.documentElement.clientWidth,
245
+ ),
246
+ )
247
+ .toBeLessThanOrEqual(1);
248
+
249
+ await page.setViewportSize({ width: 1440, height: 900 });
250
+ await name.fill('Flow E2E');
251
+ await level.selectOption('2');
252
+ await createdAtStart.fill('2026-01-10');
253
+ await createdAtEnd.fill('2026-01-20');
254
+
255
+ const searchResponse = waitForStudentSelect(page);
256
+ await search.click();
257
+ const searchUrl = new URL((await searchResponse).url());
258
+ expect(searchUrl.searchParams.get('name')).toBe('Flow E2E');
259
+ expect(searchUrl.searchParams.get('level')).toBe('2');
260
+ expect(searchUrl.searchParams.get('createdAt')).toBe('2026-01-10~2026-01-20');
261
+ await expect(name).toHaveValue('Flow E2E');
262
+ await expect(level).toHaveValue('2');
263
+
264
+ await reset.click();
265
+ await expect(name).toHaveValue('');
266
+ await expect(level).toHaveValue('');
267
+ await expect(createdAtStart).toHaveValue('');
268
+ await expect(createdAtEnd).toHaveValue('');
269
+ expect(pageErrors).toEqual([]);
270
+ },
271
+ );
@@ -0,0 +1,61 @@
1
+ import { expect, test } from '@playwright/test';
2
+
3
+ const privateMarkers = ['cart', 'address', 'order', 'coupon', 'payment'];
4
+
5
+ function collectPageErrors(page) {
6
+ const errors: Error[] = [];
7
+ page.on('pageerror', error => {
8
+ errors.push(error);
9
+ });
10
+ return errors;
11
+ }
12
+
13
+ test(
14
+ 'ATP-SSR-01: anonymous Commerce HTML is public and hydration completes',
15
+ { tag: ['@web', '@smoke'] },
16
+ async ({ page, request }) => {
17
+ const response = await request.get('/commerce');
18
+ expect(response.ok()).toBeTruthy();
19
+ const html = await response.text();
20
+ const normalizedHtml = html.toLowerCase();
21
+ expect(normalizedHtml).not.toContain('data-zova-hydrated');
22
+
23
+ for (const marker of privateMarkers) {
24
+ expect(normalizedHtml).not.toContain(`"${marker}"`);
25
+ }
26
+
27
+ const pageErrors = collectPageErrors(page);
28
+ const documentResponse = await page.goto('/commerce', { waitUntil: 'load' });
29
+ expect(documentResponse?.ok()).toBeTruthy();
30
+ await expect(page).toHaveURL(/\/commerce(?:\/|$)/);
31
+ await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerce');
32
+ await expect(page.getByText('Web: en-us')).toBeVisible();
33
+ await expect(page.getByText('Dashboard')).toHaveCount(0);
34
+ await expect(page.locator('body')).toBeVisible();
35
+ await expect(page).not.toHaveTitle(/error/i);
36
+ expect(pageErrors).toEqual([]);
37
+ },
38
+ );
39
+
40
+ test(
41
+ 'ATP-SSR-02: Commerce Admin is an independent SSR site',
42
+ { tag: ['@admin', '@smoke'] },
43
+ async ({ page, request }) => {
44
+ const response = await request.get('/commerce-admin/', { maxRedirects: 0 });
45
+ expect(response.status()).toBe(302);
46
+ const loginPath = response.headers().location;
47
+ expect(loginPath).toMatch(/^\/commerce-admin\/login(?:\?|$)/);
48
+
49
+ const loginResponse = await request.get(loginPath!);
50
+ expect(loginResponse.ok()).toBeTruthy();
51
+ expect((await loginResponse.text()).toLowerCase()).not.toContain('data-zova-hydrated');
52
+
53
+ const pageErrors = collectPageErrors(page);
54
+ const documentResponse = await page.goto('/commerce-admin/', { waitUntil: 'load' });
55
+ expect(documentResponse?.ok()).toBeTruthy();
56
+ await expect(page).toHaveURL(/\/commerce-admin\/login(?:\?|$)/);
57
+ await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerceAdmin');
58
+ await expect(page.locator('body')).toBeVisible();
59
+ expect(pageErrors).toEqual([]);
60
+ },
61
+ );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cabloy",
3
- "version": "5.1.108",
3
+ "version": "5.1.110",
4
4
  "gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
5
5
  "description": "A Node.js fullstack framework",
6
6
  "keywords": [
@@ -28,16 +28,31 @@
28
28
  "vona": "node ./vona/packages-cli/cli/src/bin/vona.ts --projectPath=vona",
29
29
  "zova": "node ./zova/packages-cli/cli/src/bin/zova.ts --projectPath=zova",
30
30
  "dev": "cd vona && npm run dev",
31
+ "dev:one": "cd vona && npm run dev:one",
31
32
  "dev:zova:admin": "cd zova && npm run dev:ssr:cabloyBasicAdmin",
32
33
  "dev:zova:web": "cd zova && npm run dev:ssr:cabloyBasicWeb",
34
+ "dev:zova:commerce:web": "cd zova && npm run dev:ssr:cabloyCommerce",
35
+ "dev:zova:commerce:admin": "cd zova && npm run dev:ssr:cabloyCommerceAdmin",
33
36
  "build": "npm run build:zova && pnpm --dir vona run build",
34
37
  "build:docker": "npm run build:zova && pnpm --dir vona run build:docker",
35
38
  "build:zova": "pnpm --dir zova run build:ssr:cabloyBasicBatch",
36
39
  "build:zova:admin": "pnpm --dir zova run build:ssr:cabloyBasicAdmin && pnpm --dir zova run build:rest:cabloyBasicAdmin",
37
40
  "build:zova:web": "pnpm --dir zova run build:ssr:cabloyBasicWeb && pnpm --dir zova run build:rest:cabloyBasicWeb",
41
+ "build:zova:commerce": "pnpm --dir zova run build:ssr:cabloyCommerceBatch",
42
+ "build:zova:commerce:web": "pnpm --dir zova run build:ssr:cabloyCommerce && pnpm --dir zova run build:rest:cabloyCommerce",
43
+ "build:zova:commerce:admin": "pnpm --dir zova run build:ssr:cabloyCommerceAdmin && pnpm --dir zova run build:rest:cabloyCommerceAdmin",
44
+ "test:e2e:basic": "playwright test --config e2e/config/playwright.basic.config.ts",
45
+ "test:e2e:basic:web": "playwright test --config e2e/config/playwright.basic.config.ts --grep @web",
46
+ "test:e2e:basic:admin": "playwright test --config e2e/config/playwright.basic.config.ts --grep @admin",
47
+ "test:e2e:basic:clean": "node e2e/scripts/testE2eClean.ts basic",
48
+ "test:e2e:commerce": "playwright test --config e2e/config/playwright.commerce.config.ts",
49
+ "test:e2e:commerce:web": "playwright test --config e2e/config/playwright.commerce.config.ts --grep @web",
50
+ "test:e2e:commerce:admin": "playwright test --config e2e/config/playwright.commerce.config.ts --grep @admin",
51
+ "test:e2e:commerce:clean": "node e2e/scripts/testE2eClean.ts commerce",
38
52
  "start": "cd vona && npm run start",
39
53
  "start:one": "cd vona && npm run start:one",
40
54
  "test": "cd vona && npm run test",
55
+ "db:reset": "cd vona && npm run db:reset",
41
56
  "tsc": "npm run tsc:zova && pnpm --dir vona run tsc",
42
57
  "tsc:zova": "pnpm --dir zova run tsc",
43
58
  "deps:vona": "pnpm --dir vona run vona :tools:deps",
@@ -63,6 +78,7 @@
63
78
  "devDependencies": {
64
79
  "@cabloy/lint": "^5.1.27",
65
80
  "@cabloy/process-helper": "^3.1.3",
81
+ "@playwright/test": "^1.61.1",
66
82
  "@types/fs-extra": "^11.0.4",
67
83
  "@types/node": "^22.19.17",
68
84
  "husky": "^9.1.7",
@@ -6,6 +6,7 @@ import {
6
6
  mkdirSync,
7
7
  readFileSync,
8
8
  rmSync,
9
+ writeFileSync,
9
10
  unlinkSync,
10
11
  } from 'node:fs';
11
12
  import { tmpdir } from 'node:os';
@@ -55,6 +56,31 @@ const OVERWRITE_DIRS_CABLOY_BASIC: string[] = [
55
56
  'zova/src/suite/a-devui',
56
57
  ];
57
58
 
59
+ const FRAMEWORK_E2E_DIRS_CABLOY_BASIC: string[] = [
60
+ 'e2e/config',
61
+ 'e2e/scripts',
62
+ 'e2e/specs/a-basic',
63
+ 'e2e/specs/a-commerce',
64
+ ];
65
+
66
+ const FRAMEWORK_E2E_SCRIPT_NAMES_CABLOY_BASIC: string[] = [
67
+ 'test:e2e:basic',
68
+ 'test:e2e:basic:web',
69
+ 'test:e2e:basic:admin',
70
+ 'test:e2e:basic:clean',
71
+ 'test:e2e:commerce',
72
+ 'test:e2e:commerce:web',
73
+ 'test:e2e:commerce:admin',
74
+ 'test:e2e:commerce:clean',
75
+ ];
76
+
77
+ const FRAMEWORK_E2E_DEV_DEPENDENCY_CABLOY_BASIC = '@playwright/test';
78
+
79
+ const OBSOLETE_FRAMEWORK_E2E_SCRIPTS_CABLOY_BASIC: Record<string, string> = {
80
+ 'test:e2e:basic:flow':
81
+ 'playwright test --config e2e/config/playwright.basic.config.ts --grep ATP-BASIC-FLOW-01',
82
+ };
83
+
58
84
  const MERGE_DIRS: string[] = [
59
85
  // Claude project assets
60
86
  '.claude/commands',
@@ -109,6 +135,12 @@ type LatestPackageInfo = {
109
135
  tarballUrl: string;
110
136
  };
111
137
 
138
+ type PackageJson = {
139
+ dependencies?: Record<string, string>;
140
+ devDependencies?: Record<string, string>;
141
+ scripts?: Record<string, string>;
142
+ };
143
+
112
144
  // --- Helpers ---
113
145
 
114
146
  // oxlint-disable no-console
@@ -130,6 +162,14 @@ function copyDirectory(src: string, dest: string): void {
130
162
  cpSync(src, dest, { recursive: true, filter: shouldCopyPath });
131
163
  }
132
164
 
165
+ function isCabloyBasic(): boolean {
166
+ return existsSync(resolve(ROOT_DIR, '__CABLOY_BASIC__'));
167
+ }
168
+
169
+ function readPackageJson(filePath: string): PackageJson {
170
+ return JSON.parse(readFileSync(filePath, 'utf-8')) as PackageJson;
171
+ }
172
+
133
173
  function isValidVersion(version: string): boolean {
134
174
  return /^\d+\.\d+\.\d+$/.test(version);
135
175
  }
@@ -188,6 +228,125 @@ async function extractTarball(tarballPath: string, targetDir: string): Promise<v
188
228
  }
189
229
  }
190
230
 
231
+ function needsBasicE2eReconciliation(): boolean {
232
+ if (!isCabloyBasic()) return false;
233
+
234
+ for (const dir of FRAMEWORK_E2E_DIRS_CABLOY_BASIC) {
235
+ if (!existsSync(resolve(ROOT_DIR, dir))) return true;
236
+ }
237
+
238
+ const packageJson = readPackageJson(resolve(ROOT_DIR, 'package.json'));
239
+ for (const name of FRAMEWORK_E2E_SCRIPT_NAMES_CABLOY_BASIC) {
240
+ const script = packageJson.scripts?.[name];
241
+ if (!script?.includes('e2e/')) return true;
242
+ }
243
+ for (const [name, value] of Object.entries(OBSOLETE_FRAMEWORK_E2E_SCRIPTS_CABLOY_BASIC)) {
244
+ if (packageJson.scripts?.[name] === value) return true;
245
+ }
246
+ return !packageJson.devDependencies?.[FRAMEWORK_E2E_DEV_DEPENDENCY_CABLOY_BASIC];
247
+ }
248
+
249
+ function mergeFrameworkE2eAssets(dryRun?: boolean): void {
250
+ if (!isCabloyBasic()) return;
251
+
252
+ for (const dir of FRAMEWORK_E2E_DIRS_CABLOY_BASIC) {
253
+ const src = resolve(TEMP_DIR, dir);
254
+ const dest = resolve(ROOT_DIR, dir);
255
+ if (!existsSync(src)) {
256
+ throw new Error(`Expected framework E2E directory in package: ${dir}`);
257
+ }
258
+ if (dryRun) {
259
+ log(` [dry-run] Merge framework E2E directory: ${dir}`);
260
+ continue;
261
+ }
262
+ copyDirectory(src, dest);
263
+ }
264
+ }
265
+
266
+ function reconcileFrameworkE2ePackageJson(dryRun?: boolean): void {
267
+ if (!isCabloyBasic()) return;
268
+
269
+ const projectPackagePath = resolve(ROOT_DIR, 'package.json');
270
+ const sourcePackagePath = resolve(TEMP_DIR, 'package.json');
271
+ const projectPackage = readPackageJson(projectPackagePath);
272
+ const sourcePackage = readPackageJson(sourcePackagePath);
273
+ const sourcePlaywrightVersion =
274
+ sourcePackage.devDependencies?.[FRAMEWORK_E2E_DEV_DEPENDENCY_CABLOY_BASIC];
275
+ if (!sourcePlaywrightVersion) {
276
+ throw new Error(
277
+ `Expected ${FRAMEWORK_E2E_DEV_DEPENDENCY_CABLOY_BASIC} in framework package devDependencies`,
278
+ );
279
+ }
280
+
281
+ let changed = false;
282
+ for (const name of FRAMEWORK_E2E_SCRIPT_NAMES_CABLOY_BASIC) {
283
+ const sourceValue = sourcePackage.scripts?.[name];
284
+ if (!sourceValue) {
285
+ throw new Error(`Expected framework E2E script in package.json: ${name}`);
286
+ }
287
+ if (projectPackage.scripts?.[name] === sourceValue) continue;
288
+ changed = true;
289
+ if (dryRun) {
290
+ log(` [dry-run] Set package.json scripts.${name}`);
291
+ } else {
292
+ projectPackage.scripts ??= {};
293
+ projectPackage.scripts[name] = sourceValue;
294
+ }
295
+ }
296
+
297
+ if (
298
+ projectPackage.devDependencies?.[FRAMEWORK_E2E_DEV_DEPENDENCY_CABLOY_BASIC] !==
299
+ sourcePlaywrightVersion
300
+ ) {
301
+ changed = true;
302
+ if (dryRun) {
303
+ log(
304
+ ` [dry-run] Set package.json devDependencies.${FRAMEWORK_E2E_DEV_DEPENDENCY_CABLOY_BASIC} = ${sourcePlaywrightVersion}`,
305
+ );
306
+ } else {
307
+ projectPackage.devDependencies ??= {};
308
+ projectPackage.devDependencies[FRAMEWORK_E2E_DEV_DEPENDENCY_CABLOY_BASIC] =
309
+ sourcePlaywrightVersion;
310
+ }
311
+ }
312
+
313
+ if (projectPackage.dependencies?.[FRAMEWORK_E2E_DEV_DEPENDENCY_CABLOY_BASIC]) {
314
+ changed = true;
315
+ if (dryRun) {
316
+ log(
317
+ ` [dry-run] Remove package.json dependencies.${FRAMEWORK_E2E_DEV_DEPENDENCY_CABLOY_BASIC}`,
318
+ );
319
+ } else {
320
+ delete projectPackage.dependencies[FRAMEWORK_E2E_DEV_DEPENDENCY_CABLOY_BASIC];
321
+ }
322
+ }
323
+
324
+ for (const [name, value] of Object.entries(OBSOLETE_FRAMEWORK_E2E_SCRIPTS_CABLOY_BASIC)) {
325
+ const currentValue = projectPackage.scripts?.[name];
326
+ if (!currentValue) continue;
327
+ if (currentValue !== value) {
328
+ log(
329
+ ` Retain customized obsolete framework E2E script package.json scripts.${name}; remove it manually if no longer needed.`,
330
+ );
331
+ continue;
332
+ }
333
+ changed = true;
334
+ if (dryRun) {
335
+ log(` [dry-run] Remove obsolete framework E2E script package.json scripts.${name}`);
336
+ } else {
337
+ delete projectPackage.scripts?.[name];
338
+ }
339
+ }
340
+
341
+ if (!changed) {
342
+ if (dryRun) log(' [dry-run] Framework E2E package entries already match');
343
+ return;
344
+ }
345
+ if (!dryRun) {
346
+ writeFileSync(projectPackagePath, `${JSON.stringify(projectPackage, null, 2)}\n`);
347
+ }
348
+ }
349
+
191
350
  // --- Step 1: Pre-flight ---
192
351
 
193
352
  function preflight(): void {
@@ -250,7 +409,7 @@ async function downloadAndExtract(tarballUrl: string): Promise<void> {
250
409
  // --- Step 3: Selective overwrite ---
251
410
 
252
411
  function selectiveOverwrite(dryRun?: boolean): void {
253
- const overwriteDirs = existsSync(resolve(ROOT_DIR, '__CABLOY_BASIC__'))
412
+ const overwriteDirs = isCabloyBasic()
254
413
  ? [...OVERWRITE_DIRS, ...OVERWRITE_DIRS_CABLOY_BASIC]
255
414
  : OVERWRITE_DIRS;
256
415
 
@@ -292,6 +451,9 @@ function selectiveOverwrite(dryRun?: boolean): void {
292
451
  rmSync(dest, { recursive: true, force: true });
293
452
  }
294
453
 
454
+ // Merge Cabloy Basic framework E2E directories without deleting project-owned paths.
455
+ mergeFrameworkE2eAssets(dryRun);
456
+
295
457
  // Overwrite files
296
458
  for (const file of WHITELIST_FILES) {
297
459
  const src = resolve(TEMP_DIR, file);
@@ -348,6 +510,7 @@ async function main(): Promise<void> {
348
510
 
349
511
  const latestPackageInfo = await fetchLatestPackageInfo();
350
512
  const currentVersion = readVersionMarker();
513
+ const basicE2eIncomplete = needsBasicE2eReconciliation();
351
514
 
352
515
  if (currentVersion) {
353
516
  log(`Current project Cabloy version: ${currentVersion}`);
@@ -361,13 +524,18 @@ async function main(): Promise<void> {
361
524
  if (currentVersion) {
362
525
  const comparison = compareVersions(currentVersion, latestPackageInfo.version);
363
526
  if (comparison === 0) {
364
- if (!dryRun) {
527
+ if (basicE2eIncomplete) {
528
+ log(
529
+ `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`,
530
+ );
531
+ } else if (!dryRun) {
365
532
  log(`Cabloy is already up to date (current: ${currentVersion}). Skipping upgrade.`);
366
533
  return;
534
+ } else {
535
+ log(
536
+ `Cabloy is already up to date (current: ${currentVersion}). Continuing dry-run to show overwrite plan.\n`,
537
+ );
367
538
  }
368
- log(
369
- `Cabloy is already up to date (current: ${currentVersion}). Continuing dry-run to show overwrite plan.\n`,
370
- );
371
539
  } else if (comparison > 0) {
372
540
  throw new Error(
373
541
  `Project Cabloy version ${currentVersion} is newer than npm latest ${latestPackageInfo.version}. Aborting upgrade.`,
@@ -390,12 +558,19 @@ async function main(): Promise<void> {
390
558
  selectiveOverwrite(dryRun);
391
559
  log('');
392
560
 
393
- // 4. Run init
561
+ // 4. Reconcile Cabloy Basic framework E2E manifest entries
562
+ if (isCabloyBasic()) {
563
+ log('Reconciling framework-owned E2E package entries...');
564
+ reconcileFrameworkE2ePackageJson(dryRun);
565
+ log('');
566
+ }
567
+
568
+ // 5. Run init
394
569
  log('Running npm run init...');
395
570
  runInit(dryRun, latestPackageInfo.version);
396
571
  log('');
397
572
 
398
- // 5. Run init:test-data
573
+ // 6. Run init:test-data
399
574
  log('Running npm run init:test-data...');
400
575
  runInitTestData(dryRun);
401
576
  log('');
@@ -0,0 +1,4 @@
1
+ {
2
+ "status": "passed",
3
+ "failedTests": []
4
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona",
3
- "version": "5.1.75",
3
+ "version": "5.1.76",
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": [