@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,1027 @@
1
+ import { migrateTranslationsSources } from './translations.js';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { createHash } from 'node:crypto';
5
+ import ts from 'typescript';
6
+ import prompts from 'prompts';
7
+ import { CONTEXT_DIRS, normalizePascalName } from '../utils/architecture.js';
8
+ import { VITE_CONFIGS } from '../utils/viteConfig.js';
9
+ import { mergePackageImports, resolvePackageImport } from '../utils/packageImports.js';
10
+ import { flattenConfigLiterals } from '../utils/flattenConfig.js';
11
+ import { parseSource } from '../checks/architecture.js';
12
+ import { inspectFactories, replaceFactory } from './presenterFactories.js';
13
+ import { migrateAsyncHelpers, migratePresenterSource } from './presenters.js';
14
+ const slash = (s) => s.split(path.sep).join('/');
15
+ const textFile = /\.(?:[cm]?[jt]sx?|svelte|css|scss|sass|less|json|html|svg)$/;
16
+ const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
17
+ const unwrap = (n) => (ts.isParenthesizedExpression(n) ? unwrap(n.expression) : n);
18
+ const propName = (n) => (n && (ts.isIdentifier(n) || ts.isStringLiteral(n)) ? n.text : undefined);
19
+ async function files(root) {
20
+ const out = [];
21
+ for (const entry of await fs.readdir(root, { withFileTypes: true })) {
22
+ if (['.backup', '.git', 'node_modules', '.svelte-kit'].includes(entry.name))
23
+ continue;
24
+ const file = path.join(root, entry.name);
25
+ if (entry.isSymbolicLink())
26
+ throw new Error(`Migration refuses symbolic links in source: ${file}`);
27
+ if (entry.isDirectory())
28
+ out.push(...(await files(file)));
29
+ else
30
+ out.push(file);
31
+ }
32
+ return out.sort();
33
+ }
34
+ function pascalFile(file) {
35
+ if (file === 'index.ts' || file === 'index.js')
36
+ return file;
37
+ const dot = file.indexOf('.');
38
+ const stem = (dot < 0 ? file : file.slice(0, dot)).replace(/^_(?=.*)/, '');
39
+ return normalizePascalName(stem) + (dot < 0 ? '' : file.slice(dot));
40
+ }
41
+ function partsPath(parts) {
42
+ return parts.map((p, i) => (i === parts.length - 1 ? pascalFile(p) : normalizePascalName(p))).join('/');
43
+ }
44
+ export async function planMigration(root = process.cwd()) {
45
+ root = path.resolve(root);
46
+ const app = path.join(root, 'src/app');
47
+ const originals = new Map();
48
+ for (const file of await files(path.join(root, 'src')))
49
+ originals.set(file, await fs.readFile(file));
50
+ for (const name of [
51
+ 'azure-net.config.ts',
52
+ 'azure-net.config.js',
53
+ 'svelte.config.js',
54
+ 'svelte.config.ts',
55
+ 'svelte.config.mjs',
56
+ 'tsconfig.json',
57
+ 'package.json',
58
+ ...VITE_CONFIGS
59
+ ]) {
60
+ try {
61
+ originals.set(path.join(root, name), await fs.readFile(path.join(root, name)));
62
+ }
63
+ catch (error) {
64
+ if (error.code !== 'ENOENT')
65
+ throw error;
66
+ }
67
+ }
68
+ const issues = [];
69
+ const issueKeys = new Set();
70
+ const issue = (file, reason) => {
71
+ const key = file + reason;
72
+ if (!issueKeys.has(key)) {
73
+ issueKeys.add(key);
74
+ issues.push({ file, reason });
75
+ }
76
+ };
77
+ const mapping = new Map();
78
+ const workspaces = new Set();
79
+ const contexts = new Set(['SharedKernel']);
80
+ const aliases = new Map([
81
+ ['$lib', path.join(root, 'src/lib')],
82
+ ['$program', path.join(root, 'src/program.ts')]
83
+ ]);
84
+ const oldRoots = new Set();
85
+ for (const file of originals.keys()) {
86
+ const relative = slash(path.relative(app, file));
87
+ if (relative.startsWith('../'))
88
+ continue;
89
+ const first = relative.split('/')[0];
90
+ if (!['Contexts', 'Presentation', 'Platform'].includes(first))
91
+ oldRoots.add(first);
92
+ }
93
+ for (const folder of oldRoots)
94
+ aliases.set(`$${folder}`, path.join(app, folder));
95
+ const svelteConfig = [...originals.keys()].find((p) => /svelte\.config\.(?:js|ts|mjs)$/.test(p));
96
+ const viteConfig = [...originals.keys()].find((p) => VITE_CONFIGS.includes(path.basename(p)));
97
+ const configPath = svelteConfig ?? viteConfig;
98
+ const configAst = ts.createSourceFile(configPath ?? 'config.ts', configPath ? originals.get(configPath).toString() : '', ts.ScriptTarget.Latest, true);
99
+ const packageImports = JSON.parse(originals.get(path.join(root, 'package.json'))?.toString() ?? '{}').imports ?? {};
100
+ const readAliases = (node) => {
101
+ if (ts.isPropertyAssignment(node) && propName(node.name) === 'alias' && ts.isObjectLiteralExpression(node.initializer)) {
102
+ for (const p of node.initializer.properties)
103
+ if (ts.isPropertyAssignment(p) && ts.isStringLiteral(p.initializer)) {
104
+ const name = propName(p.name);
105
+ if (name)
106
+ aliases.set(name, path.resolve(root, p.initializer.text));
107
+ }
108
+ }
109
+ ts.forEachChild(node, readAliases);
110
+ };
111
+ readAliases(configAst);
112
+ if (viteConfig && svelteConfig)
113
+ readAliases(ts.createSourceFile(viteConfig, originals.get(viteConfig).toString(), ts.ScriptTarget.Latest, true));
114
+ function resolve(from, specifier) {
115
+ const clean = specifier[0] + specifier.slice(1).split(/[?#]/)[0];
116
+ let base;
117
+ if (clean.startsWith('.'))
118
+ base = path.resolve(path.dirname(from), clean);
119
+ else if (clean.startsWith('#'))
120
+ base = resolvePackageImport(packageImports, clean, root) ?? undefined;
121
+ else
122
+ for (const [alias, target] of [...aliases].sort((a, b) => b[0].length - a[0].length)) {
123
+ if (clean === alias || clean.startsWith(alias + '/')) {
124
+ base = path.join(target, clean.slice(alias.length));
125
+ break;
126
+ }
127
+ }
128
+ if (!base)
129
+ return undefined;
130
+ for (const candidate of [
131
+ base,
132
+ ...['.ts', '.js', '.svelte', '.scss', '.css', '/index.ts', '/index.js'].map((ext) => base + ext),
133
+ ...['.scss', '.sass', '.css'].flatMap((ext) => [
134
+ path.join(path.dirname(base), '_' + path.basename(base) + ext),
135
+ base + '/index' + ext,
136
+ base + '/_index' + ext
137
+ ]),
138
+ base.replace(/\.js$/, '.ts')
139
+ ]) {
140
+ if (originals.has(candidate))
141
+ return candidate;
142
+ }
143
+ return undefined;
144
+ }
145
+ const domains = new Map();
146
+ for (const file of originals.keys()) {
147
+ const m = slash(path.relative(app, file)).match(/^([^/]+)\/layers\/domain\/([^/]+)\//);
148
+ if (m) {
149
+ const names = domains.get(m[1]) ?? new Set();
150
+ names.add(m[2]);
151
+ domains.set(m[1], names);
152
+ }
153
+ }
154
+ const backup = (file, why) => {
155
+ issue(file, why);
156
+ return path.join(app, '.backup', path.relative(app, file));
157
+ };
158
+ for (const file of originals.keys()) {
159
+ const rel = slash(path.relative(app, file));
160
+ const p = rel.split('/');
161
+ if (!oldRoots.has(p[0]))
162
+ continue;
163
+ const area = p[0];
164
+ const workspace = /^(shared-kernel|shared)$/i.test(area) ? 'Shared' : normalizePascalName(area);
165
+ let dest;
166
+ try {
167
+ if (p[1] === 'ui') {
168
+ workspaces.add(workspace);
169
+ dest = `Presentation/${workspace}/Ui/${partsPath(p.slice(2))}`;
170
+ }
171
+ else if (p[1] === 'layers' && p[2] === 'presentation') {
172
+ workspaces.add(workspace);
173
+ dest = `Presentation/${workspace}/Scopes/${partsPath(p.slice(3))}`;
174
+ }
175
+ else if (p[1] === 'layers' && p[2] === 'domain' && p.length >= 5) {
176
+ const context = normalizePascalName(p[3]);
177
+ contexts.add(context);
178
+ const folders = { model: 'Entities', models: 'Entities', ports: 'Ports', enums: 'Enums', enum: 'Enums' };
179
+ const rest = [...p.slice(4)];
180
+ if (folders[rest[0]])
181
+ rest[0] = folders[rest[0]];
182
+ dest = `Contexts/${context}/Domain/${partsPath(rest)}`;
183
+ if (/\/I[A-Z][^/]*\.ts$/.test(dest))
184
+ dest = dest.replace(/\/I([A-Z][^/]*)\.ts$/, '/$1.ts');
185
+ }
186
+ else if (p[1] === 'layers' &&
187
+ (p.includes('repositories') || p.includes('repositores') || p.includes('use-cases')) &&
188
+ !/^index\./.test(p.at(-1))) {
189
+ const base = path.basename(file).replace(/(?:Repository|UseCases?)\.[^.]+$/, '');
190
+ const match = [...(domains.get(area) ?? [])].filter((d) => normalizePascalName(d) === base);
191
+ if (match.length === 1) {
192
+ contexts.add(base);
193
+ dest = `Contexts/${base}/${p.includes('use-cases') ? 'Application/UseCases' : 'Infrastructure/Repositories'}/${pascalFile(p.at(-1))}`;
194
+ }
195
+ }
196
+ else if (p[1] === 'runtime')
197
+ dest = `Platform/Bootstrap/${partsPath(p.slice(p[2] === 'bootstrap' ? 3 : 2))}`;
198
+ else if (['essentials', 'internal'].includes(p[1])) {
199
+ if (p[2] === 'foundation') {
200
+ const rest = p.slice(p[3] === 'constructs' ? 4 : 3);
201
+ if (rest[0] !== 'provider' && rest[0] !== 'providers')
202
+ dest = `Platform/Foundation/${partsPath(rest)}`;
203
+ }
204
+ else if (p[2] === 'localization' || p[2] === 'pipelines')
205
+ dest = `Platform/Bootstrap/${partsPath(p.slice(2))}`;
206
+ else if (p[2] === 'config')
207
+ dest = `Platform/Foundation/${partsPath(p.slice(2))}`;
208
+ }
209
+ }
210
+ catch {
211
+ /* Invalid names are archived instead of guessed. */
212
+ }
213
+ mapping.set(file, dest ? path.join(app, dest) : backup(file, 'No unambiguous target (legacy provider, aggregate barrel, or unsupported folder).'));
214
+ }
215
+ // No last-writer wins: preserve every conflicting file for manual resolution.
216
+ const destinations = new Map();
217
+ for (const [from, to] of mapping) {
218
+ const key = to.toLowerCase();
219
+ const list = destinations.get(key) ?? [];
220
+ list.push(from);
221
+ destinations.set(key, list);
222
+ }
223
+ for (const list of destinations.values()) {
224
+ const target = mapping.get(list[0]);
225
+ if (target.includes(`${path.sep}.backup${path.sep}`))
226
+ continue;
227
+ if (list.length > 1 || (originals.has(target) && !mapping.has(target))) {
228
+ // Domain barrels are safely reconstituted later from each original export list.
229
+ if (list.length > 1 &&
230
+ path.basename(target) === 'index.ts' &&
231
+ !originals.has(target) &&
232
+ list.every((file) => ts.createSourceFile(file, originals.get(file).toString(), ts.ScriptTarget.Latest, true).statements.every(ts.isExportDeclaration)))
233
+ continue;
234
+ for (const file of list)
235
+ mapping.set(file, backup(file, `Target collision: ${target}; no file overwritten.`));
236
+ }
237
+ }
238
+ const isBackup = (file) => file.includes(`${path.sep}.backup${path.sep}`);
239
+ function specifier(to, from) {
240
+ const relative = slash(path.relative(app, to));
241
+ let value;
242
+ const first = relative.split('/')[0];
243
+ const prefix = { Contexts: '#contexts', Presentation: '#presentation', Platform: '#platform' };
244
+ if (prefix[first])
245
+ value = prefix[first] + relative.slice(first.length);
246
+ else {
247
+ value = slash(path.relative(path.dirname(from), to));
248
+ if (!value.startsWith('.'))
249
+ value = './' + value;
250
+ }
251
+ return value.replace(/\.(?:ts|js)$/, '').replace(/\/index$/, '');
252
+ }
253
+ function exportedSymbol(file, name, seen = new Set()) {
254
+ const key = `${file}:${name}`;
255
+ if (seen.has(key))
256
+ return undefined;
257
+ seen.add(key);
258
+ if (file.endsWith('.svelte') && name === 'default')
259
+ return { file, name };
260
+ const source = parseSource(originals.get(file)?.toString() ?? '', file);
261
+ for (const statement of source.statements) {
262
+ if (ts.isExportDeclaration(statement) && statement.moduleSpecifier && ts.isStringLiteral(statement.moduleSpecifier)) {
263
+ const target = resolve(file, statement.moduleSpecifier.text);
264
+ if (!target)
265
+ continue;
266
+ if (!statement.exportClause) {
267
+ const found = exportedSymbol(target, name, seen);
268
+ if (found)
269
+ return found;
270
+ }
271
+ else if (ts.isNamedExports(statement.exportClause))
272
+ for (const element of statement.exportClause.elements) {
273
+ if (element.name.text === name)
274
+ return (exportedSymbol(target, element.propertyName?.text ?? name, seen) ?? {
275
+ file: target,
276
+ name: element.propertyName?.text ?? name
277
+ });
278
+ }
279
+ }
280
+ else {
281
+ if (name === 'default' && ts.isExportAssignment(statement))
282
+ return { file, name };
283
+ if ('name' in statement && propName(statement.name) === name)
284
+ return { file, name };
285
+ if (ts.isVariableStatement(statement) && statement.declarationList.declarations.some((d) => propName(d.name) === name))
286
+ return { file, name };
287
+ }
288
+ }
289
+ return undefined;
290
+ }
291
+ const presenterNames = new Map();
292
+ const upgradedSources = new Map();
293
+ const factorySymbols = new Map();
294
+ const factoryDefinitions = new Map();
295
+ const legacyFactories = new Set();
296
+ const consumedFactories = new Set();
297
+ for (const [file, bytes] of originals) {
298
+ if (!/\.[jt]s$/.test(file))
299
+ continue;
300
+ factoryDefinitions.set(file, inspectFactories(bytes.toString(), file));
301
+ const source = parseSource(bytes.toString(), file);
302
+ const creators = new Set();
303
+ for (const statement of source.statements) {
304
+ if (!ts.isImportDeclaration(statement) ||
305
+ !ts.isStringLiteral(statement.moduleSpecifier) ||
306
+ !['@azure-net/kit/delivery', '@azure-net/edges'].includes(statement.moduleSpecifier.text))
307
+ continue;
308
+ const bindings = statement.importClause?.namedBindings;
309
+ if (bindings && ts.isNamedImports(bindings))
310
+ for (const binding of bindings.elements)
311
+ if ((binding.propertyName ?? binding.name).text === 'createPresenterFactory')
312
+ creators.add(binding.name.text);
313
+ }
314
+ const names = new Set();
315
+ for (const statement of source.statements) {
316
+ if (!ts.isVariableStatement(statement))
317
+ continue;
318
+ for (const declaration of statement.declarationList.declarations) {
319
+ const init = declaration.initializer;
320
+ if (ts.isIdentifier(declaration.name) &&
321
+ init &&
322
+ ts.isCallExpression(init) &&
323
+ ts.isIdentifier(init.expression) &&
324
+ creators.has(init.expression.text))
325
+ names.add(declaration.name.text);
326
+ }
327
+ }
328
+ if (names.size)
329
+ factorySymbols.set(file, names);
330
+ }
331
+ for (const [file, bytes] of originals) {
332
+ if (!file.startsWith(path.join(root, 'src') + path.sep) || !/\.(?:ts|js|svelte)$/.test(file))
333
+ continue;
334
+ if (mapping.has(file) && isBackup(mapping.get(file)))
335
+ continue;
336
+ const source = parseSource(bytes.toString(), file);
337
+ const factories = new Set();
338
+ const dependencies = new Map();
339
+ const definitions = new Map();
340
+ for (const statement of source.statements) {
341
+ if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier))
342
+ continue;
343
+ const bindings = statement.importClause?.namedBindings;
344
+ if (!bindings || !ts.isNamedImports(bindings))
345
+ continue;
346
+ for (const binding of bindings.elements) {
347
+ const name = (binding.propertyName ?? binding.name).text;
348
+ const target = resolve(file, statement.moduleSpecifier.text);
349
+ const symbol = target && exportedSymbol(target, name);
350
+ if ((!target &&
351
+ name === 'createPresenter' &&
352
+ ['@azure-net/kit/delivery', '@azure-net/edges'].includes(statement.moduleSpecifier.text)) ||
353
+ (symbol && factorySymbols.get(symbol.file)?.has(symbol.name)))
354
+ factories.add(binding.name.text);
355
+ if (symbol) {
356
+ const definition = factoryDefinitions.get(symbol.file)?.find((value) => value.name === symbol.name);
357
+ if (definition) {
358
+ let module = slash(path.relative(path.dirname(file), symbol.file)).replace(/\.[jt]s$/, '');
359
+ if (!module.startsWith('.'))
360
+ module = './' + module;
361
+ dependencies.set(binding.name.text, { ...definition, source: module });
362
+ definitions.set(binding.name.text, definition);
363
+ }
364
+ }
365
+ }
366
+ }
367
+ const result = file.endsWith('.svelte')
368
+ ? { content: bytes.toString(), converted: new Map(), issues: [] }
369
+ : migratePresenterSource(bytes.toString(), file, factories, dependencies);
370
+ for (const [local, definition] of definitions) {
371
+ if (result.converted.size)
372
+ consumedFactories.add(definition);
373
+ const remaining = ts.createSourceFile(file, result.content, ts.ScriptTarget.Latest, true);
374
+ const visit = (node) => {
375
+ if (ts.isImportDeclaration(node))
376
+ return;
377
+ if (ts.isIdentifier(node) && node.text === local)
378
+ legacyFactories.add(definition);
379
+ ts.forEachChild(node, visit);
380
+ };
381
+ visit(remaining);
382
+ }
383
+ if (result.converted.size)
384
+ presenterNames.set(file, result.converted);
385
+ for (const reason of result.issues)
386
+ issue(file, reason);
387
+ let upgraded = result.content;
388
+ try {
389
+ upgraded = migrateAsyncHelpers(upgraded, file);
390
+ }
391
+ catch (error) {
392
+ issue(file, `Async helpers need manual migration: ${String(error)}`);
393
+ }
394
+ if (upgraded !== bytes.toString())
395
+ upgradedSources.set(file, upgraded);
396
+ }
397
+ for (const [file, definitions] of factoryDefinitions) {
398
+ let content = originals.get(file).toString();
399
+ let changed = false;
400
+ for (const definition of definitions) {
401
+ if (!consumedFactories.has(definition))
402
+ continue;
403
+ const keepLegacy = legacyFactories.has(definition);
404
+ content = replaceFactory(content, file, definition, keepLegacy);
405
+ changed = true;
406
+ if (!keepLegacy) {
407
+ const names = presenterNames.get(file) ?? new Map();
408
+ names.set(definition.name, definition.exportName);
409
+ presenterNames.set(file, names);
410
+ }
411
+ else
412
+ issue(file, 'Some factory consumers could not be migrated; legacy factory retained alongside explicit dependencies.');
413
+ }
414
+ if (changed) {
415
+ try {
416
+ content = migrateAsyncHelpers(content, file);
417
+ }
418
+ catch (error) {
419
+ issue(file, String(error));
420
+ }
421
+ upgradedSources.set(file, content);
422
+ }
423
+ }
424
+ const translationMigration = migrateTranslationsSources(new Map([...originals]
425
+ .filter(([file]) => file.startsWith(path.join(root, 'src') + path.sep))
426
+ .map(([file, bytes]) => [file, upgradedSources.get(file) ?? bytes.toString()])), resolve);
427
+ for (const [file, content] of translationMigration.updates)
428
+ upgradedSources.set(file, content);
429
+ for (const problem of translationMigration.issues)
430
+ issue(problem.file, problem.reason);
431
+ for (const [file, bytes] of originals) {
432
+ if (!/\.[jt]s$/.test(file) || isBackup(mapping.get(file) ?? file))
433
+ continue;
434
+ const source = parseSource(upgradedSources.get(file) ?? bytes.toString(), file);
435
+ const removed = new Set(['createPresenterFactory', 'createPresenter', 'createStoreFactory', 'createStore', 'createAsyncHelpers']);
436
+ const apps = new Set();
437
+ for (const statement of source.statements) {
438
+ if (!ts.isImportDeclaration(statement) ||
439
+ !ts.isStringLiteral(statement.moduleSpecifier) ||
440
+ !['@azure-net/kit', '@azure-net/kit/delivery'].includes(statement.moduleSpecifier.text))
441
+ continue;
442
+ const bindings = statement.importClause?.namedBindings;
443
+ if (!bindings || !ts.isNamedImports(bindings))
444
+ continue;
445
+ for (const binding of bindings.elements) {
446
+ const name = (binding.propertyName ?? binding.name).text;
447
+ if (removed.has(name))
448
+ issue(file, 'Removed kit API ' + name + ' remains; manual migration is required before build.');
449
+ if (name === 'createApp')
450
+ apps.add(binding.name.text);
451
+ }
452
+ }
453
+ const inspect = (node) => {
454
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && apps.has(node.expression.text)) {
455
+ const callback = node.arguments[0];
456
+ if (callback && (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback))) {
457
+ const parameter = callback.parameters[0]?.name;
458
+ const check = (child) => {
459
+ if (ts.isCallExpression(child) &&
460
+ ts.isPropertyAccessExpression(child.expression) &&
461
+ child.expression.name.text === 'dependencies') {
462
+ let base = child.expression.expression;
463
+ while (ts.isCallExpression(base) && ts.isPropertyAccessExpression(base.expression))
464
+ base = base.expression.expression;
465
+ if (parameter && ts.isIdentifier(parameter) && ts.isIdentifier(base) && base.text === parameter.text)
466
+ issue(file, 'createApp.dependencies/Container was removed. Move these bindings to a DiContainer; consumer changes require manual review.');
467
+ }
468
+ ts.forEachChild(child, check);
469
+ };
470
+ check(callback.body);
471
+ }
472
+ }
473
+ ts.forEachChild(node, inspect);
474
+ };
475
+ inspect(source);
476
+ }
477
+ const providers = new Map();
478
+ for (const [file, bytes] of originals)
479
+ if (/Provider\.[jt]s$/.test(file) && mapping.has(file)) {
480
+ const source = parseSource(bytes.toString(), file);
481
+ const imports = new Map();
482
+ for (const s of source.statements)
483
+ if (ts.isImportDeclaration(s) && ts.isStringLiteral(s.moduleSpecifier)) {
484
+ const target = resolve(file, s.moduleSpecifier.text);
485
+ if (s.importClause?.name)
486
+ imports.set(s.importClause.name.text, { file: target, source: s.moduleSpecifier.text, exported: 'default' });
487
+ const bindings = s.importClause?.namedBindings;
488
+ if (bindings && ts.isNamedImports(bindings))
489
+ for (const e of bindings.elements)
490
+ imports.set(e.name.text, {
491
+ file: target && (exportedSymbol(target, e.propertyName?.text ?? e.name.text)?.file ?? target),
492
+ source: s.moduleSpecifier.text,
493
+ exported: (target && exportedSymbol(target, e.propertyName?.text ?? e.name.text)?.name) || e.propertyName?.text || e.name.text
494
+ });
495
+ }
496
+ for (const statement of source.statements)
497
+ if (ts.isVariableStatement(statement))
498
+ for (const d of statement.declarationList.declarations) {
499
+ if (!ts.isIdentifier(d.name) || !d.initializer || !ts.isCallExpression(d.initializer))
500
+ continue;
501
+ if (!ts.isIdentifier(d.initializer.expression) ||
502
+ !['createBoundaryProvider', 'CreateDiContainer'].includes(imports.get(d.initializer.expression.text)?.exported ?? ''))
503
+ continue;
504
+ if (source.statements.some((s) => ts.isImportDeclaration(s) && s.importClause?.namedBindings && ts.isNamespaceImport(s.importClause.namedBindings))) {
505
+ issue(file, 'Namespace imports in a provider require manual DI migration.');
506
+ continue;
507
+ }
508
+ const settings = d.initializer.arguments[1];
509
+ if (!settings || !ts.isObjectLiteralExpression(settings))
510
+ continue;
511
+ const register = settings.properties.find((p) => propName(p.name) === 'register');
512
+ if (!register || !ts.isPropertyAssignment(register) || !ts.isArrowFunction(register.initializer))
513
+ continue;
514
+ const body = register.initializer.body;
515
+ if (ts.isBlock(body)) {
516
+ issue(file, 'Custom block register requires manual DI migration.');
517
+ continue;
518
+ }
519
+ const values = unwrap(body);
520
+ if (!ts.isObjectLiteralExpression(values))
521
+ continue;
522
+ if (settings.properties.some((p) => !['dependsOn', 'register'].includes(propName(p.name) ?? ''))) {
523
+ issue(file, 'Provider has custom settings/boot; not inlined.');
524
+ continue;
525
+ }
526
+ if (source.statements.some((s) => !ts.isImportDeclaration(s) && s !== statement && !ts.isEmptyStatement(s))) {
527
+ issue(file, 'Provider has extra module declarations; manual migration required.');
528
+ continue;
529
+ }
530
+ providers.set(d.name.text, { file, name: d.name.text, source, settings, values, imports });
531
+ }
532
+ }
533
+ const diFile = path.join(app, 'Platform/Foundation/DI/ApplicationProvider.ts');
534
+ const diImports = new Set(["import { CreateDiContainer } from '@azure-net/kit';"]);
535
+ const expressions = new Map();
536
+ const locals = [];
537
+ const cached = new Map();
538
+ const active = new Set();
539
+ let counter = 0;
540
+ function expand(provider, key) {
541
+ const id = `${provider.name}.${key}`;
542
+ if (cached.has(id))
543
+ return cached.get(id);
544
+ if (active.has(id))
545
+ throw new Error(`Circular DI: ${id}`);
546
+ active.add(id);
547
+ const property = provider.values.properties.find((p) => propName(p.name) === key);
548
+ if (!property ||
549
+ !ts.isPropertyAssignment(property) ||
550
+ !ts.isArrowFunction(property.initializer) ||
551
+ property.initializer.parameters.length ||
552
+ ts.isBlock(property.initializer.body))
553
+ throw new Error(`Unsupported factory ${id}`);
554
+ const expression = property.initializer.body;
555
+ const result = ts.transform(expression, [
556
+ (context) => {
557
+ const visit = (node) => {
558
+ if (ts.isShorthandPropertyAssignment(node) && provider.imports.has(node.name.text)) {
559
+ const visited = visit(node.name);
560
+ const imported = provider.imports.get(node.name.text);
561
+ if (!providers.has(imported.exported))
562
+ return ts.factory.createPropertyAssignment(node.name.text, ts.factory.createIdentifier(`M${[...providers.keys()].indexOf(provider.name)}_${visited.text}`));
563
+ }
564
+ if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression)) {
565
+ const imported = provider.imports.get(node.expression.text);
566
+ const dep = providers.get(imported?.exported ?? node.expression.text);
567
+ if (dep)
568
+ return ts.factory.createCallExpression(ts.factory.createIdentifier(expand(dep, node.name.text)), undefined, []);
569
+ }
570
+ if (ts.isIdentifier(node) &&
571
+ !(node.parent &&
572
+ ((ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) ||
573
+ (ts.isPropertyAssignment(node.parent) && node.parent.name === node)))) {
574
+ const imported = provider.imports.get(node.text);
575
+ if (imported &&
576
+ !providers.has(imported.exported) &&
577
+ !['createBoundaryProvider', 'CreateDiContainer'].includes(imported.exported)) {
578
+ const to = imported.file && mapping.get(imported.file);
579
+ if (to && isBackup(to))
580
+ throw new Error(`Dependency is archived: ${imported.file}`);
581
+ const local = `M${[...providers.keys()].indexOf(provider.name)}_${node.text}`;
582
+ const source = imported.file ? specifier(to ?? imported.file, diFile) : imported.source;
583
+ diImports.add(imported.exported === 'default'
584
+ ? `import ${local} from ${JSON.stringify(source)};`
585
+ : `import { ${imported.exported} as ${local} } from ${JSON.stringify(source)};`);
586
+ if (node.parent && ts.isShorthandPropertyAssignment(node.parent))
587
+ return node;
588
+ return ts.factory.createIdentifier(local);
589
+ }
590
+ }
591
+ return ts.visitEachChild(node, visit, context);
592
+ };
593
+ return (node) => ts.visitNode(node, visit);
594
+ }
595
+ ]);
596
+ const code = printer.printNode(ts.EmitHint.Expression, result.transformed[0], provider.source);
597
+ result.dispose();
598
+ expressions.set(id, code);
599
+ const local = `get${++counter}_${key}`;
600
+ // Private lazy getters preserve original per-service sharing without nested providers.
601
+ if (/DatasourceProvider$/.test(provider.name)) {
602
+ locals.push(`const datasource${counter} = ${code};\nconst ${local} = () => datasource${counter};`);
603
+ }
604
+ else if (!/ApplicationProvider$/.test(provider.name))
605
+ locals.push(`const ${local} = (() => {\nlet initialized = false;\nlet value: ReturnType<typeof create>;\nconst create = () => (${code});\nreturn () => { if (!initialized) { value = create(); initialized = true; } return value; };\n})();`);
606
+ cached.set(id, local);
607
+ active.delete(id);
608
+ return local;
609
+ }
610
+ const publicServices = new Map();
611
+ const converted = new Set();
612
+ for (const provider of providers.values())
613
+ if (/ApplicationProvider$/.test(provider.name)) {
614
+ const checkpoint = {
615
+ locals: locals.length,
616
+ counter,
617
+ cached: new Map(cached),
618
+ expressions: new Map(expressions),
619
+ imports: new Set(diImports)
620
+ };
621
+ try {
622
+ const entries = [];
623
+ for (const property of provider.values.properties) {
624
+ const key = propName(property.name);
625
+ if (!key || !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key))
626
+ throw new Error('Unsupported service key');
627
+ if (publicServices.has(key))
628
+ throw new Error(`Duplicate application service ${key}`);
629
+ expand(provider, key);
630
+ entries.push([key, `() => (${expressions.get(`${provider.name}.${key}`)})`]);
631
+ }
632
+ for (const [key, value] of entries)
633
+ publicServices.set(key, value);
634
+ converted.add(provider.name);
635
+ }
636
+ catch (error) {
637
+ locals.length = checkpoint.locals;
638
+ counter = checkpoint.counter;
639
+ cached.clear();
640
+ for (const [key, value] of checkpoint.cached)
641
+ cached.set(key, value);
642
+ expressions.clear();
643
+ for (const [key, value] of checkpoint.expressions)
644
+ expressions.set(key, value);
645
+ diImports.clear();
646
+ for (const value of checkpoint.imports)
647
+ diImports.add(value);
648
+ active.clear();
649
+ issue(provider.file, `DI not migrated: ${String(error)}`);
650
+ }
651
+ }
652
+ const writes = new Map();
653
+ const replacements = [];
654
+ function namespacePresenterName(file, name, seen = new Set()) {
655
+ if (seen.has(file))
656
+ return undefined;
657
+ seen.add(file);
658
+ const direct = presenterNames.get(file)?.get(name);
659
+ if (direct)
660
+ return direct;
661
+ const source = parseSource(originals.get(file)?.toString() ?? '', file);
662
+ for (const statement of source.statements) {
663
+ if (!ts.isExportDeclaration(statement))
664
+ continue;
665
+ // Named exports keep their public alias when rewritten below.
666
+ if (statement.exportClause &&
667
+ ts.isNamedExports(statement.exportClause) &&
668
+ statement.exportClause.elements.some((entry) => entry.name.text === name))
669
+ return name;
670
+ }
671
+ for (const statement of source.statements) {
672
+ if (!ts.isExportDeclaration(statement) ||
673
+ statement.exportClause ||
674
+ !statement.moduleSpecifier ||
675
+ !ts.isStringLiteral(statement.moduleSpecifier))
676
+ continue;
677
+ const target = resolve(file, statement.moduleSpecifier.text);
678
+ const renamed = target && namespacePresenterName(target, name, seen);
679
+ if (renamed)
680
+ return renamed;
681
+ }
682
+ return undefined;
683
+ }
684
+ function rewrite(file, destination, input) {
685
+ replacements.length = 0;
686
+ const source = parseSource(input, file);
687
+ const namespaces = new Map();
688
+ for (const statement of source.statements) {
689
+ if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier))
690
+ continue;
691
+ const bindings = statement.importClause?.namedBindings;
692
+ const target = resolve(file, statement.moduleSpecifier.text);
693
+ if (target && bindings && ts.isNamespaceImport(bindings))
694
+ namespaces.set(bindings.name.text, target);
695
+ }
696
+ const replacement = (start, end, text) => {
697
+ replacements.push({ start, end, text });
698
+ };
699
+ const checkBoundary = (to) => {
700
+ const a = slash(path.relative(app, destination)).split('/'), b = slash(path.relative(app, to)).split('/');
701
+ if (a[0] === 'Contexts' && b[0] === 'Contexts' && a[1] !== b[1] && b[1] !== 'SharedKernel')
702
+ issue(destination, `Review cross-context dependency on ${to}; not moved into SharedKernel automatically.`);
703
+ if (a[0] === 'Presentation' && b[0] === 'Presentation' && a[1] !== b[1] && b[1] !== 'Shared')
704
+ issue(destination, `Review cross-workspace dependency on ${to}.`);
705
+ };
706
+ const newTarget = (value) => {
707
+ const resolved = resolve(file, value);
708
+ const target = resolved && (mapping.get(resolved) ?? resolved);
709
+ if (!target) {
710
+ if (value.startsWith('.') || [...aliases.keys()].some((a) => value === a || value.startsWith(a + '/')))
711
+ issue(destination, `Unresolved local module/asset: ${value}`);
712
+ return undefined;
713
+ }
714
+ if (isBackup(target)) {
715
+ issue(destination, `Import needs review: ${value} -> ${target}`);
716
+ return undefined;
717
+ }
718
+ checkBoundary(target);
719
+ return specifier(target, destination) + (value.slice(1).match(/[?#].*$/)?.[0] ?? '');
720
+ };
721
+ const visit = (node) => {
722
+ if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression)) {
723
+ const target = namespaces.get(node.expression.text);
724
+ const name = target && namespacePresenterName(target, node.name.text);
725
+ if (name)
726
+ replacement(node.name.getStart(source), node.name.end, name);
727
+ }
728
+ if (ts.isElementAccessExpression(node) && ts.isIdentifier(node.expression) && ts.isStringLiteral(node.argumentExpression)) {
729
+ const target = namespaces.get(node.expression.text);
730
+ const name = target && namespacePresenterName(target, node.argumentExpression.text);
731
+ if (name)
732
+ replacement(node.argumentExpression.getStart(source), node.argumentExpression.end, JSON.stringify(name));
733
+ }
734
+ if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
735
+ const target = resolve(file, node.moduleSpecifier.text);
736
+ const bindings = node.importClause?.namedBindings;
737
+ if (target && bindings && ts.isNamedImports(bindings) && !node.importClause?.name) {
738
+ const groups = new Map();
739
+ for (const e of bindings.elements) {
740
+ const name = e.propertyName?.text ?? e.name.text;
741
+ const symbol = exportedSymbol(target, name);
742
+ const declared = symbol?.file ?? target;
743
+ const to = mapping.get(declared) ?? declared;
744
+ const isProvider = converted.has(name) && providers.get(name)?.file === declared;
745
+ const spec = isProvider ? '#platform/Foundation/DI/ApplicationProvider' : isBackup(to) ? undefined : specifier(to, destination);
746
+ if (!spec) {
747
+ issue(destination, `Unresolved imported symbol ${name} from ${node.moduleSpecifier.text}`);
748
+ continue;
749
+ }
750
+ checkBoundary(to);
751
+ const imported = isProvider
752
+ ? 'ApplicationProvider'
753
+ : (presenterNames.get(declared)?.get(symbol?.name ?? name) ?? symbol?.name ?? name);
754
+ const parts = groups.get(spec) ?? [];
755
+ parts.push(`${!node.importClause?.isTypeOnly && e.isTypeOnly ? 'type ' : ''}${imported}${imported === e.name.text ? '' : ` as ${e.name.text}`}`);
756
+ groups.set(spec, parts);
757
+ }
758
+ if ([...groups.values()].reduce((n, g) => n + g.length, 0) === bindings.elements.length) {
759
+ replacement(node.getStart(source), node.end, [...groups]
760
+ .map(([spec, names]) => `import ${node.importClause?.isTypeOnly ? 'type ' : ''}{ ${names.join(', ')} } from ${JSON.stringify(spec)};`)
761
+ .join('\n'));
762
+ return;
763
+ }
764
+ }
765
+ const next = newTarget(node.moduleSpecifier.text);
766
+ if (next)
767
+ replacement(node.moduleSpecifier.getStart(source), node.moduleSpecifier.end, JSON.stringify(next));
768
+ return;
769
+ }
770
+ if (ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
771
+ const target = resolve(file, node.moduleSpecifier.text);
772
+ if (target && node.exportClause && ts.isNamedExports(node.exportClause)) {
773
+ for (const entry of node.exportClause.elements) {
774
+ const name = (entry.propertyName ?? entry.name).text;
775
+ const symbol = exportedSymbol(target, name);
776
+ const renamed = symbol && presenterNames.get(symbol.file)?.get(symbol.name);
777
+ if (renamed)
778
+ replacement(entry.getStart(source), entry.end, `${entry.isTypeOnly ? 'type ' : ''}${renamed} as ${entry.name.text}`);
779
+ }
780
+ }
781
+ const next = newTarget(node.moduleSpecifier.text);
782
+ if (next)
783
+ replacement(node.moduleSpecifier.getStart(source), node.moduleSpecifier.end, JSON.stringify(next));
784
+ return;
785
+ }
786
+ if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === 'URL') {
787
+ const arg = node.arguments?.[0];
788
+ if (arg && ts.isStringLiteral(arg)) {
789
+ const resolved = resolve(file, arg.text);
790
+ const target = resolved && mapping.get(resolved);
791
+ if (target && !isBackup(target)) {
792
+ let relative = slash(path.relative(path.dirname(destination), target));
793
+ if (!relative.startsWith('.'))
794
+ relative = './' + relative;
795
+ replacement(arg.getStart(source), arg.end, JSON.stringify(relative));
796
+ }
797
+ }
798
+ }
799
+ if (ts.isCallExpression(node) &&
800
+ ts.isPropertyAccessExpression(node.expression) &&
801
+ node.expression.getText(source).startsWith('import.meta.glob'))
802
+ issue(destination, 'Review import.meta.glob patterns after folder relocation.');
803
+ if (ts.isCallExpression(node) &&
804
+ (node.expression.kind === ts.SyntaxKind.ImportKeyword || (ts.isIdentifier(node.expression) && node.expression.text === 'require'))) {
805
+ const arg = node.arguments[0];
806
+ if (arg && (ts.isStringLiteral(arg) || ts.isNoSubstitutionTemplateLiteral(arg))) {
807
+ const target = resolve(file, arg.text);
808
+ if (target && presenterNames.has(target))
809
+ issue(destination, `Dynamic presenter import: use the new use{Name}Presenter export from ${arg.text}.`);
810
+ const next = newTarget(arg.text);
811
+ if (next)
812
+ replacement(arg.getStart(source), arg.end, JSON.stringify(next));
813
+ }
814
+ else
815
+ issue(destination, 'Computed import/require needs manual review.');
816
+ }
817
+ if (ts.isStringLiteral(node) && node.parent && ts.isLiteralTypeNode(node.parent) && ts.isImportTypeNode(node.parent.parent)) {
818
+ const next = newTarget(node.text);
819
+ if (next)
820
+ replacement(node.getStart(source), node.end, JSON.stringify(next));
821
+ }
822
+ ts.forEachChild(node, visit);
823
+ };
824
+ visit(source);
825
+ let output = input;
826
+ for (const edit of replacements.sort((a, b) => b.start - a.start))
827
+ output = output.slice(0, edit.start) + edit.text + output.slice(edit.end);
828
+ // Local style/asset references are filesystem links too; ordinary strings are not rewritten.
829
+ output = output.replace(/((?:@use|@forward|@import)\s+|\b(?:src|href)\s*=\s*|\burl\(\s*)(['"])([^'"\n]+)\2/g, (whole, prefix, quote, value, offset) => {
830
+ const resolved = resolve(file, value) ?? (/^@/.test(prefix) ? resolve(file, './' + value) : undefined);
831
+ const target = resolved && (mapping.get(resolved) ?? resolved);
832
+ if (!target || isBackup(target))
833
+ return whole;
834
+ let relative = slash(path.relative(path.dirname(destination), target));
835
+ if (!relative.startsWith('.'))
836
+ relative = './' + relative;
837
+ const oldNamespace = path
838
+ .basename(value)
839
+ .replace(/^_/, '')
840
+ .replace(/\.(scss|sass|css)$/, '');
841
+ const keepNamespace = prefix.startsWith('@use') && !/^\s+as\b/.test(output.slice(offset + whole.length)) && /^[A-Za-z_][\w-]*$/.test(oldNamespace);
842
+ return prefix + quote + relative + quote + (keepNamespace ? ` as ${oldNamespace}` : '');
843
+ });
844
+ return output;
845
+ }
846
+ for (const [file, bytes] of originals) {
847
+ if (!file.startsWith(path.join(root, 'src') + path.sep))
848
+ continue;
849
+ const to = mapping.get(file) ?? file;
850
+ if (!oldRoots.size && !upgradedSources.size)
851
+ continue;
852
+ const output = isBackup(to) || !textFile.test(file) ? bytes : Buffer.from(rewrite(file, to, upgradedSources.get(file) ?? bytes.toString()));
853
+ if (writes.has(to) && path.basename(to) === 'index.ts') {
854
+ const barrel = ts.createSourceFile(to, writes.get(to).toString() + '\n' + output.toString(), ts.ScriptTarget.Latest, true);
855
+ writes.set(to, Buffer.from([...new Set(barrel.statements.map((statement) => printer.printNode(ts.EmitHint.Unspecified, statement, barrel)))].join('\n') +
856
+ '\n'));
857
+ }
858
+ else if (to !== file || !bytes.equals(output))
859
+ writes.set(to, output);
860
+ }
861
+ if (oldRoots.size) {
862
+ if (originals.has(diFile))
863
+ throw new Error(`Refusing to overwrite existing ${diFile}`);
864
+ writes.set(diFile, Buffer.from(`${[...diImports].join('\n')}\n\nexport const ApplicationProvider = CreateDiContainer('ApplicationProvider', {\nregister: () => {\n${locals.join('\n')}\nreturn {\n${[...publicServices].map(([name, getter]) => `${name}: ${getter}`).join(',\n')}\n};\n}\n});\n`));
865
+ writes.set(path.join(path.dirname(diFile), 'index.ts'), Buffer.from("export * from './ApplicationProvider';\n"));
866
+ workspaces.add('Shared');
867
+ for (const context of contexts)
868
+ for (const folder of CONTEXT_DIRS) {
869
+ const index = path.join(app, 'Contexts', context, folder, 'index.ts');
870
+ if (!writes.has(index) && !originals.has(index))
871
+ writes.set(index, Buffer.from('export {};\n'));
872
+ }
873
+ for (const workspace of workspaces)
874
+ for (const folder of ['Ui', 'Scopes', 'Shell']) {
875
+ const index = path.join(app, 'Presentation', workspace, folder, 'index.ts');
876
+ if (!writes.has(index) && !originals.has(index))
877
+ writes.set(index, Buffer.from('export {};\n'));
878
+ }
879
+ const packageFile = path.join(root, 'package.json');
880
+ writes.set(packageFile, Buffer.from(await mergePackageImports(originals.get(packageFile)?.toString() ?? '{}')));
881
+ }
882
+ const configFile = [...originals.keys()].find((p) => /azure-net\.config\.[jt]s$/.test(p)) ?? path.join(root, 'azure-net.config.ts');
883
+ let config = flattenConfigLiterals(originals.get(configFile)?.toString() ?? 'export default {};');
884
+ const configSource = ts.createSourceFile(configFile, config, ts.ScriptTarget.Latest, true);
885
+ const exported = configSource.statements.find(ts.isExportAssignment);
886
+ if (!exported || !ts.isObjectLiteralExpression(exported.expression))
887
+ throw new Error('Migration requires an object-literal default config; normalize it first.');
888
+ const kept = exported.expression.properties.filter((p) => !['defaultPresenterFactory', ...(oldRoots.size ? ['contexts', 'coreAlias', 'sharedAlias', 'presentationWorkspaces'] : [])].includes(propName(p.name) ?? ''));
889
+ if (oldRoots.size) {
890
+ kept.push(ts.factory.createPropertyAssignment('presentationWorkspaces', ts.factory.createArrayLiteralExpression([...workspaces].sort().map((w) => ts.factory.createStringLiteral(w)))));
891
+ if (!kept.some((p) => propName(p.name) === 'runChecksBeforePush'))
892
+ kept.push(ts.factory.createPropertyAssignment('runChecksBeforePush', ts.factory.createTrue()));
893
+ }
894
+ config =
895
+ config.slice(0, exported.expression.getStart(configSource)) +
896
+ printer.printNode(ts.EmitHint.Expression, ts.factory.createObjectLiteralExpression(kept, true), configSource) +
897
+ config.slice(exported.expression.end);
898
+ if (oldRoots.size || kept.length !== exported.expression.properties.length)
899
+ writes.set(configFile, Buffer.from(config));
900
+ const tsconfig = path.join(root, 'tsconfig.json');
901
+ if (oldRoots.size && originals.has(tsconfig)) {
902
+ const parsed = ts.parseConfigFileTextToJson(tsconfig, originals.get(tsconfig).toString());
903
+ if (parsed.error)
904
+ throw new Error('Cannot parse tsconfig.json safely.');
905
+ const exclude = parsed.config.exclude ?? ['node_modules', 'bower_components', 'jspm_packages'];
906
+ parsed.config.exclude = [...new Set([...exclude, 'src/app/.backup/**'])];
907
+ writes.set(tsconfig, Buffer.from(JSON.stringify(parsed.config, null, '\t') + '\n'));
908
+ }
909
+ if (oldRoots.size) {
910
+ const diSource = ts.createSourceFile(diFile, writes.get(diFile).toString(), ts.ScriptTarget.Latest, true);
911
+ writes.set(diFile, Buffer.from(printer.printFile(diSource)));
912
+ }
913
+ if (!oldRoots.size && !upgradedSources.size && !issues.length)
914
+ throw new Error('Nothing to migrate.');
915
+ const moves = [...mapping].map(([from, to]) => ({ from, to }));
916
+ for (const item of issues)
917
+ item.file = mapping.get(item.file) ?? item.file;
918
+ return {
919
+ root,
920
+ moves,
921
+ issues,
922
+ writes,
923
+ originals,
924
+ removes: [...mapping.keys()],
925
+ workspaces: [...workspaces]
926
+ };
927
+ }
928
+ export async function applyMigration(plan) {
929
+ // Verify the plan before creating any backup or touching user files.
930
+ for (const file of await files(path.join(plan.root, 'src')))
931
+ if (!plan.originals.has(file))
932
+ throw new Error(`New source file since planning: ${file}`);
933
+ for (const [file, bytes] of plan.originals)
934
+ if (!(await fs.readFile(file)).equals(bytes))
935
+ throw new Error(`File changed since planning: ${file}`);
936
+ for (const target of plan.writes.keys())
937
+ if (!plan.originals.has(target)) {
938
+ try {
939
+ await fs.access(target);
940
+ throw new Error(`Migration target already exists: ${target}`);
941
+ }
942
+ catch (error) {
943
+ if (error.code !== 'ENOENT')
944
+ throw error;
945
+ }
946
+ }
947
+ const snapshot = await fs.mkdtemp(path.join(await fs
948
+ .mkdir(path.join(plan.root, '.azure-net/migrations'), { recursive: true })
949
+ .then(() => path.join(plan.root, '.azure-net/migrations')), 'migration-'));
950
+ const touched = new Set([...plan.removes, ...plan.writes.keys()]);
951
+ for (const file of touched)
952
+ if (plan.originals.has(file)) {
953
+ const target = path.join(snapshot, 'original', path.relative(plan.root, file));
954
+ await fs.mkdir(path.dirname(target), { recursive: true });
955
+ await fs.writeFile(target, plan.originals.get(file));
956
+ }
957
+ await fs.writeFile(path.join(snapshot, 'manifest.json'), JSON.stringify({
958
+ moves: plan.moves,
959
+ generated: [...plan.writes.keys()].filter((p) => !plan.originals.has(p)),
960
+ issues: plan.issues,
961
+ hashes: [...touched]
962
+ .filter((p) => plan.originals.has(p))
963
+ .map((p) => ({ file: p, sha256: createHash('sha256').update(plan.originals.get(p)).digest('hex') }))
964
+ }, null, 2));
965
+ const report = path.join(snapshot, 'Report.md');
966
+ await fs.writeFile(report, `# Migration report\n\n${plan.moves.length} files classified. Original bytes: ./original. Unknown files: src/app/.backup.\n\nRecognized presenter factories are converted to classes with scoped RegisterProvider accessors. Closure state is preserved. Async helpers are imported directly. Review unsupported factories and lifecycle requirements. Run azure-net check internal and your package manager's check/build.\n\n## Review\n${plan.issues.map((i) => `- [${slash(path.relative(plan.root, i.file))}](${i.file}): ${i.reason}`).join('\n')}\n\n## Moves\n${plan.moves.map((m) => `- ${slash(path.relative(plan.root, m.from))} -> [${slash(path.relative(plan.root, m.to))}](${m.to})`).join('\n')}\n`);
967
+ try {
968
+ for (const [file, bytes] of plan.writes) {
969
+ await fs.mkdir(path.dirname(file), { recursive: true });
970
+ await fs.writeFile(file, bytes);
971
+ }
972
+ for (const file of plan.removes)
973
+ if (!plan.writes.has(file))
974
+ await fs.unlink(file);
975
+ }
976
+ catch (error) {
977
+ for (const file of touched) {
978
+ const original = plan.originals.get(file);
979
+ if (original) {
980
+ await fs.mkdir(path.dirname(file), { recursive: true });
981
+ await fs.writeFile(file, original);
982
+ }
983
+ else
984
+ await fs.rm(file, { force: true });
985
+ }
986
+ throw new Error(`Migration rolled back after I/O failure. Snapshot: ${snapshot}`, { cause: error });
987
+ }
988
+ async function removeEmpty(dir) {
989
+ for (const entry of await fs.readdir(dir, { withFileTypes: true }))
990
+ if (entry.isDirectory() && entry.name !== '.backup')
991
+ await removeEmpty(path.join(dir, entry.name));
992
+ if (!(await fs.readdir(dir)).length)
993
+ await fs.rmdir(dir);
994
+ }
995
+ try {
996
+ await removeEmpty(path.join(plan.root, 'src/app'));
997
+ }
998
+ catch (error) {
999
+ console.warn(`Files migrated, but empty directory cleanup failed: ${String(error)}`);
1000
+ }
1001
+ return report;
1002
+ }
1003
+ export default async function migrate(options = {}) {
1004
+ const plan = await planMigration();
1005
+ console.log(`Migration plan: ${plan.moves.length} files, ${plan.workspaces.length} workspaces, ${plan.issues.length} review items.`);
1006
+ for (const move of plan.moves)
1007
+ console.log(`${path.relative(plan.root, move.from)} -> ${path.relative(plan.root, move.to)}`);
1008
+ if (options.dryRun) {
1009
+ for (const issue of plan.issues)
1010
+ console.warn(`${issue.file}: ${issue.reason}`);
1011
+ return;
1012
+ }
1013
+ if (!options.yes) {
1014
+ const answer = await prompts({
1015
+ type: 'confirm',
1016
+ name: 'apply',
1017
+ initial: false,
1018
+ message: 'Apply migration? Original files will be backed up; review/check/build is required afterwards.'
1019
+ });
1020
+ if (!answer.apply)
1021
+ return;
1022
+ }
1023
+ const report = await applyMigration(plan);
1024
+ console.log(`Migration complete. Review report: ${report}`);
1025
+ for (const issue of plan.issues)
1026
+ console.warn(`${issue.file}: ${issue.reason}`);
1027
+ }