@azure-net/cli 5.1.1 → 6.0.0-next.3

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 (180) hide show
  1. package/README.md +201 -0
  2. package/bin/azure-net.js +236 -221
  3. package/dist/bin/azure-net.d.ts +2 -0
  4. package/dist/src/checks/architecture.d.ts +20 -0
  5. package/dist/src/checks/checkDomain.d.ts +1 -0
  6. package/dist/src/checks/checkFoldersStructure.d.ts +1 -0
  7. package/dist/src/checks/checkHooks.d.ts +1 -0
  8. package/dist/src/checks/checkImports.d.ts +1 -0
  9. package/dist/src/checks/checkInternal.d.ts +1 -0
  10. package/dist/src/checks/checkLayerBoundaries.d.ts +1 -0
  11. package/dist/src/checks/checkLayerImports.d.ts +1 -0
  12. package/dist/src/checks/checkPresenterNames.d.ts +1 -0
  13. package/dist/src/checks/checkProject.d.ts +1 -0
  14. package/dist/src/checks/checkProviderGraph.d.ts +1 -0
  15. package/dist/src/checks/checkProviderNames.d.ts +1 -0
  16. package/dist/src/generate/layers/generateRepository.d.ts +1 -0
  17. package/dist/src/generate/layers/generateRepositoryMethod.d.ts +1 -0
  18. package/dist/src/generate/layers/generateUseCases.d.ts +1 -0
  19. package/dist/src/generate/module/generateDomain.d.ts +8 -0
  20. package/dist/src/generate/module/generateModule.d.ts +1 -0
  21. package/dist/src/generate/module/generateModulePreset.d.ts +1 -0
  22. package/dist/src/generate/module/generatePresenter.d.ts +29 -0
  23. package/dist/src/generate/module/generateRepository.d.ts +15 -0
  24. package/dist/src/generate/module/generateRepositoryMethod.d.ts +2 -0
  25. package/dist/src/generate/module/generateSchemaFromType.d.ts +9 -0
  26. package/dist/src/generate/module/generateStore.d.ts +1 -0
  27. package/dist/src/generate/module/generateTypesJson.d.ts +2 -0
  28. package/dist/src/generate/module/generateUseCases.d.ts +1 -0
  29. package/dist/src/generate/module/repositoryModuleShared.d.ts +89 -0
  30. package/dist/src/generate/routes/generateRoutes.d.ts +14 -0
  31. package/dist/src/generate/ui/generateComponent.d.ts +1 -0
  32. package/dist/src/generate/ui/generateDesignComponent.d.ts +1 -0
  33. package/dist/src/generate/ui/generateWidget.d.ts +1 -0
  34. package/dist/src/git/commit.d.ts +23 -0
  35. package/dist/src/git/push.d.ts +11 -0
  36. package/dist/src/git/runGit.d.ts +2 -0
  37. package/dist/src/init/initAliases.d.ts +12 -0
  38. package/dist/src/init/initEdges.d.ts +2 -0
  39. package/dist/src/init/initStructure.d.ts +1 -0
  40. package/dist/src/install/configureCommitTools.d.ts +1 -0
  41. package/dist/src/install/installFresh.d.ts +1 -0
  42. package/dist/src/migrate/migrate.d.ts +22 -0
  43. package/dist/src/migrate/presenterFactories.d.ts +9 -0
  44. package/dist/src/migrate/presenters.d.ts +13 -0
  45. package/dist/src/migrate/translations.d.ts +8 -0
  46. package/dist/src/plugins/addLint.d.ts +5 -0
  47. package/dist/src/scaffolds/createCoreSchema.d.ts +1 -0
  48. package/dist/src/scaffolds/createDatasource.d.ts +1 -0
  49. package/dist/src/scaffolds/createProgram.d.ts +1 -0
  50. package/dist/src/scaffolds/createResponse.d.ts +1 -0
  51. package/dist/src/scaffolds/createSchemaRule.d.ts +1 -0
  52. package/dist/src/scaffolds/createTranslationManager.d.ts +1 -0
  53. package/{src/scaffolds/index.ts → dist/src/scaffolds/index.d.ts} +0 -2
  54. package/dist/src/scaffolds/registerDatasource.d.ts +2 -0
  55. package/dist/src/scaffolds/updateDefaultFactoryConfig.d.ts +1 -0
  56. package/dist/src/utils/architecture.d.ts +31 -0
  57. package/dist/src/utils/contextUtils.d.ts +6 -0
  58. package/dist/src/utils/fileUtils.d.ts +5 -0
  59. package/dist/src/utils/flattenConfig.d.ts +2 -0
  60. package/dist/src/utils/legacyAliases.d.ts +1 -0
  61. package/dist/src/utils/loadConfig.d.ts +16 -0
  62. package/dist/src/utils/packageImports.d.ts +4 -0
  63. package/dist/src/utils/sharedFoundation.d.ts +20 -0
  64. package/dist/src/utils/viteConfig.d.ts +9 -0
  65. package/package.json +17 -7
  66. package/src/checks/architecture.js +154 -0
  67. package/src/checks/checkDomain.js +124 -154
  68. package/src/checks/checkFoldersStructure.js +64 -52
  69. package/src/checks/checkHooks.js +12 -12
  70. package/src/checks/checkImports.js +26 -142
  71. package/src/checks/checkInternal.js +27 -24
  72. package/src/checks/checkLayerBoundaries.js +22 -197
  73. package/src/checks/checkLayerImports.js +14 -134
  74. package/src/checks/checkPresenterNames.js +156 -143
  75. package/src/checks/checkProject.js +27 -26
  76. package/src/checks/checkProviderGraph.js +182 -166
  77. package/src/checks/checkProviderNames.js +123 -112
  78. package/src/generate/layers/generateRepository.js +1 -109
  79. package/src/generate/layers/generateRepositoryMethod.js +1 -90
  80. package/src/generate/layers/generateUseCases.js +1 -44
  81. package/src/generate/module/generateDomain.js +81 -105
  82. package/src/generate/module/generateModule.js +34 -68
  83. package/src/generate/module/generateModulePreset.js +188 -248
  84. package/src/generate/module/generatePresenter.js +441 -369
  85. package/src/generate/module/generateRepository.js +102 -121
  86. package/src/generate/module/generateRepositoryMethod.js +117 -0
  87. package/src/generate/module/generateSchemaFromType.js +283 -280
  88. package/src/generate/module/generateStore.js +41 -0
  89. package/src/generate/module/generateTypesJson.js +196 -195
  90. package/src/generate/module/generateUseCases.js +47 -55
  91. package/src/generate/module/repositoryModuleShared.js +480 -459
  92. package/src/generate/routes/generateRoutes.js +4 -7
  93. package/src/generate/ui/generateComponent.js +44 -64
  94. package/src/generate/ui/generateDesignComponent.js +21 -41
  95. package/src/generate/ui/generateWidget.js +21 -41
  96. package/src/git/commit.js +67 -0
  97. package/src/git/push.js +19 -0
  98. package/src/git/runGit.js +11 -0
  99. package/src/init/initAliases.js +19 -135
  100. package/src/init/initEdges.js +79 -54
  101. package/src/init/initStructure.js +31 -145
  102. package/src/install/configureCommitTools.js +1 -0
  103. package/src/install/installFresh.js +39 -68
  104. package/src/migrate/migrate.js +1027 -0
  105. package/src/migrate/presenterFactories.js +189 -0
  106. package/src/migrate/presenters.js +373 -0
  107. package/src/migrate/translations.js +213 -0
  108. package/src/plugins/addLint.js +52 -219
  109. package/src/scaffolds/createCoreSchema.js +65 -91
  110. package/src/scaffolds/createDatasource.js +67 -101
  111. package/src/scaffolds/createProgram.js +8 -8
  112. package/src/scaffolds/createResponse.js +29 -29
  113. package/src/scaffolds/createSchemaRule.js +19 -19
  114. package/src/scaffolds/createTranslationManager.js +84 -37
  115. package/src/scaffolds/index.js +0 -2
  116. package/src/scaffolds/registerDatasource.js +59 -0
  117. package/src/scaffolds/updateDefaultFactoryConfig.js +31 -0
  118. package/src/utils/architecture.js +145 -0
  119. package/src/utils/contextUtils.js +32 -65
  120. package/src/utils/fileUtils.js +69 -50
  121. package/src/utils/flattenConfig.js +38 -0
  122. package/src/utils/legacyAliases.js +75 -0
  123. package/src/utils/loadConfig.js +76 -40
  124. package/src/utils/packageImports.js +63 -0
  125. package/src/utils/sharedFoundation.js +36 -42
  126. package/src/utils/viteConfig.js +295 -0
  127. package/.prettierrc +0 -7
  128. package/bin/azure-net.ts +0 -308
  129. package/eslint.config.js +0 -34
  130. package/src/checks/checkDomain.ts +0 -223
  131. package/src/checks/checkFoldersStructure.ts +0 -85
  132. package/src/checks/checkHooks.ts +0 -22
  133. package/src/checks/checkImports.ts +0 -193
  134. package/src/checks/checkInternal.ts +0 -48
  135. package/src/checks/checkLayerBoundaries.ts +0 -255
  136. package/src/checks/checkLayerImports.ts +0 -185
  137. package/src/checks/checkPresenterNames.ts +0 -203
  138. package/src/checks/checkProject.ts +0 -45
  139. package/src/checks/checkProviderGraph.ts +0 -234
  140. package/src/checks/checkProviderNames.ts +0 -165
  141. package/src/generate/layers/generateRepository.ts +0 -123
  142. package/src/generate/layers/generateRepositoryMethod.ts +0 -102
  143. package/src/generate/layers/generateUseCases.ts +0 -50
  144. package/src/generate/layers/repositoryShared.js +0 -506
  145. package/src/generate/layers/repositoryShared.ts +0 -662
  146. package/src/generate/module/generateDomain.ts +0 -147
  147. package/src/generate/module/generateModule.ts +0 -84
  148. package/src/generate/module/generateModulePreset.ts +0 -338
  149. package/src/generate/module/generatePresenter.ts +0 -498
  150. package/src/generate/module/generateRepository.ts +0 -158
  151. package/src/generate/module/generateSchemaFromType.ts +0 -368
  152. package/src/generate/module/generateTypesJson.ts +0 -259
  153. package/src/generate/module/generateUseCases.ts +0 -67
  154. package/src/generate/module/repositoryModuleShared.ts +0 -669
  155. package/src/generate/routes/generateRoutes.ts +0 -275
  156. package/src/generate/ui/generateComponent.ts +0 -100
  157. package/src/generate/ui/generateDesignComponent.ts +0 -71
  158. package/src/generate/ui/generateWidget.ts +0 -71
  159. package/src/init/initAliases.ts +0 -172
  160. package/src/init/initEdges.ts +0 -74
  161. package/src/init/initStructure.ts +0 -172
  162. package/src/install/installFresh.ts +0 -93
  163. package/src/plugins/addLint.ts +0 -274
  164. package/src/scaffolds/createCoreSchema.ts +0 -130
  165. package/src/scaffolds/createDatasource.ts +0 -161
  166. package/src/scaffolds/createDatasourceProvider.js +0 -127
  167. package/src/scaffolds/createDatasourceProvider.ts +0 -161
  168. package/src/scaffolds/createPresenterFactory.js +0 -83
  169. package/src/scaffolds/createPresenterFactory.ts +0 -102
  170. package/src/scaffolds/createProgram.ts +0 -19
  171. package/src/scaffolds/createResponse.ts +0 -66
  172. package/src/scaffolds/createSchemaRule.ts +0 -44
  173. package/src/scaffolds/createTranslationManager.ts +0 -80
  174. package/src/utils/contextUtils.ts +0 -88
  175. package/src/utils/fileUtils.ts +0 -74
  176. package/src/utils/loadConfig.ts +0 -68
  177. package/src/utils/sharedFoundation.ts +0 -70
  178. package/tsconfig.json +0 -19
  179. /package/{src/checks/index.ts → dist/src/checks/index.d.ts} +0 -0
  180. /package/{src/plugins/index.ts → dist/src/plugins/index.d.ts} +0 -0
@@ -0,0 +1,145 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import prompts from 'prompts';
4
+ import { writeIfNotExists } from './fileUtils.js';
5
+ export const SHARED_CONTEXT = 'SharedKernel';
6
+ export const SHARED_WORKSPACE = 'Shared';
7
+ export const ARCHITECTURE_ALIASES = {
8
+ '#contexts': './src/app/Contexts',
9
+ '#presentation': './src/app/Presentation',
10
+ '#platform': './src/app/Platform'
11
+ };
12
+ export const CONTEXT_DIRS = [
13
+ 'Domain/Entities',
14
+ 'Domain/Ports',
15
+ 'Domain/Enums',
16
+ 'Application/UseCases',
17
+ 'Infrastructure/Repositories'
18
+ ];
19
+ export const PRESENTATION_DIRS = ['Ui', 'Scopes', 'Shell'];
20
+ export function normalizePascalName(value) {
21
+ const raw = value.trim();
22
+ if (!/^[A-Za-z][A-Za-z0-9 _-]*$/.test(raw))
23
+ throw new Error(`Invalid architecture name: "${value}". Use letters, numbers, spaces, hyphens or underscores.`);
24
+ return raw
25
+ .split(/[-_\s]+/)
26
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
27
+ .join('');
28
+ }
29
+ export const getAppPath = (cwd = process.cwd()) => path.join(cwd, 'src', 'app');
30
+ export const getContextsPath = (cwd = process.cwd()) => path.join(getAppPath(cwd), 'Contexts');
31
+ export const getContextPath = (context, cwd = process.cwd()) => path.join(getContextsPath(cwd), normalizePascalName(context));
32
+ export const getPresentationPath = (workspace, cwd = process.cwd()) => workspace ? path.join(getAppPath(cwd), 'Presentation', normalizePascalName(workspace)) : path.join(getAppPath(cwd), 'Presentation');
33
+ export const getPlatformPath = (cwd = process.cwd()) => path.join(getAppPath(cwd), 'Platform');
34
+ export const getContextImportPath = (context) => `#contexts/${normalizePascalName(context)}`;
35
+ export const getPresentationImportPath = (workspace) => workspace ? `#presentation/${normalizePascalName(workspace)}` : '#presentation';
36
+ async function directories(root) {
37
+ try {
38
+ return (await fs.readdir(root, { withFileTypes: true }))
39
+ .filter((entry) => entry.isDirectory())
40
+ .map((entry) => entry.name)
41
+ .sort();
42
+ }
43
+ catch (error) {
44
+ if (error.code === 'ENOENT')
45
+ return [];
46
+ throw error;
47
+ }
48
+ }
49
+ export const discoverContexts = async (cwd = process.cwd()) => (await directories(getContextsPath(cwd))).filter((name) => /^[A-Z][A-Za-z0-9]*$/.test(name));
50
+ export const discoverPresentationWorkspaces = async (cwd = process.cwd()) => (await directories(getPresentationPath(undefined, cwd))).filter((name) => !PRESENTATION_DIRS.includes(name) && /^[A-Z][A-Za-z0-9]*$/.test(name));
51
+ export async function ensureContextStructure(context, cwd = process.cwd()) {
52
+ const name = normalizePascalName(context);
53
+ for (const dir of CONTEXT_DIRS) {
54
+ await writeIfNotExists(path.join(getContextPath(name, cwd), dir, 'index.ts'), 'export {};\n');
55
+ }
56
+ return name;
57
+ }
58
+ export async function ensurePresentationStructure(workspace, cwd = process.cwd()) {
59
+ for (const dir of PRESENTATION_DIRS)
60
+ await fs.mkdir(path.join(getPresentationPath(workspace, cwd), dir), { recursive: true });
61
+ }
62
+ export async function selectContext(message = 'Select context:') {
63
+ await ensureContextStructure(SHARED_CONTEXT);
64
+ const names = await discoverContexts();
65
+ const result = await prompts({
66
+ type: 'select',
67
+ name: 'context',
68
+ message,
69
+ choices: [...names.map((name) => ({ title: name, value: name })), { title: 'Create new context', value: '__create__' }]
70
+ });
71
+ if (!result.context)
72
+ return null;
73
+ if (result.context !== '__create__')
74
+ return String(result.context);
75
+ const answer = await prompts({
76
+ type: 'text',
77
+ name: 'name',
78
+ message: 'Context name:',
79
+ validate: (value) => {
80
+ try {
81
+ normalizePascalName(value);
82
+ return true;
83
+ }
84
+ catch (error) {
85
+ return error.message;
86
+ }
87
+ }
88
+ });
89
+ return answer.name ? ensureContextStructure(String(answer.name)) : null;
90
+ }
91
+ /** null means cancelled; undefined means the flat Presentation layout. */
92
+ export async function selectPresentationWorkspace(message = 'Select presentation workspace:') {
93
+ const workspaces = await discoverPresentationWorkspaces();
94
+ if (!workspaces.length)
95
+ return undefined;
96
+ await ensurePresentationStructure(SHARED_WORKSPACE);
97
+ if (!workspaces.includes(SHARED_WORKSPACE))
98
+ workspaces.push(SHARED_WORKSPACE);
99
+ const answer = await prompts({
100
+ type: 'select',
101
+ name: 'workspace',
102
+ message,
103
+ choices: workspaces.sort().map((name) => ({ title: name, value: name }))
104
+ });
105
+ return answer.workspace ? String(answer.workspace) : null;
106
+ }
107
+ export async function selectWorkspace() {
108
+ const name = await selectPresentationWorkspace();
109
+ if (name === null)
110
+ return null;
111
+ return { name: name ?? '', path: getPresentationPath(name), alias: getPresentationImportPath(name) };
112
+ }
113
+ export async function selectScope(workspace) {
114
+ const root = path.join(workspace.path, 'Scopes');
115
+ const scopes = (await directories(root)).filter((name) => /^[A-Z][A-Za-z0-9]*$/.test(name));
116
+ const answer = await prompts({
117
+ type: 'select',
118
+ name: 'scope',
119
+ message: 'Select scope:',
120
+ choices: [...scopes.map((name) => ({ title: name, value: name })), { title: 'Create new scope', value: '__create__' }]
121
+ });
122
+ if (!answer.scope)
123
+ return null;
124
+ if (answer.scope !== '__create__')
125
+ return String(answer.scope);
126
+ const created = await prompts({
127
+ type: 'text',
128
+ name: 'name',
129
+ message: 'Scope name:',
130
+ validate: (value) => {
131
+ try {
132
+ normalizePascalName(value);
133
+ return true;
134
+ }
135
+ catch (error) {
136
+ return error.message;
137
+ }
138
+ }
139
+ });
140
+ if (!created.name)
141
+ return null;
142
+ const name = normalizePascalName(String(created.name));
143
+ await fs.mkdir(path.join(root, name), { recursive: true });
144
+ return name;
145
+ }
@@ -1,73 +1,40 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
3
- import prompts from 'prompts';
4
- import { loadUserConfig } from './loadConfig.js';
5
- const APP_PATH = path.join(process.cwd(), 'src/app');
6
- function normalizeContextNames(rawContexts) {
7
- if (!Array.isArray(rawContexts)) return [];
8
- return rawContexts
9
- .map((item) => {
10
- if (typeof item === 'string') {
11
- return item.trim();
12
- }
13
- if (!item || typeof item !== 'object') return '';
14
- return String(item.name ?? '').trim();
15
- })
16
- .filter(Boolean);
17
- }
18
- export async function selectContext(message = 'Select context:') {
19
- const config = await loadUserConfig();
20
- const contexts = normalizeContextNames(config.contexts);
21
- const { context } = await prompts({
22
- type: 'select',
23
- name: 'context',
24
- message,
25
- choices: contexts.map((c) => ({ title: c, value: c }))
26
- });
27
- return context ? String(context) : null;
28
- }
29
- export function getContextPath(context) {
30
- return path.join(APP_PATH, context, 'layers');
31
- }
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { getPlatformPath, normalizePascalName } from './architecture.js';
4
+ export { getContextPath, selectContext, selectPresentationWorkspace, discoverContexts, discoverPresentationWorkspaces, normalizePascalName } from './architecture.js';
32
5
  export async function getAvailableFiles(dir, pattern = '.ts') {
33
- try {
34
- const files = await fs.readdir(dir);
35
- return files.filter((f) => f.endsWith(pattern) && f !== 'index.ts').map((f) => f.replace(pattern, ''));
36
- } catch {
37
- return [];
38
- }
39
- }
40
- export async function getApplicationProviderServices(context) {
41
- const contextPath = getContextPath(context);
42
- const providerPath = path.join(contextPath, 'application/providers/ApplicationProvider.ts');
43
- try {
44
- const content = await fs.readFile(providerPath, 'utf-8');
45
- // Extract service names from ApplicationProvider
46
- const serviceRegex = /(\w+Service):\s*\(/g;
47
- const services = [];
48
- let match;
49
- while ((match = serviceRegex.exec(content)) !== null) {
50
- const serviceName = match[1];
51
- if (serviceName) services.push(serviceName);
52
- }
53
- return services;
54
- } catch {
55
- return [];
56
- }
6
+ try {
7
+ return (await fs.readdir(dir))
8
+ .filter((file) => file.endsWith(pattern) && file !== 'index.ts')
9
+ .map((file) => file.slice(0, -pattern.length));
10
+ }
11
+ catch (error) {
12
+ if (error.code === 'ENOENT')
13
+ return [];
14
+ throw error;
15
+ }
16
+ }
17
+ export async function getApplicationProviderServices() {
18
+ try {
19
+ const content = await fs.readFile(path.join(getPlatformPath(), 'Foundation/DI/ApplicationProvider.ts'), 'utf8');
20
+ return Array.from(content.matchAll(/(\w+Service):\s*\(/g), (match) => match[1]);
21
+ }
22
+ catch (error) {
23
+ if (error.code === 'ENOENT')
24
+ return [];
25
+ throw error;
26
+ }
57
27
  }
58
28
  export function toPascalCase(str) {
59
- return str
60
- .split(/[-_\s]+/)
61
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
62
- .join('');
29
+ return str.trim() ? normalizePascalName(str) : '';
63
30
  }
64
31
  export function toCamelCase(str) {
65
- const pascal = toPascalCase(str);
66
- return pascal.charAt(0).toLowerCase() + pascal.slice(1);
32
+ const pascal = toPascalCase(str);
33
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
67
34
  }
68
35
  export function toKebabCase(str) {
69
- return str
70
- .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
71
- .replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
72
- .toLowerCase();
36
+ return str
37
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
38
+ .replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
39
+ .toLowerCase();
73
40
  }
@@ -1,60 +1,79 @@
1
1
  import fs from 'fs/promises';
2
2
  import path from 'path';
3
3
  export async function writeIfNotExists(filepath, content) {
4
- try {
5
- await fs.access(filepath);
6
- console.log(`⚠️ Skip existing: ${filepath}`);
7
- return false;
8
- } catch {
9
- await fs.mkdir(path.dirname(filepath), { recursive: true });
10
- await fs.writeFile(filepath, content, 'utf-8');
11
- console.log(`✅ Created: ${filepath}`);
12
- return true;
13
- }
4
+ await fs.mkdir(path.dirname(filepath), { recursive: true });
5
+ try {
6
+ await fs.writeFile(filepath, content, { encoding: 'utf8', flag: 'wx' });
7
+ }
8
+ catch (error) {
9
+ if (error.code !== 'EEXIST')
10
+ throw error;
11
+ console.log(`Skip existing: ${filepath}`);
12
+ return false;
13
+ }
14
+ console.log(`Created: ${filepath}`);
15
+ return true;
16
+ }
17
+ export async function appendBarrelExports(filepath, sources) {
18
+ let content = '';
19
+ try {
20
+ content = await fs.readFile(filepath, 'utf8');
21
+ }
22
+ catch (error) {
23
+ if (error.code !== 'ENOENT')
24
+ throw error;
25
+ }
26
+ const additions = sources
27
+ .filter((source) => !content.includes("from '" + source + "'") && !content.includes('from "' + source + '"'))
28
+ .map((source) => 'export * from ' + JSON.stringify(source) + ';');
29
+ if (!additions.length && content.trim())
30
+ return;
31
+ await fs.mkdir(path.dirname(filepath), { recursive: true });
32
+ await fs.writeFile(filepath, content + (content && !content.endsWith('\n') ? '\n' : '') + (additions.length ? additions.join('\n') : 'export {};') + '\n');
14
33
  }
15
34
  export async function updateIndexTs(dir, filePattern = '.ts', ignore = ['index.ts']) {
16
- try {
17
- const files = (await fs.readdir(dir)).filter((f) => f.endsWith(filePattern) && !ignore.includes(f));
18
- const content = files.map((f) => `export * from './${f.replace('.ts', '')}';`).join('\n') + '\n';
19
- await fs.writeFile(path.join(dir, 'index.ts'), content, 'utf-8');
20
- } catch {
21
- // Directory doesn't exist yet
22
- }
35
+ const files = (await fs.readdir(dir))
36
+ .filter((file) => file.endsWith(filePattern) && !file.endsWith('.d.ts') && !ignore.includes(file))
37
+ .sort();
38
+ await appendBarrelExports(path.join(dir, 'index.ts'), files.map((file) => './' + file.slice(0, -filePattern.length)));
23
39
  }
24
40
  export async function ensureIndexExports(layerPath) {
25
- try {
26
- const subdirs = await fs.readdir(layerPath, { withFileTypes: true });
27
- const exportLines = subdirs.filter((d) => d.isDirectory()).map((d) => `export * from './${d.name}';`);
28
- if (exportLines.length > 0) {
29
- await fs.writeFile(path.join(layerPath, 'index.ts'), exportLines.join('\n') + '\n', 'utf-8');
30
- }
31
- } catch {
32
- // Directory doesn't exist
33
- }
41
+ try {
42
+ const subdirs = await fs.readdir(layerPath, { withFileTypes: true });
43
+ const exportLines = subdirs.filter((d) => d.isDirectory()).map((d) => `export * from './${d.name}';`);
44
+ if (exportLines.length > 0) {
45
+ await fs.writeFile(path.join(layerPath, 'index.ts'), exportLines.join('\n') + '\n', 'utf-8');
46
+ }
47
+ }
48
+ catch {
49
+ // Directory doesn't exist
50
+ }
34
51
  }
35
52
  export async function injectIntoFile(filepath, searchPattern, newContent, position = 'after') {
36
- try {
37
- let content = await fs.readFile(filepath, 'utf-8');
38
- const index = content.indexOf(searchPattern);
39
- if (index === -1) {
40
- console.warn(`Pattern not found in ${filepath}`);
41
- return false;
42
- }
43
- if (content.includes(newContent.trim())) {
44
- console.log(`Content already exists in ${filepath}`);
45
- return false;
46
- }
47
- if (position === 'after') {
48
- const insertIndex = index + searchPattern.length;
49
- content = content.slice(0, insertIndex) + '\n' + newContent + content.slice(insertIndex);
50
- } else {
51
- content = content.slice(0, index) + newContent + '\n' + content.slice(index);
52
- }
53
- await fs.writeFile(filepath, content, 'utf-8');
54
- console.log(`✅ Updated: ${filepath}`);
55
- return true;
56
- } catch (error) {
57
- console.error(`Failed to update ${filepath}:`, error instanceof Error ? error.message : String(error));
58
- return false;
59
- }
53
+ try {
54
+ let content = await fs.readFile(filepath, 'utf-8');
55
+ const index = content.indexOf(searchPattern);
56
+ if (index === -1) {
57
+ console.warn(`Pattern not found in ${filepath}`);
58
+ return false;
59
+ }
60
+ if (content.includes(newContent.trim())) {
61
+ console.log(`Content already exists in ${filepath}`);
62
+ return false;
63
+ }
64
+ if (position === 'after') {
65
+ const insertIndex = index + searchPattern.length;
66
+ content = content.slice(0, insertIndex) + '\n' + newContent + content.slice(insertIndex);
67
+ }
68
+ else {
69
+ content = content.slice(0, index) + newContent + '\n' + content.slice(index);
70
+ }
71
+ await fs.writeFile(filepath, content, 'utf-8');
72
+ console.log(`✅ Updated: ${filepath}`);
73
+ return true;
74
+ }
75
+ catch (error) {
76
+ console.error(`Failed to update ${filepath}:`, error instanceof Error ? error.message : String(error));
77
+ return false;
78
+ }
60
79
  }
@@ -0,0 +1,38 @@
1
+ import ts from 'typescript';
2
+ /** Flatten literal-only spreads without evaluating user configuration or duplicating effects. */
3
+ export function flattenConfigLiterals(content) {
4
+ const source = ts.createSourceFile('azure-net.config.ts', content, ts.ScriptTarget.Latest, true);
5
+ const exported = source.statements.find(ts.isExportAssignment);
6
+ if (!exported)
7
+ return content;
8
+ const unwrap = (node) => (ts.isParenthesizedExpression(node) ? unwrap(node.expression) : node);
9
+ const root = unwrap(exported.expression);
10
+ if (!ts.isObjectLiteralExpression(root))
11
+ return content;
12
+ let changed = false;
13
+ const flatten = (object) => object.properties.flatMap((property) => {
14
+ if (ts.isSpreadAssignment(property)) {
15
+ const value = unwrap(property.expression);
16
+ if (ts.isObjectLiteralExpression(value) &&
17
+ value.properties.every((p) => !ts.isGetAccessorDeclaration(p) &&
18
+ !ts.isSetAccessorDeclaration(p) &&
19
+ !(p.name && p.name.getText(source).replace(/['"]/g, '') === '__proto__'))) {
20
+ changed = true;
21
+ return flatten(value);
22
+ }
23
+ }
24
+ return [property];
25
+ });
26
+ const properties = flatten(root);
27
+ if (!changed)
28
+ return content;
29
+ const names = properties
30
+ .filter((p) => p.name && !ts.isComputedPropertyName(p.name))
31
+ .map((p) => p.name.getText(source).replace(/['"]/g, ''));
32
+ // Duplicate literal keys can produce invalid TypeScript; retain the original spread semantics.
33
+ if (new Set(names).size !== names.length)
34
+ return content;
35
+ const updated = ts.factory.updateObjectLiteralExpression(root, properties);
36
+ const printed = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }).printNode(ts.EmitHint.Expression, updated, source);
37
+ return content.slice(0, root.getStart(source)) + printed + content.slice(root.end);
38
+ }
@@ -0,0 +1,75 @@
1
+ import ts from 'typescript';
2
+ export function mergeAliasesInSvelteConfig(content, aliases) {
3
+ const source = ts.createSourceFile('svelte.config.js', content, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
4
+ function resolve(expression, seen = new Set()) {
5
+ if (ts.isObjectLiteralExpression(expression))
6
+ return expression;
7
+ if (ts.isParenthesizedExpression(expression) || ts.isAsExpression(expression) || ts.isSatisfiesExpression(expression))
8
+ return resolve(expression.expression, seen);
9
+ if (ts.isIdentifier(expression) && !seen.has(expression.text)) {
10
+ seen.add(expression.text);
11
+ for (const statement of source.statements)
12
+ if (ts.isVariableStatement(statement)) {
13
+ for (const declaration of statement.declarationList.declarations)
14
+ if (ts.isIdentifier(declaration.name) && declaration.name.text === expression.text && declaration.initializer)
15
+ return resolve(declaration.initializer, seen);
16
+ }
17
+ }
18
+ throw new Error('Cannot safely merge Svelte aliases: use an object literal or a local object variable for the config and kit.');
19
+ }
20
+ const exported = source.statements.find(ts.isExportAssignment);
21
+ if (!exported)
22
+ throw new Error('Cannot safely merge Svelte aliases: no default export found.');
23
+ const root = resolve(exported.expression);
24
+ const property = (object, name) => [...object.properties]
25
+ .reverse()
26
+ .find((entry) => entry.name && (ts.isIdentifier(entry.name) || ts.isStringLiteral(entry.name)) && entry.name.text === name);
27
+ const value = (entry) => {
28
+ if (ts.isPropertyAssignment(entry))
29
+ return entry.initializer;
30
+ if (ts.isShorthandPropertyAssignment(entry))
31
+ return entry.name;
32
+ throw new Error('Cannot safely merge Svelte aliases: unsupported property declaration.');
33
+ };
34
+ const insert = (object, text) => {
35
+ const last = object.properties.at(-1);
36
+ const comma = last && !object.properties.hasTrailingComma ? ',' : '';
37
+ // Insert the separator before any trailing line comment.
38
+ let next = content.slice(0, object.end - 1) + `\n${text}\n` + content.slice(object.end - 1);
39
+ if (last && comma)
40
+ next = next.slice(0, last.end) + comma + next.slice(last.end);
41
+ return next;
42
+ };
43
+ const entries = Object.entries(aliases)
44
+ .map(([key, target]) => `${JSON.stringify(key)}: ${JSON.stringify(target)}`)
45
+ .join(',\n');
46
+ const kit = property(root, 'kit');
47
+ if (!kit)
48
+ return insert(root, `kit: { alias: { ${entries} } }`);
49
+ const kitObject = resolve(value(kit));
50
+ const alias = property(kitObject, 'alias');
51
+ if (!alias)
52
+ return insert(kitObject, `alias: { ${entries} }`);
53
+ const expression = value(alias);
54
+ if (ts.isObjectLiteralExpression(expression)) {
55
+ const missing = Object.entries(aliases).filter(([key, target]) => {
56
+ const entry = property(expression, key);
57
+ if (!entry || !ts.isPropertyAssignment(entry) || !ts.isStringLiteral(entry.initializer) || entry.initializer.text !== target)
58
+ return true;
59
+ // Later spreads or computed keys can override an otherwise correct entry.
60
+ return expression.properties
61
+ .slice(expression.properties.indexOf(entry) + 1)
62
+ .some((later) => ts.isSpreadAssignment(later) || (later.name && ts.isComputedPropertyName(later.name)));
63
+ });
64
+ if (!missing.length)
65
+ return content;
66
+ if (missing.every(([key]) => !property(expression, key))) {
67
+ return insert(expression, missing.map(([key, target]) => `${JSON.stringify(key)}: ${JSON.stringify(target)}`).join(',\n'));
68
+ }
69
+ }
70
+ // Preserve every user expression, spread and comment; fixed architecture aliases win.
71
+ const replacement = `{ ...(${expression.getText(source)}), ${entries} }`;
72
+ if (ts.isShorthandPropertyAssignment(alias))
73
+ return content.slice(0, alias.getStart(source)) + `alias: ${replacement}` + content.slice(alias.end);
74
+ return content.slice(0, expression.getStart(source)) + replacement + content.slice(expression.end);
75
+ }
@@ -1,46 +1,82 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import fsSync from 'node:fs';
3
3
  import path from 'node:path';
4
- const DEFAULT_CONFIG = {
5
- contexts: [],
6
- defaultContext: 'web'
7
- };
8
- function toObjectFromDefaultExport(content) {
9
- const source = String(content ?? '').trim();
10
- if (!source.length) return {};
11
- const replacedExport = source.replace(/^\s*export\s+default\s+/m, 'return ');
12
- if (replacedExport === source) return {};
13
- const withSemicolon = /;\s*$/.test(replacedExport) ? replacedExport : `${replacedExport};`;
14
- try {
15
- return new Function(withSemicolon)() ?? {};
16
- } catch {
17
- return {};
18
- }
19
- }
4
+ import { createRequire } from 'node:module';
5
+ import ts from 'typescript';
6
+ import { flattenConfigLiterals } from './flattenConfig.js';
20
7
  export function resolveConfigFile(cwd = process.cwd()) {
21
- const tsPath = path.join(cwd, 'azure-net.config.ts');
22
- const jsPath = path.join(cwd, 'azure-net.config.js');
23
- if (fsSync.existsSync(tsPath)) {
24
- return { exists: true, filepath: tsPath, type: 'ts' };
25
- }
26
- if (fsSync.existsSync(jsPath)) {
27
- return { exists: true, filepath: jsPath, type: 'js' };
28
- }
29
- return { exists: false, filepath: tsPath, type: 'ts' };
8
+ for (const type of ['ts', 'js']) {
9
+ const filepath = path.join(cwd, `azure-net.config.${type}`);
10
+ if (fsSync.existsSync(filepath))
11
+ return { exists: true, filepath, type };
12
+ }
13
+ return { exists: false, filepath: path.join(cwd, 'azure-net.config.ts'), type: 'ts' };
14
+ }
15
+ export async function loadUserConfig(cwd = process.cwd()) {
16
+ const { exists, filepath } = resolveConfigFile(cwd);
17
+ if (!exists)
18
+ return {};
19
+ try {
20
+ const source = await fs.readFile(filepath, 'utf8');
21
+ const result = ts.transpileModule(source, {
22
+ fileName: filepath,
23
+ reportDiagnostics: true,
24
+ compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS, esModuleInterop: true }
25
+ });
26
+ const errors = result.diagnostics?.filter((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error) ?? [];
27
+ if (errors.length)
28
+ throw new Error(errors.map((error) => ts.flattenDiagnosticMessageText(error.messageText, '\n')).join('\n'));
29
+ const module = { exports: {} };
30
+ new Function('module', 'exports', 'require', '__filename', '__dirname', result.outputText)(module, module.exports, createRequire(filepath), filepath, path.dirname(filepath));
31
+ const config = module.exports.default;
32
+ if (!config || typeof config !== 'object' || Array.isArray(config))
33
+ throw new Error('Expected a default-exported configuration object.');
34
+ const parsed = config;
35
+ if (parsed.presentationWorkspaces !== undefined &&
36
+ (!Array.isArray(parsed.presentationWorkspaces) ||
37
+ parsed.presentationWorkspaces.some((name) => typeof name !== 'string' || !/^[A-Za-z][A-Za-z0-9 _-]*$/.test(name.trim()))))
38
+ throw new Error('presentationWorkspaces must be an array of valid workspace names.');
39
+ if (parsed.runChecksBeforePush !== undefined && typeof parsed.runChecksBeforePush !== 'boolean')
40
+ throw new Error('runChecksBeforePush must be a boolean.');
41
+ return parsed;
42
+ }
43
+ catch (error) {
44
+ throw new Error(`Unable to load ${filepath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
45
+ }
30
46
  }
31
- export async function loadUserConfig() {
32
- const { exists, filepath } = resolveConfigFile();
33
- if (!exists) {
34
- return { ...DEFAULT_CONFIG };
35
- }
36
- try {
37
- const content = await fs.readFile(filepath, 'utf-8');
38
- const parsed = toObjectFromDefaultExport(content);
39
- if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
40
- return { ...DEFAULT_CONFIG };
41
- }
42
- return { ...DEFAULT_CONFIG, ...parsed };
43
- } catch {
44
- return { ...DEFAULT_CONFIG };
45
- }
47
+ /** Add the default without rewriting or dropping legacy/user configuration. */
48
+ export async function initializeConfigDefaults(cwd = process.cwd()) {
49
+ const existing = resolveConfigFile(cwd);
50
+ if (existing.exists) {
51
+ const original = await fs.readFile(existing.filepath, 'utf8');
52
+ const flattened = flattenConfigLiterals(original);
53
+ if (flattened !== original)
54
+ await fs.writeFile(existing.filepath, flattened);
55
+ }
56
+ const config = await loadUserConfig(cwd);
57
+ if (config.runChecksBeforePush !== undefined)
58
+ return;
59
+ const resolved = resolveConfigFile(cwd);
60
+ if (!resolved.exists) {
61
+ await fs.writeFile(resolved.filepath, 'export default {\n\trunChecksBeforePush: true\n};\n', { flag: 'wx' });
62
+ return;
63
+ }
64
+ const content = await fs.readFile(resolved.filepath, 'utf8');
65
+ const source = ts.createSourceFile(resolved.filepath, content, ts.ScriptTarget.Latest, true);
66
+ const exported = source.statements.find(ts.isExportAssignment);
67
+ if (!exported || exported.isExportEquals)
68
+ throw new Error(`Cannot initialize ${resolved.filepath}: expected a default export.`);
69
+ const expression = exported.expression;
70
+ let nextContent;
71
+ if (ts.isObjectLiteralExpression(expression)) {
72
+ const last = expression.properties.at(-1);
73
+ nextContent = content.slice(0, expression.end - 1) + '\n\trunChecksBeforePush: true\n' + content.slice(expression.end - 1);
74
+ if (last && !expression.properties.hasTrailingComma)
75
+ nextContent = nextContent.slice(0, last.end) + ',' + nextContent.slice(last.end);
76
+ }
77
+ else {
78
+ const replacement = `{ ...(${expression.getText(source)}), runChecksBeforePush: true }`;
79
+ nextContent = content.slice(0, expression.getStart(source)) + replacement + content.slice(expression.end);
80
+ }
81
+ await fs.writeFile(resolved.filepath, nextContent, 'utf8');
46
82
  }