@azure-net/cli 2.3.62 → 4.0.0-0

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 (91) hide show
  1. package/.prettierrc +7 -0
  2. package/bin/azure-net.js +247 -157
  3. package/bin/azure-net.ts +253 -0
  4. package/eslint.config.js +19 -0
  5. package/package.json +12 -5
  6. package/src/commands/generate/generateCrudBase.js +95 -114
  7. package/src/commands/generate/generateCrudPresenter.js +135 -142
  8. package/src/commands/generate/generateModule.js +21 -21
  9. package/src/commands/generate/generateModuleBase.js +92 -114
  10. package/src/commands/generate/generateModulePresenter.js +73 -80
  11. package/src/commands/generate/generatePresenter.js +38 -44
  12. package/src/commands/generate/generateRepository.js +41 -47
  13. package/src/commands/generate/generateSchema.js +74 -70
  14. package/src/commands/generate/generateService.js +32 -37
  15. package/src/commands/generate/index.js +0 -5
  16. package/src/generate/layers/generateRepository.js +124 -0
  17. package/src/generate/layers/generateRepositoryMethod.js +102 -0
  18. package/src/generate/layers/generateUseCases.js +50 -0
  19. package/src/generate/layers/repositoryShared.js +569 -0
  20. package/src/generate/module/generateDomain.js +146 -0
  21. package/src/generate/module/generateDomain.ts +147 -0
  22. package/src/generate/module/generateModule.js +84 -0
  23. package/src/generate/module/generateModule.ts +84 -0
  24. package/src/generate/module/generatePresenter.js +419 -0
  25. package/src/generate/module/generatePresenter.ts +442 -0
  26. package/src/generate/module/generateRepository.js +144 -0
  27. package/src/generate/module/generateRepository.ts +158 -0
  28. package/src/generate/module/generateSchemaFromType.js +320 -0
  29. package/src/generate/module/generateSchemaFromType.ts +325 -0
  30. package/src/generate/module/generateTypesJson.js +252 -0
  31. package/src/generate/module/generateTypesJson.ts +259 -0
  32. package/src/generate/module/generateUseCases.js +67 -0
  33. package/src/generate/module/generateUseCases.ts +67 -0
  34. package/src/generate/module/repositoryModuleShared.js +598 -0
  35. package/src/generate/ui/generateComponent.js +98 -0
  36. package/src/generate/ui/generateComponent.ts +100 -0
  37. package/src/generate/ui/generateDesignComponent.js +71 -0
  38. package/src/generate/ui/generateDesignComponent.ts +71 -0
  39. package/src/generate/ui/generateWidget.js +71 -0
  40. package/src/generate/ui/generateWidget.ts +71 -0
  41. package/src/init/initAliases.js +159 -0
  42. package/src/init/initAliases.ts +167 -0
  43. package/src/init/initEdges.js +74 -0
  44. package/src/init/initEdges.ts +74 -0
  45. package/src/init/initStructure.js +122 -0
  46. package/src/init/initStructure.ts +124 -0
  47. package/src/install/installFresh.js +101 -0
  48. package/src/install/installFresh.ts +101 -0
  49. package/src/plugins/addLint.js +257 -0
  50. package/src/plugins/addLint.ts +274 -0
  51. package/src/plugins/index.js +1 -0
  52. package/src/plugins/index.ts +1 -0
  53. package/src/scaffolds/createCoreMiddleware.js +27 -0
  54. package/src/scaffolds/createCoreMiddleware.ts +27 -0
  55. package/src/scaffolds/createCoreSchema.js +141 -0
  56. package/src/scaffolds/createCoreSchema.ts +142 -0
  57. package/src/scaffolds/createDatasource.js +173 -0
  58. package/src/scaffolds/createDatasource.ts +175 -0
  59. package/src/scaffolds/createDatasourceProvider.js +164 -0
  60. package/src/scaffolds/createDatasourceProvider.ts +175 -0
  61. package/src/scaffolds/createMiddleware.js +105 -0
  62. package/src/scaffolds/createMiddleware.ts +105 -0
  63. package/src/scaffolds/createPresenterFactory.js +112 -0
  64. package/src/scaffolds/createPresenterFactory.ts +114 -0
  65. package/src/scaffolds/createResponse.js +64 -0
  66. package/src/scaffolds/createResponse.ts +64 -0
  67. package/src/scaffolds/createSchemaRule.js +40 -0
  68. package/src/scaffolds/createSchemaRule.ts +42 -0
  69. package/src/scaffolds/createTranslationManager.js +78 -0
  70. package/src/scaffolds/createTranslationManager.ts +78 -0
  71. package/src/scaffolds/index.js +9 -0
  72. package/src/scaffolds/index.ts +9 -0
  73. package/src/utils/contextUtils.js +56 -49
  74. package/src/utils/fileUtils.js +72 -84
  75. package/src/utils/loadConfig.js +57 -13
  76. package/tsconfig.json +19 -0
  77. package/src/commands/generate/generateComponent.js +0 -53
  78. package/src/commands/generate/generateDatasource.js +0 -84
  79. package/src/commands/generate/generateDesignComponent.js +0 -53
  80. package/src/commands/generate/generateResponse.js +0 -41
  81. package/src/commands/generate/generateWidget.js +0 -53
  82. package/src/commands/init/index.js +0 -9
  83. package/src/commands/init/initAliases.js +0 -52
  84. package/src/commands/init/initEdges.js +0 -42
  85. package/src/commands/init/initLint.js +0 -138
  86. package/src/commands/init/initMiddleware.js +0 -21
  87. package/src/commands/init/initPresenter.js +0 -25
  88. package/src/commands/init/initSchema.js +0 -22
  89. package/src/commands/init/initStructure.js +0 -140
  90. package/src/commands/init/initTranslations.js +0 -59
  91. package/src/commands/init/migrateStructure.js +0 -144
@@ -0,0 +1,114 @@
1
+ import fs from 'node:fs/promises';
2
+ import fsSync from 'node:fs';
3
+ import path from 'node:path';
4
+ import prompts from 'prompts';
5
+ import { writeIfNotExists, updateIndexTs } from '../utils/fileUtils.js';
6
+ import { toPascalCase } from '../utils/contextUtils.js';
7
+
8
+ type ConfigPath = 'ts' | 'js';
9
+
10
+ const createPresenterFactoryTemplate = (
11
+ factoryName: string
12
+ ): string => `import { createErrorHandler, createAsyncHelpers, createPresenterFactory } from '@azure-net/kit/delivery';
13
+
14
+ export const AsyncHelpers = createAsyncHelpers({ handler: createErrorHandler() });
15
+
16
+ export const ${factoryName} = createPresenterFactory({ ...AsyncHelpers });
17
+ `;
18
+
19
+ const resolveConfigFile = (cwd: string): { filepath: string; type: ConfigPath; exists: boolean; content: string } => {
20
+ const tsPath = path.join(cwd, 'azure-net.config.ts');
21
+ const jsPath = path.join(cwd, 'azure-net.config.js');
22
+
23
+ if (fsSync.existsSync(tsPath)) {
24
+ return { filepath: tsPath, type: 'ts', exists: true, content: fsSync.readFileSync(tsPath, 'utf-8') };
25
+ }
26
+ if (fsSync.existsSync(jsPath)) {
27
+ return { filepath: jsPath, type: 'js', exists: true, content: fsSync.readFileSync(jsPath, 'utf-8') };
28
+ }
29
+
30
+ return { filepath: tsPath, type: 'ts', exists: false, content: '' };
31
+ };
32
+
33
+ const buildDefaultPresenterFactoryConfigEntry = (factoryName: string): string => {
34
+ return `defaultPresenterFactory: '${factoryName}'`;
35
+ };
36
+
37
+ const upsertDefaultPresenterFactoryConfig = (content: string, factoryName: string): string => {
38
+ const hasExportDefaultObject = /export\s+default\s*\{[\s\S]*\}\s*;?/.test(content);
39
+ const defaultFactoryEntry = buildDefaultPresenterFactoryConfigEntry(factoryName);
40
+ const coreAliasEntry = `coreAlias: '$core'`;
41
+
42
+ if (!hasExportDefaultObject) {
43
+ return `export default {\n\t${coreAliasEntry},\n\t${defaultFactoryEntry}\n};\n`;
44
+ }
45
+
46
+ let nextContent = content;
47
+
48
+ if (/defaultPresenterFactory\s*:/.test(nextContent)) {
49
+ nextContent = nextContent.replace(/defaultPresenterFactory\s*:\s*(\{[\s\S]*?\n\s*\}|['"`][^'"`]+['"`])/m, defaultFactoryEntry);
50
+ } else if (/defaultPresenter\s*:/.test(nextContent)) {
51
+ nextContent = nextContent.replace(/defaultPresenter\s*:\s*(\{[\s\S]*?\n\s*\}|['"`][^'"`]+['"`])/m, defaultFactoryEntry);
52
+ } else {
53
+ nextContent = nextContent.replace(/export\s+default\s*\{([\s\S]*)\}\s*;?/, (_full, body: string) => {
54
+ const trimmedRight = body.replace(/\s*$/, '');
55
+ if (!trimmedRight.length) {
56
+ return `export default {\n\t${defaultFactoryEntry}\n};`;
57
+ }
58
+
59
+ const withComma = trimmedRight.endsWith(',') ? trimmedRight : `${trimmedRight},`;
60
+ return `export default {${withComma}\n\t${defaultFactoryEntry}\n};`;
61
+ });
62
+ }
63
+
64
+ if (/coreAlias\s*:/.test(nextContent)) {
65
+ return nextContent.replace(/coreAlias\s*:\s*['"`][^'"`]+['"`]/, coreAliasEntry);
66
+ }
67
+
68
+ return nextContent.replace(/export\s+default\s*\{([\s\S]*)\}\s*;?/, (_full, body: string) => {
69
+ const trimmedRight = body.replace(/\s*$/, '');
70
+ if (!trimmedRight.length) {
71
+ return `export default {\n\t${coreAliasEntry}\n};`;
72
+ }
73
+
74
+ const withComma = trimmedRight.endsWith(',') ? trimmedRight : `${trimmedRight},`;
75
+ return `export default {${withComma}\n\t${coreAliasEntry}\n};`;
76
+ });
77
+ };
78
+
79
+ export default async function createPresenterFactory(): Promise<void> {
80
+ const { presenterFactoryNameRaw } = await prompts({
81
+ type: 'text',
82
+ name: 'presenterFactoryNameRaw',
83
+ message: 'Presenter factory name:',
84
+ initial: 'AppPresenter'
85
+ });
86
+
87
+ const presenterFactoryName = toPascalCase(String(presenterFactoryNameRaw ?? 'AppPresenter')) || 'AppPresenter';
88
+ const presenterRootPath = path.join(process.cwd(), 'src', 'core', 'presenter');
89
+ const filePath = path.join(presenterRootPath, `${presenterFactoryName}.ts`);
90
+
91
+ await fs.mkdir(presenterRootPath, { recursive: true });
92
+
93
+ const created = await writeIfNotExists(filePath, createPresenterFactoryTemplate(presenterFactoryName));
94
+ if (!created) {
95
+ console.log(`⚠️ Presenter factory "${presenterFactoryName}" already exists. File was not overwritten.`);
96
+ } else {
97
+ await updateIndexTs(presenterRootPath);
98
+ console.log(`✅ Presenter factory created: ${filePath}`);
99
+ }
100
+
101
+ const { useAsDefault } = await prompts({
102
+ type: 'confirm',
103
+ name: 'useAsDefault',
104
+ message: 'Use this presenter factory as default for the project?',
105
+ initial: true
106
+ });
107
+
108
+ if (!useAsDefault) return;
109
+
110
+ const config = resolveConfigFile(process.cwd());
111
+ const nextContent = upsertDefaultPresenterFactoryConfig(config.content, presenterFactoryName);
112
+ await fs.writeFile(config.filepath, nextContent, 'utf-8');
113
+ console.log(`✅ Default presenter factory saved to ${path.basename(config.filepath)}`);
114
+ }
@@ -0,0 +1,64 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import prompts from 'prompts';
4
+ import { writeIfNotExists, updateIndexTs } from '../utils/fileUtils.js';
5
+ import { toPascalCase } from '../utils/contextUtils.js';
6
+
7
+ const createResponseWithWrapperTemplate = (name) => {
8
+ const interfaceName = `I${name}`;
9
+ return `import { ResponseBuilder } from '@azure-net/kit/infra';
10
+
11
+ export interface ${interfaceName}<T> {
12
+ \tdata: T;
13
+ }
14
+
15
+ export class ${name}<TData = unknown, TMeta = unknown> extends ResponseBuilder<TData, TMeta, ${interfaceName}<TData>> {
16
+ \tprotected unwrapData(data: ${interfaceName}<TData>): TData {
17
+ \t\treturn data.data;
18
+ \t}
19
+ }
20
+ `;
21
+ };
22
+
23
+ const createResponseSimpleTemplate = (name) => `import { ResponseBuilder } from '@azure-net/kit/infra';
24
+
25
+ export class ${name}<TData = unknown, TMeta = unknown> extends ResponseBuilder<TData, TMeta> {}
26
+ `;
27
+
28
+ const ensureResponseName = (rawName) => {
29
+ const normalized = toPascalCase(rawName || 'ApiResponse') || 'ApiResponse';
30
+ return normalized.endsWith('Response') ? normalized : `${normalized}Response`;
31
+ };
32
+
33
+ export default async function createResponse() {
34
+ const { responseNameRaw } = await prompts({
35
+ type: 'text',
36
+ name: 'responseNameRaw',
37
+ message: 'Response class name:',
38
+ initial: 'ApiResponse'
39
+ });
40
+
41
+ const responseName = ensureResponseName(String(responseNameRaw ?? 'ApiResponse'));
42
+
43
+ const { useCommonResponseWrapper } = await prompts({
44
+ type: 'confirm',
45
+ name: 'useCommonResponseWrapper',
46
+ message: 'Use common backend response wrapper interface?',
47
+ initial: true
48
+ });
49
+
50
+ const responseRoot = path.join(process.cwd(), 'src', 'core', 'response');
51
+ const filePath = path.join(responseRoot, `${responseName}.ts`);
52
+ await fs.mkdir(responseRoot, { recursive: true });
53
+
54
+ const template = useCommonResponseWrapper ? createResponseWithWrapperTemplate(responseName) : createResponseSimpleTemplate(responseName);
55
+ const created = await writeIfNotExists(filePath, template);
56
+ await updateIndexTs(responseRoot);
57
+
58
+ if (!created) {
59
+ console.log(`⚠️ Response "${responseName}" already exists. File was not overwritten.`);
60
+ return;
61
+ }
62
+
63
+ console.log(`✅ Response created: ${filePath}`);
64
+ }
@@ -0,0 +1,64 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import prompts from 'prompts';
4
+ import { writeIfNotExists, updateIndexTs } from '../utils/fileUtils.js';
5
+ import { toPascalCase } from '../utils/contextUtils.js';
6
+
7
+ const createResponseWithWrapperTemplate = (name: string): string => {
8
+ const interfaceName = `I${name}`;
9
+ return `import { ResponseBuilder } from '@azure-net/kit/infra';
10
+
11
+ export interface ${interfaceName}<T> {
12
+ \tdata: T;
13
+ }
14
+
15
+ export class ${name}<TData = unknown, TMeta = unknown> extends ResponseBuilder<TData, TMeta, ${interfaceName}<TData>> {
16
+ \tprotected unwrapData(data: ${interfaceName}<TData>): TData {
17
+ \t\treturn data.data;
18
+ \t}
19
+ }
20
+ `;
21
+ };
22
+
23
+ const createResponseSimpleTemplate = (name: string): string => `import { ResponseBuilder } from '@azure-net/kit/infra';
24
+
25
+ export class ${name}<TData = unknown, TMeta = unknown> extends ResponseBuilder<TData, TMeta> {}
26
+ `;
27
+
28
+ const ensureResponseName = (rawName: string): string => {
29
+ const normalized = toPascalCase(rawName || 'ApiResponse') || 'ApiResponse';
30
+ return normalized.endsWith('Response') ? normalized : `${normalized}Response`;
31
+ };
32
+
33
+ export default async function createResponse(): Promise<void> {
34
+ const { responseNameRaw } = await prompts({
35
+ type: 'text',
36
+ name: 'responseNameRaw',
37
+ message: 'Response class name:',
38
+ initial: 'ApiResponse'
39
+ });
40
+
41
+ const responseName = ensureResponseName(String(responseNameRaw ?? 'ApiResponse'));
42
+
43
+ const { useCommonResponseWrapper } = await prompts({
44
+ type: 'confirm',
45
+ name: 'useCommonResponseWrapper',
46
+ message: 'Use common backend response wrapper interface?',
47
+ initial: true
48
+ });
49
+
50
+ const responseRoot = path.join(process.cwd(), 'src', 'core', 'response');
51
+ const filePath = path.join(responseRoot, `${responseName}.ts`);
52
+ await fs.mkdir(responseRoot, { recursive: true });
53
+
54
+ const template = useCommonResponseWrapper ? createResponseWithWrapperTemplate(responseName) : createResponseSimpleTemplate(responseName);
55
+ const created = await writeIfNotExists(filePath, template);
56
+ await updateIndexTs(responseRoot);
57
+
58
+ if (!created) {
59
+ console.log(`⚠️ Response "${responseName}" already exists. File was not overwritten.`);
60
+ return;
61
+ }
62
+
63
+ console.log(`✅ Response created: ${filePath}`);
64
+ }
@@ -0,0 +1,40 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import prompts from 'prompts';
4
+ import { writeIfNotExists, updateIndexTs } from '../utils/fileUtils.js';
5
+ import { toPascalCase, toCamelCase } from '../utils/contextUtils.js';
6
+
7
+ const createSchemaRuleTemplate = (ruleName) => `import type { ValidationParams, ValidationRuleResult } from '@azure-net/kit/delivery';
8
+
9
+ export const ${ruleName} = <T = unknown, D = unknown>(): ValidationRuleResult<T, D> => {
10
+ \treturn (validationParams: ValidationParams<T, D>) => {
11
+ \t\treturn undefined;
12
+ \t};
13
+ };
14
+ `;
15
+
16
+ export default async function createSchemaRule() {
17
+ const { ruleNameRaw } = await prompts({
18
+ type: 'text',
19
+ name: 'ruleNameRaw',
20
+ message: 'Schema rule name:',
21
+ initial: 'File'
22
+ });
23
+
24
+ const pascalName = toPascalCase(String(ruleNameRaw ?? 'File')) || 'File';
25
+ const camelName = toCamelCase(pascalName) || 'file';
26
+ const rulesPath = path.join(process.cwd(), 'src', 'core', 'schema', 'custom-rules');
27
+ const filePath = path.join(rulesPath, `${pascalName}.ts`);
28
+
29
+ await fs.mkdir(rulesPath, { recursive: true });
30
+
31
+ const created = await writeIfNotExists(filePath, createSchemaRuleTemplate(camelName));
32
+ await updateIndexTs(rulesPath);
33
+
34
+ if (!created) {
35
+ console.log(`⚠️ Schema rule "${pascalName}" already exists. File was not overwritten.`);
36
+ return;
37
+ }
38
+
39
+ console.log(`✅ Schema rule created: ${filePath}`);
40
+ }
@@ -0,0 +1,42 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import prompts from 'prompts';
4
+ import { writeIfNotExists, updateIndexTs } from '../utils/fileUtils.js';
5
+ import { toPascalCase, toCamelCase } from '../utils/contextUtils.js';
6
+
7
+ const createSchemaRuleTemplate = (
8
+ ruleName: string
9
+ ): string => `import type { ValidationParams, ValidationRuleResult } from '@azure-net/kit/delivery';
10
+
11
+ export const ${ruleName} = <T = unknown, D = unknown>(): ValidationRuleResult<T, D> => {
12
+ \treturn (validationParams: ValidationParams<T, D>) => {
13
+ \t\treturn undefined;
14
+ \t};
15
+ };
16
+ `;
17
+
18
+ export default async function createSchemaRule(): Promise<void> {
19
+ const { ruleNameRaw } = await prompts({
20
+ type: 'text',
21
+ name: 'ruleNameRaw',
22
+ message: 'Schema rule name:',
23
+ initial: 'File'
24
+ });
25
+
26
+ const pascalName = toPascalCase(String(ruleNameRaw ?? 'File')) || 'File';
27
+ const camelName = toCamelCase(pascalName) || 'file';
28
+ const rulesPath = path.join(process.cwd(), 'src', 'core', 'schema', 'custom-rules');
29
+ const filePath = path.join(rulesPath, `${pascalName}.ts`);
30
+
31
+ await fs.mkdir(rulesPath, { recursive: true });
32
+
33
+ const created = await writeIfNotExists(filePath, createSchemaRuleTemplate(camelName));
34
+ await updateIndexTs(rulesPath);
35
+
36
+ if (!created) {
37
+ console.log(`⚠️ Schema rule "${pascalName}" already exists. File was not overwritten.`);
38
+ return;
39
+ }
40
+
41
+ console.log(`✅ Schema rule created: ${filePath}`);
42
+ }
@@ -0,0 +1,78 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import prompts from 'prompts';
4
+ import { writeIfNotExists } from '../utils/fileUtils.js';
5
+
6
+ const parseLocales = (rawLocales) => {
7
+ const parsed = String(rawLocales ?? '')
8
+ .split(',')
9
+ .map((item) => item.trim().toLowerCase())
10
+ .filter(Boolean);
11
+
12
+ const unique = Array.from(new Set(parsed));
13
+ return unique.length ? unique : ['en', 'ru'];
14
+ };
15
+
16
+ const createTranslationIndexTemplate = (defaultLocale) => `import { messages } from './locales/index.js';
17
+ import { createTranslations, type Path } from '@azure-net/kit/i18n';
18
+
19
+ export const TranslationManager = createTranslations({
20
+ \tmessages,
21
+ \tinitLang: '${defaultLocale}',
22
+ \tinitLangFromAcceptLanguage: true,
23
+ \tcookieName: 'current-language'
24
+ });
25
+
26
+ type MessagesList = Awaited<ReturnType<(typeof messages)[keyof typeof messages]>>;
27
+ type TranslationExtended = { key: MessageKeys | string; vars?: Record<string, unknown> };
28
+
29
+ export type MessageKeys = Path<MessagesList>;
30
+ export type TranslationParam = MessageKeys | string | TranslationExtended;
31
+ export type AvailableLocales = keyof typeof messages;
32
+ `;
33
+
34
+ const createLocalesIndexTemplate = (locales) => {
35
+ const lines = locales.map((locale) => `\t${locale}: () => import('./${locale}/index.js').then((res) => res.default)`);
36
+ return `export const messages = {\n${lines.join(',\n')}\n};\n`;
37
+ };
38
+
39
+ const createLocaleTemplate = () => `export default {
40
+ \ttest: 'test'
41
+ };
42
+ `;
43
+
44
+ export default async function createTranslationManager() {
45
+ const { localesRaw } = await prompts({
46
+ type: 'text',
47
+ name: 'localesRaw',
48
+ message: 'Enter locales (comma separated):',
49
+ initial: 'en, ru'
50
+ });
51
+
52
+ const locales = parseLocales(String(localesRaw ?? 'en, ru'));
53
+
54
+ const { defaultLocale } = await prompts({
55
+ type: 'select',
56
+ name: 'defaultLocale',
57
+ message: 'Choose default locale:',
58
+ choices: locales.map((locale) => ({ title: locale, value: locale })),
59
+ initial: 0
60
+ });
61
+
62
+ const selectedDefaultLocale = String(defaultLocale ?? locales[0] ?? 'en');
63
+
64
+ const translationRoot = path.join(process.cwd(), 'src', 'core', 'translation');
65
+ const localesRoot = path.join(translationRoot, 'locales');
66
+ await fs.mkdir(localesRoot, { recursive: true });
67
+
68
+ await writeIfNotExists(path.join(translationRoot, 'index.ts'), createTranslationIndexTemplate(selectedDefaultLocale));
69
+ await writeIfNotExists(path.join(localesRoot, 'index.ts'), createLocalesIndexTemplate(locales));
70
+
71
+ for (const locale of locales) {
72
+ const localeDir = path.join(localesRoot, locale);
73
+ await fs.mkdir(localeDir, { recursive: true });
74
+ await writeIfNotExists(path.join(localeDir, 'index.ts'), createLocaleTemplate());
75
+ }
76
+
77
+ console.log(`✅ Translation manager created: ${translationRoot}`);
78
+ }
@@ -0,0 +1,78 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import prompts from 'prompts';
4
+ import { writeIfNotExists } from '../utils/fileUtils.js';
5
+
6
+ const parseLocales = (rawLocales: string): string[] => {
7
+ const parsed = String(rawLocales ?? '')
8
+ .split(',')
9
+ .map((item) => item.trim().toLowerCase())
10
+ .filter(Boolean);
11
+
12
+ const unique = Array.from(new Set(parsed));
13
+ return unique.length ? unique : ['en', 'ru'];
14
+ };
15
+
16
+ const createTranslationIndexTemplate = (defaultLocale: string): string => `import { messages } from './locales/index.js';
17
+ import { createTranslations, type Path } from '@azure-net/kit/i18n';
18
+
19
+ export const TranslationManager = createTranslations({
20
+ \tmessages,
21
+ \tinitLang: '${defaultLocale}',
22
+ \tinitLangFromAcceptLanguage: true,
23
+ \tcookieName: 'current-language'
24
+ });
25
+
26
+ type MessagesList = Awaited<ReturnType<(typeof messages)[keyof typeof messages]>>;
27
+ type TranslationExtended = { key: MessageKeys | string; vars?: Record<string, unknown> };
28
+
29
+ export type MessageKeys = Path<MessagesList>;
30
+ export type TranslationParam = MessageKeys | string | TranslationExtended;
31
+ export type AvailableLocales = keyof typeof messages;
32
+ `;
33
+
34
+ const createLocalesIndexTemplate = (locales: string[]): string => {
35
+ const lines = locales.map((locale) => `\t${locale}: () => import('./${locale}/index.js').then((res) => res.default)`);
36
+ return `export const messages = {\n${lines.join(',\n')}\n};\n`;
37
+ };
38
+
39
+ const createLocaleTemplate = (): string => `export default {
40
+ \ttest: 'test'
41
+ };
42
+ `;
43
+
44
+ export default async function createTranslationManager(): Promise<void> {
45
+ const { localesRaw } = await prompts({
46
+ type: 'text',
47
+ name: 'localesRaw',
48
+ message: 'Enter locales (comma separated):',
49
+ initial: 'en, ru'
50
+ });
51
+
52
+ const locales = parseLocales(String(localesRaw ?? 'en, ru'));
53
+
54
+ const { defaultLocale } = await prompts({
55
+ type: 'select',
56
+ name: 'defaultLocale',
57
+ message: 'Choose default locale:',
58
+ choices: locales.map((locale) => ({ title: locale, value: locale })),
59
+ initial: 0
60
+ });
61
+
62
+ const selectedDefaultLocale = String(defaultLocale ?? locales[0] ?? 'en');
63
+
64
+ const translationRoot = path.join(process.cwd(), 'src', 'core', 'translation');
65
+ const localesRoot = path.join(translationRoot, 'locales');
66
+ await fs.mkdir(localesRoot, { recursive: true });
67
+
68
+ await writeIfNotExists(path.join(translationRoot, 'index.ts'), createTranslationIndexTemplate(selectedDefaultLocale));
69
+ await writeIfNotExists(path.join(localesRoot, 'index.ts'), createLocalesIndexTemplate(locales));
70
+
71
+ for (const locale of locales) {
72
+ const localeDir = path.join(localesRoot, locale);
73
+ await fs.mkdir(localeDir, { recursive: true });
74
+ await writeIfNotExists(path.join(localeDir, 'index.ts'), createLocaleTemplate());
75
+ }
76
+
77
+ console.log(`✅ Translation manager created: ${translationRoot}`);
78
+ }
@@ -0,0 +1,9 @@
1
+ export { default as createCoreSchema } from './createCoreSchema.js';
2
+ export { default as createCoreMiddleware } from './createCoreMiddleware.js';
3
+ export { default as createTranslationManager } from './createTranslationManager.js';
4
+ export { default as createPresenterFactory } from './createPresenterFactory.js';
5
+ export { default as createSchemaRule } from './createSchemaRule.js';
6
+ export { default as createMiddleware } from './createMiddleware.js';
7
+ export { default as createResponse } from './createResponse.js';
8
+ export { default as createDatasource } from './createDatasource.js';
9
+ export { default as createDatasourceProvider } from './createDatasourceProvider.js';
@@ -0,0 +1,9 @@
1
+ export { default as createCoreSchema } from './createCoreSchema.js';
2
+ export { default as createCoreMiddleware } from './createCoreMiddleware.js';
3
+ export { default as createTranslationManager } from './createTranslationManager.js';
4
+ export { default as createPresenterFactory } from './createPresenterFactory.js';
5
+ export { default as createSchemaRule } from './createSchemaRule.js';
6
+ export { default as createMiddleware } from './createMiddleware.js';
7
+ export { default as createResponse } from './createResponse.js';
8
+ export { default as createDatasource } from './createDatasource.js';
9
+ export { default as createDatasourceProvider } from './createDatasourceProvider.js';
@@ -6,76 +6,83 @@ import { loadUserConfig } from './loadConfig.js';
6
6
  const APP_PATH = path.join(process.cwd(), 'src/app');
7
7
  const CORE_PATH = path.join(process.cwd(), 'src/core');
8
8
 
9
+ function normalizeContextNames(rawContexts) {
10
+ if (!Array.isArray(rawContexts)) return [];
11
+
12
+ return rawContexts
13
+ .map((item) => {
14
+ if (typeof item === 'string') {
15
+ return item.trim();
16
+ }
17
+ if (!item || typeof item !== 'object') return '';
18
+ return String(item.name ?? '').trim();
19
+ })
20
+ .filter(Boolean);
21
+ }
22
+
9
23
  export async function selectContext(message = 'Select context:') {
10
- const config = await loadUserConfig();
11
- const contexts = config.contexts || ['app'];
24
+ const config = await loadUserConfig();
25
+ const contexts = normalizeContextNames(config.contexts);
12
26
 
13
- const { context } = await prompts({
14
- type: 'select',
15
- name: 'context',
16
- message,
17
- choices: [
18
- ...contexts.map(c => ({ title: c, value: c })),
19
- { title: 'core', value: 'core' }
20
- ]
21
- });
27
+ const { context } = await prompts({
28
+ type: 'select',
29
+ name: 'context',
30
+ message,
31
+ choices: [...contexts.map((c) => ({ title: c, value: c })), { title: 'core', value: 'core' }]
32
+ });
22
33
 
23
- return context;
34
+ return context;
24
35
  }
25
36
 
26
37
  export function getContextPath(context) {
27
- return context === 'core'
28
- ? CORE_PATH
29
- : path.join(APP_PATH, context, 'layers');
38
+ return context === 'core' ? CORE_PATH : path.join(APP_PATH, context, 'layers');
30
39
  }
31
40
 
32
41
  export async function getAvailableFiles(dir, pattern = '.ts') {
33
- try {
34
- const files = await fs.readdir(dir);
35
- return files
36
- .filter(f => f.endsWith(pattern) && f !== 'index.ts')
37
- .map(f => f.replace(pattern, ''));
38
- } catch {
39
- return [];
40
- }
42
+ try {
43
+ const files = await fs.readdir(dir);
44
+ return files.filter((f) => f.endsWith(pattern) && f !== 'index.ts').map((f) => f.replace(pattern, ''));
45
+ } catch {
46
+ return [];
47
+ }
41
48
  }
42
49
 
43
50
  export async function getApplicationProviderServices(context) {
44
- const contextPath = getContextPath(context);
45
- const providerPath = path.join(contextPath, 'application/providers/ApplicationProvider.ts');
51
+ const contextPath = getContextPath(context);
52
+ const providerPath = path.join(contextPath, 'application/providers/ApplicationProvider.ts');
46
53
 
47
- try {
48
- const content = await fs.readFile(providerPath, 'utf-8');
49
- // Extract service names from ApplicationProvider
50
- const serviceRegex = /(\w+Service):\s*\(/g;
51
- const services = [];
52
- let match;
54
+ try {
55
+ const content = await fs.readFile(providerPath, 'utf-8');
56
+ // Extract service names from ApplicationProvider
57
+ const serviceRegex = /(\w+Service):\s*\(/g;
58
+ const services = [];
59
+ let match;
53
60
 
54
- while ((match = serviceRegex.exec(content)) !== null) {
55
- services.push(match[1]);
56
- }
61
+ while ((match = serviceRegex.exec(content)) !== null) {
62
+ services.push(match[1]);
63
+ }
57
64
 
58
- return services;
59
- } catch {
60
- return [];
61
- }
65
+ return services;
66
+ } catch {
67
+ return [];
68
+ }
62
69
  }
63
70
 
64
71
  export function toPascalCase(str) {
65
- return str
66
- .split(/[-_\s]+/)
67
- .map(word => word.charAt(0).toUpperCase() + word.slice(1))
68
- .join('');
72
+ return str
73
+ .split(/[-_\s]+/)
74
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
75
+ .join('');
69
76
  }
70
77
 
71
78
  export function toCamelCase(str) {
72
- const pascal = toPascalCase(str);
73
- return pascal.charAt(0).toLowerCase() + pascal.slice(1);
79
+ const pascal = toPascalCase(str);
80
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
74
81
  }
75
82
 
76
83
  export function toKebabCase(str) {
77
- return str
78
- .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
79
- .replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
80
- .toLowerCase();
81
- }
84
+ return str
85
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
86
+ .replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
87
+ .toLowerCase();
88
+ }