@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,164 @@
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
+ import { loadUserConfig, resolveConfigFile } from '../utils/loadConfig.js';
7
+ import { normalizeContexts } from '../init/initAliases.js';
8
+
9
+ const createProviderTemplateWithoutDatasource = (providerName, contextPrefix) => `import { createBoundaryProvider } from '@azure-net/kit';
10
+
11
+ export const ${providerName} = createBoundaryProvider('${contextPrefix}${providerName}', {
12
+ \tregister: () => ({})
13
+ });
14
+ `;
15
+
16
+ const createProviderTemplateWithDatasource = (providerName, contextPrefix, datasourceName, datasourceImportPath) => {
17
+ const factoryNameBase = datasourceName.endsWith('Datasource') ? datasourceName.slice(0, -'Datasource'.length) : datasourceName;
18
+ const datasourceFactoryName = `${factoryNameBase || datasourceName}Rest`;
19
+
20
+ return `import { ${datasourceName} } from '${datasourceImportPath}';
21
+ import { createHttpServiceInstance } from '@azure-net/kit/infra';
22
+ import { createBoundaryProvider } from '@azure-net/kit';
23
+
24
+ export const ${providerName} = createBoundaryProvider('${contextPrefix}${providerName}', {
25
+ \tregister: () => ({
26
+ \t\t${datasourceFactoryName}: () =>
27
+ \t\t\tnew ${datasourceName}({
28
+ \t\t\t\thttp: createHttpServiceInstance({
29
+ \t\t\t\t\tprefixUrl: 'https://localhost'
30
+ \t\t\t\t})
31
+ \t\t\t})
32
+ \t})
33
+ });
34
+ `;
35
+ };
36
+
37
+ const getCoreAliasOrThrow = async () => {
38
+ const configRef = resolveConfigFile();
39
+ if (!configRef.exists) {
40
+ throw new Error(
41
+ 'azure-net.config is not configured. Run "azure-net init folders-structure" and do not forget to fill contexts if you need them.'
42
+ );
43
+ }
44
+
45
+ const content = await fs.readFile(configRef.filepath, 'utf-8');
46
+ const aliasMatch = content.match(/coreAlias\s*:\s*['"`]([^'"`]+)['"`]/);
47
+ const alias = aliasMatch?.[1]?.trim();
48
+ if (!alias) {
49
+ throw new Error(
50
+ 'azure-net.config is not configured. Run "azure-net init folders-structure" and do not forget to fill contexts if you need them.'
51
+ );
52
+ }
53
+
54
+ return alias.startsWith('$') ? alias : `$${alias}`;
55
+ };
56
+
57
+ const getDatasourcesFromPath = async (targetPath) => {
58
+ try {
59
+ const files = await fs.readdir(targetPath);
60
+ return files.filter((file) => file.endsWith('.ts') && file !== 'index.ts').map((file) => file.replace(/\.ts$/, ''));
61
+ } catch {
62
+ return [];
63
+ }
64
+ };
65
+
66
+ const resolveContextPrefix = (target) => (target === 'core' ? 'Core' : toPascalCase(target));
67
+
68
+ const resolveProviderPath = (target) => {
69
+ if (target === 'core') {
70
+ return path.join(process.cwd(), 'src', 'core', 'provider');
71
+ }
72
+
73
+ return path.join(process.cwd(), 'src', 'app', target, 'layers', 'infrastructure', 'providers');
74
+ };
75
+
76
+ const resolveContextAlias = (contexts, targetContext) => {
77
+ const found = contexts.find((context) => context.name === targetContext);
78
+ if (!found) {
79
+ return `$${targetContext}`;
80
+ }
81
+
82
+ return found.alias.startsWith('$') ? found.alias : `$${found.alias}`;
83
+ };
84
+
85
+ const ensureProviderName = (rawName) => {
86
+ const normalized = toPascalCase(rawName || 'DatasourceProvider') || 'DatasourceProvider';
87
+ return normalized.endsWith('Provider') ? normalized : `${normalized}Provider`;
88
+ };
89
+
90
+ export default async function createDatasourceProvider() {
91
+ const { providerNameRaw } = await prompts({
92
+ type: 'text',
93
+ name: 'providerNameRaw',
94
+ message: 'Datasource provider name:',
95
+ initial: 'DatasourceProvider'
96
+ });
97
+
98
+ const providerName = ensureProviderName(String(providerNameRaw ?? 'DatasourceProvider'));
99
+ const config = await loadUserConfig();
100
+ const contexts = normalizeContexts(config.contexts);
101
+ const contextChoices = [{ title: 'core', value: 'core' }, ...contexts.map((context) => ({ title: context.name, value: context.name }))];
102
+
103
+ const { target } = await prompts({
104
+ type: 'select',
105
+ name: 'target',
106
+ message: 'Where to create datasource provider?',
107
+ choices: contextChoices,
108
+ initial: 0
109
+ });
110
+
111
+ const selectedTarget = String(target ?? 'core');
112
+ const coreDatasources = await getDatasourcesFromPath(path.join(process.cwd(), 'src', 'core', 'datasource'));
113
+ const contextDatasources =
114
+ selectedTarget === 'core'
115
+ ? []
116
+ : await getDatasourcesFromPath(
117
+ path.join(process.cwd(), 'src', 'app', selectedTarget, 'layers', 'infrastructure', 'http', 'datasources')
118
+ );
119
+
120
+ const datasourceChoices = [{ title: 'No datasource', value: null }];
121
+ for (const datasource of coreDatasources) {
122
+ datasourceChoices.push({ title: `${datasource} (core)`, value: { name: datasource, source: 'core' } });
123
+ }
124
+ for (const datasource of contextDatasources) {
125
+ datasourceChoices.push({ title: `${datasource} (${selectedTarget})`, value: { name: datasource, source: 'context' } });
126
+ }
127
+
128
+ const { datasourceSelection } = await prompts({
129
+ type: 'select',
130
+ name: 'datasourceSelection',
131
+ message: 'Select datasource:',
132
+ choices: datasourceChoices,
133
+ initial: 0
134
+ });
135
+
136
+ const selectedDatasource = datasourceSelection ?? null;
137
+ const contextPrefix = resolveContextPrefix(selectedTarget);
138
+ let content;
139
+
140
+ if (!selectedDatasource) {
141
+ content = createProviderTemplateWithoutDatasource(providerName, contextPrefix);
142
+ } else {
143
+ const importPath =
144
+ selectedDatasource.source === 'core'
145
+ ? `${await getCoreAliasOrThrow()}/datasource`
146
+ : `${resolveContextAlias(contexts, selectedTarget)}/layers/infrastructure/http/datasources`;
147
+
148
+ content = createProviderTemplateWithDatasource(providerName, contextPrefix, selectedDatasource.name, importPath);
149
+ }
150
+
151
+ const providerPath = resolveProviderPath(selectedTarget);
152
+ await fs.mkdir(providerPath, { recursive: true });
153
+
154
+ const filePath = path.join(providerPath, `${providerName}.ts`);
155
+ const created = await writeIfNotExists(filePath, content);
156
+ await updateIndexTs(providerPath);
157
+
158
+ if (!created) {
159
+ console.log(`⚠️ Datasource provider "${providerName}" already exists. File was not overwritten.`);
160
+ return;
161
+ }
162
+
163
+ console.log(`✅ Datasource provider created: ${filePath}`);
164
+ }
@@ -0,0 +1,175 @@
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
+ import { loadUserConfig, resolveConfigFile } from '../utils/loadConfig.js';
7
+ import { normalizeContexts } from '../init/initAliases.js';
8
+
9
+ type NormalizedContext = { name: string; alias: string };
10
+ type DatasourceChoiceValue = null | { name: string; source: 'core' | 'context' };
11
+
12
+ const createProviderTemplateWithoutDatasource = (
13
+ providerName: string,
14
+ contextPrefix: string
15
+ ): string => `import { createBoundaryProvider } from '@azure-net/kit';
16
+
17
+ export const ${providerName} = createBoundaryProvider('${contextPrefix}${providerName}', {
18
+ \tregister: () => ({})
19
+ });
20
+ `;
21
+
22
+ const createProviderTemplateWithDatasource = (
23
+ providerName: string,
24
+ contextPrefix: string,
25
+ datasourceName: string,
26
+ datasourceImportPath: string
27
+ ): string => {
28
+ const factoryNameBase = datasourceName.endsWith('Datasource') ? datasourceName.slice(0, -'Datasource'.length) : datasourceName;
29
+ const datasourceFactoryName = `${factoryNameBase || datasourceName}Rest`;
30
+
31
+ return `import { ${datasourceName} } from '${datasourceImportPath}';
32
+ import { createHttpServiceInstance } from '@azure-net/kit/infra';
33
+ import { createBoundaryProvider } from '@azure-net/kit';
34
+
35
+ export const ${providerName} = createBoundaryProvider('${contextPrefix}${providerName}', {
36
+ \tregister: () => ({
37
+ \t\t${datasourceFactoryName}: () =>
38
+ \t\t\tnew ${datasourceName}({
39
+ \t\t\t\thttp: createHttpServiceInstance({
40
+ \t\t\t\t\tprefixUrl: 'https://localhost'
41
+ \t\t\t\t})
42
+ \t\t\t})
43
+ \t})
44
+ });
45
+ `;
46
+ };
47
+
48
+ const getCoreAliasOrThrow = async (): Promise<string> => {
49
+ const configRef = resolveConfigFile();
50
+ if (!configRef.exists) {
51
+ throw new Error(
52
+ 'azure-net.config is not configured. Run "azure-net init folders-structure" and do not forget to fill contexts if you need them.'
53
+ );
54
+ }
55
+
56
+ const content = await fs.readFile(configRef.filepath, 'utf-8');
57
+ const aliasMatch = content.match(/coreAlias\s*:\s*['"`]([^'"`]+)['"`]/);
58
+ const alias = aliasMatch?.[1]?.trim();
59
+ if (!alias) {
60
+ throw new Error(
61
+ 'azure-net.config is not configured. Run "azure-net init folders-structure" and do not forget to fill contexts if you need them.'
62
+ );
63
+ }
64
+
65
+ return alias.startsWith('$') ? alias : `$${alias}`;
66
+ };
67
+
68
+ const getDatasourcesFromPath = async (targetPath: string): Promise<string[]> => {
69
+ try {
70
+ const files = await fs.readdir(targetPath);
71
+ return files.filter((file) => file.endsWith('.ts') && file !== 'index.ts').map((file) => file.replace(/\.ts$/, ''));
72
+ } catch {
73
+ return [];
74
+ }
75
+ };
76
+
77
+ const resolveContextPrefix = (target: string): string => (target === 'core' ? 'Core' : toPascalCase(target));
78
+
79
+ const resolveProviderPath = (target: string): string => {
80
+ if (target === 'core') {
81
+ return path.join(process.cwd(), 'src', 'core', 'provider');
82
+ }
83
+
84
+ return path.join(process.cwd(), 'src', 'app', target, 'layers', 'infrastructure', 'providers');
85
+ };
86
+
87
+ const resolveContextAlias = (contexts: NormalizedContext[], targetContext: string): string => {
88
+ const found = contexts.find((context) => context.name === targetContext);
89
+ if (!found) {
90
+ return `$${targetContext}`;
91
+ }
92
+
93
+ return found.alias.startsWith('$') ? found.alias : `$${found.alias}`;
94
+ };
95
+
96
+ const ensureProviderName = (rawName: string): string => {
97
+ const normalized = toPascalCase(rawName || 'DatasourceProvider') || 'DatasourceProvider';
98
+ return normalized.endsWith('Provider') ? normalized : `${normalized}Provider`;
99
+ };
100
+
101
+ export default async function createDatasourceProvider(): Promise<void> {
102
+ const { providerNameRaw } = await prompts({
103
+ type: 'text',
104
+ name: 'providerNameRaw',
105
+ message: 'Datasource provider name:',
106
+ initial: 'DatasourceProvider'
107
+ });
108
+
109
+ const providerName = ensureProviderName(String(providerNameRaw ?? 'DatasourceProvider'));
110
+ const config = await loadUserConfig();
111
+ const contexts = normalizeContexts(config.contexts) as NormalizedContext[];
112
+ const contextChoices = [{ title: 'core', value: 'core' }, ...contexts.map((context) => ({ title: context.name, value: context.name }))];
113
+
114
+ const { target } = await prompts({
115
+ type: 'select',
116
+ name: 'target',
117
+ message: 'Where to create datasource provider?',
118
+ choices: contextChoices,
119
+ initial: 0
120
+ });
121
+
122
+ const selectedTarget = String(target ?? 'core');
123
+ const coreDatasources = await getDatasourcesFromPath(path.join(process.cwd(), 'src', 'core', 'datasource'));
124
+ const contextDatasources =
125
+ selectedTarget === 'core'
126
+ ? []
127
+ : await getDatasourcesFromPath(
128
+ path.join(process.cwd(), 'src', 'app', selectedTarget, 'layers', 'infrastructure', 'http', 'datasources')
129
+ );
130
+
131
+ const datasourceChoices: Array<{ title: string; value: DatasourceChoiceValue }> = [{ title: 'No datasource', value: null }];
132
+ for (const datasource of coreDatasources) {
133
+ datasourceChoices.push({ title: `${datasource} (core)`, value: { name: datasource, source: 'core' } });
134
+ }
135
+ for (const datasource of contextDatasources) {
136
+ datasourceChoices.push({ title: `${datasource} (${selectedTarget})`, value: { name: datasource, source: 'context' } });
137
+ }
138
+
139
+ const { datasourceSelection } = await prompts({
140
+ type: 'select',
141
+ name: 'datasourceSelection',
142
+ message: 'Select datasource:',
143
+ choices: datasourceChoices,
144
+ initial: 0
145
+ });
146
+
147
+ const selectedDatasource = (datasourceSelection ?? null) as DatasourceChoiceValue;
148
+ const contextPrefix = resolveContextPrefix(selectedTarget);
149
+ let content: string;
150
+
151
+ if (!selectedDatasource) {
152
+ content = createProviderTemplateWithoutDatasource(providerName, contextPrefix);
153
+ } else {
154
+ const importPath =
155
+ selectedDatasource.source === 'core'
156
+ ? `${await getCoreAliasOrThrow()}/datasource`
157
+ : `${resolveContextAlias(contexts, selectedTarget)}/layers/infrastructure/http/datasources`;
158
+
159
+ content = createProviderTemplateWithDatasource(providerName, contextPrefix, selectedDatasource.name, importPath);
160
+ }
161
+
162
+ const providerPath = resolveProviderPath(selectedTarget);
163
+ await fs.mkdir(providerPath, { recursive: true });
164
+
165
+ const filePath = path.join(providerPath, `${providerName}.ts`);
166
+ const created = await writeIfNotExists(filePath, content);
167
+ await updateIndexTs(providerPath);
168
+
169
+ if (!created) {
170
+ console.log(`⚠️ Datasource provider "${providerName}" already exists. File was not overwritten.`);
171
+ return;
172
+ }
173
+
174
+ console.log(`✅ Datasource provider created: ${filePath}`);
175
+ }
@@ -0,0 +1,105 @@
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 createMiddlewareTemplate = (middlewareName) => `import { type IMiddleware } from '@azure-net/kit';
8
+
9
+ export const ${middlewareName}: IMiddleware = async ({ next }) => {
10
+ \treturn next();
11
+ };
12
+ `;
13
+
14
+ const addMiddlewareToManager = (content, middlewareName) => {
15
+ const wordRegex = new RegExp(`\\b${middlewareName}\\b`);
16
+ if (wordRegex.test(content)) return content;
17
+
18
+ const importLine = `import { ${middlewareName} } from './middlewares';`;
19
+ if (!content.includes(importLine)) {
20
+ const importMatches = Array.from(content.matchAll(/^import[^\n]*$/gm));
21
+ if (importMatches.length) {
22
+ const lastImport = importMatches[importMatches.length - 1];
23
+ const insertPos = (lastImport.index ?? 0) + lastImport[0].length;
24
+ content = `${content.slice(0, insertPos)}\n${importLine}${content.slice(insertPos)}`;
25
+ } else {
26
+ content = `${importLine}\n${content}`;
27
+ }
28
+ }
29
+
30
+ const managerCallRegex = /createMiddlewareManager\s*\(\s*\[([\s\S]*?)\]\s*\)/m;
31
+ const match = content.match(managerCallRegex);
32
+ if (!match) return content;
33
+
34
+ const arrayBody = match[1] ?? '';
35
+ if (wordRegex.test(arrayBody)) return content;
36
+
37
+ const isMultiline = arrayBody.includes('\n');
38
+ const hasItems = arrayBody.trim().length > 0;
39
+
40
+ if (!isMultiline) {
41
+ const trimmed = arrayBody.trim();
42
+ const nextArrayBody = hasItems ? `${trimmed}, ${middlewareName}` : middlewareName;
43
+ return content.replace(managerCallRegex, `createMiddlewareManager([${nextArrayBody}])`);
44
+ }
45
+
46
+ if (!hasItems) {
47
+ const baseIndent = arrayBody.match(/\n([ \t]*)$/)?.[1] ?? '\t';
48
+ const nextArrayBody = `\n${baseIndent}${middlewareName}\n`;
49
+ return content.replace(managerCallRegex, `createMiddlewareManager([${nextArrayBody}])`);
50
+ }
51
+
52
+ const trimmedBody = arrayBody.trimEnd();
53
+ const lastItemIndent = trimmedBody.match(/(?:^|\n)([ \t]*)\S[^\n]*$/)?.[1] ?? '\t';
54
+ const nextArrayBody = `${trimmedBody},\n${lastItemIndent}${middlewareName}\n`;
55
+
56
+ return content.replace(managerCallRegex, `createMiddlewareManager([${nextArrayBody}])`);
57
+ };
58
+
59
+ export default async function createMiddleware() {
60
+ const { middlewareNameRaw } = await prompts({
61
+ type: 'text',
62
+ name: 'middlewareNameRaw',
63
+ message: 'Middleware name:',
64
+ initial: 'RoutesGuard'
65
+ });
66
+
67
+ const middlewareName = toPascalCase(String(middlewareNameRaw ?? 'RoutesGuard')) || 'RoutesGuard';
68
+ const middlewareManagerPath = path.join(process.cwd(), 'src', 'core', 'middleware-manager');
69
+ const middlewaresPath = path.join(middlewareManagerPath, 'middlewares');
70
+ const middlewareFilePath = path.join(middlewaresPath, `${middlewareName}.ts`);
71
+ const middlewaresIndexPath = path.join(middlewaresPath, 'index.ts');
72
+ const middlewareManagerFile = path.join(middlewareManagerPath, 'MiddlewareManager.ts');
73
+
74
+ await fs.mkdir(middlewaresPath, { recursive: true });
75
+
76
+ const created = await writeIfNotExists(middlewareFilePath, createMiddlewareTemplate(middlewareName));
77
+ await updateIndexTs(middlewaresPath);
78
+
79
+ if (!created) {
80
+ console.log(`⚠️ Middleware "${middlewareName}" already exists. File was not overwritten.`);
81
+ return;
82
+ }
83
+
84
+ let managerContent;
85
+ try {
86
+ managerContent = await fs.readFile(middlewareManagerFile, 'utf-8');
87
+ } catch {
88
+ console.log(`⚠️ Middleware manager not found at ${middlewareManagerFile}. Run "azure-net create middleware-manager" first.`);
89
+ return;
90
+ }
91
+
92
+ const nextManagerContent = addMiddlewareToManager(managerContent, middlewareName);
93
+ await fs.writeFile(middlewareManagerFile, nextManagerContent, 'utf-8');
94
+
95
+ const hasIndex = await fs
96
+ .access(middlewaresIndexPath)
97
+ .then(() => true)
98
+ .catch(() => false);
99
+
100
+ console.log(`✅ Middleware created: ${middlewareFilePath}`);
101
+ if (hasIndex) {
102
+ console.log(`✅ Middlewares index updated: ${middlewaresIndexPath}`);
103
+ }
104
+ console.log(`✅ Middleware manager updated: ${middlewareManagerFile}`);
105
+ }
@@ -0,0 +1,105 @@
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 createMiddlewareTemplate = (middlewareName: string): string => `import { type IMiddleware } from '@azure-net/kit';
8
+
9
+ export const ${middlewareName}: IMiddleware = async ({ next }) => {
10
+ \treturn next();
11
+ };
12
+ `;
13
+
14
+ const addMiddlewareToManager = (content: string, middlewareName: string): string => {
15
+ const wordRegex = new RegExp(`\\b${middlewareName}\\b`);
16
+ if (wordRegex.test(content)) return content;
17
+
18
+ const importLine = `import { ${middlewareName} } from './middlewares';`;
19
+ if (!content.includes(importLine)) {
20
+ const importMatches = Array.from(content.matchAll(/^import[^\n]*$/gm));
21
+ const lastImport = importMatches.at(-1);
22
+ if (lastImport) {
23
+ const insertPos = (lastImport.index ?? 0) + lastImport[0].length;
24
+ content = `${content.slice(0, insertPos)}\n${importLine}${content.slice(insertPos)}`;
25
+ } else {
26
+ content = `${importLine}\n${content}`;
27
+ }
28
+ }
29
+
30
+ const managerCallRegex = /createMiddlewareManager\s*\(\s*\[([\s\S]*?)\]\s*\)/m;
31
+ const match = content.match(managerCallRegex);
32
+ if (!match) return content;
33
+
34
+ const arrayBody = match[1] ?? '';
35
+ if (wordRegex.test(arrayBody)) return content;
36
+
37
+ const isMultiline = arrayBody.includes('\n');
38
+ const hasItems = arrayBody.trim().length > 0;
39
+
40
+ if (!isMultiline) {
41
+ const trimmed = arrayBody.trim();
42
+ const nextArrayBody = hasItems ? `${trimmed}, ${middlewareName}` : middlewareName;
43
+ return content.replace(managerCallRegex, `createMiddlewareManager([${nextArrayBody}])`);
44
+ }
45
+
46
+ if (!hasItems) {
47
+ const baseIndent = arrayBody.match(/\n([ \t]*)$/)?.[1] ?? '\t';
48
+ const nextArrayBody = `\n${baseIndent}${middlewareName}\n`;
49
+ return content.replace(managerCallRegex, `createMiddlewareManager([${nextArrayBody}])`);
50
+ }
51
+
52
+ const trimmedBody = arrayBody.trimEnd();
53
+ const lastItemIndent = trimmedBody.match(/(?:^|\n)([ \t]*)\S[^\n]*$/)?.[1] ?? '\t';
54
+ const nextArrayBody = `${trimmedBody},\n${lastItemIndent}${middlewareName}\n`;
55
+
56
+ return content.replace(managerCallRegex, `createMiddlewareManager([${nextArrayBody}])`);
57
+ };
58
+
59
+ export default async function createMiddleware(): Promise<void> {
60
+ const { middlewareNameRaw } = await prompts({
61
+ type: 'text',
62
+ name: 'middlewareNameRaw',
63
+ message: 'Middleware name:',
64
+ initial: 'RoutesGuard'
65
+ });
66
+
67
+ const middlewareName = toPascalCase(String(middlewareNameRaw ?? 'RoutesGuard')) || 'RoutesGuard';
68
+ const middlewareManagerPath = path.join(process.cwd(), 'src', 'core', 'middleware-manager');
69
+ const middlewaresPath = path.join(middlewareManagerPath, 'middlewares');
70
+ const middlewareFilePath = path.join(middlewaresPath, `${middlewareName}.ts`);
71
+ const middlewaresIndexPath = path.join(middlewaresPath, 'index.ts');
72
+ const middlewareManagerFile = path.join(middlewareManagerPath, 'MiddlewareManager.ts');
73
+
74
+ await fs.mkdir(middlewaresPath, { recursive: true });
75
+
76
+ const created = await writeIfNotExists(middlewareFilePath, createMiddlewareTemplate(middlewareName));
77
+ await updateIndexTs(middlewaresPath);
78
+
79
+ if (!created) {
80
+ console.log(`⚠️ Middleware "${middlewareName}" already exists. File was not overwritten.`);
81
+ return;
82
+ }
83
+
84
+ let managerContent: string;
85
+ try {
86
+ managerContent = await fs.readFile(middlewareManagerFile, 'utf-8');
87
+ } catch {
88
+ console.log(`⚠️ Middleware manager not found at ${middlewareManagerFile}. Run "azure-net create middleware-manager" first.`);
89
+ return;
90
+ }
91
+
92
+ const nextManagerContent = addMiddlewareToManager(managerContent, middlewareName);
93
+ await fs.writeFile(middlewareManagerFile, nextManagerContent, 'utf-8');
94
+
95
+ const hasIndex = await fs
96
+ .access(middlewaresIndexPath)
97
+ .then(() => true)
98
+ .catch(() => false);
99
+
100
+ console.log(`✅ Middleware created: ${middlewareFilePath}`);
101
+ if (hasIndex) {
102
+ console.log(`✅ Middlewares index updated: ${middlewaresIndexPath}`);
103
+ }
104
+ console.log(`✅ Middleware manager updated: ${middlewareManagerFile}`);
105
+ }
@@ -0,0 +1,112 @@
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
+ const createPresenterFactoryTemplate = (
9
+ factoryName
10
+ ) => `import { createErrorHandler, createAsyncHelpers, createPresenterFactory } from '@azure-net/kit/delivery';
11
+
12
+ export const AsyncHelpers = createAsyncHelpers({ handler: createErrorHandler() });
13
+
14
+ export const ${factoryName} = createPresenterFactory({ ...AsyncHelpers });
15
+ `;
16
+
17
+ const resolveConfigFile = (cwd) => {
18
+ const tsPath = path.join(cwd, 'azure-net.config.ts');
19
+ const jsPath = path.join(cwd, 'azure-net.config.js');
20
+
21
+ if (fsSync.existsSync(tsPath)) {
22
+ return { filepath: tsPath, type: 'ts', exists: true, content: fsSync.readFileSync(tsPath, 'utf-8') };
23
+ }
24
+ if (fsSync.existsSync(jsPath)) {
25
+ return { filepath: jsPath, type: 'js', exists: true, content: fsSync.readFileSync(jsPath, 'utf-8') };
26
+ }
27
+
28
+ return { filepath: tsPath, type: 'ts', exists: false, content: '' };
29
+ };
30
+
31
+ const buildDefaultPresenterFactoryConfigEntry = (factoryName) => {
32
+ return `defaultPresenterFactory: '${factoryName}'`;
33
+ };
34
+
35
+ const upsertDefaultPresenterFactoryConfig = (content, factoryName) => {
36
+ const hasExportDefaultObject = /export\s+default\s*\{[\s\S]*\}\s*;?/.test(content);
37
+ const defaultFactoryEntry = buildDefaultPresenterFactoryConfigEntry(factoryName);
38
+ const coreAliasEntry = `coreAlias: '$core'`;
39
+
40
+ if (!hasExportDefaultObject) {
41
+ return `export default {\n\t${coreAliasEntry},\n\t${defaultFactoryEntry}\n};\n`;
42
+ }
43
+
44
+ let nextContent = content;
45
+
46
+ if (/defaultPresenterFactory\s*:/.test(nextContent)) {
47
+ nextContent = nextContent.replace(/defaultPresenterFactory\s*:\s*(\{[\s\S]*?\n\s*\}|['"`][^'"`]+['"`])/m, defaultFactoryEntry);
48
+ } else if (/defaultPresenter\s*:/.test(nextContent)) {
49
+ nextContent = nextContent.replace(/defaultPresenter\s*:\s*(\{[\s\S]*?\n\s*\}|['"`][^'"`]+['"`])/m, defaultFactoryEntry);
50
+ } else {
51
+ nextContent = nextContent.replace(/export\s+default\s*\{([\s\S]*)\}\s*;?/, (_full, body) => {
52
+ const trimmedRight = body.replace(/\s*$/, '');
53
+ if (!trimmedRight.length) {
54
+ return `export default {\n\t${defaultFactoryEntry}\n};`;
55
+ }
56
+
57
+ const withComma = trimmedRight.endsWith(',') ? trimmedRight : `${trimmedRight},`;
58
+ return `export default {${withComma}\n\t${defaultFactoryEntry}\n};`;
59
+ });
60
+ }
61
+
62
+ if (/coreAlias\s*:/.test(nextContent)) {
63
+ return nextContent.replace(/coreAlias\s*:\s*['"`][^'"`]+['"`]/, coreAliasEntry);
64
+ }
65
+
66
+ return nextContent.replace(/export\s+default\s*\{([\s\S]*)\}\s*;?/, (_full, body) => {
67
+ const trimmedRight = body.replace(/\s*$/, '');
68
+ if (!trimmedRight.length) {
69
+ return `export default {\n\t${coreAliasEntry}\n};`;
70
+ }
71
+
72
+ const withComma = trimmedRight.endsWith(',') ? trimmedRight : `${trimmedRight},`;
73
+ return `export default {${withComma}\n\t${coreAliasEntry}\n};`;
74
+ });
75
+ };
76
+
77
+ export default async function createPresenterFactory() {
78
+ const { presenterFactoryNameRaw } = await prompts({
79
+ type: 'text',
80
+ name: 'presenterFactoryNameRaw',
81
+ message: 'Presenter factory name:',
82
+ initial: 'AppPresenter'
83
+ });
84
+
85
+ const presenterFactoryName = toPascalCase(String(presenterFactoryNameRaw ?? 'AppPresenter')) || 'AppPresenter';
86
+ const presenterRootPath = path.join(process.cwd(), 'src', 'core', 'presenter');
87
+ const filePath = path.join(presenterRootPath, `${presenterFactoryName}.ts`);
88
+
89
+ await fs.mkdir(presenterRootPath, { recursive: true });
90
+
91
+ const created = await writeIfNotExists(filePath, createPresenterFactoryTemplate(presenterFactoryName));
92
+ if (!created) {
93
+ console.log(`⚠️ Presenter factory "${presenterFactoryName}" already exists. File was not overwritten.`);
94
+ } else {
95
+ await updateIndexTs(presenterRootPath);
96
+ console.log(`✅ Presenter factory created: ${filePath}`);
97
+ }
98
+
99
+ const { useAsDefault } = await prompts({
100
+ type: 'confirm',
101
+ name: 'useAsDefault',
102
+ message: 'Use this presenter factory as default for the project?',
103
+ initial: true
104
+ });
105
+
106
+ if (!useAsDefault) return;
107
+
108
+ const config = resolveConfigFile(process.cwd());
109
+ const nextContent = upsertDefaultPresenterFactoryConfig(config.content, presenterFactoryName);
110
+ await fs.writeFile(config.filepath, nextContent, 'utf-8');
111
+ console.log(`✅ Default presenter factory saved to ${path.basename(config.filepath)}`);
112
+ }