cabloy 5.1.248 → 5.1.250

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 (121) hide show
  1. package/.cabloy-version +1 -1
  2. package/.github/workflows/create-cabloy.yml +50 -3
  3. package/.vscode/settings.json +1 -2
  4. package/CHANGELOG.md +31 -0
  5. package/package.json +1 -7
  6. package/repo-agent-governance/managed-assets.json +12 -12
  7. package/repo-agent-governance/skills/cabloy-spec-execution/references/status-and-evidence.md +1 -1
  8. package/repo-agent-governance/skills/cabloy-spec-generation/references/canonical-spec-input.md +1 -1
  9. package/repo-agent-governance/skills/cabloy-spec-generation/references/repo-specs-document-set.md +2 -2
  10. package/repo-agent-governance/skills/cabloy-spec-generation/references/traceability-and-status-rules.md +1 -1
  11. package/repo-agent-governance/tools/spec-charts/generate-implementation-charts.mjs +73 -22
  12. package/repo-agent-governance/tools/spec-charts/generate-implementation-charts.test.mjs +98 -7
  13. package/repo-docs/ai/playbook-spec-execution.md +1 -1
  14. package/repo-docs/backend/crud-workflow.md +92 -1
  15. package/repo-docs/backend/dto-guide.md +16 -1
  16. package/repo-docs/backend/dto-infer-generation.md +23 -5
  17. package/repo-docs/backend/orm-aggregate-group-guide.md +32 -0
  18. package/repo-docs/backend/validation-guide.md +38 -2
  19. package/repo-e2e/specs/cabloy-basic.spec.ts +9 -2
  20. package/vona/package.original.json +1 -7
  21. package/vona/packages-cli/cli/package.json +1 -1
  22. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudAnalysis/aggregate/src/dto/{{resourceName}}AggregateRes.tsx_ +11 -0
  23. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudAnalysis/common/src/lib/{{resourceName}}Analysis.ts_ +14 -0
  24. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudAnalysis/common/test/{{resourceName}}Analysis.test.ts_ +81 -0
  25. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudAnalysis/group/src/dto/{{resourceName}}GroupResItem.tsx_ +11 -0
  26. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/controller/{{resourceName}}.ts_ +18 -1
  27. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/dto/{{resourceName}}SelectReq.tsx_ +2 -0
  28. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/service/{{resourceName}}.ts_ +14 -1
  29. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/test/{{resourceName}}.test.ts_ +4 -0
  30. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/snippets/3-en-us.ts +2 -0
  31. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/snippets/4-zh-cn.ts +2 -0
  32. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/controller/{{resourceName}}.ts_ +18 -1
  33. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/dto/{{resourceName}}SelectReq.tsx_ +2 -0
  34. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/service/{{resourceName}}.ts_ +14 -1
  35. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/test/{{resourceName}}.test.ts_ +1 -1
  36. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/snippets/3-en-us.ts +2 -0
  37. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/snippets/4-zh-cn.ts +2 -0
  38. package/vona/packages-cli/cli-set-api/package.json +1 -1
  39. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.tools.crudBasic.ts +2 -0
  40. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.tools.crudStart.ts +2 -0
  41. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.tools.masterDetail.ts +9 -1
  42. package/vona/packages-cli/cli-set-api/src/lib/crudAnalysis.ts +11 -0
  43. package/vona/packages-cli/cli-set-api/test/crudAnalysis.test.ts +385 -0
  44. package/vona/packages-utils/babel-plugin-vona-bean-module/package.json +1 -1
  45. package/vona/packages-vona/vona/package.json +1 -1
  46. package/vona/packages-vona/vona-core/package.json +1 -1
  47. package/vona/packages-vona/vona-mock/package.json +1 -1
  48. package/vona/pnpm-lock.yaml +76 -1008
  49. package/vona/src/suite/a-training/modules/training-student/src/.metadata/index.ts +18 -0
  50. package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +2 -0
  51. package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +2 -0
  52. package/vona/src/suite/a-training/modules/training-student/src/controller/student.ts +28 -1
  53. package/vona/src/suite/a-training/modules/training-student/src/dto/studentAggregateRes.tsx +11 -0
  54. package/vona/src/suite/a-training/modules/training-student/src/dto/studentGroupResItem.tsx +11 -0
  55. package/vona/src/suite/a-training/modules/training-student/src/dto/studentSelectReq.tsx +2 -0
  56. package/vona/src/suite/a-training/modules/training-student/src/lib/studentAnalysis.ts +12 -0
  57. package/vona/src/suite/a-training/modules/training-student/src/service/student.ts +22 -1
  58. package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +4 -0
  59. package/vona/src/suite/a-training/modules/training-student/test/studentAnalysis.test.ts +422 -0
  60. package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
  61. package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
  62. package/vona/src/suite-vendor/a-captcha/modules/a-captcha/package.json +1 -1
  63. package/vona/src/suite-vendor/a-captcha/package.json +1 -1
  64. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  65. package/vona/src/suite-vendor/a-vona/modules/a-hmr/package.json +1 -1
  66. package/vona/src/suite-vendor/a-vona/modules/a-locale/package.json +1 -1
  67. package/vona/src/suite-vendor/a-vona/modules/a-openapi/package.json +1 -1
  68. package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/package.json +1 -1
  69. package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/src/lib/utils.ts +1 -1
  70. package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
  71. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/.metadata/index.ts +26 -26
  72. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/aggregateResult.ts +53 -0
  73. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/analysis/controls.ts +163 -0
  74. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/analysis/execute.ts +49 -0
  75. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/analysis/index.ts +3 -0
  76. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/analysis/policy.ts +140 -0
  77. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/bean.model/bean.model_cache.ts +4 -2
  78. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/bean.model/bean.model_crud_inner.ts +8 -3
  79. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dto.ts +3 -0
  80. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoAggregate.ts +4 -1
  81. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoAnalysis.ts +34 -0
  82. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/index.ts +1 -0
  83. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/analysis.ts +137 -0
  84. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/index.ts +1 -0
  85. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/relationsAggr.ts +1 -1
  86. package/vona/src/suite-vendor/a-vona/modules/a-orm/test/aggregateResult.test.ts +81 -0
  87. package/vona/src/suite-vendor/a-vona/modules/a-orm/test/analysis.test.ts +208 -0
  88. package/vona/src/suite-vendor/a-vona/modules/a-orm/test/analysis.types.test.ts +112 -0
  89. package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
  90. package/vona/src/suite-vendor/a-vona/modules/a-queue/package.json +1 -1
  91. package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/package.json +1 -1
  92. package/vona/src/suite-vendor/a-vona/modules/a-user/package.json +1 -1
  93. package/vona/src/suite-vendor/a-vona/modules/a-web/package.json +1 -1
  94. package/vona/src/suite-vendor/a-vona/modules/a-web/src/.metadata/index.ts +40 -40
  95. package/vona/src/suite-vendor/a-vona/modules/a-web/src/bean/pipe.filter.ts +44 -3
  96. package/vona/src/suite-vendor/a-vona/modules/a-web/src/lib/analysisFilter.ts +69 -0
  97. package/vona/src/suite-vendor/a-vona/modules/a-web/src/lib/decorator/arguments.ts +6 -4
  98. package/vona/src/suite-vendor/a-vona/modules/a-web/src/lib/index.ts +1 -0
  99. package/vona/src/suite-vendor/a-vona/modules/a-web/src/types/dto.ts +2 -1
  100. package/vona/src/suite-vendor/a-vona/modules/a-web/src/types/filter.ts +7 -2
  101. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  102. package/zova/package.original.json +0 -6
  103. package/zova/packages-cli/cli/package.json +2 -2
  104. package/zova/packages-cli/cli-set-front/package.json +2 -2
  105. package/zova/packages-utils/zova-jsx/package.json +2 -2
  106. package/zova/packages-utils/zova-vite/package.json +1 -1
  107. package/zova/packages-zova/zova/package.json +3 -3
  108. package/zova/packages-zova/zova-core/package.json +1 -1
  109. package/zova/pnpm-lock.yaml +11 -963
  110. package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.header.tsx +4 -1
  111. package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.tabs.tsx +2 -2
  112. package/zova/src/suite/a-training/modules/training-student/src/api/openapi/schemas.ts +16 -2
  113. package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +201 -7
  114. package/zova/src/suite/a-training/modules/training-student/src/api/trainingStudent.ts +42 -0
  115. package/zova/src/suite/a-training/modules/training-student/src/apiSchema/trainingStudent.ts +10 -0
  116. package/zova/src/suite-vendor/a-zova/modules/a-ssr/package.json +1 -1
  117. package/zova/src/suite-vendor/a-zova/modules/a-ssrserver/package.json +1 -1
  118. package/zova/src/suite-vendor/a-zova/modules/a-table/package.json +1 -1
  119. package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
  120. package/zova/src/suite-vendor/a-zova/package.json +5 -5
  121. package/vona/.textlintrc.json +0 -19
@@ -0,0 +1,385 @@
1
+ import assert from 'node:assert/strict';
2
+ import { execFileSync } from 'node:child_process';
3
+ import fs from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { describe, it } from 'node:test';
7
+ import { fileURLToPath } from 'node:url';
8
+ import ts from 'typescript';
9
+
10
+ import { LocalTemplate } from '../../cabloy-cli/src/lib/local.template.ts';
11
+ import { CliToolsCrud } from '../src/lib/bean/cli.tools.crud.ts';
12
+ import { CliToolsCrudBasic } from '../src/lib/bean/cli.tools.crudBasic.ts';
13
+ import { CliToolsCrudStart } from '../src/lib/bean/cli.tools.crudStart.ts';
14
+ import { CliToolsMasterDetail } from '../src/lib/bean/cli.tools.masterDetail.ts';
15
+ import crud from '../src/lib/command/tools.crud.ts';
16
+ import basic from '../src/lib/command/tools.crudBasic.ts';
17
+ import start from '../src/lib/command/tools.crudStart.ts';
18
+
19
+ const packageRoot = fileURLToPath(new URL('../', import.meta.url));
20
+ const templateRoot = path.join(packageRoot, 'cli/templates');
21
+ const editions = { basic: CliToolsCrudBasic, start: CliToolsCrudStart };
22
+
23
+ // Exercise the authored templates with the production renderer; packaged templates
24
+ // are checked separately because existing dist-cli output can intentionally be stale.
25
+ class SourceTemplate extends LocalTemplate {
26
+ resolveTemplatePath(_set: string, target: string) {
27
+ return path.isAbsolute(target) ? target : path.join(templateRoot, target);
28
+ }
29
+ }
30
+
31
+ async function fixture<T>(fn: (root: string) => Promise<T>) {
32
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'crud-analysis-'));
33
+ try {
34
+ return await fn(root);
35
+ } finally {
36
+ await fs.rm(root, { recursive: true, force: true });
37
+ }
38
+ }
39
+
40
+ async function makeCli(
41
+ root: string,
42
+ Class: typeof CliToolsCrudBasic | typeof CliToolsCrudStart | typeof CliToolsCrud,
43
+ Template: typeof LocalTemplate = SourceTemplate,
44
+ ) {
45
+ const moduleRoot = path.join(root, 'module');
46
+ await fs.mkdir(path.join(moduleRoot, 'src/bean'), { recursive: true });
47
+ await fs.mkdir(path.join(moduleRoot, 'src/config/locale'), { recursive: true });
48
+ await fs.writeFile(
49
+ path.join(moduleRoot, 'package.json'),
50
+ JSON.stringify({ type: 'module', vonaModule: { fileVersion: 0 } }),
51
+ );
52
+ await fs.writeFile(
53
+ path.join(moduleRoot, 'src/bean/meta.index.ts'),
54
+ "import { Meta } from 'vona-module-a-meta';\n@Meta({ indexes: {} }) class MetaIndex {}\n",
55
+ );
56
+ await fs.writeFile(
57
+ path.join(moduleRoot, 'src/bean/meta.version.ts'),
58
+ 'class MetaVersion { async update(_options: IMetaVersionUpdateOptions) {} }\n',
59
+ );
60
+ for (const locale of ['en-us', 'zh-cn']) {
61
+ await fs.writeFile(
62
+ path.join(moduleRoot, `src/config/locale/${locale}.ts`),
63
+ 'export default {};\n',
64
+ );
65
+ }
66
+ const argv = {
67
+ _: [],
68
+ module: 'test-record',
69
+ resourceName: 'record',
70
+ projectPath: root,
71
+ };
72
+ const cli = new Class({ context: { argv } } as never);
73
+ cli.modulesMeta = { modules: { 'test-record': { root: moduleRoot } } } as never;
74
+ cli.__template = new Template(cli);
75
+ cli.console.log = async () => {};
76
+ cli.helper.formatFile = async () => {};
77
+ const invocations: string[][] = [];
78
+ cli.helper.invokeCli = async args => {
79
+ invocations.push(args);
80
+ };
81
+ return { cli, moduleRoot, invocations };
82
+ }
83
+
84
+ async function filesUnder(root: string): Promise<string[]> {
85
+ const files: string[] = [];
86
+ for (const entry of await fs.readdir(root, { withFileTypes: true })) {
87
+ if (entry.isDirectory()) {
88
+ files.push(
89
+ ...(await filesUnder(path.join(root, entry.name))).map(file => `${entry.name}/${file}`),
90
+ );
91
+ } else {
92
+ files.push(entry.name);
93
+ }
94
+ }
95
+ return files.sort();
96
+ }
97
+
98
+ async function read(root: string, file: string) {
99
+ return await fs.readFile(path.join(root, file), 'utf8');
100
+ }
101
+
102
+ describe('default CRUD analysis generation', { concurrency: false }, () => {
103
+ it('removes analysis flags from command options and usage without adding questions', () => {
104
+ for (const definition of [crud, basic, start]) {
105
+ assert.deepEqual(Object.keys(definition.options), ['module']);
106
+ assert.doesNotMatch(definition.info.usage, /--aggregate|--groupBy/);
107
+ assert.deepEqual(Object.keys(definition.groups.default.questions), [
108
+ 'resourceName',
109
+ 'module',
110
+ ]);
111
+ }
112
+ });
113
+
114
+ for (const [edition, Class] of Object.entries(editions)) {
115
+ it(`${edition}: generates both actions with one finite name/count policy by default`, async () =>
116
+ fixture(async root => {
117
+ const { cli, moduleRoot, invocations } = await makeCli(root, Class);
118
+ await cli.execute();
119
+ const files = await filesUnder(moduleRoot);
120
+ for (const file of [
121
+ 'src/lib/recordAnalysis.ts',
122
+ 'test/recordAnalysis.test.ts',
123
+ 'src/dto/recordGroupResItem.tsx',
124
+ 'src/dto/recordAggregateRes.tsx',
125
+ ]) {
126
+ assert.ok(files.includes(file), file);
127
+ }
128
+ assert.equal(
129
+ files.some(file => file.includes('AnalysisReq')),
130
+ false,
131
+ );
132
+ assert.deepEqual(invocations, [[':tools:metadata', 'test-record']]);
133
+ const controller = await read(moduleRoot, 'src/controller/record.ts');
134
+ const service = await read(moduleRoot, 'src/service/record.ts');
135
+ const req = await read(moduleRoot, 'src/dto/recordSelectReq.tsx');
136
+ const policy = await read(moduleRoot, 'src/lib/recordAnalysis.ts');
137
+ assert.doesNotMatch(controller, /AnalysisReq|\$schema|dataScope/);
138
+ assert.ok(
139
+ controller.includes(
140
+ 'async select(@Arg.filter(DtoRecordSelectReq) params: IQueryParams<ModelRecord>)',
141
+ ),
142
+ );
143
+ assert.match(req, /\$Dto.queryPage\(EntityRecord, \['name', 'createdAt'\]\)/);
144
+ assert.match(req, /analysis: recordAnalysis/);
145
+ assert.match(req, /import \{ recordAnalysis \} from '\.\.\/lib\/recordAnalysis\.ts'/);
146
+ assert.match(
147
+ policy,
148
+ /export const recordAnalysis = \$defineAnalysis\(\(\) => ModelRecord, \{/,
149
+ );
150
+ assert.match(policy, /groups: \['name'\]/);
151
+ assert.match(policy, /aggrs: \{ count: \['\*'\] \}/);
152
+ assert.match(policy, /group: \{ groups: \['name'\], aggrs: \{ count: \['\*'\] \} \}/);
153
+ assert.match(policy, /aggregate: \{ aggrs: \{ count: \['\*'\] \} \}/);
154
+ assert.match(policy, /limits: \{ maxGroups: 3, maxMetrics: 10, maxGroupRows: 1000 \}/);
155
+ assert.doesNotMatch(policy, /description|AnalysisAggrs|AnalysisGroup/);
156
+ assert.doesNotMatch(
157
+ service,
158
+ /\.\.\.params|\.group\(|\.aggregate\(|items\.map|Object\.hasOwn|normalizeRecordCount|count_all:/,
159
+ );
160
+ assert.equal(controller.match(/actionInherit: 'select'/g)?.length, 2);
161
+ for (const action of ['group', 'aggregate']) {
162
+ const mode = action === 'group' ? 'Group' : 'Aggregate';
163
+ assert.ok(
164
+ controller.includes(
165
+ `async ${action}(@Arg.filter(DtoRecordSelectReq, { mode: '${action}' }) params: TypeAnalysis${mode}Params<typeof recordAnalysis>)`,
166
+ ),
167
+ `${action} must derive action controls from the same SelectReq policy`,
168
+ );
169
+ assert.ok(
170
+ controller.indexOf(`@Web.get('${action}'`) < controller.indexOf("@Web.get(':id'"),
171
+ );
172
+ assert.ok(
173
+ service.includes(
174
+ `async ${action}(params?: TypeAnalysis${mode}Params<typeof recordAnalysis>)`,
175
+ ),
176
+ );
177
+ assert.ok(
178
+ service.includes(
179
+ `return await $executeAnalysis${mode}(this.scope.model.record, recordAnalysis, params)`,
180
+ ),
181
+ );
182
+ }
183
+ const groupDto = await read(moduleRoot, 'src/dto/recordGroupResItem.tsx');
184
+ assert.match(groupDto, /\$Dto\.analysisGroup\(recordAnalysis\)/);
185
+ assert.doesNotMatch(groupDto, /description|\$Class|\$makeSchema|zod/);
186
+ assert.match(
187
+ await read(moduleRoot, 'src/dto/recordAggregateRes.tsx'),
188
+ /\$Dto\.analysisAggregate\(recordAnalysis\)/,
189
+ );
190
+ for (const locale of ['en-us', 'zh-cn']) {
191
+ const source = await read(moduleRoot, `src/config/locale/${locale}.ts`);
192
+ assert.match(source, /RecordGroup/);
193
+ assert.match(source, /RecordAggregate/);
194
+ }
195
+ const analysisTest = await read(moduleRoot, 'test/recordAnalysis.test.ts');
196
+ assert.doesNotMatch(analysisTest, /normalizeRecordCount|normalizes only/);
197
+ assert.match(
198
+ analysisTest,
199
+ /returns default and requested-only string counts with validated controls/,
200
+ );
201
+ assert.match(analysisTest, /JSON\.stringify\(\{ count: \['\*'\] \}\)/);
202
+ assert.match(
203
+ analysisTest,
204
+ /The shared policy does not enable analysis controls on ordinary select/,
205
+ );
206
+ assert.match(analysisTest, /\{ name \}/);
207
+ assert.match(analysisTest, /where: \{ id: fixture.id \}/);
208
+ assert.match(
209
+ analysisTest,
210
+ /columns: \['id'\], orders: 'name,desc', pageNo: 2, pageSize: 1/,
211
+ );
212
+ assert.match(analysisTest, /assert.rejects\(/);
213
+ assert.match(analysisTest, /for \(const action of \['group', 'aggregate'\] as const\)/);
214
+ assert.match(analysisTest, /\['description'\]/);
215
+ assert.match(analysisTest, /pageNo: 2, pageSize: 0/);
216
+ const actionTest = await read(moduleRoot, 'test/record.test.ts');
217
+ if (edition === 'basic') {
218
+ assert.match(
219
+ actionTest,
220
+ /group: \['get', `\$\{moduleApiPath\}\/group`, 'Group Record'\]/,
221
+ );
222
+ assert.match(
223
+ actionTest,
224
+ /aggregate: \['get', `\$\{moduleApiPath\}\/aggregate`, 'Aggregate Record'\]/,
225
+ );
226
+ assert.match(actionTest, /group: `\$\{controllerBeanFullName\}#select`/);
227
+ assert.match(actionTest, /aggregate: `\$\{controllerBeanFullName\}#select`/);
228
+ } else {
229
+ assert.match(
230
+ actionTest,
231
+ /const actions = \['create', 'select', 'view', 'update', 'delete', 'group', 'aggregate'\]/,
232
+ );
233
+ }
234
+ for (const file of files.filter(file => /\.tsx?$/.test(file))) {
235
+ const source = await read(moduleRoot, file);
236
+ assert.ok(source.trim(), file);
237
+ assert.doesNotMatch(source, /<%/);
238
+ const result = ts.transpileModule(source, {
239
+ fileName: file,
240
+ reportDiagnostics: true,
241
+ compilerOptions: {
242
+ target: ts.ScriptTarget.ESNext,
243
+ module: ts.ModuleKind.ESNext,
244
+ experimentalDecorators: true,
245
+ },
246
+ });
247
+ assert.deepEqual(
248
+ result.diagnostics?.filter(
249
+ diagnostic => diagnostic.category === ts.DiagnosticCategory.Error,
250
+ ),
251
+ [],
252
+ file,
253
+ );
254
+ }
255
+ }));
256
+
257
+ it(`${edition}: aggregate master-detail cleanup removes generated standalone analysis artifacts`, async () =>
258
+ fixture(async root => {
259
+ const { cli, moduleRoot } = await makeCli(root, Class);
260
+ await cli.execute();
261
+ const masterDetail = new CliToolsMasterDetail({
262
+ context: {
263
+ argv: {
264
+ _detailModule: { root: moduleRoot },
265
+ detailResourceName: 'record',
266
+ createdDetailResource: true,
267
+ detailMode: 'aggregate',
268
+ },
269
+ },
270
+ } as never);
271
+ (masterDetail as any)._handleAggregateDetailMode(false);
272
+ const files = await filesUnder(moduleRoot);
273
+ for (const file of [
274
+ 'src/controller/record.ts',
275
+ 'src/service/record.ts',
276
+ 'src/lib/recordAnalysis.ts',
277
+ 'test/record.test.ts',
278
+ 'test/recordAnalysis.test.ts',
279
+ ]) {
280
+ assert.ok(!files.includes(file), file);
281
+ }
282
+ assert.ok(!files.some(file => file.startsWith('src/dto/')));
283
+ assert.ok(files.includes('src/entity/record.tsx'));
284
+ assert.ok(files.includes('src/model/record.ts'));
285
+ }));
286
+ }
287
+
288
+ it(
289
+ 'freshly packaged beans and production templates match default source generation',
290
+ { skip: process.env.CABLOY_TEST_PACKAGED_CLI !== '1' },
291
+ async () => {
292
+ // Run after tsc:publish. Published packages ship raw EJS in cli/ and compiled
293
+ // snippets in dist-cli/; verify both are packaged, not just present locally.
294
+ const [pack] = JSON.parse(
295
+ execFileSync('npm', ['pack', '--dry-run', '--ignore-scripts', '--json'], {
296
+ cwd: packageRoot,
297
+ encoding: 'utf8',
298
+ }),
299
+ ) as { files: { path: string }[] }[];
300
+ const packedFiles = new Set(pack.files.map(file => file.path));
301
+ assert.ok(packedFiles.has('dist/index.js'));
302
+ const { beans, commands } = await import('../dist/index.js');
303
+ for (const command of ['crud', 'crudBasic', 'crudStart']) {
304
+ assert.deepEqual(Object.keys(commands.tools[command].options), ['module']);
305
+ assert.doesNotMatch(commands.tools[command].info.usage, /--aggregate|--groupBy/);
306
+ }
307
+ const previousBrand = process.env.CabloyCliBrandName;
308
+ process.env.CabloyCliBrandName = 'vona';
309
+ try {
310
+ for (const [edition, Class] of Object.entries(editions)) {
311
+ await fixture(async root => {
312
+ const source = await makeCli(path.join(root, 'source'), Class);
313
+ const packaged = await makeCli(
314
+ path.join(root, 'packaged'),
315
+ beans[edition === 'basic' ? 'tools.crudBasic' : 'tools.crudStart'],
316
+ LocalTemplate,
317
+ );
318
+ for (const target of [
319
+ `tools/crud${edition === 'basic' ? 'Basic' : 'Start'}/boilerplate`,
320
+ `tools/crud${edition === 'basic' ? 'Basic' : 'Start'}/snippets`,
321
+ 'tools/crudAnalysis/common',
322
+ 'tools/crudAnalysis/group',
323
+ 'tools/crudAnalysis/aggregate',
324
+ ]) {
325
+ const resolved = packaged.cli.template.resolveTemplatePath('api', target);
326
+ const prefix = target.endsWith('/snippets') ? 'dist-cli/templates' : 'cli/templates';
327
+ assert.equal(resolved, path.join(packageRoot, prefix, target));
328
+ const templateFiles = await filesUnder(resolved);
329
+ assert.ok(templateFiles.length, target);
330
+ for (const file of templateFiles) {
331
+ assert.ok(
332
+ packedFiles.has(`${prefix}/${target}/${file}`),
333
+ `missing packaged template: ${target}/${file}`,
334
+ );
335
+ }
336
+ }
337
+ await source.cli.execute();
338
+ await packaged.cli.execute();
339
+ const files = await filesUnder(source.moduleRoot);
340
+ assert.deepEqual(await filesUnder(packaged.moduleRoot), files);
341
+ for (const file of files) {
342
+ assert.equal(
343
+ await read(packaged.moduleRoot, file),
344
+ await read(source.moduleRoot, file),
345
+ `${edition} ${file}`,
346
+ );
347
+ }
348
+ assert.deepEqual(packaged.invocations, source.invocations);
349
+ });
350
+ }
351
+ } finally {
352
+ if (previousBrand === undefined) delete process.env.CabloyCliBrandName;
353
+ else process.env.CabloyCliBrandName = previousBrand;
354
+ }
355
+ },
356
+ );
357
+
358
+ it('wrapper invokes default edition generation and refreshes metadata after all rendering', async () => {
359
+ for (const edition of ['basic', 'start']) {
360
+ await fixture(async root => {
361
+ const { cli, moduleRoot, invocations } = await makeCli(root, CliToolsCrud);
362
+ await fs.writeFile(
363
+ path.join(root, edition === 'start' ? '__CABLOY_START__' : '__CABLOY_BASIC__'),
364
+ '',
365
+ );
366
+ cli.helper.invokeCli = async args => {
367
+ invocations.push(args);
368
+ if (args[0] === ':tools:metadata') {
369
+ assert.match(await read(moduleRoot, 'src/bean/ssrMenu.record.ts'), /record/);
370
+ }
371
+ };
372
+ await cli.execute();
373
+ assert.deepEqual(invocations, [
374
+ [
375
+ edition === 'start' ? ':tools:crudStart' : ':tools:crudBasic',
376
+ 'record',
377
+ '--module=test-record',
378
+ '--nometadata',
379
+ ],
380
+ [':tools:metadata', 'test-record'],
381
+ ]);
382
+ });
383
+ }
384
+ });
385
+ });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "babel-plugin-vona-bean-module",
3
- "version": "1.1.10",
3
+ "version": "1.1.11",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "babel-plugin-vona-bean-module",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona",
3
- "version": "5.1.124",
3
+ "version": "5.1.125",
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": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-core",
3
- "version": "5.1.54",
3
+ "version": "5.1.55",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "vona",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-mock",
3
- "version": "6.1.54",
3
+ "version": "6.1.55",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "vona mock",
6
6
  "keywords": [