@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.
- package/README.md +201 -0
- package/bin/azure-net.js +236 -221
- package/dist/bin/azure-net.d.ts +2 -0
- package/dist/src/checks/architecture.d.ts +20 -0
- package/dist/src/checks/checkDomain.d.ts +1 -0
- package/dist/src/checks/checkFoldersStructure.d.ts +1 -0
- package/dist/src/checks/checkHooks.d.ts +1 -0
- package/dist/src/checks/checkImports.d.ts +1 -0
- package/dist/src/checks/checkInternal.d.ts +1 -0
- package/dist/src/checks/checkLayerBoundaries.d.ts +1 -0
- package/dist/src/checks/checkLayerImports.d.ts +1 -0
- package/dist/src/checks/checkPresenterNames.d.ts +1 -0
- package/dist/src/checks/checkProject.d.ts +1 -0
- package/dist/src/checks/checkProviderGraph.d.ts +1 -0
- package/dist/src/checks/checkProviderNames.d.ts +1 -0
- package/dist/src/generate/layers/generateRepository.d.ts +1 -0
- package/dist/src/generate/layers/generateRepositoryMethod.d.ts +1 -0
- package/dist/src/generate/layers/generateUseCases.d.ts +1 -0
- package/dist/src/generate/module/generateDomain.d.ts +8 -0
- package/dist/src/generate/module/generateModule.d.ts +1 -0
- package/dist/src/generate/module/generateModulePreset.d.ts +1 -0
- package/dist/src/generate/module/generatePresenter.d.ts +29 -0
- package/dist/src/generate/module/generateRepository.d.ts +15 -0
- package/dist/src/generate/module/generateRepositoryMethod.d.ts +2 -0
- package/dist/src/generate/module/generateSchemaFromType.d.ts +9 -0
- package/dist/src/generate/module/generateStore.d.ts +1 -0
- package/dist/src/generate/module/generateTypesJson.d.ts +2 -0
- package/dist/src/generate/module/generateUseCases.d.ts +1 -0
- package/dist/src/generate/module/repositoryModuleShared.d.ts +89 -0
- package/dist/src/generate/routes/generateRoutes.d.ts +14 -0
- package/dist/src/generate/ui/generateComponent.d.ts +1 -0
- package/dist/src/generate/ui/generateDesignComponent.d.ts +1 -0
- package/dist/src/generate/ui/generateWidget.d.ts +1 -0
- package/dist/src/git/commit.d.ts +23 -0
- package/dist/src/git/push.d.ts +11 -0
- package/dist/src/git/runGit.d.ts +2 -0
- package/dist/src/init/initAliases.d.ts +12 -0
- package/dist/src/init/initEdges.d.ts +2 -0
- package/dist/src/init/initStructure.d.ts +1 -0
- package/dist/src/install/configureCommitTools.d.ts +1 -0
- package/dist/src/install/installFresh.d.ts +1 -0
- package/dist/src/migrate/migrate.d.ts +22 -0
- package/dist/src/migrate/presenterFactories.d.ts +9 -0
- package/dist/src/migrate/presenters.d.ts +13 -0
- package/dist/src/migrate/translations.d.ts +8 -0
- package/dist/src/plugins/addLint.d.ts +5 -0
- package/dist/src/scaffolds/createCoreSchema.d.ts +1 -0
- package/dist/src/scaffolds/createDatasource.d.ts +1 -0
- package/dist/src/scaffolds/createProgram.d.ts +1 -0
- package/dist/src/scaffolds/createResponse.d.ts +1 -0
- package/dist/src/scaffolds/createSchemaRule.d.ts +1 -0
- package/dist/src/scaffolds/createTranslationManager.d.ts +1 -0
- package/{src/scaffolds/index.ts → dist/src/scaffolds/index.d.ts} +0 -2
- package/dist/src/scaffolds/registerDatasource.d.ts +2 -0
- package/dist/src/scaffolds/updateDefaultFactoryConfig.d.ts +1 -0
- package/dist/src/utils/architecture.d.ts +31 -0
- package/dist/src/utils/contextUtils.d.ts +6 -0
- package/dist/src/utils/fileUtils.d.ts +5 -0
- package/dist/src/utils/flattenConfig.d.ts +2 -0
- package/dist/src/utils/legacyAliases.d.ts +1 -0
- package/dist/src/utils/loadConfig.d.ts +16 -0
- package/dist/src/utils/packageImports.d.ts +4 -0
- package/dist/src/utils/sharedFoundation.d.ts +20 -0
- package/dist/src/utils/viteConfig.d.ts +9 -0
- package/package.json +17 -7
- package/src/checks/architecture.js +154 -0
- package/src/checks/checkDomain.js +124 -154
- package/src/checks/checkFoldersStructure.js +64 -52
- package/src/checks/checkHooks.js +12 -12
- package/src/checks/checkImports.js +26 -142
- package/src/checks/checkInternal.js +27 -24
- package/src/checks/checkLayerBoundaries.js +22 -197
- package/src/checks/checkLayerImports.js +14 -134
- package/src/checks/checkPresenterNames.js +156 -143
- package/src/checks/checkProject.js +27 -26
- package/src/checks/checkProviderGraph.js +182 -166
- package/src/checks/checkProviderNames.js +123 -112
- package/src/generate/layers/generateRepository.js +1 -109
- package/src/generate/layers/generateRepositoryMethod.js +1 -90
- package/src/generate/layers/generateUseCases.js +1 -44
- package/src/generate/module/generateDomain.js +81 -105
- package/src/generate/module/generateModule.js +34 -68
- package/src/generate/module/generateModulePreset.js +188 -248
- package/src/generate/module/generatePresenter.js +441 -369
- package/src/generate/module/generateRepository.js +102 -121
- package/src/generate/module/generateRepositoryMethod.js +117 -0
- package/src/generate/module/generateSchemaFromType.js +283 -280
- package/src/generate/module/generateStore.js +41 -0
- package/src/generate/module/generateTypesJson.js +196 -195
- package/src/generate/module/generateUseCases.js +47 -55
- package/src/generate/module/repositoryModuleShared.js +480 -459
- package/src/generate/routes/generateRoutes.js +4 -7
- package/src/generate/ui/generateComponent.js +44 -64
- package/src/generate/ui/generateDesignComponent.js +21 -41
- package/src/generate/ui/generateWidget.js +21 -41
- package/src/git/commit.js +67 -0
- package/src/git/push.js +19 -0
- package/src/git/runGit.js +11 -0
- package/src/init/initAliases.js +19 -135
- package/src/init/initEdges.js +79 -54
- package/src/init/initStructure.js +31 -145
- package/src/install/configureCommitTools.js +1 -0
- package/src/install/installFresh.js +39 -68
- package/src/migrate/migrate.js +1027 -0
- package/src/migrate/presenterFactories.js +189 -0
- package/src/migrate/presenters.js +373 -0
- package/src/migrate/translations.js +213 -0
- package/src/plugins/addLint.js +52 -219
- package/src/scaffolds/createCoreSchema.js +65 -91
- package/src/scaffolds/createDatasource.js +67 -101
- package/src/scaffolds/createProgram.js +8 -8
- package/src/scaffolds/createResponse.js +29 -29
- package/src/scaffolds/createSchemaRule.js +19 -19
- package/src/scaffolds/createTranslationManager.js +84 -37
- package/src/scaffolds/index.js +0 -2
- package/src/scaffolds/registerDatasource.js +59 -0
- package/src/scaffolds/updateDefaultFactoryConfig.js +31 -0
- package/src/utils/architecture.js +145 -0
- package/src/utils/contextUtils.js +32 -65
- package/src/utils/fileUtils.js +69 -50
- package/src/utils/flattenConfig.js +38 -0
- package/src/utils/legacyAliases.js +75 -0
- package/src/utils/loadConfig.js +76 -40
- package/src/utils/packageImports.js +63 -0
- package/src/utils/sharedFoundation.js +36 -42
- package/src/utils/viteConfig.js +295 -0
- package/.prettierrc +0 -7
- package/bin/azure-net.ts +0 -308
- package/eslint.config.js +0 -34
- package/src/checks/checkDomain.ts +0 -223
- package/src/checks/checkFoldersStructure.ts +0 -85
- package/src/checks/checkHooks.ts +0 -22
- package/src/checks/checkImports.ts +0 -193
- package/src/checks/checkInternal.ts +0 -48
- package/src/checks/checkLayerBoundaries.ts +0 -255
- package/src/checks/checkLayerImports.ts +0 -185
- package/src/checks/checkPresenterNames.ts +0 -203
- package/src/checks/checkProject.ts +0 -45
- package/src/checks/checkProviderGraph.ts +0 -234
- package/src/checks/checkProviderNames.ts +0 -165
- package/src/generate/layers/generateRepository.ts +0 -123
- package/src/generate/layers/generateRepositoryMethod.ts +0 -102
- package/src/generate/layers/generateUseCases.ts +0 -50
- package/src/generate/layers/repositoryShared.js +0 -506
- package/src/generate/layers/repositoryShared.ts +0 -662
- package/src/generate/module/generateDomain.ts +0 -147
- package/src/generate/module/generateModule.ts +0 -84
- package/src/generate/module/generateModulePreset.ts +0 -338
- package/src/generate/module/generatePresenter.ts +0 -498
- package/src/generate/module/generateRepository.ts +0 -158
- package/src/generate/module/generateSchemaFromType.ts +0 -368
- package/src/generate/module/generateTypesJson.ts +0 -259
- package/src/generate/module/generateUseCases.ts +0 -67
- package/src/generate/module/repositoryModuleShared.ts +0 -669
- package/src/generate/routes/generateRoutes.ts +0 -275
- package/src/generate/ui/generateComponent.ts +0 -100
- package/src/generate/ui/generateDesignComponent.ts +0 -71
- package/src/generate/ui/generateWidget.ts +0 -71
- package/src/init/initAliases.ts +0 -172
- package/src/init/initEdges.ts +0 -74
- package/src/init/initStructure.ts +0 -172
- package/src/install/installFresh.ts +0 -93
- package/src/plugins/addLint.ts +0 -274
- package/src/scaffolds/createCoreSchema.ts +0 -130
- package/src/scaffolds/createDatasource.ts +0 -161
- package/src/scaffolds/createDatasourceProvider.js +0 -127
- package/src/scaffolds/createDatasourceProvider.ts +0 -161
- package/src/scaffolds/createPresenterFactory.js +0 -83
- package/src/scaffolds/createPresenterFactory.ts +0 -102
- package/src/scaffolds/createProgram.ts +0 -19
- package/src/scaffolds/createResponse.ts +0 -66
- package/src/scaffolds/createSchemaRule.ts +0 -44
- package/src/scaffolds/createTranslationManager.ts +0 -80
- package/src/utils/contextUtils.ts +0 -88
- package/src/utils/fileUtils.ts +0 -74
- package/src/utils/loadConfig.ts +0 -68
- package/src/utils/sharedFoundation.ts +0 -70
- package/tsconfig.json +0 -19
- /package/{src/checks/index.ts → dist/src/checks/index.d.ts} +0 -0
- /package/{src/plugins/index.ts → dist/src/plugins/index.d.ts} +0 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
const kitModules = new Set(['@azure-net/kit/delivery', '@azure-net/edges']);
|
|
3
|
+
const helperNames = new Set(['createAsyncAction', 'createAsyncResource']);
|
|
4
|
+
const unwrap = (node) => ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isTypeAssertionExpression(node) || ts.isSatisfiesExpression(node)
|
|
5
|
+
? unwrap(node.expression)
|
|
6
|
+
: node;
|
|
7
|
+
/** Only evaluate syntax. Never import or execute the application's factory/configuration. */
|
|
8
|
+
export function inspectFactories(input, file) {
|
|
9
|
+
const source = ts.createSourceFile(file, input, ts.ScriptTarget.Latest, true);
|
|
10
|
+
const creators = new Set();
|
|
11
|
+
const helperFactories = new Set();
|
|
12
|
+
const helpers = new Map();
|
|
13
|
+
const occupied = new Set();
|
|
14
|
+
const visit = (node) => {
|
|
15
|
+
if (ts.isIdentifier(node))
|
|
16
|
+
occupied.add(node.text);
|
|
17
|
+
ts.forEachChild(node, visit);
|
|
18
|
+
};
|
|
19
|
+
visit(source);
|
|
20
|
+
for (const statement of source.statements) {
|
|
21
|
+
if (!ts.isImportDeclaration(statement) ||
|
|
22
|
+
!ts.isStringLiteral(statement.moduleSpecifier) ||
|
|
23
|
+
!kitModules.has(statement.moduleSpecifier.text) ||
|
|
24
|
+
statement.importClause?.isTypeOnly)
|
|
25
|
+
continue;
|
|
26
|
+
const bindings = statement.importClause?.namedBindings;
|
|
27
|
+
if (!bindings || !ts.isNamedImports(bindings))
|
|
28
|
+
continue;
|
|
29
|
+
for (const binding of bindings.elements) {
|
|
30
|
+
if (binding.isTypeOnly)
|
|
31
|
+
continue;
|
|
32
|
+
const name = (binding.propertyName ?? binding.name).text;
|
|
33
|
+
if (name === 'createPresenterFactory')
|
|
34
|
+
creators.add(binding.name.text);
|
|
35
|
+
if (name === 'createAsyncHelpers')
|
|
36
|
+
helperFactories.add(binding.name.text);
|
|
37
|
+
if (helperNames.has(name))
|
|
38
|
+
helpers.set(binding.name.text, name);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const statement of source.statements) {
|
|
42
|
+
if (!ts.isVariableStatement(statement))
|
|
43
|
+
continue;
|
|
44
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
45
|
+
const init = declaration.initializer && unwrap(declaration.initializer);
|
|
46
|
+
if (!init || !ts.isObjectBindingPattern(declaration.name))
|
|
47
|
+
continue;
|
|
48
|
+
const available = new Set();
|
|
49
|
+
if (ts.isCallExpression(init) &&
|
|
50
|
+
ts.isIdentifier(init.expression) &&
|
|
51
|
+
helperFactories.has(init.expression.text) &&
|
|
52
|
+
!init.arguments.length) {
|
|
53
|
+
for (const helper of helperNames)
|
|
54
|
+
available.add(helper);
|
|
55
|
+
}
|
|
56
|
+
else if (ts.isObjectLiteralExpression(init)) {
|
|
57
|
+
for (const property of init.properties) {
|
|
58
|
+
if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name))
|
|
59
|
+
continue;
|
|
60
|
+
const value = unwrap(property.initializer);
|
|
61
|
+
if (ts.isIdentifier(value) && helpers.get(value.text) === property.name.text)
|
|
62
|
+
available.add(property.name.text);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
for (const binding of declaration.name.elements) {
|
|
66
|
+
const key = binding.propertyName ?? binding.name;
|
|
67
|
+
if (ts.isIdentifier(key) &&
|
|
68
|
+
ts.isIdentifier(binding.name) &&
|
|
69
|
+
available.has(key.text) &&
|
|
70
|
+
!binding.initializer &&
|
|
71
|
+
!binding.dotDotDotToken)
|
|
72
|
+
helpers.set(binding.name.text, key.text);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const found = [];
|
|
77
|
+
for (const statement of source.statements) {
|
|
78
|
+
if (!ts.isVariableStatement(statement) || statement.declarationList.declarations.length !== 1)
|
|
79
|
+
continue;
|
|
80
|
+
const declaration = statement.declarationList.declarations[0];
|
|
81
|
+
const init = declaration.initializer;
|
|
82
|
+
if (!ts.isIdentifier(declaration.name) ||
|
|
83
|
+
!init ||
|
|
84
|
+
!ts.isCallExpression(init) ||
|
|
85
|
+
!ts.isIdentifier(init.expression) ||
|
|
86
|
+
!creators.has(init.expression.text) ||
|
|
87
|
+
init.arguments.length !== 1)
|
|
88
|
+
continue;
|
|
89
|
+
const config = init.arguments[0];
|
|
90
|
+
if (!ts.isObjectLiteralExpression(config))
|
|
91
|
+
continue;
|
|
92
|
+
const keys = new Set();
|
|
93
|
+
const directHelpers = new Set();
|
|
94
|
+
let safe = true;
|
|
95
|
+
for (const property of config.properties) {
|
|
96
|
+
if (ts.isSpreadAssignment(property)) {
|
|
97
|
+
const call = property.expression;
|
|
98
|
+
if (!ts.isCallExpression(call) ||
|
|
99
|
+
!ts.isIdentifier(call.expression) ||
|
|
100
|
+
!helperFactories.has(call.expression.text) ||
|
|
101
|
+
call.arguments.length) {
|
|
102
|
+
safe = false;
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
for (const helper of helperNames) {
|
|
106
|
+
keys.add(helper);
|
|
107
|
+
directHelpers.add(helper);
|
|
108
|
+
}
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if ((!ts.isPropertyAssignment(property) && !ts.isShorthandPropertyAssignment(property)) ||
|
|
112
|
+
!property.name ||
|
|
113
|
+
(!ts.isIdentifier(property.name) && !ts.isStringLiteral(property.name))) {
|
|
114
|
+
safe = false;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
const key = property.name.text;
|
|
118
|
+
if (keys.has(key)) {
|
|
119
|
+
safe = false;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
keys.add(key);
|
|
123
|
+
const value = ts.isShorthandPropertyAssignment(property) ? property.name : property.initializer;
|
|
124
|
+
if (ts.isIdentifier(value) && helpers.get(value.text) === key)
|
|
125
|
+
directHelpers.add(key);
|
|
126
|
+
}
|
|
127
|
+
if (!safe)
|
|
128
|
+
continue;
|
|
129
|
+
let exportName = declaration.name.text + 'Dependencies';
|
|
130
|
+
while (occupied.has(exportName))
|
|
131
|
+
exportName += '_';
|
|
132
|
+
occupied.add(exportName);
|
|
133
|
+
found.push({ name: declaration.name.text, exportName, keys, helpers: directHelpers });
|
|
134
|
+
}
|
|
135
|
+
return found;
|
|
136
|
+
}
|
|
137
|
+
export function replaceFactory(input, file, definition, keepLegacy) {
|
|
138
|
+
const source = ts.createSourceFile(file, input, ts.ScriptTarget.Latest, true);
|
|
139
|
+
let creator;
|
|
140
|
+
for (const statement of source.statements) {
|
|
141
|
+
if (!ts.isVariableStatement(statement) || statement.declarationList.declarations.length !== 1)
|
|
142
|
+
continue;
|
|
143
|
+
const declaration = statement.declarationList.declarations[0];
|
|
144
|
+
if (!ts.isIdentifier(declaration.name) ||
|
|
145
|
+
declaration.name.text !== definition.name ||
|
|
146
|
+
!declaration.initializer ||
|
|
147
|
+
!ts.isCallExpression(declaration.initializer))
|
|
148
|
+
continue;
|
|
149
|
+
const call = declaration.initializer;
|
|
150
|
+
creator = call.expression.getText(source);
|
|
151
|
+
const config = call.arguments[0].getText(source);
|
|
152
|
+
const replacement = `export const ${definition.exportName} = ${config};\n` +
|
|
153
|
+
(keepLegacy ? `export const ${definition.name} = ${creator}(${definition.exportName});` : '');
|
|
154
|
+
input = input.slice(0, statement.getStart(source)) + replacement + input.slice(statement.end);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
if (!creator || keepLegacy)
|
|
158
|
+
return input;
|
|
159
|
+
const parsed = ts.createSourceFile(file, input, ts.ScriptTarget.Latest, true);
|
|
160
|
+
let used = false;
|
|
161
|
+
const visit = (node) => {
|
|
162
|
+
if (ts.isImportDeclaration(node))
|
|
163
|
+
return;
|
|
164
|
+
if (ts.isIdentifier(node) && node.text === creator)
|
|
165
|
+
used = true;
|
|
166
|
+
ts.forEachChild(node, visit);
|
|
167
|
+
};
|
|
168
|
+
visit(parsed);
|
|
169
|
+
if (used)
|
|
170
|
+
return input;
|
|
171
|
+
for (const statement of [...parsed.statements].reverse()) {
|
|
172
|
+
if (!ts.isImportDeclaration(statement) ||
|
|
173
|
+
!statement.importClause?.namedBindings ||
|
|
174
|
+
!ts.isNamedImports(statement.importClause.namedBindings))
|
|
175
|
+
continue;
|
|
176
|
+
const bindings = statement.importClause.namedBindings;
|
|
177
|
+
const kept = bindings.elements.filter((binding) => binding.name.text !== creator);
|
|
178
|
+
if (kept.length === bindings.elements.length)
|
|
179
|
+
continue;
|
|
180
|
+
if (!kept.length && !statement.importClause.name)
|
|
181
|
+
input = input.slice(0, statement.getStart(parsed)) + input.slice(statement.end);
|
|
182
|
+
else
|
|
183
|
+
input =
|
|
184
|
+
input.slice(0, bindings.getStart(parsed)) +
|
|
185
|
+
`{ ${kept.map((binding) => binding.getText(parsed)).join(', ')} }` +
|
|
186
|
+
input.slice(bindings.end);
|
|
187
|
+
}
|
|
188
|
+
return input;
|
|
189
|
+
}
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import { parseSource } from '../checks/architecture.js';
|
|
3
|
+
const printer = ts.createPrinter();
|
|
4
|
+
const helperNames = new Set(['createAsyncAction', 'createAsyncResource']);
|
|
5
|
+
const apply = (input, edits) => {
|
|
6
|
+
for (const edit of edits.sort((a, b) => b.start - a.start))
|
|
7
|
+
input = input.slice(0, edit.start) + edit.text + input.slice(edit.end);
|
|
8
|
+
return input;
|
|
9
|
+
};
|
|
10
|
+
export function migrateAsyncHelpers(input, file) {
|
|
11
|
+
const source = parseSource(input, file);
|
|
12
|
+
const edits = [];
|
|
13
|
+
const factories = new Set();
|
|
14
|
+
const occupied = new Set();
|
|
15
|
+
const collect = (node) => {
|
|
16
|
+
if (ts.isIdentifier(node))
|
|
17
|
+
occupied.add(node.text);
|
|
18
|
+
ts.forEachChild(node, collect);
|
|
19
|
+
};
|
|
20
|
+
collect(source);
|
|
21
|
+
const unique = (base) => {
|
|
22
|
+
let name = base;
|
|
23
|
+
while (occupied.has(name))
|
|
24
|
+
name += '_';
|
|
25
|
+
occupied.add(name);
|
|
26
|
+
return name;
|
|
27
|
+
};
|
|
28
|
+
const action = unique('kitCreateAsyncAction');
|
|
29
|
+
const resource = unique('kitCreateAsyncResource');
|
|
30
|
+
let helpersImported = false;
|
|
31
|
+
for (const statement of source.statements) {
|
|
32
|
+
if (!ts.isImportDeclaration(statement) ||
|
|
33
|
+
!ts.isStringLiteral(statement.moduleSpecifier) ||
|
|
34
|
+
statement.moduleSpecifier.text !== '@azure-net/kit/delivery')
|
|
35
|
+
continue;
|
|
36
|
+
const bindings = statement.importClause?.namedBindings;
|
|
37
|
+
if (!bindings || !ts.isNamedImports(bindings))
|
|
38
|
+
continue;
|
|
39
|
+
const kept = bindings.elements.filter((entry) => {
|
|
40
|
+
if ((entry.propertyName ?? entry.name).text !== 'createAsyncHelpers')
|
|
41
|
+
return true;
|
|
42
|
+
factories.add(entry.name.text);
|
|
43
|
+
return false;
|
|
44
|
+
});
|
|
45
|
+
if (kept.length === bindings.elements.length)
|
|
46
|
+
continue;
|
|
47
|
+
const extra = helpersImported ? [] : [`createAsyncAction as ${action}`, `createAsyncResource as ${resource}`];
|
|
48
|
+
helpersImported = true;
|
|
49
|
+
edits.push({
|
|
50
|
+
start: bindings.getStart(source),
|
|
51
|
+
end: bindings.end,
|
|
52
|
+
text: `{ ${[...kept.map((e) => e.getText(source)), ...extra].join(', ')} }`
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (!factories.size)
|
|
56
|
+
return input;
|
|
57
|
+
const validate = (node) => {
|
|
58
|
+
if (ts.isImportDeclaration(node))
|
|
59
|
+
return;
|
|
60
|
+
if (ts.isIdentifier(node) && factories.has(node.text)) {
|
|
61
|
+
if (!ts.isCallExpression(node.parent) || node.parent.expression !== node || node.parent.arguments.length) {
|
|
62
|
+
throw new Error(`${node.text} is not a direct zero-argument call`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
ts.forEachChild(node, validate);
|
|
66
|
+
};
|
|
67
|
+
validate(source);
|
|
68
|
+
const specialize = (name, errorType) => errorType
|
|
69
|
+
? `${name} as <Res = never, Req = never>(...args: Parameters<typeof ${name}<Res, Req, ${errorType}>>) => ReturnType<typeof ${name}<Res, Req, ${errorType}>>`
|
|
70
|
+
: name;
|
|
71
|
+
const visit = (node) => {
|
|
72
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && factories.has(node.expression.text) && !node.arguments.length) {
|
|
73
|
+
const errorType = node.typeArguments?.[0]?.getText(source);
|
|
74
|
+
let keys = [...helperNames];
|
|
75
|
+
const parent = node.parent;
|
|
76
|
+
if (ts.isVariableDeclaration(parent) &&
|
|
77
|
+
ts.isObjectBindingPattern(parent.name) &&
|
|
78
|
+
!parent.name.elements.some((b) => b.dotDotDotToken)) {
|
|
79
|
+
keys = parent.name.elements.map((b) => (b.propertyName ?? b.name).getText(source));
|
|
80
|
+
if (keys.some((key) => !helperNames.has(key)))
|
|
81
|
+
throw new Error('Removed async helper members require manual migration');
|
|
82
|
+
}
|
|
83
|
+
edits.push({
|
|
84
|
+
start: node.getStart(source),
|
|
85
|
+
end: node.end,
|
|
86
|
+
text: `({ ${keys.map((key) => `${key}: ${specialize(key === 'createAsyncAction' ? action : resource, errorType)}`).join(', ')} })`
|
|
87
|
+
});
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
ts.forEachChild(node, visit);
|
|
91
|
+
};
|
|
92
|
+
visit(source);
|
|
93
|
+
return apply(input, edits);
|
|
94
|
+
}
|
|
95
|
+
/** Preserve lexical behavior while moving instance creation and dependencies to RegisterProvider. */
|
|
96
|
+
export function migratePresenterSource(input, file, factories, dependencies = new Map()) {
|
|
97
|
+
const source = ts.createSourceFile(file, input, ts.ScriptTarget.Latest, true);
|
|
98
|
+
const edits = [];
|
|
99
|
+
const converted = new Map();
|
|
100
|
+
const issues = [];
|
|
101
|
+
const imports = new Set();
|
|
102
|
+
const removedFactories = new Set();
|
|
103
|
+
const existingImports = new Set();
|
|
104
|
+
const dependencyImports = [];
|
|
105
|
+
const occupied = new Set();
|
|
106
|
+
const collectNames = (node) => {
|
|
107
|
+
if (ts.isIdentifier(node))
|
|
108
|
+
occupied.add(node.text);
|
|
109
|
+
ts.forEachChild(node, collectNames);
|
|
110
|
+
};
|
|
111
|
+
collectNames(source);
|
|
112
|
+
const unique = (base) => {
|
|
113
|
+
let value = base;
|
|
114
|
+
while (occupied.has(value))
|
|
115
|
+
value += '_';
|
|
116
|
+
occupied.add(value);
|
|
117
|
+
return value;
|
|
118
|
+
};
|
|
119
|
+
for (const statement of source.statements) {
|
|
120
|
+
if (!ts.isImportDeclaration(statement) ||
|
|
121
|
+
!ts.isStringLiteral(statement.moduleSpecifier) ||
|
|
122
|
+
statement.moduleSpecifier.text !== '@azure-net/kit/delivery' ||
|
|
123
|
+
statement.importClause?.isTypeOnly)
|
|
124
|
+
continue;
|
|
125
|
+
const bindings = statement.importClause?.namedBindings;
|
|
126
|
+
if (bindings && ts.isNamedImports(bindings))
|
|
127
|
+
for (const entry of bindings.elements) {
|
|
128
|
+
if (!entry.isTypeOnly)
|
|
129
|
+
existingImports.add(entry.propertyName ? `${entry.propertyName.text} as ${entry.name.text}` : entry.name.text);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const existingRegistration = [...existingImports].find((name) => name === 'RegisterProvider' || name.startsWith('RegisterProvider as '));
|
|
133
|
+
const registrationName = existingRegistration ? existingRegistration.split(' as ').at(-1) : unique('RegisterProvider');
|
|
134
|
+
for (const statement of source.statements) {
|
|
135
|
+
if (!ts.isVariableStatement(statement) || statement.declarationList.declarations.length !== 1)
|
|
136
|
+
continue;
|
|
137
|
+
const declaration = statement.declarationList.declarations[0];
|
|
138
|
+
const call = declaration.initializer;
|
|
139
|
+
if (!ts.isIdentifier(declaration.name) ||
|
|
140
|
+
!call ||
|
|
141
|
+
!ts.isCallExpression(call) ||
|
|
142
|
+
!ts.isIdentifier(call.expression) ||
|
|
143
|
+
!factories.has(call.expression.text))
|
|
144
|
+
continue;
|
|
145
|
+
const name = declaration.name.text;
|
|
146
|
+
try {
|
|
147
|
+
if (call.typeArguments?.length || declaration.type)
|
|
148
|
+
throw new Error('Explicit factory result types require manual review');
|
|
149
|
+
const callback = call.arguments.find((arg) => ts.isArrowFunction(arg) || ts.isFunctionExpression(arg));
|
|
150
|
+
if (!callback ||
|
|
151
|
+
(!ts.isArrowFunction(callback) && !ts.isFunctionExpression(callback)) ||
|
|
152
|
+
!ts.isBlock(callback.body) ||
|
|
153
|
+
callback.typeParameters ||
|
|
154
|
+
callback.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword))
|
|
155
|
+
throw new Error('Expected a synchronous block factory');
|
|
156
|
+
if (!((call.arguments.length === 2 && ts.isStringLiteral(call.arguments[0])) ||
|
|
157
|
+
(call.arguments.length === 1 && call.arguments[0] === callback)))
|
|
158
|
+
throw new Error('Custom factory arguments need manual review');
|
|
159
|
+
const requestedImports = new Set();
|
|
160
|
+
const dependency = dependencies.get(call.expression.text);
|
|
161
|
+
const dependencyAlias = unique(name + 'Dependencies');
|
|
162
|
+
const extraParameters = [];
|
|
163
|
+
const extraArguments = [];
|
|
164
|
+
const extraRegistrations = [];
|
|
165
|
+
const helperSetup = [];
|
|
166
|
+
const javascript = file.endsWith('.js');
|
|
167
|
+
let needsDependencyImport = false;
|
|
168
|
+
let runtimeDependencyImport = false;
|
|
169
|
+
if (callback.parameters.length) {
|
|
170
|
+
const param = callback.parameters[0];
|
|
171
|
+
if (callback.parameters.length !== 1 || !ts.isObjectBindingPattern(param.name) || param.initializer)
|
|
172
|
+
throw new Error('Unsupported injected dependencies');
|
|
173
|
+
for (const binding of param.name.elements) {
|
|
174
|
+
const original = (binding.propertyName ?? binding.name).getText(source);
|
|
175
|
+
if (!ts.isIdentifier(binding.name) || binding.initializer || binding.dotDotDotToken)
|
|
176
|
+
throw new Error('Nested, default or rest injected bindings need manual conversion');
|
|
177
|
+
if (dependency) {
|
|
178
|
+
if (!dependency.keys.has(original) && helperNames.has(original)) {
|
|
179
|
+
requestedImports.add(original === binding.name.text ? original : `${original} as ${binding.name.text}`);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (!dependency.keys.has(original))
|
|
183
|
+
throw new Error(`Injected dependency ${original} is not statically known in its factory`);
|
|
184
|
+
if (helperNames.has(original) && dependency.helpers.has(original)) {
|
|
185
|
+
const helperAlias = unique('kit' + original[0].toUpperCase() + original.slice(1));
|
|
186
|
+
requestedImports.add(`${original} as ${helperAlias}`);
|
|
187
|
+
helperSetup.push(`const ${binding.name.text}${javascript ? '' : ': typeof ' + dependencyAlias + '[' + JSON.stringify(original) + ']'} = ${helperAlias};`);
|
|
188
|
+
if (!javascript)
|
|
189
|
+
needsDependencyImport = true;
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
needsDependencyImport = runtimeDependencyImport = true;
|
|
193
|
+
extraParameters.push(`${binding.name.text}: typeof ${dependencyAlias}[${JSON.stringify(original)}]`);
|
|
194
|
+
extraArguments.push(binding.name.text);
|
|
195
|
+
extraRegistrations.push(`${dependencyAlias}[${JSON.stringify(original)}]`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
if (!helperNames.has(original))
|
|
200
|
+
throw new Error(`Injected dependency ${original} requires manual conversion`);
|
|
201
|
+
requestedImports.add(original === binding.name.text ? original : `${original} as ${binding.name.text}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const statements = [...callback.body.statements];
|
|
206
|
+
let usesThis = false;
|
|
207
|
+
const findThis = (node) => {
|
|
208
|
+
if (node.kind === ts.SyntaxKind.ThisKeyword)
|
|
209
|
+
usesThis = true;
|
|
210
|
+
ts.forEachChild(node, findThis);
|
|
211
|
+
};
|
|
212
|
+
findThis(callback.body);
|
|
213
|
+
if (usesThis)
|
|
214
|
+
throw new Error('Explicit this binding requires manual conversion');
|
|
215
|
+
const result = statements.at(-1);
|
|
216
|
+
if (!result || !ts.isReturnStatement(result) || !result.expression || !ts.isObjectLiteralExpression(result.expression))
|
|
217
|
+
throw new Error('Expected a final object-literal return');
|
|
218
|
+
const validateFlow = (node) => {
|
|
219
|
+
if (ts.isFunctionLike(node) || ts.isClassLike(node))
|
|
220
|
+
return;
|
|
221
|
+
if (ts.isReturnStatement(node) && node !== result)
|
|
222
|
+
throw new Error('Conditional early returns require manual conversion');
|
|
223
|
+
if (ts.isIdentifier(node) && node.text === 'arguments')
|
|
224
|
+
throw new Error('Factory arguments binding requires manual conversion');
|
|
225
|
+
ts.forEachChild(node, validateFlow);
|
|
226
|
+
};
|
|
227
|
+
validateFlow(callback.body);
|
|
228
|
+
const keys = result.expression.properties.map((prop) => {
|
|
229
|
+
if (!prop.name || !ts.isIdentifier(prop.name))
|
|
230
|
+
throw new Error('Spread/computed return keys require manual conversion');
|
|
231
|
+
if (prop.name.text === 'constructor')
|
|
232
|
+
throw new Error('Reserved presenter member');
|
|
233
|
+
return prop.name.text;
|
|
234
|
+
});
|
|
235
|
+
if (new Set(keys).size !== keys.length)
|
|
236
|
+
throw new Error('Duplicate return keys');
|
|
237
|
+
const parameters = [...extraParameters];
|
|
238
|
+
const argumentsList = [...extraArguments];
|
|
239
|
+
const registrationArguments = [...extraRegistrations];
|
|
240
|
+
const registrations = [];
|
|
241
|
+
const body = statements.filter((stmt) => {
|
|
242
|
+
if (!ts.isVariableStatement(stmt) || stmt.declarationList.declarations.length !== 1)
|
|
243
|
+
return true;
|
|
244
|
+
const binding = stmt.declarationList.declarations[0];
|
|
245
|
+
const value = binding.initializer;
|
|
246
|
+
if (!ts.isObjectBindingPattern(binding.name) ||
|
|
247
|
+
!value ||
|
|
248
|
+
!ts.isCallExpression(value) ||
|
|
249
|
+
!ts.isIdentifier(value.expression) ||
|
|
250
|
+
!/Provider$/.test(value.expression.text))
|
|
251
|
+
return true;
|
|
252
|
+
if (value.arguments.length)
|
|
253
|
+
throw new Error('Provider arguments require manual conversion');
|
|
254
|
+
for (const entry of binding.name.elements) {
|
|
255
|
+
if (!ts.isIdentifier(entry.name) || entry.dotDotDotToken || entry.initializer)
|
|
256
|
+
throw new Error('Unsupported provider destructuring');
|
|
257
|
+
const key = (entry.propertyName ?? entry.name).getText(source).replace(/^['"]|['"]$/g, '');
|
|
258
|
+
parameters.push(`${entry.name.text}: ReturnType<typeof ${value.expression.text}>[${JSON.stringify(key)}]`);
|
|
259
|
+
argumentsList.push(entry.name.text);
|
|
260
|
+
registrationArguments.push(entry.name.text);
|
|
261
|
+
}
|
|
262
|
+
registrations.push(stmt.getText(source));
|
|
263
|
+
return false;
|
|
264
|
+
});
|
|
265
|
+
// Keep lexical state and callbacks intact rather than guessing this-binding rewrites.
|
|
266
|
+
const setup = `create${name}State`;
|
|
267
|
+
if (new RegExp(`\\b(?:use${name}|${setup})\\b`).test(input))
|
|
268
|
+
throw new Error('Generated declaration name already exists');
|
|
269
|
+
const parameterList = (javascript ? argumentsList : parameters).join(', ');
|
|
270
|
+
const members = result.expression.properties.map((property, index) => {
|
|
271
|
+
const key = keys[index];
|
|
272
|
+
return (`get ${key}() { return this.#state.${key}; }` +
|
|
273
|
+
(ts.isGetAccessorDeclaration(property) || callback.type
|
|
274
|
+
? ''
|
|
275
|
+
: `\nset ${key}(value${javascript ? '' : `: ReturnType<typeof ${setup}>[${JSON.stringify(key)}]`}) { this.#state.${key} = value; }`));
|
|
276
|
+
});
|
|
277
|
+
const text = `function ${setup}(${parameterList})${callback.type ? ': ' + callback.type.getText(source) : ''} {\n${helperSetup.join('\n')}\n${body.map((stmt) => stmt.getText(source)).join('\n')}\n}\n\nexport class ${name} {\n#state${javascript ? '' : `: ReturnType<typeof ${setup}>`};\nconstructor(${parameterList}) { this.#state = ${setup}(${argumentsList.join(', ')}); }\n${members.join('\n')}\n}\n\nexport const use${name} = ${registrationName}(() => {\n${registrations.join('\n')}\nreturn new ${name}(${registrationArguments.join(', ')});\n}, 'scoped');`;
|
|
278
|
+
if (needsDependencyImport && dependency)
|
|
279
|
+
dependencyImports.push(`import ${runtimeDependencyImport ? '' : 'type '}{ ${dependency.exportName} as ${dependencyAlias} } from ${JSON.stringify(dependency.source)};`);
|
|
280
|
+
edits.push({ start: statement.getStart(source), end: statement.end, text });
|
|
281
|
+
converted.set(name, `use${name}`);
|
|
282
|
+
removedFactories.add(call.expression.text);
|
|
283
|
+
imports.add(registrationName === 'RegisterProvider' ? 'RegisterProvider' : `RegisterProvider as ${registrationName}`);
|
|
284
|
+
for (const helper of requestedImports)
|
|
285
|
+
imports.add(helper);
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
issues.push(`${name}: ${String(error)}. Original presenter retained.`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
let output = apply(input, edits);
|
|
292
|
+
if (converted.size) {
|
|
293
|
+
const parsed = ts.createSourceFile(file, output, ts.ScriptTarget.Latest, true);
|
|
294
|
+
const importEdits = [];
|
|
295
|
+
for (const statement of parsed.statements) {
|
|
296
|
+
if (!ts.isImportDeclaration(statement) ||
|
|
297
|
+
!statement.importClause?.namedBindings ||
|
|
298
|
+
!ts.isNamedImports(statement.importClause.namedBindings))
|
|
299
|
+
continue;
|
|
300
|
+
const bindings = statement.importClause.namedBindings;
|
|
301
|
+
const kept = bindings.elements.filter((entry) => {
|
|
302
|
+
if (!removedFactories.has(entry.name.text))
|
|
303
|
+
return true;
|
|
304
|
+
let used = false;
|
|
305
|
+
const visit = (node) => {
|
|
306
|
+
if (ts.isImportDeclaration(node))
|
|
307
|
+
return;
|
|
308
|
+
if (ts.isIdentifier(node) && node.text === entry.name.text)
|
|
309
|
+
used = true;
|
|
310
|
+
ts.forEachChild(node, visit);
|
|
311
|
+
};
|
|
312
|
+
visit(parsed);
|
|
313
|
+
return used;
|
|
314
|
+
});
|
|
315
|
+
if (kept.length === bindings.elements.length)
|
|
316
|
+
continue;
|
|
317
|
+
const updated = kept.length || statement.importClause.name
|
|
318
|
+
? ts.factory.updateImportDeclaration(statement, statement.modifiers, ts.factory.updateImportClause(statement.importClause, statement.importClause.isTypeOnly, statement.importClause.name, kept.length ? ts.factory.updateNamedImports(bindings, kept) : undefined), statement.moduleSpecifier, statement.attributes)
|
|
319
|
+
: undefined;
|
|
320
|
+
importEdits.push({
|
|
321
|
+
start: statement.getStart(parsed),
|
|
322
|
+
end: statement.end,
|
|
323
|
+
text: updated ? printer.printNode(ts.EmitHint.Unspecified, updated, parsed) : ''
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
const neededImports = [...imports].filter((name) => !existingImports.has(name));
|
|
327
|
+
output =
|
|
328
|
+
dependencyImports.join('\n') +
|
|
329
|
+
'\n' +
|
|
330
|
+
(neededImports.length ? `import { ${neededImports.join(', ')} } from '@azure-net/kit/delivery';\n` : '') +
|
|
331
|
+
apply(output, importEdits);
|
|
332
|
+
const updated = ts.createSourceFile(file, output, ts.ScriptTarget.Latest, true);
|
|
333
|
+
// Bind only this in-memory module: distinguish lexical shadows without loading dependencies.
|
|
334
|
+
const options = { noLib: true, noResolve: true, target: ts.ScriptTarget.ES2022 };
|
|
335
|
+
const host = ts.createCompilerHost(options);
|
|
336
|
+
host.getSourceFile = (name) => (name === file ? updated : undefined);
|
|
337
|
+
host.fileExists = (name) => name === file;
|
|
338
|
+
host.readFile = (name) => (name === file ? output : undefined);
|
|
339
|
+
const checker = ts.createProgram([file], options, host).getTypeChecker();
|
|
340
|
+
const symbols = new Map();
|
|
341
|
+
for (const statement of updated.statements) {
|
|
342
|
+
if (!ts.isClassDeclaration(statement) || !statement.name || !converted.has(statement.name.text))
|
|
343
|
+
continue;
|
|
344
|
+
const symbol = checker.getSymbolAtLocation(statement.name);
|
|
345
|
+
if (symbol)
|
|
346
|
+
symbols.set(symbol, converted.get(statement.name.text));
|
|
347
|
+
}
|
|
348
|
+
const references = [];
|
|
349
|
+
const visit = (node) => {
|
|
350
|
+
if (ts.isShorthandPropertyAssignment(node)) {
|
|
351
|
+
const symbol = checker.getShorthandAssignmentValueSymbol(node);
|
|
352
|
+
const name = symbol && symbols.get(symbol);
|
|
353
|
+
if (name) {
|
|
354
|
+
references.push({ start: node.getStart(updated), end: node.end, text: `${node.name.text}: ${name}` });
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (ts.isIdentifier(node) && converted.has(node.text)) {
|
|
359
|
+
const parent = node.parent;
|
|
360
|
+
const declaration = ts.isClassDeclaration(parent) && parent.name === node;
|
|
361
|
+
const construction = ts.isNewExpression(parent) && parent.expression === node;
|
|
362
|
+
const symbol = checker.getSymbolAtLocation(node);
|
|
363
|
+
const name = symbol && symbols.get(symbol);
|
|
364
|
+
if (name && !declaration && !construction)
|
|
365
|
+
references.push({ start: node.getStart(updated), end: node.end, text: name });
|
|
366
|
+
}
|
|
367
|
+
ts.forEachChild(node, visit);
|
|
368
|
+
};
|
|
369
|
+
visit(updated);
|
|
370
|
+
output = apply(output, references);
|
|
371
|
+
}
|
|
372
|
+
return { content: output, converted, issues };
|
|
373
|
+
}
|