vona-cli-set-api 1.1.110 → 1.1.117

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.
@@ -25,7 +25,7 @@
25
25
  "tsc:publish": "npm run clean && node <%=argv.suite?'../../../../../':'../../../'%>packages-cli/cli/src/bin/vona.ts :bin:buildModule --sourcemap && tsc -p tsconfig.build.json",
26
26
  "prepublishOnly": "npm run tsc:publish",
27
27
  "prepack": "clean-package",
28
- "postpack": "clean-package restore && npm run clean"
28
+ "postpack": "clean-package restore"
29
29
  },
30
30
  "clean-package": {
31
31
  "indent": 2,
@@ -0,0 +1,18 @@
1
+ import type { IDecoratorDtoOptions } from 'vona-module-a-web';
2
+
3
+ import { $makeMetadata } from 'vona-module-a-openapiutils';
4
+ import { $Dto } from 'vona-module-a-orm';
5
+ import { Dto } from 'vona-module-a-web';
6
+ import { Model<%=argv.detailResourceNameCapitalize%> } from '<%=argv.detailPackageName%>';
7
+ import { ZovaRender } from 'zova-rest-cabloy-basic-admin';
8
+
9
+ export interface IDtoOptions<%=cli.helper.firstCharToUpperCase(argv.detailDtoBaseName)%> extends IDecoratorDtoOptions {}
10
+
11
+ @Dto<IDtoOptions<%=cli.helper.firstCharToUpperCase(argv.detailDtoBaseName)%>>({
12
+ fields: {
13
+ id: $makeMetadata(ZovaRender.visible(false)),
14
+ },
15
+ })
16
+ export class Dto<%=cli.helper.firstCharToUpperCase(argv.detailDtoBaseName)%> extends $Dto.get(() => Model<%=argv.detailResourceNameCapitalize%>, {
17
+ columns: ['id', 'deleted', 'name', 'description'],
18
+ }) {}
@@ -0,0 +1,17 @@
1
+ import type { IDecoratorDtoOptions } from 'vona-module-a-web';
2
+
3
+ import { $Dto } from 'vona-module-a-orm';
4
+ import { Dto } from 'vona-module-a-web';
5
+ import { Model<%=argv.detailResourceNameCapitalize%> } from '<%=argv.detailPackageName%>';
6
+ import { ZovaRender } from 'zova-rest-cabloy-basic-admin';
7
+
8
+ import { Dto<%=cli.helper.firstCharToUpperCase(argv.detailDtoBaseName)%> } from './<%=argv.detailDtoBaseName%>.tsx';
9
+
10
+ export interface IDtoOptions<%=cli.helper.firstCharToUpperCase(argv.detailDtoMutateName)%> extends IDecoratorDtoOptions {}
11
+
12
+ @Dto<IDtoOptions<%=cli.helper.firstCharToUpperCase(argv.detailDtoMutateName)%>>({
13
+ blocks: [ZovaRender.block('basic-details:blockForm')],
14
+ })
15
+ export class Dto<%=cli.helper.firstCharToUpperCase(argv.detailDtoMutateName)%> extends $Dto.mutate(() => Model<%=argv.detailResourceNameCapitalize%>, {
16
+ dtoClass: Dto<%=cli.helper.firstCharToUpperCase(argv.detailDtoBaseName)%>,
17
+ }) {}
@@ -0,0 +1,60 @@
1
+ import type { IDecoratorDtoOptions } from 'vona-module-a-web';
2
+
3
+ import { $makeMetadata, Api, v } from 'vona-module-a-openapiutils';
4
+ import { $Dto } from 'vona-module-a-orm';
5
+ import { Dto } from 'vona-module-a-web';
6
+ import { Model<%=argv.detailResourceNameCapitalize%> } from '<%=argv.detailPackageName%>';
7
+ import { ZovaRender } from 'zova-rest-cabloy-basic-admin';
8
+
9
+ import { $locale } from '../.metadata/locales.ts';
10
+ import { Dto<%=cli.helper.firstCharToUpperCase(argv.detailDtoBaseName)%> } from './<%=argv.detailDtoBaseName%>.tsx';
11
+
12
+ export interface IDtoOptions<%=cli.helper.firstCharToUpperCase(argv.detailDtoResItemName)%> extends IDecoratorDtoOptions {}
13
+
14
+ @Dto<IDtoOptions<%=cli.helper.firstCharToUpperCase(argv.detailDtoResItemName)%>>({
15
+ blocks: [
16
+ ZovaRender.block('basic-details:blockDetails', {
17
+ blocks: [
18
+ ZovaRender.block('basic-details:blockToolbarBulk', {
19
+ actions: [
20
+ ZovaRender.detailsActionBulk('basic-details:actionCreate', {
21
+ dialogOptions: { title: $locale('Add<%=argv.detailDialogTitleCapitalize%>') },
22
+ permission: { formScene: ['create', 'edit'] },
23
+ }),
24
+ ],
25
+ }),
26
+ ZovaRender.block('basic-details:blockTable'),
27
+ ],
28
+ }),
29
+ ],
30
+ fields: {
31
+ name: $makeMetadata(
32
+ ZovaRender.cell('basic-details:actionView', {
33
+ dialogOptions: { title: $locale('View<%=argv.detailDialogTitleCapitalize%>') },
34
+ }),
35
+ ),
36
+ },
37
+ })
38
+ export class Dto<%=cli.helper.firstCharToUpperCase(argv.detailDtoResItemName)%> extends $Dto.get(() => Model<%=argv.detailResourceNameCapitalize%>, {
39
+ dtoClass: Dto<%=cli.helper.firstCharToUpperCase(argv.detailDtoBaseName)%>,
40
+ }) {
41
+ @Api.field(v.title('#'), ZovaRender.order(1, 'core'), ZovaRender.cell('basic-details:lineNumber'))
42
+ _lineNumber?: number;
43
+
44
+ @Api.field(
45
+ v.title($locale('Operations')),
46
+ ZovaRender.order(1, 'max'),
47
+ ZovaRender.cell('basic-details:actionOperationsRow', {
48
+ actions: [
49
+ ZovaRender.detailsActionRow('basic-details:actionUpdate', {
50
+ dialogOptions: { title: $locale('Edit<%=argv.detailDialogTitleCapitalize%>') },
51
+ permission: { formScene: ['create', 'edit'] },
52
+ }),
53
+ ZovaRender.detailsActionRow('basic-details:actionDelete', {
54
+ permission: { formScene: ['create', 'edit'] },
55
+ }),
56
+ ],
57
+ }),
58
+ )
59
+ _operationsRow?: unknown;
60
+ }
@@ -0,0 +1,17 @@
1
+ import type { IDecoratorDtoOptions } from 'vona-module-a-web';
2
+
3
+ import { $Dto } from 'vona-module-a-orm';
4
+ import { Dto } from 'vona-module-a-web';
5
+ import { Model<%=argv.detailResourceNameCapitalize%> } from '<%=argv.detailPackageName%>';
6
+ import { ZovaRender } from 'zova-rest-cabloy-basic-admin';
7
+
8
+ import { Dto<%=cli.helper.firstCharToUpperCase(argv.detailDtoBaseName)%> } from './<%=argv.detailDtoBaseName%>.tsx';
9
+
10
+ export interface IDtoOptions<%=cli.helper.firstCharToUpperCase(argv.detailDtoViewName)%> extends IDecoratorDtoOptions {}
11
+
12
+ @Dto<IDtoOptions<%=cli.helper.firstCharToUpperCase(argv.detailDtoViewName)%>>({
13
+ blocks: [ZovaRender.block('basic-details:blockForm')],
14
+ })
15
+ export class Dto<%=cli.helper.firstCharToUpperCase(argv.detailDtoViewName)%> extends $Dto.get(() => Model<%=argv.detailResourceNameCapitalize%>, {
16
+ dtoClass: Dto<%=cli.helper.firstCharToUpperCase(argv.detailDtoBaseName)%>,
17
+ }) {}
package/dist/index.js CHANGED
@@ -1067,12 +1067,8 @@ class CliCreateBean extends BeanCliBase {
1067
1067
  argv.ssrSiteRestNpmWeb = fs.existsSync(path.join(argv.projectPath, 'src/suite/cabloy-start')) ? 'zova-rest-cabloy-start-web' : 'zova-rest-cabloy-basic-web';
1068
1068
  // module name/info
1069
1069
  const moduleName = argv.module;
1070
- argv.moduleInfo = this.helper.parseModuleInfo(moduleName);
1071
- // check if exists
1072
- const _module = this.helper.findModule(moduleName);
1073
- if (!_module) {
1074
- throw new Error(`module does not exist: ${moduleName}`);
1075
- }
1070
+ argv.moduleInfo = this.helper.parseModuleInfoCanonical(moduleName);
1071
+ const _module = this.helper.findModuleCanonical(moduleName);
1076
1072
  // target dir
1077
1073
  const targetDir = await this.helper.ensureDir(_module.root);
1078
1074
  // scene name
@@ -1166,12 +1162,9 @@ class CliCreateModule extends BeanCliBase {
1166
1162
  // suite name/info
1167
1163
  const suiteName = argv.suite;
1168
1164
  if (suiteName) {
1169
- argv.suiteInfo = this.helper.parseSuiteInfo(suiteName);
1165
+ argv.suiteInfo = this.helper.parseSuiteInfoCanonical(suiteName);
1170
1166
  // check if exists
1171
- argv._suite = this.helper.findSuite(suiteName);
1172
- if (!argv._suite) {
1173
- throw new Error(`suite does not exist: ${suiteName}`);
1174
- }
1167
+ argv._suite = this.helper.findSuiteCanonical(suiteName);
1175
1168
  }
1176
1169
  // nameMeta
1177
1170
  const nameMeta = this.helper.parseNameMeta(argv.name);
@@ -1179,7 +1172,7 @@ class CliCreateModule extends BeanCliBase {
1179
1172
  argv.name = nameMeta.short;
1180
1173
  // module name/info
1181
1174
  const moduleName = argv.name;
1182
- argv.moduleInfo = this.helper.parseModuleInfo(moduleName);
1175
+ argv.moduleInfo = this.helper.parseModuleInfoCanonical(moduleName);
1183
1176
  argv.relativeNameCapitalize = this.helper.stringToCapitalize(argv.moduleInfo.relativeName, '-');
1184
1177
  // check if exists
1185
1178
  const _module = this.helper.findModule(moduleName);
@@ -1246,7 +1239,7 @@ class CliCreateSuite extends BeanCliBase {
1246
1239
  argv.name = nameMeta.short;
1247
1240
  // suite name/info
1248
1241
  const suiteName = argv.name;
1249
- argv.suiteInfo = this.helper.parseSuiteInfo(suiteName);
1242
+ argv.suiteInfo = this.helper.parseSuiteInfoCanonical(suiteName);
1250
1243
  // check if exists
1251
1244
  const _suite = this.helper.findSuite(suiteName);
1252
1245
  if (_suite) {
@@ -1274,12 +1267,8 @@ class CliCreateTest extends BeanCliBase {
1274
1267
  await super.execute();
1275
1268
  // module name/info
1276
1269
  const moduleName = argv.module;
1277
- argv.moduleInfo = this.helper.parseModuleInfo(moduleName);
1278
- // check if exists
1279
- const _module = this.helper.findModule(moduleName);
1280
- if (!_module) {
1281
- throw new Error(`module does not exist: ${moduleName}`);
1282
- }
1270
+ argv.moduleInfo = this.helper.parseModuleInfoCanonical(moduleName);
1271
+ const _module = this.helper.findModuleCanonical(moduleName);
1283
1272
  // target dir
1284
1273
  const targetDir = await this.helper.ensureDir(_module.root);
1285
1274
  // name
@@ -1441,12 +1430,8 @@ class CliInitAsset extends BeanCliBase {
1441
1430
  await super.execute();
1442
1431
  // module name/info
1443
1432
  const moduleName = argv.module;
1444
- argv.moduleInfo = this.helper.parseModuleInfo(moduleName);
1445
- // check if exists
1446
- const _module = this.helper.findModule(moduleName);
1447
- if (!_module) {
1448
- throw new Error(`module does not exist: ${moduleName}`);
1449
- }
1433
+ argv.moduleInfo = this.helper.parseModuleInfoCanonical(moduleName);
1434
+ const _module = this.helper.findModuleCanonical(moduleName);
1450
1435
  // target dir
1451
1436
  const targetDir = await this.helper.ensureDir(_module.root);
1452
1437
  // scene
@@ -1470,12 +1455,8 @@ class CliInitConfig extends BeanCliBase {
1470
1455
  // module name/info
1471
1456
  const moduleName = argv._[0];
1472
1457
  if (!moduleName) return;
1473
- argv.moduleInfo = this.helper.parseModuleInfo(moduleName);
1474
- // check if exists
1475
- const _module = this.helper.findModule(moduleName);
1476
- if (!_module) {
1477
- throw new Error(`module does not exist: ${moduleName}`);
1478
- }
1458
+ argv.moduleInfo = this.helper.parseModuleInfoCanonical(moduleName);
1459
+ const _module = this.helper.findModuleCanonical(moduleName);
1479
1460
  // target dir
1480
1461
  const targetDir = await this.helper.ensureDir(_module.root);
1481
1462
  const configFile = path.join(targetDir, 'src/config/config.ts');
@@ -1508,12 +1489,8 @@ class CliInitConstant extends BeanCliBase {
1508
1489
  // module name/info
1509
1490
  const moduleName = argv._[0];
1510
1491
  if (!moduleName) return;
1511
- argv.moduleInfo = this.helper.parseModuleInfo(moduleName);
1512
- // check if exists
1513
- const _module = this.helper.findModule(moduleName);
1514
- if (!_module) {
1515
- throw new Error(`module does not exist: ${moduleName}`);
1516
- }
1492
+ argv.moduleInfo = this.helper.parseModuleInfoCanonical(moduleName);
1493
+ const _module = this.helper.findModuleCanonical(moduleName);
1517
1494
  // target dir
1518
1495
  const targetDir = await this.helper.ensureDir(_module.root);
1519
1496
  const constantFile = path.join(targetDir, 'src/config/constants.ts');
@@ -1546,12 +1523,8 @@ class CliInitError extends BeanCliBase {
1546
1523
  // module name/info
1547
1524
  const moduleName = argv._[0];
1548
1525
  if (!moduleName) return;
1549
- argv.moduleInfo = this.helper.parseModuleInfo(moduleName);
1550
- // check if exists
1551
- const _module = this.helper.findModule(moduleName);
1552
- if (!_module) {
1553
- throw new Error(`module does not exist: ${moduleName}`);
1554
- }
1526
+ argv.moduleInfo = this.helper.parseModuleInfoCanonical(moduleName);
1527
+ const _module = this.helper.findModuleCanonical(moduleName);
1555
1528
  // target dir
1556
1529
  const targetDir = await this.helper.ensureDir(_module.root);
1557
1530
  const errorFile = path.join(targetDir, 'src/config/errors.ts');
@@ -1595,12 +1568,8 @@ class CliInitLib extends BeanCliBase {
1595
1568
  // module name/info
1596
1569
  const moduleName = argv._[0];
1597
1570
  if (!moduleName) return;
1598
- argv.moduleInfo = this.helper.parseModuleInfo(moduleName);
1599
- // check if exists
1600
- const _module = this.helper.findModule(moduleName);
1601
- if (!_module) {
1602
- throw new Error(`module does not exist: ${moduleName}`);
1603
- }
1571
+ argv.moduleInfo = this.helper.parseModuleInfoCanonical(moduleName);
1572
+ const _module = this.helper.findModuleCanonical(moduleName);
1604
1573
  // target dir
1605
1574
  const targetDir = await this.helper.ensureDir(_module.root);
1606
1575
  const mainFile = path.join(targetDir, 'src/lib/index.ts');
@@ -1633,12 +1602,8 @@ class CliInitLocale extends BeanCliBase {
1633
1602
  // module name/info
1634
1603
  const moduleName = argv._[0];
1635
1604
  if (!moduleName) return;
1636
- argv.moduleInfo = this.helper.parseModuleInfo(moduleName);
1637
- // check if exists
1638
- const _module = this.helper.findModule(moduleName);
1639
- if (!_module) {
1640
- throw new Error(`module does not exist: ${moduleName}`);
1641
- }
1605
+ argv.moduleInfo = this.helper.parseModuleInfoCanonical(moduleName);
1606
+ const _module = this.helper.findModuleCanonical(moduleName);
1642
1607
  // target dir
1643
1608
  const targetDir = await this.helper.ensureDir(_module.root);
1644
1609
  const localeFile = path.join(targetDir, 'src/config/locale');
@@ -1671,12 +1636,8 @@ class CliInitMain extends BeanCliBase {
1671
1636
  // module name/info
1672
1637
  const moduleName = argv._[0];
1673
1638
  if (!moduleName) return;
1674
- argv.moduleInfo = this.helper.parseModuleInfo(moduleName);
1675
- // check if exists
1676
- const _module = this.helper.findModule(moduleName);
1677
- if (!_module) {
1678
- throw new Error(`module does not exist: ${moduleName}`);
1679
- }
1639
+ argv.moduleInfo = this.helper.parseModuleInfoCanonical(moduleName);
1640
+ const _module = this.helper.findModuleCanonical(moduleName);
1680
1641
  // target dir
1681
1642
  const targetDir = await this.helper.ensureDir(_module.root);
1682
1643
  const mainFile = path.join(targetDir, 'src/main.ts');
@@ -1709,12 +1670,8 @@ class CliInitMonkey extends BeanCliBase {
1709
1670
  // module name/info
1710
1671
  const moduleName = argv._[0];
1711
1672
  if (!moduleName) return;
1712
- argv.moduleInfo = this.helper.parseModuleInfo(moduleName);
1713
- // check if exists
1714
- const _module = this.helper.findModule(moduleName);
1715
- if (!_module) {
1716
- throw new Error(`module does not exist: ${moduleName}`);
1717
- }
1673
+ argv.moduleInfo = this.helper.parseModuleInfoCanonical(moduleName);
1674
+ const _module = this.helper.findModuleCanonical(moduleName);
1718
1675
  // target dir
1719
1676
  const targetDir = await this.helper.ensureDir(_module.root);
1720
1677
  const monkeyFile = path.join(targetDir, 'src/monkey.ts');
@@ -1771,12 +1728,8 @@ class CliInitStatic extends BeanCliBase {
1771
1728
  // module name/info
1772
1729
  const moduleName = argv._[0];
1773
1730
  if (!moduleName) return;
1774
- argv.moduleInfo = this.helper.parseModuleInfo(moduleName);
1775
- // check if exists
1776
- const _module = this.helper.findModule(moduleName);
1777
- if (!_module) {
1778
- throw new Error(`module does not exist: ${moduleName}`);
1779
- }
1731
+ argv.moduleInfo = this.helper.parseModuleInfoCanonical(moduleName);
1732
+ const _module = this.helper.findModuleCanonical(moduleName);
1780
1733
  // target dir
1781
1734
  const targetDir = await this.helper.ensureDir(_module.root);
1782
1735
  const staticDir = path.join(targetDir, 'assets/static');
@@ -1807,12 +1760,8 @@ class CliInitTypes extends BeanCliBase {
1807
1760
  // module name/info
1808
1761
  const moduleName = argv._[0];
1809
1762
  if (!moduleName) return;
1810
- argv.moduleInfo = this.helper.parseModuleInfo(moduleName);
1811
- // check if exists
1812
- const _module = this.helper.findModule(moduleName);
1813
- if (!_module) {
1814
- throw new Error(`module does not exist: ${moduleName}`);
1815
- }
1763
+ argv.moduleInfo = this.helper.parseModuleInfoCanonical(moduleName);
1764
+ const _module = this.helper.findModuleCanonical(moduleName);
1816
1765
  // target dir
1817
1766
  const targetDir = await this.helper.ensureDir(_module.root);
1818
1767
  const mainFile = path.join(targetDir, 'src/types/index.ts');
@@ -1849,12 +1798,8 @@ class CliToolsCrud extends BeanCliBase {
1849
1798
  argv.ssrSiteGroupName = isCabloyStart ? 'start-siteadmin:management' : 'basic-siteadmin:management';
1850
1799
  // module name/info
1851
1800
  const moduleName = argv.module;
1852
- argv.moduleInfo = this.helper.parseModuleInfo(moduleName);
1853
- // check if exists
1854
- const _module = this.helper.findModule(moduleName);
1855
- if (!_module) {
1856
- throw new Error(`module does not exist: ${moduleName}`);
1857
- }
1801
+ argv.moduleInfo = this.helper.parseModuleInfoCanonical(moduleName);
1802
+ const _module = this.helper.findModuleCanonical(moduleName);
1858
1803
  // target dir
1859
1804
  const targetDir = await this.helper.ensureDir(_module.root);
1860
1805
  // resourceName
@@ -1895,12 +1840,8 @@ class CliToolsCrudBasic extends BeanCliBase {
1895
1840
  await super.execute();
1896
1841
  // module name/info
1897
1842
  const moduleName = argv.module;
1898
- argv.moduleInfo = this.helper.parseModuleInfo(moduleName);
1899
- // check if exists
1900
- const _module = this.helper.findModule(moduleName);
1901
- if (!_module) {
1902
- throw new Error(`module does not exist: ${moduleName}`);
1903
- }
1843
+ argv.moduleInfo = this.helper.parseModuleInfoCanonical(moduleName);
1844
+ const _module = this.helper.findModuleCanonical(moduleName);
1904
1845
  // target dir
1905
1846
  const targetDir = await this.helper.ensureDir(_module.root);
1906
1847
  // resourceName
@@ -1939,12 +1880,8 @@ class CliToolsCrudStart extends BeanCliBase {
1939
1880
  await super.execute();
1940
1881
  // module name/info
1941
1882
  const moduleName = argv.module;
1942
- argv.moduleInfo = this.helper.parseModuleInfo(moduleName);
1943
- // check if exists
1944
- const _module = this.helper.findModule(moduleName);
1945
- if (!_module) {
1946
- throw new Error(`module does not exist: ${moduleName}`);
1947
- }
1883
+ argv.moduleInfo = this.helper.parseModuleInfoCanonical(moduleName);
1884
+ const _module = this.helper.findModuleCanonical(moduleName);
1948
1885
  // target dir
1949
1886
  const targetDir = await this.helper.ensureDir(_module.root);
1950
1887
  // resourceName
@@ -2049,6 +1986,529 @@ class CliToolsDeps extends BeanCliBase {
2049
1986
  }
2050
1987
  }
2051
1988
 
1989
+ class CliToolsMasterDetail extends BeanCliBase {
1990
+ async execute() {
1991
+ await super.execute();
1992
+ this._prepareArgv();
1993
+ await this._ensureDetailModule();
1994
+ await this._ensureDetailResourceShape();
1995
+ await this._patchDetailModule();
1996
+ await this._renderMasterDetailDtos();
1997
+ await this._patchMasterModule();
1998
+ await this._refreshMetadata();
1999
+ }
2000
+ _prepareArgv() {
2001
+ const {
2002
+ argv
2003
+ } = this.context;
2004
+ argv.detailMode = argv.detailMode || 'aggregate';
2005
+ if (argv.detailMode !== 'aggregate' && argv.detailMode !== 'standalone') {
2006
+ throw new Error(`mode is not valid: ${argv.detailMode}`);
2007
+ }
2008
+ argv.moduleInfo = this.helper.parseModuleInfoCanonical(argv.module, 'master module');
2009
+ const _module = this.helper.findModuleCanonical(argv.module, 'master module');
2010
+ argv._module = _module;
2011
+ argv.resourceNameCapitalize = this.helper.firstCharToUpperCase(argv.resourceName);
2012
+ argv.detailModuleInfo = this.helper.parseModuleInfoCanonical(argv.detailModule, 'detail module');
2013
+ argv._detailModule = this.helper.findModule(argv.detailModule);
2014
+ argv.detailResourceNameCapitalize = this.helper.firstCharToUpperCase(argv.detailResourceName);
2015
+ argv.relationName = argv.relationName || `${argv.detailResourceName}s`;
2016
+ argv.relationNameCapitalize = this.helper.firstCharToUpperCase(argv.relationName);
2017
+ argv.fk = argv.fk || `${argv.resourceName}Id`;
2018
+ argv.detailPackageName = `vona-module-${argv.detailModuleInfo.relativeName}`;
2019
+ argv.detailModuleCapitalize = this.helper.stringToCapitalize(argv.detailModuleInfo.relativeName, '-');
2020
+ argv.detailDtoBaseName = `detail${argv.detailResourceNameCapitalize}Base`;
2021
+ argv.detailDtoMutateName = `detail${argv.detailResourceNameCapitalize}Mutate`;
2022
+ argv.detailDtoViewName = `detail${argv.detailResourceNameCapitalize}View`;
2023
+ argv.detailDtoResItemName = `detail${argv.detailResourceNameCapitalize}ResItem`;
2024
+ argv.detailFieldPrivateName = `_${argv.relationName}`;
2025
+ argv.detailDialogTitleCapitalize = argv.detailModuleCapitalize;
2026
+ }
2027
+ async _ensureDetailModule() {
2028
+ const {
2029
+ argv
2030
+ } = this.context;
2031
+ if (argv._detailModule) return;
2032
+ const args = [':create:module', argv.detailModule, '--nometadata'];
2033
+ if (argv._module.suite) {
2034
+ args.push(`--suite=${argv._module.suite}`);
2035
+ }
2036
+ await this.helper.invokeCli(args, {
2037
+ cwd: argv.projectPath
2038
+ });
2039
+ argv.createdDetailModule = true;
2040
+ argv._detailModule = this.helper.findModule(argv.detailModule);
2041
+ if (!argv._detailModule) {
2042
+ const root = this._resolveCreatedDetailModuleRoot();
2043
+ argv._detailModule = {
2044
+ name: argv.detailModuleInfo.relativeName,
2045
+ info: argv.detailModuleInfo,
2046
+ root,
2047
+ pkg: path.join(root, 'package.json'),
2048
+ package: await this.helper.loadJSONFile(path.join(root, 'package.json')),
2049
+ suite: argv._module.suite
2050
+ };
2051
+ }
2052
+ }
2053
+ async _ensureDetailResourceShape() {
2054
+ const {
2055
+ argv
2056
+ } = this.context;
2057
+ const {
2058
+ hasCoreDetailFiles,
2059
+ hasStandaloneSurface
2060
+ } = this._detailModuleShape();
2061
+ if (!hasCoreDetailFiles) {
2062
+ await this._createDetailCoreResource();
2063
+ argv.createdDetailResource = true;
2064
+ }
2065
+ if (argv.detailMode === 'aggregate') {
2066
+ this._handleAggregateDetailMode(hasStandaloneSurface);
2067
+ return;
2068
+ }
2069
+ this._handleStandaloneDetailMode();
2070
+ }
2071
+ async _patchDetailModule() {
2072
+ await this._runSequential(this._detailPatchTasks());
2073
+ }
2074
+ async _patchDetailEntity() {
2075
+ const {
2076
+ argv
2077
+ } = this.context;
2078
+ await this._patchFileContent({
2079
+ fileName: this._detailPaths().entity,
2080
+ existsNeedle: `${argv.fk}: TableIdentity;`,
2081
+ patch: (content, fileName) => {
2082
+ content = this._patchDetailEntityImport(content);
2083
+ return this._patchDetailEntityField(content, fileName);
2084
+ }
2085
+ });
2086
+ }
2087
+ _patchDetailEntityImport(content) {
2088
+ if (!content.includes("import type { TableIdentity } from 'table-identity';")) {
2089
+ content = `import type { TableIdentity } from 'table-identity';\n${content}`;
2090
+ }
2091
+ return content;
2092
+ }
2093
+ _patchDetailEntityField(content, fileName) {
2094
+ const {
2095
+ argv
2096
+ } = this.context;
2097
+ const marker = `export class Entity${argv.detailResourceNameCapitalize} extends EntityBase {\n`;
2098
+ if (!content.includes(marker)) {
2099
+ throw new Error(`detail entity is not in the expected shape: ${fileName}`);
2100
+ }
2101
+ const fieldCode = ` @Api.field(v.required(), ZovaRender.visible(false))\n ${argv.fk}: TableIdentity;\n\n`;
2102
+ return content.replace(marker, `${marker}${fieldCode}`);
2103
+ }
2104
+ async _patchDetailMetaVersion() {
2105
+ const {
2106
+ argv
2107
+ } = this.context;
2108
+ await this._patchFileByMarker({
2109
+ fileName: this._detailPaths().metaVersion,
2110
+ existsNeedle: `entity${argv.detailResourceNameCapitalize}.${argv.fk}`,
2111
+ marker: ' table.basicFields();\n',
2112
+ line: this._detailMetaVersionLine(),
2113
+ shapeName: 'detail meta.version'
2114
+ });
2115
+ }
2116
+ async _patchDetailMetaIndex() {
2117
+ const {
2118
+ argv
2119
+ } = this.context;
2120
+ await this._patchFileByMarker({
2121
+ fileName: this._detailPaths().metaIndex,
2122
+ existsNeedle: `'${argv.fk}'`,
2123
+ marker: ' indexes: {\n',
2124
+ line: this._detailMetaIndexLine(),
2125
+ shapeName: 'detail meta.index'
2126
+ });
2127
+ }
2128
+ _detailMetaVersionLine() {
2129
+ const {
2130
+ argv
2131
+ } = this.context;
2132
+ return ` table.tableIdentity(entity${argv.detailResourceNameCapitalize}.${argv.fk}).comment(entity${argv.detailResourceNameCapitalize}.$comment.${argv.fk});\n`;
2133
+ }
2134
+ _detailMetaIndexLine() {
2135
+ const {
2136
+ argv
2137
+ } = this.context;
2138
+ return ` ...$tableColumns('${this.helper.combineModuleNameAndResource(argv.detailModuleInfo.relativeName, argv.detailResourceName)}', '${argv.fk}'),\n`;
2139
+ }
2140
+ async _patchFileByMarker({
2141
+ fileName,
2142
+ existsNeedle,
2143
+ marker,
2144
+ line,
2145
+ shapeName
2146
+ }) {
2147
+ let content = this._readFile(fileName);
2148
+ if (content.includes(existsNeedle)) return;
2149
+ if (!content.includes(marker)) {
2150
+ throw new Error(`${shapeName} is not in the expected shape: ${fileName}`);
2151
+ }
2152
+ content = content.replace(marker, `${marker}${line}`);
2153
+ await this._saveFile(fileName, content);
2154
+ }
2155
+ async _patchFileContent({
2156
+ fileName,
2157
+ existsNeedle,
2158
+ patch
2159
+ }) {
2160
+ let content = this._readFile(fileName);
2161
+ if (content.includes(existsNeedle)) return;
2162
+ content = patch(content, fileName);
2163
+ await this._saveFile(fileName, content);
2164
+ }
2165
+ async _renderMasterDetailDtos() {
2166
+ await this.helper.ensureDir(this._masterPaths().dtoDir);
2167
+ for (const [dtoBaseName, templateBaseName] of this._detailDtoTemplates()) {
2168
+ await this._renderMasterDetailDtoFile(dtoBaseName, templateBaseName);
2169
+ }
2170
+ }
2171
+ async _patchMasterModule() {
2172
+ await this._runSequential(this._masterPatchTasks());
2173
+ }
2174
+ _detailPatchTasks() {
2175
+ return [() => this._patchDetailEntity(), () => this._patchDetailMetaVersion(), () => this._patchDetailMetaIndex()];
2176
+ }
2177
+ _masterPatchTasks() {
2178
+ return [() => this._patchMasterModel(), () => this._patchMasterService(), ...this._masterDtoScenes().map(scene => () => this._patchMasterDto(scene)), ...this._masterLocales().map(locale => () => this._patchMasterLocale(locale))];
2179
+ }
2180
+ async _runSequential(tasks) {
2181
+ for (const task of tasks) {
2182
+ await task();
2183
+ }
2184
+ }
2185
+ async _patchMasterModel() {
2186
+ const {
2187
+ argv
2188
+ } = this.context;
2189
+ const fileName = this._masterPaths().model;
2190
+ let content = this._readFile(fileName);
2191
+ if (content.includes(`${argv.relationName}: $relation.hasMany(`)) return;
2192
+ content = this._patchMasterModelImport(content, fileName);
2193
+ content = this._patchMasterModelRelations(content, fileName);
2194
+ await this._saveFile(fileName, content);
2195
+ }
2196
+ async _patchMasterService() {
2197
+ const {
2198
+ argv
2199
+ } = this.context;
2200
+ const fileName = this._masterPaths().service;
2201
+ let content = this._readFile(fileName);
2202
+ if (content.includes(`include: { ${argv.relationName}: true }`)) return;
2203
+ for (const [search, replacement] of this._masterServiceIncludeReplacements()) {
2204
+ content = this._replaceStrict(content, search, replacement, fileName);
2205
+ }
2206
+ await this._saveFile(fileName, content);
2207
+ }
2208
+ _patchMasterModelImport(content, fileName) {
2209
+ if (content.includes("import { $relation, BeanModelBase, Model } from 'vona-module-a-orm';")) {
2210
+ return content;
2211
+ }
2212
+ return this._replaceStrict(content, "import { BeanModelBase, Model } from 'vona-module-a-orm';", "import { $relation, BeanModelBase, Model } from 'vona-module-a-orm';", fileName);
2213
+ }
2214
+ _patchMasterModelRelations(content, fileName) {
2215
+ const relationsBlockMarker = ' relations: {\n';
2216
+ if (content.includes(relationsBlockMarker)) {
2217
+ return content.replace(relationsBlockMarker, `${relationsBlockMarker}${this._masterModelRelationCode()}`);
2218
+ }
2219
+ return this._patchMasterModelDecorator(content, fileName);
2220
+ }
2221
+ _patchMasterModelDecorator(content, fileName) {
2222
+ const {
2223
+ argv
2224
+ } = this.context;
2225
+ const marker = `@Model<IModelOptions${argv.resourceNameCapitalize}>({ entity: Entity${argv.resourceNameCapitalize} })`;
2226
+ if (!content.includes(marker)) {
2227
+ throw new Error(`master model is not in the expected shape: ${fileName}`);
2228
+ }
2229
+ return content.replace(marker, `@Model<IModelOptions${argv.resourceNameCapitalize}>({\n entity: Entity${argv.resourceNameCapitalize},\n relations: {\n${this._masterModelRelationCode()} },\n})`);
2230
+ }
2231
+ _masterModelRelationCode() {
2232
+ const {
2233
+ argv
2234
+ } = this.context;
2235
+ return ` ${argv.relationName}: $relation.hasMany('${argv.detailModuleInfo.relativeName}:${argv.detailResourceName}', '${argv.fk}', {\n columns: ['id', 'name', 'description'],\n }),\n`;
2236
+ }
2237
+ _masterServiceIncludeReplacements() {
2238
+ const {
2239
+ argv
2240
+ } = this.context;
2241
+ return [[` return await this.scope.model.${argv.resourceName}.insert(${argv.resourceName});`, ` return await this.scope.model.${argv.resourceName}.insert(${argv.resourceName}, { include: { ${argv.relationName}: true } });`], [` return await this.scope.model.${argv.resourceName}.getById(id);`, ` return await this.scope.model.${argv.resourceName}.getById(id, { include: { ${argv.relationName}: true } });`], [` return await this.scope.model.${argv.resourceName}.updateById(id, ${argv.resourceName});`, ` return await this.scope.model.${argv.resourceName}.updateById(id, ${argv.resourceName}, {\n include: { ${argv.relationName}: true },\n });`], [` return await this.scope.model.${argv.resourceName}.deleteById(id);`, ` return await this.scope.model.${argv.resourceName}.deleteById(id, { include: { ${argv.relationName}: true } });`]];
2242
+ }
2243
+ async _patchMasterDto(scene) {
2244
+ const {
2245
+ argv
2246
+ } = this.context;
2247
+ const fileName = path.join(argv._module.root, 'src/dto', `${argv.resourceName}${scene}.tsx`);
2248
+ let content = this._readFile(fileName);
2249
+ const detailDtoResItemCapitalize = this._detailDtoResItemCapitalize();
2250
+ const detailDtoClassCapitalize = this._detailDtoClassCapitalize(scene);
2251
+ if (content.includes(`${argv.detailFieldPrivateName}?: Dto${detailDtoResItemCapitalize}[];`)) {
2252
+ return;
2253
+ }
2254
+ content = this._patchMasterDtoImports(content, fileName, scene, detailDtoClassCapitalize, detailDtoResItemCapitalize);
2255
+ content = this._patchMasterDtoFields(content, fileName, scene);
2256
+ content = this._patchMasterDtoClass(content, fileName, scene, detailDtoClassCapitalize, detailDtoResItemCapitalize);
2257
+ await this._saveFile(fileName, content);
2258
+ }
2259
+ async _patchMasterLocale(locale) {
2260
+ const fileName = path.join(this._masterPaths().localeDir, `${locale}.ts`);
2261
+ let content = this._readFile(fileName);
2262
+ content = this._patchLocaleAdditions(content, this._masterLocaleAdditions(locale));
2263
+ await this._saveFile(fileName, content);
2264
+ }
2265
+ async _refreshMetadata() {
2266
+ const {
2267
+ argv
2268
+ } = this.context;
2269
+ for (const moduleName of this._metadataModules()) {
2270
+ await this.helper.invokeCli([':tools:metadata', moduleName], {
2271
+ cwd: argv.projectPath
2272
+ });
2273
+ }
2274
+ }
2275
+ _patchLocaleAdditions(content, additions) {
2276
+ for (const addition of additions) {
2277
+ const key = this._localeAdditionKey(addition);
2278
+ if (content.includes(`${key}:`)) continue;
2279
+ content = content.replace('};\n', ` ${addition},\n};\n`);
2280
+ }
2281
+ return content;
2282
+ }
2283
+ _localeAdditionKey(addition) {
2284
+ return addition.split(':')[0];
2285
+ }
2286
+ _metadataModules() {
2287
+ const {
2288
+ argv
2289
+ } = this.context;
2290
+ return [argv.detailModule, argv.module];
2291
+ }
2292
+ async _renderMasterDetailDtoFile(dtoBaseName, templateBaseName) {
2293
+ const targetFile = path.join(this._masterPaths().dtoDir, `${dtoBaseName}.tsx`);
2294
+ if (fs.existsSync(targetFile)) return;
2295
+ const templateFile = this.template.resolveTemplatePath(__ThisSetName__, `tools/masterDetail/boilerplate/dto/${templateBaseName}`);
2296
+ const templateContent = fs.readFileSync(templateFile).toString('utf8');
2297
+ const renderedContent = await this.template.renderContent({
2298
+ content: templateContent
2299
+ });
2300
+ await this._saveFile(targetFile, renderedContent);
2301
+ }
2302
+ _patchMasterDtoImports(content, fileName, scene, detailDtoClassCapitalize, detailDtoResItemCapitalize) {
2303
+ const {
2304
+ argv
2305
+ } = this.context;
2306
+ if (!content.includes("import { $makeMetadata, Api, v } from 'vona-module-a-openapiutils';")) {
2307
+ content = this._replaceStrict(content, "import { $Dto } from 'vona-module-a-orm';", "import { $makeMetadata, Api, v } from 'vona-module-a-openapiutils';\nimport { $Dto } from 'vona-module-a-orm';", fileName);
2308
+ }
2309
+ if (!content.includes("import { $locale } from '../.metadata/locales.ts';")) {
2310
+ content = this._replaceStrict(content, "import { ZovaRender } from 'zova-rest-cabloy-basic-admin';\n", "import { ZovaRender } from 'zova-rest-cabloy-basic-admin';\n\nimport { $locale } from '../.metadata/locales.ts';\n", fileName);
2311
+ }
2312
+ const importMarker = `import { Model${argv.resourceNameCapitalize} } from '../model/${argv.resourceName}.ts';\n`;
2313
+ if (!content.includes(`import { Dto${detailDtoClassCapitalize} } from './`)) {
2314
+ content = this._replaceStrict(content, importMarker, `${importMarker}${this._masterDtoDetailImports(scene, detailDtoClassCapitalize, detailDtoResItemCapitalize)}`, fileName);
2315
+ }
2316
+ return content;
2317
+ }
2318
+ _patchMasterDtoFields(content, fileName, scene) {
2319
+ const fieldCode = this._masterDtoFieldCode(scene);
2320
+ if (content.includes(' fields: {\n')) {
2321
+ return content.replace(' fields: {\n', ` fields: {\n${fieldCode}`);
2322
+ }
2323
+ const decoratorMarker = ' ],\n})';
2324
+ if (!content.includes(decoratorMarker)) {
2325
+ throw new Error(`master dto decorator is not in the expected shape: ${fileName}`);
2326
+ }
2327
+ return content.replace(decoratorMarker, ` ],\n fields: {\n${fieldCode} },\n})`);
2328
+ }
2329
+ _patchMasterDtoClass(content, fileName, scene, detailDtoClassCapitalize, detailDtoResItemCapitalize) {
2330
+ const classReplaceSource = this._masterDtoClassReplaceSource(scene);
2331
+ if (!content.includes(classReplaceSource)) {
2332
+ throw new Error(`master dto class is not in the expected generated shape: ${fileName}`);
2333
+ }
2334
+ return content.replace(classReplaceSource, this._masterDtoClassReplaceTarget(scene, detailDtoClassCapitalize, detailDtoResItemCapitalize));
2335
+ }
2336
+ _masterDtoDetailImports(scene, detailDtoClassCapitalize, detailDtoResItemCapitalize) {
2337
+ const {
2338
+ argv
2339
+ } = this.context;
2340
+ return `import { Dto${detailDtoClassCapitalize} } from './${scene === 'View' ? argv.detailDtoViewName : argv.detailDtoMutateName}.tsx';\nimport { Dto${detailDtoResItemCapitalize} } from './${argv.detailDtoResItemName}.tsx';\n`;
2341
+ }
2342
+ _masterDtoFieldCode(scene) {
2343
+ const {
2344
+ argv
2345
+ } = this.context;
2346
+ return ` ${argv.relationName}: $makeMetadata(\n v.title($locale('${argv.relationNameCapitalize}')),\n ZovaRender.order(5),\n ZovaRender.field('basic-details:formFieldDetails'),\n${scene === 'Create' ? ' v.optional(),\n' : ''} ),\n`;
2347
+ }
2348
+ _masterDtoClassReplaceSource(scene) {
2349
+ const {
2350
+ argv
2351
+ } = this.context;
2352
+ const kind = scene === 'Create' ? 'create' : scene === 'Update' ? 'update' : 'get';
2353
+ return `export class Dto${argv.resourceNameCapitalize}${scene} extends $Dto.${kind}(() => Model${argv.resourceNameCapitalize}) {}`;
2354
+ }
2355
+ _masterDtoClassReplaceTarget(scene, detailDtoClassCapitalize, detailDtoResItemCapitalize) {
2356
+ const {
2357
+ argv
2358
+ } = this.context;
2359
+ const kind = scene === 'Create' ? 'create' : scene === 'Update' ? 'update' : 'get';
2360
+ return `export class Dto${argv.resourceNameCapitalize}${scene} extends $Dto.${kind}(() => Model${argv.resourceNameCapitalize}, {\n include: { ${argv.relationName}: { dtoClass: Dto${detailDtoClassCapitalize} } },\n}) {\n @Api.field(ZovaRender.visible(false), v.optional(), v.array(Dto${detailDtoResItemCapitalize}))\n ${argv.detailFieldPrivateName}?: Dto${detailDtoResItemCapitalize}[];\n}`;
2361
+ }
2362
+ _detailDtoClassCapitalize(scene) {
2363
+ const {
2364
+ argv
2365
+ } = this.context;
2366
+ return scene === 'View' ? this._capitalize(argv.detailDtoViewName) : this._capitalize(argv.detailDtoMutateName);
2367
+ }
2368
+ _masterLocaleAdditions(locale) {
2369
+ const {
2370
+ argv
2371
+ } = this.context;
2372
+ if (locale === 'en-us') {
2373
+ return [`${argv.relationNameCapitalize}: '${this._titleize(argv.relationName)}'`, `Add${argv.detailDialogTitleCapitalize}: 'Add ${this._titleize(argv.detailModuleInfo.relativeName)}'`, `Edit${argv.detailDialogTitleCapitalize}: 'Edit ${this._titleize(argv.detailModuleInfo.relativeName)}'`, `View${argv.detailDialogTitleCapitalize}: 'View ${this._titleize(argv.detailModuleInfo.relativeName)}'`];
2374
+ }
2375
+ return [`${argv.relationNameCapitalize}: '明细'`, `Add${argv.detailDialogTitleCapitalize}: '添加明细'`, `Edit${argv.detailDialogTitleCapitalize}: '编辑明细'`, `View${argv.detailDialogTitleCapitalize}: '查看明细'`];
2376
+ }
2377
+ async _createDetailCoreResource() {
2378
+ const {
2379
+ argv
2380
+ } = this.context;
2381
+ await this.helper.invokeCli([':tools:crudBasic', argv.detailResourceName, `--module=${argv.detailModule}`, '--nometadata'], {
2382
+ cwd: argv.projectPath
2383
+ });
2384
+ }
2385
+ _handleAggregateDetailMode(hasStandaloneSurface) {
2386
+ const {
2387
+ argv
2388
+ } = this.context;
2389
+ if (hasStandaloneSurface && !argv.createdDetailResource) {
2390
+ throw new Error(`detail module already has a standalone resource surface: ${argv.detailModule}:${argv.detailResourceName}`);
2391
+ }
2392
+ if (this._hasStandaloneDetailSurface()) {
2393
+ this._removeStandaloneDetailSurface();
2394
+ }
2395
+ }
2396
+ _handleStandaloneDetailMode() {
2397
+ const {
2398
+ argv
2399
+ } = this.context;
2400
+ if (!this._hasStandaloneDetailSurface()) {
2401
+ throw new Error(`detail module does not have a standalone resource surface: ${argv.detailModule}:${argv.detailResourceName}`);
2402
+ }
2403
+ }
2404
+ _detailModuleShape() {
2405
+ const files = this._detailPaths();
2406
+ return {
2407
+ hasCoreDetailFiles: fs.existsSync(files.entity) && fs.existsSync(files.model),
2408
+ hasStandaloneSurface: this._hasStandaloneDetailSurface()
2409
+ };
2410
+ }
2411
+ _masterPaths() {
2412
+ const {
2413
+ argv
2414
+ } = this.context;
2415
+ const masterRoot = argv._module.root;
2416
+ return {
2417
+ root: masterRoot,
2418
+ model: path.join(masterRoot, 'src/model', `${argv.resourceName}.ts`),
2419
+ service: path.join(masterRoot, 'src/service', `${argv.resourceName}.ts`),
2420
+ dtoDir: path.join(masterRoot, 'src/dto'),
2421
+ localeDir: path.join(masterRoot, 'src/config/locale')
2422
+ };
2423
+ }
2424
+ _detailPaths() {
2425
+ const {
2426
+ argv
2427
+ } = this.context;
2428
+ const detailRoot = argv._detailModule.root;
2429
+ return {
2430
+ root: detailRoot,
2431
+ entity: path.join(detailRoot, 'src/entity', `${argv.detailResourceName}.tsx`),
2432
+ model: path.join(detailRoot, 'src/model', `${argv.detailResourceName}.ts`),
2433
+ controller: path.join(detailRoot, 'src/controller', `${argv.detailResourceName}.ts`),
2434
+ service: path.join(detailRoot, 'src/service', `${argv.detailResourceName}.ts`),
2435
+ dtoDir: path.join(detailRoot, 'src/dto'),
2436
+ test: path.join(detailRoot, 'test', `${argv.detailResourceName}.test.ts`),
2437
+ metaVersion: path.join(detailRoot, 'src/bean', 'meta.version.ts'),
2438
+ metaIndex: path.join(detailRoot, 'src/bean', 'meta.index.ts')
2439
+ };
2440
+ }
2441
+ _hasStandaloneDetailSurface() {
2442
+ const files = this._detailPaths();
2443
+ return fs.existsSync(files.controller) || fs.existsSync(files.service) || fs.existsSync(files.dtoDir);
2444
+ }
2445
+ _removeStandaloneDetailSurface() {
2446
+ const files = this._detailPaths();
2447
+ for (const file of [files.controller, files.service, files.test]) {
2448
+ if (fs.existsSync(file)) {
2449
+ fs.rmSync(file, {
2450
+ force: true
2451
+ });
2452
+ }
2453
+ }
2454
+ if (fs.existsSync(files.dtoDir)) {
2455
+ fs.rmSync(files.dtoDir, {
2456
+ recursive: true,
2457
+ force: true
2458
+ });
2459
+ }
2460
+ }
2461
+ _detailDtoResItemCapitalize() {
2462
+ return this._capitalize(this.context.argv.detailDtoResItemName);
2463
+ }
2464
+ _detailDtoTemplates() {
2465
+ const {
2466
+ argv
2467
+ } = this.context;
2468
+ return [[argv.detailDtoBaseName, '<%=argv.detailDtoBaseName%>.tsx_'], [argv.detailDtoMutateName, '<%=argv.detailDtoMutateName%>.tsx_'], [argv.detailDtoViewName, '<%=argv.detailDtoViewName%>.tsx_'], [argv.detailDtoResItemName, '<%=argv.detailDtoResItemName%>.tsx_']];
2469
+ }
2470
+ _masterDtoScenes() {
2471
+ return ['Create', 'Update', 'View'];
2472
+ }
2473
+ _masterLocales() {
2474
+ return ['en-us', 'zh-cn'];
2475
+ }
2476
+ _resolveCreatedDetailModuleRoot() {
2477
+ const {
2478
+ argv
2479
+ } = this.context;
2480
+ if (argv._module.suite) {
2481
+ return path.join(argv.projectPath, 'src/suite', argv._module.suite, 'modules', argv.detailModule);
2482
+ }
2483
+ return path.join(argv.projectPath, 'src/module', argv.detailModule);
2484
+ }
2485
+ _capitalize(name) {
2486
+ return this.helper.firstCharToUpperCase(name);
2487
+ }
2488
+ _titleize(name) {
2489
+ return this.helper.stringToCapitalize(name, '-').replace(/([A-Z])/g, ' $1').trim();
2490
+ }
2491
+ _readFile(fileName) {
2492
+ if (!fs.existsSync(fileName)) {
2493
+ throw new Error(`file does not exist: ${fileName}`);
2494
+ }
2495
+ return fs.readFileSync(fileName).toString('utf8');
2496
+ }
2497
+ async _saveFile(fileName, content) {
2498
+ fs.writeFileSync(fileName, content);
2499
+ await this.helper.formatFile({
2500
+ fileName,
2501
+ logPrefix: 'format: '
2502
+ });
2503
+ }
2504
+ _replaceStrict(content, search, replacement, fileName) {
2505
+ if (!content.includes(search)) {
2506
+ throw new Error(`file is not in the expected shape: ${fileName}`);
2507
+ }
2508
+ return content.replace(search, replacement);
2509
+ }
2510
+ }
2511
+
2052
2512
  function checkIgnoreOfParts(parts) {
2053
2513
  const indexLast = parts.length - 1;
2054
2514
  if (parts[indexLast].endsWith('_')) {
@@ -2542,8 +3002,7 @@ class CliToolsMetadata extends BeanCliBase {
2542
3002
  }
2543
3003
  }
2544
3004
  async _generateMetadata(moduleName, force) {
2545
- const module = this.helper.findModule(moduleName);
2546
- if (!module) throw new Error(`module not found: ${moduleName}`);
3005
+ const module = this.helper.findModuleCanonical(moduleName);
2547
3006
  const modulePath = module.root;
2548
3007
  const metaDir = path.join(modulePath, 'src/.metadata');
2549
3008
  const metaIndexFile = path.join(metaDir, 'index.ts');
@@ -2788,7 +3247,8 @@ const beans = {
2788
3247
  'tools.metadata': CliToolsMetadata,
2789
3248
  'tools.crudBasic': CliToolsCrudBasic,
2790
3249
  'tools.crudStart': CliToolsCrudStart,
2791
- 'tools.crud': CliToolsCrud
3250
+ 'tools.crud': CliToolsCrud,
3251
+ 'tools.masterDetail': CliToolsMasterDetail
2792
3252
  };
2793
3253
 
2794
3254
  var binBuild = {
@@ -3361,6 +3821,84 @@ var toolsDeps = {
3361
3821
  options: {}
3362
3822
  };
3363
3823
 
3824
+ var toolsMasterDetail = {
3825
+ bean: 'tools.masterDetail',
3826
+ info: {
3827
+ version: '5.0.0',
3828
+ title: 'Cli: Tools: Master Detail',
3829
+ usage: 'npm run vona :tools:masterDetail resourceName -- [--module=] [--detailModule=] [--detailResourceName=] [--relationName=] [--fk=] [--detailMode=aggregate|standalone]'
3830
+ },
3831
+ options: {
3832
+ module: {
3833
+ description: 'master module relative name, such as training-student',
3834
+ type: 'string'
3835
+ },
3836
+ detailModule: {
3837
+ description: 'detail module relative name, such as training-record',
3838
+ type: 'string'
3839
+ },
3840
+ detailResourceName: {
3841
+ description: 'detail resource name',
3842
+ type: 'string'
3843
+ },
3844
+ relationName: {
3845
+ description: 'master relation name',
3846
+ type: 'string'
3847
+ },
3848
+ fk: {
3849
+ description: 'detail foreign key field name',
3850
+ type: 'string'
3851
+ },
3852
+ detailMode: {
3853
+ description: 'detail mode: aggregate or standalone',
3854
+ type: 'string'
3855
+ }
3856
+ },
3857
+ groups: {
3858
+ default: {
3859
+ questions: {
3860
+ resourceName: {
3861
+ type: 'input',
3862
+ message: 'master resourceName',
3863
+ initial: {
3864
+ expression: 'arg0'
3865
+ },
3866
+ required: true
3867
+ },
3868
+ module: {
3869
+ type: 'input',
3870
+ message: 'master module relative name (for example training-student)',
3871
+ required: true
3872
+ },
3873
+ detailModule: {
3874
+ type: 'input',
3875
+ message: 'detail module relative name (for example training-record)',
3876
+ required: true
3877
+ },
3878
+ detailResourceName: {
3879
+ type: 'input',
3880
+ message: 'detail resourceName',
3881
+ required: true
3882
+ },
3883
+ relationName: {
3884
+ type: 'input',
3885
+ message: 'master relation name'
3886
+ },
3887
+ fk: {
3888
+ type: 'input',
3889
+ message: 'detail foreign key field name'
3890
+ },
3891
+ detailMode: {
3892
+ type: 'input',
3893
+ message: 'detail mode (aggregate|standalone)',
3894
+ initial: 'aggregate',
3895
+ required: true
3896
+ }
3897
+ }
3898
+ }
3899
+ }
3900
+ };
3901
+
3364
3902
  var toolsMetadata = {
3365
3903
  bean: 'tools.metadata',
3366
3904
  info: {
@@ -3415,7 +3953,8 @@ const commands = {
3415
3953
  metadata: toolsMetadata,
3416
3954
  crudBasic: toolsCrudBasic,
3417
3955
  crudStart: toolsCrudStart,
3418
- crud: toolsCrud
3956
+ crud: toolsCrud,
3957
+ masterDetail: toolsMasterDetail
3419
3958
  }
3420
3959
  };
3421
3960
 
@@ -0,0 +1,94 @@
1
+ import type { IModule, IModuleInfo } from '@cabloy/module-info';
2
+ import { BeanCliBase } from '@cabloy/cli';
3
+ type DetailMode = 'aggregate' | 'standalone';
4
+ declare module '@cabloy/cli' {
5
+ interface ICommandArgv {
6
+ module: string;
7
+ moduleInfo: IModuleInfo;
8
+ _module: IModule;
9
+ resourceName: string;
10
+ resourceNameCapitalize: string;
11
+ detailModule: string;
12
+ detailModuleInfo: IModuleInfo;
13
+ _detailModule?: IModule;
14
+ detailResourceName: string;
15
+ detailResourceNameCapitalize: string;
16
+ relationName: string;
17
+ relationNameCapitalize: string;
18
+ fk: string;
19
+ detailMode: DetailMode;
20
+ detailPackageName: string;
21
+ detailModuleCapitalize: string;
22
+ detailDtoBaseName: string;
23
+ detailDtoMutateName: string;
24
+ detailDtoViewName: string;
25
+ detailDtoResItemName: string;
26
+ detailFieldPrivateName: string;
27
+ detailDialogTitleCapitalize: string;
28
+ createdDetailModule?: boolean;
29
+ createdDetailResource?: boolean;
30
+ }
31
+ }
32
+ export declare class CliToolsMasterDetail extends BeanCliBase {
33
+ execute(): Promise<void>;
34
+ private _prepareArgv;
35
+ private _ensureDetailModule;
36
+ private _ensureDetailResourceShape;
37
+ private _patchDetailModule;
38
+ private _patchDetailEntity;
39
+ private _patchDetailEntityImport;
40
+ private _patchDetailEntityField;
41
+ private _patchDetailMetaVersion;
42
+ private _patchDetailMetaIndex;
43
+ private _detailMetaVersionLine;
44
+ private _detailMetaIndexLine;
45
+ private _patchFileByMarker;
46
+ private _patchFileContent;
47
+ private _renderMasterDetailDtos;
48
+ private _patchMasterModule;
49
+ private _detailPatchTasks;
50
+ private _masterPatchTasks;
51
+ private _runSequential;
52
+ private _patchMasterModel;
53
+ private _patchMasterService;
54
+ private _patchMasterModelImport;
55
+ private _patchMasterModelRelations;
56
+ private _patchMasterModelDecorator;
57
+ private _masterModelRelationCode;
58
+ private _masterServiceIncludeReplacements;
59
+ private _patchMasterDto;
60
+ private _patchMasterLocale;
61
+ private _refreshMetadata;
62
+ private _patchLocaleAdditions;
63
+ private _localeAdditionKey;
64
+ private _metadataModules;
65
+ private _renderMasterDetailDtoFile;
66
+ private _patchMasterDtoImports;
67
+ private _patchMasterDtoFields;
68
+ private _patchMasterDtoClass;
69
+ private _masterDtoDetailImports;
70
+ private _masterDtoFieldCode;
71
+ private _masterDtoClassReplaceSource;
72
+ private _masterDtoClassReplaceTarget;
73
+ private _detailDtoClassCapitalize;
74
+ private _masterLocaleAdditions;
75
+ private _createDetailCoreResource;
76
+ private _handleAggregateDetailMode;
77
+ private _handleStandaloneDetailMode;
78
+ private _detailModuleShape;
79
+ private _masterPaths;
80
+ private _detailPaths;
81
+ private _hasStandaloneDetailSurface;
82
+ private _removeStandaloneDetailSurface;
83
+ private _detailDtoResItemCapitalize;
84
+ private _detailDtoTemplates;
85
+ private _masterDtoScenes;
86
+ private _masterLocales;
87
+ private _resolveCreatedDetailModuleRoot;
88
+ private _capitalize;
89
+ private _titleize;
90
+ private _readFile;
91
+ private _saveFile;
92
+ private _replaceStrict;
93
+ }
94
+ export {};
@@ -26,6 +26,7 @@ import { CliToolsCrud } from './bean/cli.tools.crud.ts';
26
26
  import { CliToolsCrudBasic } from './bean/cli.tools.crudBasic.ts';
27
27
  import { CliToolsCrudStart } from './bean/cli.tools.crudStart.ts';
28
28
  import { CliToolsDeps } from './bean/cli.tools.deps.ts';
29
+ import { CliToolsMasterDetail } from './bean/cli.tools.masterDetail.ts';
29
30
  import { CliToolsMetadata } from './bean/cli.tools.metadata.ts';
30
31
  export declare const beans: {
31
32
  'default.list': typeof CliDefaultList;
@@ -57,4 +58,5 @@ export declare const beans: {
57
58
  'tools.crudBasic': typeof CliToolsCrudBasic;
58
59
  'tools.crudStart': typeof CliToolsCrudStart;
59
60
  'tools.crud': typeof CliToolsCrud;
61
+ 'tools.masterDetail': typeof CliToolsMasterDetail;
60
62
  };
@@ -0,0 +1,78 @@
1
+ declare const _default: {
2
+ bean: string;
3
+ info: {
4
+ version: string;
5
+ title: string;
6
+ usage: string;
7
+ };
8
+ options: {
9
+ module: {
10
+ description: string;
11
+ type: string;
12
+ };
13
+ detailModule: {
14
+ description: string;
15
+ type: string;
16
+ };
17
+ detailResourceName: {
18
+ description: string;
19
+ type: string;
20
+ };
21
+ relationName: {
22
+ description: string;
23
+ type: string;
24
+ };
25
+ fk: {
26
+ description: string;
27
+ type: string;
28
+ };
29
+ detailMode: {
30
+ description: string;
31
+ type: string;
32
+ };
33
+ };
34
+ groups: {
35
+ default: {
36
+ questions: {
37
+ resourceName: {
38
+ type: string;
39
+ message: string;
40
+ initial: {
41
+ expression: string;
42
+ };
43
+ required: boolean;
44
+ };
45
+ module: {
46
+ type: string;
47
+ message: string;
48
+ required: boolean;
49
+ };
50
+ detailModule: {
51
+ type: string;
52
+ message: string;
53
+ required: boolean;
54
+ };
55
+ detailResourceName: {
56
+ type: string;
57
+ message: string;
58
+ required: boolean;
59
+ };
60
+ relationName: {
61
+ type: string;
62
+ message: string;
63
+ };
64
+ fk: {
65
+ type: string;
66
+ message: string;
67
+ };
68
+ detailMode: {
69
+ type: string;
70
+ message: string;
71
+ initial: string;
72
+ required: boolean;
73
+ };
74
+ };
75
+ };
76
+ };
77
+ };
78
+ export default _default;
@@ -545,5 +545,82 @@ export declare const commands: {
545
545
  };
546
546
  };
547
547
  };
548
+ masterDetail: {
549
+ bean: string;
550
+ info: {
551
+ version: string;
552
+ title: string;
553
+ usage: string;
554
+ };
555
+ options: {
556
+ module: {
557
+ description: string;
558
+ type: string;
559
+ };
560
+ detailModule: {
561
+ description: string;
562
+ type: string;
563
+ };
564
+ detailResourceName: {
565
+ description: string;
566
+ type: string;
567
+ };
568
+ relationName: {
569
+ description: string;
570
+ type: string;
571
+ };
572
+ fk: {
573
+ description: string;
574
+ type: string;
575
+ };
576
+ detailMode: {
577
+ description: string;
578
+ type: string;
579
+ };
580
+ };
581
+ groups: {
582
+ default: {
583
+ questions: {
584
+ resourceName: {
585
+ type: string;
586
+ message: string;
587
+ initial: {
588
+ expression: string;
589
+ };
590
+ required: boolean;
591
+ };
592
+ module: {
593
+ type: string;
594
+ message: string;
595
+ required: boolean;
596
+ };
597
+ detailModule: {
598
+ type: string;
599
+ message: string;
600
+ required: boolean;
601
+ };
602
+ detailResourceName: {
603
+ type: string;
604
+ message: string;
605
+ required: boolean;
606
+ };
607
+ relationName: {
608
+ type: string;
609
+ message: string;
610
+ };
611
+ fk: {
612
+ type: string;
613
+ message: string;
614
+ };
615
+ detailMode: {
616
+ type: string;
617
+ message: string;
618
+ initial: string;
619
+ required: boolean;
620
+ };
621
+ };
622
+ };
623
+ };
624
+ };
548
625
  };
549
626
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vona-cli-set-api",
3
- "version": "1.1.110",
4
- "gitHead": "ca10457f38716ad28bf5918dd67c85dd1f7dc9d0",
3
+ "version": "1.1.117",
4
+ "gitHead": "1a39e8008e1b48001cfeff475ddd53a2a0d867c4",
5
5
  "description": "vona cli-set-api",
6
6
  "keywords": [
7
7
  "framework",
@@ -35,13 +35,13 @@
35
35
  "tsc:publish": "npm run clean && node ../cli/src/bin/vona.ts :bin:buildGeneral && tsc -p tsconfig.build.json && tsc -p tsconfig.cli.json && tsc -p tsconfig.isolate.json",
36
36
  "prepublishOnly": "npm run tsc:publish",
37
37
  "prepack": "clean-package",
38
- "postpack": "clean-package restore && npm run clean"
38
+ "postpack": "clean-package restore"
39
39
  },
40
40
  "dependencies": {
41
41
  "@babel/plugin-proposal-decorators": "^7.29.0",
42
42
  "@babel/plugin-transform-class-properties": "^7.28.6",
43
43
  "@babel/plugin-transform-typescript": "^7.28.6",
44
- "@cabloy/cli": "^3.1.18",
44
+ "@cabloy/cli": "^3.1.23",
45
45
  "@cabloy/dotenv": "^1.2.8",
46
46
  "@cabloy/extend": "^3.2.8",
47
47
  "@cabloy/module-glob": "^5.3.13",
@@ -73,7 +73,7 @@
73
73
  "ts-node-maintained": "^10.9.6",
74
74
  "urllib": "^4.9.0",
75
75
  "uuid": "^11.1.0",
76
- "vona-core": "^5.1.23",
76
+ "vona-core": "^5.1.24",
77
77
  "why-is-node-running": "^3.2.2",
78
78
  "yargs-parser": "^22.0.0"
79
79
  }