cabloy 5.1.152 → 5.1.154

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 (95) hide show
  1. package/.cabloy-version +1 -1
  2. package/.claude/skills/cabloy-module-removal/SKILL.md +1 -1
  3. package/.claude/skills/cabloy-resource-field-update/SKILL.md +1 -1
  4. package/.claude/skills/cabloy-spec-generation/SKILL.md +1 -1
  5. package/.claude/skills/cabloy-spec-generation/references/repo-aware-discovery.md +1 -1
  6. package/.claude/skills/cabloy-spec-generation/scripts/generate-implementation-charts.mjs +46 -11
  7. package/.claude/skills/cabloy-spec-generation/scripts/generate-implementation-charts.test.mjs +13 -0
  8. package/.claude/skills/cabloy-workflow/SKILL.md +13 -14
  9. package/.claude/skills/cabloy-workflow/evals/evals.json +4 -4
  10. package/CHANGELOG.md +27 -0
  11. package/CLAUDE.md +2 -3
  12. package/package.json +1 -1
  13. package/repo-docs/.vitepress/config.mjs +5 -0
  14. package/repo-docs/ai/cli-to-skill-map.md +19 -0
  15. package/repo-docs/ai/docs-skills-rules-mapping.md +21 -6
  16. package/repo-docs/ai/future-skill-roadmap.md +17 -72
  17. package/repo-docs/ai/introduction.md +15 -5
  18. package/repo-docs/ai/playbook-spec-execution.md +144 -0
  19. package/repo-docs/ai/playbook-spec-generation.md +155 -0
  20. package/repo-docs/ai/repo-guidance.md +9 -5
  21. package/repo-docs/ai/rules-and-config.md +2 -2
  22. package/repo-docs/ai/skills.md +4 -0
  23. package/repo-docs/ai/virtual-decorator-guidance.md +1 -1
  24. package/repo-docs/backend/introduction.md +1 -1
  25. package/repo-docs/backend/menu-guide.md +3 -2
  26. package/repo-docs/frontend/model-resource-cookbook.md +38 -7
  27. package/repo-docs/frontend/model-resource-usage-guide.md +2 -1
  28. package/repo-docs/frontend/router-tabs-introduction.md +1 -1
  29. package/repo-docs/frontend/use-state-data-best-practices.md +39 -6
  30. package/repo-docs/reference/package-map.md +4 -1
  31. package/repo-docs/reference/repo-scripts.md +25 -0
  32. package/repo-e2e/specs/cabloy-basic.spec.ts +69 -20
  33. package/test-results/.last-run.json +4 -2
  34. package/test-results/cabloy-basic-ATP-BASIC-SUM-58d8a-ry-states-and-Markdown-HTML/error-context.md +195 -0
  35. package/vona/env/.env +8 -0
  36. package/vona/packages-vona/vona/package.json +1 -1
  37. package/vona/pnpm-lock.yaml +81 -145
  38. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/package.json +1 -2
  39. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/.metadata/index.ts +40 -65
  40. package/vona/src/suite/a-home/modules/home-base/src/.metadata/index.ts +78 -72
  41. package/vona/src/suite/a-home/modules/home-base/src/.metadata/locales.ts +18 -0
  42. package/vona/src/suite/a-home/modules/home-base/src/config/locale/en-us.ts +3 -0
  43. package/vona/src/suite/a-home/modules/home-base/src/config/locale/zh-cn.ts +3 -0
  44. package/vona/src/suite/a-home/modules/home-base/src/index.ts +1 -0
  45. package/vona/src/suite/a-home/modules/home-base/src/service/siteCatalog.ts +17 -10
  46. package/vona/src/suite/a-home/modules/home-user/src/bean/meta.version.ts +5 -0
  47. package/vona/src/suite/a-home/modules/home-user/src/config/config.ts +36 -5
  48. package/vona/src/suite/a-home/modules/home-user/src/config/roles.ts +7 -2
  49. package/vona/src/suite/a-home/modules/home-user/src/entity/role.ts +3 -0
  50. package/vona/src/suite/a-home/modules/home-user/src/types/env.ts +11 -0
  51. package/vona/src/suite/a-home/modules/home-user/src/types/index.ts +1 -0
  52. package/vona/src/suite/a-home/modules/home-user/test/config.test.ts +65 -0
  53. package/vona/src/suite/a-home/modules/home-user/test/role.test.ts +14 -6
  54. package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
  55. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/package.json +1 -1
  56. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/.metadata/index.ts +17 -2
  57. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/bean/event.resolveMenuVisibility.ts +19 -0
  58. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/lib/beanSsrSiteBase.ts +32 -16
  59. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/lib/index.ts +2 -0
  60. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/lib/ssrMenuEligibility.ts +107 -0
  61. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/lib/ssrMenuVisibility.ts +91 -0
  62. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/service/ssr.ts +30 -0
  63. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/types/ssrMenu.ts +67 -0
  64. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/test/ssrMenuEligibility.test.ts +135 -0
  65. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/test/ssrMenuVisibility.test.ts +64 -0
  66. package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
  67. package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/package.json +1 -1
  68. package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/src/lib/const/index.ts +1 -0
  69. package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/src/lib/const/site.ts +1 -0
  70. package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
  71. package/vona/src/suite-vendor/a-vona/modules/a-user/package.json +1 -1
  72. package/vona/src/suite-vendor/a-vona/modules/a-user/src/types/role.ts +1 -0
  73. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  74. package/zova/pnpm-lock.yaml +1 -1
  75. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx +3 -2
  76. package/zova/src/suite/a-home/modules/home-api/mock/passport.fake.ts +6 -6
  77. package/zova/src/suite/a-home/modules/home-base/src/lib/index.ts +1 -0
  78. package/zova/src/suite/a-home/modules/home-base/src/lib/roleSiteAdmission.ts +15 -0
  79. package/zova/src/suite/a-home/modules/home-base/src/service/routerGuards.ts +3 -3
  80. package/zova/src/suite/a-home/modules/home-layoutadmin/src/model/menu.ts +4 -3
  81. package/zova/src/suite/a-home/modules/home-layoutweb/src/model/menu.ts +4 -3
  82. package/zova/src/suite/a-home/modules/home-passport/package.json +2 -1
  83. package/zova/src/suite/a-home/modules/home-passport/src/model/passport.ts +2 -2
  84. package/zova/src/suite/a-home/modules/home-passport/test/lib/roleSiteAdmission.test.ts +23 -0
  85. package/zova/src/suite/a-training/modules/training-student/src/bean/tableCell.actionSummary.tsx +29 -6
  86. package/zova/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +1 -0
  87. package/zova/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +1 -0
  88. package/zova/src/suite/a-training/modules/training-student/src/model/student.ts +1 -1
  89. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
  90. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/lib/mutationSuccess.ts +11 -0
  91. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +12 -7
  92. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/test/lib/mutationSuccess.test.ts +95 -0
  93. package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
  94. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/bean/meta.version.ts +0 -32
  95. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/test/meta.version.test.ts +0 -44
@@ -1,76 +1,6 @@
1
1
  // eslint-disable
2
2
  import type { TypeSymbolKeyFieldsMore } from 'vona-module-a-orm';
3
3
  import type { TypeEntityOptionsFields,TypeControllerOptionsActions } from 'vona-module-a-openapi';
4
- /** service: begin */
5
- export * from '../service/menu.ts';
6
- export * from '../service/permission.ts';
7
- export * from '../service/siteCatalog.ts';
8
-
9
- import 'vona-module-a-bean';
10
- declare module 'vona-module-a-bean' {
11
-
12
- export interface IServiceRecord {
13
- 'home-base:menu': never;
14
- 'home-base:permission': never;
15
- 'home-base:siteCatalog': never;
16
- }
17
-
18
-
19
- }
20
- declare module 'vona-module-home-base' {
21
-
22
- export interface ServiceMenu {
23
- /** @internal */
24
- get scope(): ScopeModuleHomeBase;
25
- }
26
-
27
- export interface ServiceMenu {
28
- get $beanFullName(): 'home-base.service.menu';
29
- get $onionName(): 'home-base:menu';
30
- }
31
-
32
- export interface ServicePermission {
33
- /** @internal */
34
- get scope(): ScopeModuleHomeBase;
35
- }
36
-
37
- export interface ServicePermission {
38
- get $beanFullName(): 'home-base.service.permission';
39
- get $onionName(): 'home-base:permission';
40
- }
41
-
42
- export interface ServiceSiteCatalog {
43
- /** @internal */
44
- get scope(): ScopeModuleHomeBase;
45
- }
46
-
47
- export interface ServiceSiteCatalog {
48
- get $beanFullName(): 'home-base.service.siteCatalog';
49
- get $onionName(): 'home-base:siteCatalog';
50
- }
51
- }
52
- /** service: end */
53
- /** service: begin */
54
- import type { ServiceMenu } from '../service/menu.ts';
55
- import type { ServicePermission } from '../service/permission.ts';
56
- import type { ServiceSiteCatalog } from '../service/siteCatalog.ts';
57
- export interface IModuleService {
58
- 'menu': ServiceMenu;
59
- 'permission': ServicePermission;
60
- 'siteCatalog': ServiceSiteCatalog;
61
- }
62
- /** service: end */
63
- /** service: begin */
64
-
65
- import 'vona';
66
- declare module 'vona' {
67
- export interface IBeanRecordGeneral {
68
- 'home-base.service.menu': ServiceMenu;
69
- 'home-base.service.permission': ServicePermission;
70
- 'home-base.service.siteCatalog': ServiceSiteCatalog;
71
- }
72
- }
73
- /** service: end */
74
4
  /** dto: begin */
75
5
  export * from '../dto/siteCatalogSelectRes.ts';
76
6
  export * from '../dto/siteCatalogSelectResItem.ts';
@@ -195,11 +125,84 @@ import 'vona-module-a-openapi';
195
125
  }
196
126
 
197
127
  /** controller: end */
128
+ /** service: begin */
129
+ export * from '../service/menu.ts';
130
+ export * from '../service/permission.ts';
131
+ export * from '../service/siteCatalog.ts';
132
+
133
+ import 'vona-module-a-bean';
134
+ declare module 'vona-module-a-bean' {
135
+
136
+ export interface IServiceRecord {
137
+ 'home-base:menu': never;
138
+ 'home-base:permission': never;
139
+ 'home-base:siteCatalog': never;
140
+ }
141
+
142
+
143
+ }
144
+ declare module 'vona-module-home-base' {
145
+
146
+ export interface ServiceMenu {
147
+ /** @internal */
148
+ get scope(): ScopeModuleHomeBase;
149
+ }
150
+
151
+ export interface ServiceMenu {
152
+ get $beanFullName(): 'home-base.service.menu';
153
+ get $onionName(): 'home-base:menu';
154
+ }
155
+
156
+ export interface ServicePermission {
157
+ /** @internal */
158
+ get scope(): ScopeModuleHomeBase;
159
+ }
160
+
161
+ export interface ServicePermission {
162
+ get $beanFullName(): 'home-base.service.permission';
163
+ get $onionName(): 'home-base:permission';
164
+ }
165
+
166
+ export interface ServiceSiteCatalog {
167
+ /** @internal */
168
+ get scope(): ScopeModuleHomeBase;
169
+ }
170
+
171
+ export interface ServiceSiteCatalog {
172
+ get $beanFullName(): 'home-base.service.siteCatalog';
173
+ get $onionName(): 'home-base:siteCatalog';
174
+ }
175
+ }
176
+ /** service: end */
177
+ /** service: begin */
178
+ import type { ServiceMenu } from '../service/menu.ts';
179
+ import type { ServicePermission } from '../service/permission.ts';
180
+ import type { ServiceSiteCatalog } from '../service/siteCatalog.ts';
181
+ export interface IModuleService {
182
+ 'menu': ServiceMenu;
183
+ 'permission': ServicePermission;
184
+ 'siteCatalog': ServiceSiteCatalog;
185
+ }
186
+ /** service: end */
187
+ /** service: begin */
188
+
189
+ import 'vona';
190
+ declare module 'vona' {
191
+ export interface IBeanRecordGeneral {
192
+ 'home-base.service.menu': ServiceMenu;
193
+ 'home-base.service.permission': ServicePermission;
194
+ 'home-base.service.siteCatalog': ServiceSiteCatalog;
195
+ }
196
+ }
197
+ /** service: end */
198
+ /** locale: begin */
199
+ import { locales } from './locales.ts';
200
+ /** locale: end */
198
201
  /** main: begin */
199
202
  export * from '../main.ts';
200
203
  /** main: end */
201
204
  /** scope: begin */
202
- import { BeanScopeBase, type BeanScopeUtil } from 'vona';
205
+ import { BeanScopeBase, type BeanScopeUtil, type TypeModuleLocales, type TypeLocaleBase } from 'vona';
203
206
  import { Scope } from 'vona-module-a-bean';
204
207
 
205
208
  @Scope()
@@ -207,6 +210,7 @@ export class ScopeModuleHomeBase extends BeanScopeBase {}
207
210
 
208
211
  export interface ScopeModuleHomeBase {
209
212
  util: BeanScopeUtil;
213
+ locale: TypeModuleLocales<(typeof locales)[TypeLocaleBase]>;
210
214
  service: IModuleService;
211
215
  }
212
216
 
@@ -222,7 +226,9 @@ declare module 'vona' {
222
226
 
223
227
 
224
228
 
225
-
229
+ export interface IBeanScopeLocale {
230
+ 'home-base': (typeof locales)[TypeLocaleBase];
231
+ }
226
232
 
227
233
 
228
234
  }
@@ -0,0 +1,18 @@
1
+ import type { TypeLocaleBase } from 'vona';
2
+
3
+ import { $makeLocaleMagic } from 'vona';
4
+
5
+ import locale_en_us from '../config/locale/en-us.ts';
6
+ import locale_zh_cn from '../config/locale/zh-cn.ts';
7
+
8
+ export const locales = {
9
+ 'en-us': locale_en_us,
10
+ 'zh-cn': locale_zh_cn,
11
+ };
12
+
13
+ export function $locale<K extends keyof (typeof locales)[TypeLocaleBase]>(
14
+ key: K,
15
+ ...args: any[]
16
+ ): any {
17
+ return $makeLocaleMagic(`home-base::${key}`, ...args);
18
+ }
@@ -0,0 +1,3 @@
1
+ export default {
2
+ SiteAll: 'All Sites',
3
+ };
@@ -0,0 +1,3 @@
1
+ export default {
2
+ SiteAll: '全部站点',
3
+ };
@@ -1 +1,2 @@
1
+ export * from './.metadata/locales.ts';
1
2
  export * from './.metadata/index.ts';
@@ -2,22 +2,29 @@ import type { IDecoratorSsrSiteOptions } from 'vona-module-a-ssr';
2
2
 
3
3
  import { BeanBase } from 'vona';
4
4
  import { Service } from 'vona-module-a-bean';
5
+ import { roleSiteIdAll } from 'vona-module-a-openapiutils';
5
6
 
6
7
  import type { DtoSiteCatalogSelectRes } from '../dto/siteCatalogSelectRes.ts';
7
8
 
8
9
  @Service()
9
10
  export class ServiceSiteCatalog extends BeanBase {
10
11
  async select(): Promise<DtoSiteCatalogSelectRes> {
11
- const list = this.app
12
- .scope('a-ssr')
13
- .service.ssr.getSitesEnabled()
14
- .map(site => {
15
- const options = site.beanOptions.options as IDecoratorSsrSiteOptions;
16
- return {
17
- siteId: String(options.siteId),
18
- title: this.app.meta.text.locale(this.ctx.locale, options.title),
19
- };
20
- });
12
+ const list = [
13
+ {
14
+ siteId: roleSiteIdAll,
15
+ title: this.scope.locale.SiteAll(),
16
+ },
17
+ ...this.app
18
+ .scope('a-ssr')
19
+ .service.ssr.getSitesEnabled()
20
+ .map(site => {
21
+ const options = site.beanOptions.options as IDecoratorSsrSiteOptions;
22
+ return {
23
+ siteId: String(options.siteId),
24
+ title: this.app.meta.text.locale(this.ctx.locale, options.title),
25
+ };
26
+ }),
27
+ ];
21
28
  return {
22
29
  list,
23
30
  total: String(list.length),
@@ -21,6 +21,10 @@ export class MetaVersion extends BeanBase implements IMetaVersionUpdate, IMetaVe
21
21
  table.string(entityRole.title, 255).comment(entityRole.$comment.title);
22
22
  table.json(entityRole.titleLocales).comment(entityRole.$comment.titleLocales);
23
23
  table.json(entityRole.siteIds).comment(entityRole.$comment.siteIds);
24
+ table
25
+ .boolean(entityRole.builtin)
26
+ .defaultTo(entityRole.$default.builtin)
27
+ .comment(entityRole.$comment.builtin);
24
28
  });
25
29
  // homeUser
26
30
  const entityUser = this.scope.entity.user;
@@ -62,6 +66,7 @@ export class MetaVersion extends BeanBase implements IMetaVersionUpdate, IMetaVe
62
66
  title: role.title,
63
67
  titleLocales: role.titleLocales ?? {},
64
68
  siteIds: role.siteIds,
69
+ builtin: role.builtin,
65
70
  });
66
71
  }
67
72
  // user: admin
@@ -1,5 +1,7 @@
1
1
  import type { VonaApplication } from 'vona';
2
2
 
3
+ import { roleSiteIdAll } from 'vona-module-a-openapiutils';
4
+
3
5
  import { builtinRoles } from './roles.ts';
4
6
 
5
7
  export { builtinRoles } from './roles.ts';
@@ -14,13 +16,42 @@ export interface IHomeUserConfig {
14
16
  builtinRoles: typeof builtinRoles;
15
17
  }
16
18
 
17
- export function config(_app: VonaApplication): IHomeUserConfig {
19
+ export function config(app: VonaApplication): IHomeUserConfig {
20
+ const { env } = app.meta;
18
21
  return {
19
22
  passwordDefault: {
20
- admin: '123456',
23
+ admin: env.HOME_USER_PASSWORD_DEFAULT_ADMIN || '123456',
24
+ },
25
+ disableBootstrapSystemAdmin: env.HOME_USER_DISABLE_BOOTSTRAP_SYSTEM_ADMIN === 'true',
26
+ disableUserAdmin: env.HOME_USER_DISABLE_USER_ADMIN === 'true',
27
+ builtinRoles: {
28
+ ...builtinRoles,
29
+ registeredUser: {
30
+ ...builtinRoles.registeredUser,
31
+ siteIds: parseBuiltinRoleSiteIds(
32
+ env.HOME_USER_BUILTIN_ROLE_REGISTERED_USER_SITE_IDS,
33
+ 'HOME_USER_BUILTIN_ROLE_REGISTERED_USER_SITE_IDS',
34
+ ),
35
+ },
36
+ systemAdmin: {
37
+ ...builtinRoles.systemAdmin,
38
+ siteIds: parseBuiltinRoleSiteIds(
39
+ env.HOME_USER_BUILTIN_ROLE_SYSTEM_ADMIN_SITE_IDS,
40
+ 'HOME_USER_BUILTIN_ROLE_SYSTEM_ADMIN_SITE_IDS',
41
+ ),
42
+ },
21
43
  },
22
- disableBootstrapSystemAdmin: false,
23
- disableUserAdmin: false,
24
- builtinRoles,
25
44
  };
26
45
  }
46
+
47
+ function parseBuiltinRoleSiteIds(value: string | undefined, variableName: string): string[] {
48
+ const siteIds = value
49
+ ?.split(',')
50
+ .map(siteId => siteId.trim())
51
+ .filter(Boolean) ?? [roleSiteIdAll];
52
+ if (siteIds.length === 0) return [roleSiteIdAll];
53
+ if (siteIds.includes(roleSiteIdAll) && siteIds.length !== 1) {
54
+ throw new Error(`${variableName} must contain only "${roleSiteIdAll}" or concrete site IDs`);
55
+ }
56
+ return siteIds;
57
+ }
@@ -1,24 +1,29 @@
1
1
  import type { LocalizedTextMap } from 'vona-module-a-user';
2
2
 
3
+ import { roleSiteIdAll } from 'vona-module-a-openapiutils';
4
+
3
5
  export interface IRoleConfig {
4
6
  title: string;
5
7
  titleLocales?: LocalizedTextMap;
6
8
  siteIds: string[];
9
+ builtin: boolean;
7
10
  }
8
11
 
9
12
  export const builtinRoles: Record<string, IRoleConfig> = {
10
13
  registeredUser: {
11
14
  title: 'Registered User',
15
+ builtin: true,
12
16
  titleLocales: {
13
17
  'zh-cn': '注册用户',
14
18
  },
15
- siteIds: ['web'],
19
+ siteIds: [roleSiteIdAll],
16
20
  },
17
21
  systemAdmin: {
18
22
  title: 'System Administrator',
23
+ builtin: true,
19
24
  titleLocales: {
20
25
  'zh-cn': '系统管理员',
21
26
  },
22
- siteIds: ['web', 'admin'],
27
+ siteIds: [roleSiteIdAll],
23
28
  },
24
29
  };
@@ -23,4 +23,7 @@ export class EntityRole extends EntityBase implements IRole {
23
23
 
24
24
  @Api.field(v.array(z.string()))
25
25
  siteIds: string[];
26
+
27
+ @Api.field(v.default(false))
28
+ builtin: boolean;
26
29
  }
@@ -0,0 +1,11 @@
1
+ declare module 'vona' {
2
+ export interface VonaConfigEnv {
3
+ HOME_USER_PASSWORD_DEFAULT_ADMIN: string | undefined;
4
+ HOME_USER_DISABLE_BOOTSTRAP_SYSTEM_ADMIN: string | undefined;
5
+ HOME_USER_DISABLE_USER_ADMIN: string | undefined;
6
+ HOME_USER_BUILTIN_ROLE_REGISTERED_USER_SITE_IDS: string | undefined;
7
+ HOME_USER_BUILTIN_ROLE_SYSTEM_ADMIN_SITE_IDS: string | undefined;
8
+ }
9
+ }
10
+
11
+ export {};
@@ -1,4 +1,5 @@
1
1
  export * from './auth.ts';
2
+ export * from './env.ts';
2
3
  export * from './jwt.ts';
3
4
  export * from './passport.ts';
4
5
  export * from './role.ts';
@@ -0,0 +1,65 @@
1
+ import assert from 'node:assert';
2
+ import { describe, it } from 'node:test';
3
+
4
+ import { config } from '../src/config/config.ts';
5
+ import { builtinRoles } from '../src/config/roles.ts';
6
+
7
+ function createConfig(env: Record<string, string | undefined>) {
8
+ return config({ meta: { env } } as never);
9
+ }
10
+
11
+ describe('config.test.ts', () => {
12
+ it('uses environment overrides and defaults without mutating static role definitions', () => {
13
+ const configured = createConfig({
14
+ HOME_USER_PASSWORD_DEFAULT_ADMIN: 'strong-admin-password',
15
+ HOME_USER_DISABLE_BOOTSTRAP_SYSTEM_ADMIN: 'true',
16
+ HOME_USER_DISABLE_USER_ADMIN: 'true',
17
+ HOME_USER_BUILTIN_ROLE_REGISTERED_USER_SITE_IDS: 'web',
18
+ HOME_USER_BUILTIN_ROLE_SYSTEM_ADMIN_SITE_IDS: 'web,admin',
19
+ });
20
+ assert.equal(configured.passwordDefault.admin, 'strong-admin-password');
21
+ assert.equal(configured.disableBootstrapSystemAdmin, true);
22
+ assert.equal(configured.disableUserAdmin, true);
23
+ assert.deepEqual(configured.builtinRoles.registeredUser.siteIds, ['web']);
24
+ assert.deepEqual(configured.builtinRoles.systemAdmin.siteIds, ['web', 'admin']);
25
+ assert.deepEqual(builtinRoles.registeredUser.siteIds, ['*']);
26
+ assert.deepEqual(builtinRoles.systemAdmin.siteIds, ['*']);
27
+
28
+ const defaults = createConfig({
29
+ HOME_USER_PASSWORD_DEFAULT_ADMIN: '',
30
+ HOME_USER_DISABLE_BOOTSTRAP_SYSTEM_ADMIN: 'false',
31
+ HOME_USER_DISABLE_USER_ADMIN: 'invalid',
32
+ });
33
+ assert.equal(defaults.passwordDefault.admin, '123456');
34
+ assert.equal(defaults.disableBootstrapSystemAdmin, false);
35
+ assert.equal(defaults.disableUserAdmin, false);
36
+ assert.deepEqual(defaults.builtinRoles.registeredUser.siteIds, ['*']);
37
+ assert.deepEqual(defaults.builtinRoles.systemAdmin.siteIds, ['*']);
38
+ });
39
+
40
+ it('parses comma-delimited role site IDs and falls back for blank values', () => {
41
+ const configured = createConfig({
42
+ HOME_USER_BUILTIN_ROLE_REGISTERED_USER_SITE_IDS: ' web, admin, , store ',
43
+ HOME_USER_BUILTIN_ROLE_SYSTEM_ADMIN_SITE_IDS: ' , ',
44
+ });
45
+ assert.deepEqual(configured.builtinRoles.registeredUser.siteIds, ['web', 'admin', 'store']);
46
+ assert.deepEqual(configured.builtinRoles.systemAdmin.siteIds, ['*']);
47
+ });
48
+
49
+ it('rejects non-exclusive wildcard role site IDs', () => {
50
+ assert.throws(
51
+ () =>
52
+ createConfig({
53
+ HOME_USER_BUILTIN_ROLE_REGISTERED_USER_SITE_IDS: '*,web',
54
+ }),
55
+ /HOME_USER_BUILTIN_ROLE_REGISTERED_USER_SITE_IDS/,
56
+ );
57
+ assert.throws(
58
+ () =>
59
+ createConfig({
60
+ HOME_USER_BUILTIN_ROLE_SYSTEM_ADMIN_SITE_IDS: 'web,*',
61
+ }),
62
+ /HOME_USER_BUILTIN_ROLE_SYSTEM_ADMIN_SITE_IDS/,
63
+ );
64
+ });
65
+ });
@@ -9,13 +9,17 @@ describe('role.test.ts', () => {
9
9
  const roleRegisteredUser = await scope.model.role.getByName('registeredUser');
10
10
  assert.equal(roleRegisteredUser?.title, 'Registered User');
11
11
  assert.deepEqual(roleRegisteredUser?.titleLocales, { 'zh-cn': '注册用户' });
12
- assert.ok(roleRegisteredUser?.siteIds.includes('web'));
12
+ assert.deepEqual(
13
+ roleRegisteredUser?.siteIds,
14
+ scope.config.builtinRoles.registeredUser.siteIds,
15
+ );
16
+ assert.equal(roleRegisteredUser?.builtin, true);
13
17
 
14
18
  const roleSystemAdmin = await scope.model.role.getByName('systemAdmin');
15
19
  assert.equal(roleSystemAdmin?.title, 'System Administrator');
16
20
  assert.deepEqual(roleSystemAdmin?.titleLocales, { 'zh-cn': '系统管理员' });
17
- assert.ok(roleSystemAdmin?.siteIds.includes('web'));
18
- assert.ok(roleSystemAdmin?.siteIds.includes('admin'));
21
+ assert.deepEqual(roleSystemAdmin?.siteIds, scope.config.builtinRoles.systemAdmin.siteIds);
22
+ assert.equal(roleSystemAdmin?.builtin, true);
19
23
 
20
24
  await app.bean.passport.signinMock();
21
25
  const passport = await app.bean.executor.performAction('get', '/home/user/passport/current');
@@ -24,13 +28,17 @@ describe('role.test.ts', () => {
24
28
  );
25
29
  assert.equal(passportRegisteredUser?.title, 'Registered User');
26
30
  assert.deepEqual(passportRegisteredUser?.titleLocales, { 'zh-cn': '注册用户' });
27
- assert.ok(passportRegisteredUser?.siteIds.includes('web'));
31
+ assert.deepEqual(
32
+ passportRegisteredUser?.siteIds,
33
+ scope.config.builtinRoles.registeredUser.siteIds,
34
+ );
35
+ assert.equal(passportRegisteredUser?.builtin, true);
28
36
 
29
37
  const passportSystemAdmin = passport.roles.find((role: any) => role.name === 'systemAdmin');
30
38
  assert.equal(passportSystemAdmin?.title, 'System Administrator');
31
39
  assert.deepEqual(passportSystemAdmin?.titleLocales, { 'zh-cn': '系统管理员' });
32
- assert.ok(passportSystemAdmin?.siteIds.includes('web'));
33
- assert.ok(passportSystemAdmin?.siteIds.includes('admin'));
40
+ assert.deepEqual(passportSystemAdmin?.siteIds, scope.config.builtinRoles.systemAdmin.siteIds);
41
+ assert.equal(passportSystemAdmin?.builtin, true);
34
42
  });
35
43
  });
36
44
  });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-rbac",
3
- "version": "5.0.1",
3
+ "version": "5.0.2",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "Vona Module"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-ssr",
3
- "version": "5.1.30",
3
+ "version": "5.1.31",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "keywords": [
@@ -162,6 +162,7 @@ declare module 'vona' {
162
162
  }
163
163
  /** service: end */
164
164
  /** event: begin */
165
+ export * from '../bean/event.resolveMenuVisibility.ts';
165
166
  export * from '../bean/event.retrieveMenus.ts';
166
167
  export * from '../bean/event.retrieveMenusSite.ts';
167
168
 
@@ -172,6 +173,16 @@ declare module 'vona' {
172
173
  }
173
174
  declare module 'vona-module-a-ssr' {
174
175
 
176
+ export interface EventResolveMenuVisibility {
177
+ /** @internal */
178
+ get scope(): ScopeModuleASsr;
179
+ }
180
+
181
+ export interface EventResolveMenuVisibility {
182
+ get $beanFullName(): 'a-ssr.event.resolveMenuVisibility';
183
+ get $onionName(): 'a-ssr:resolveMenuVisibility';
184
+ }
185
+
175
186
  export interface EventRetrieveMenus {
176
187
  /** @internal */
177
188
  get scope(): ScopeModuleASsr;
@@ -194,20 +205,24 @@ declare module 'vona-module-a-ssr' {
194
205
  }
195
206
  /** event: end */
196
207
  /** event: begin */
208
+ import type { EventResolveMenuVisibility } from '../bean/event.resolveMenuVisibility.ts';
197
209
  import type { EventRetrieveMenus } from '../bean/event.retrieveMenus.ts';
198
210
  import type { EventRetrieveMenusSite } from '../bean/event.retrieveMenusSite.ts';
199
211
  export interface IModuleEvent {
200
- 'retrieveMenus': EventRetrieveMenus;
212
+ 'resolveMenuVisibility': EventResolveMenuVisibility;
213
+ 'retrieveMenus': EventRetrieveMenus;
201
214
  'retrieveMenusSite': EventRetrieveMenusSite;
202
215
  }
203
216
  /** event: end */
204
217
  /** event: begin */
218
+ import type { TypeEventResolveMenuVisibilityData, TypeEventResolveMenuVisibilityResult } from '../bean/event.resolveMenuVisibility.ts';
205
219
  import type { TypeEventRetrieveMenusData, TypeEventRetrieveMenusResult } from '../bean/event.retrieveMenus.ts';
206
220
  import type { TypeEventRetrieveMenusSiteData, TypeEventRetrieveMenusSiteResult } from '../bean/event.retrieveMenusSite.ts';
207
221
  import type { EventOn } from 'vona-module-a-event';
208
222
  declare module 'vona-module-a-event' {
209
223
  export interface IEventRecord {
210
- 'a-ssr:retrieveMenus': EventOn<TypeEventRetrieveMenusData, TypeEventRetrieveMenusResult>;
224
+ 'a-ssr:resolveMenuVisibility': EventOn<TypeEventResolveMenuVisibilityData, TypeEventResolveMenuVisibilityResult>;
225
+ 'a-ssr:retrieveMenus': EventOn<TypeEventRetrieveMenusData, TypeEventRetrieveMenusResult>;
211
226
  'a-ssr:retrieveMenusSite': EventOn<TypeEventRetrieveMenusSiteData, TypeEventRetrieveMenusSiteResult>;
212
227
  }
213
228
  }
@@ -0,0 +1,19 @@
1
+ import type { TableIdentity } from 'table-identity';
2
+
3
+ import { BeanEventBase, Event } from 'vona-module-a-event';
4
+
5
+ import type { ISsrMenuItemPrepared } from '../types/ssrMenu.ts';
6
+
7
+ export interface TypeEventResolveMenuVisibilityData {
8
+ ssrSiteName: string;
9
+ menus: ISsrMenuItemPrepared[];
10
+ currentRoleIds: TableIdentity[];
11
+ }
12
+
13
+ export type TypeEventResolveMenuVisibilityResult = ISsrMenuItemPrepared[];
14
+
15
+ @Event()
16
+ export class EventResolveMenuVisibility extends BeanEventBase<
17
+ TypeEventResolveMenuVisibilityData,
18
+ TypeEventResolveMenuVisibilityResult
19
+ > {}