@ethlete/query 6.0.0-next.45 → 6.0.0-next.48
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/CHANGELOG.md +106 -0
- package/fesm2022/ethlete-query-testing.mjs +138 -52
- package/fesm2022/ethlete-query-testing.mjs.map +1 -1
- package/fesm2022/ethlete-query.mjs +1960 -984
- package/fesm2022/ethlete-query.mjs.map +1 -1
- package/generators/migrate-query-opt-in-features/migration.js +13 -1
- package/generators/migrate-to-query-v3/auth-provider-scaffold.js +6 -4
- package/generators/migrate-to-query-v3/cleanup-migration.js +13 -52
- package/generators/migrate-to-query-v3/legacy-prepare-migration.js +9 -12
- package/generators/migrate-to-query-v3/legacy-query-creator-migration.js +81 -15
- package/generators/migrate-to-query-v3/migration-scope.js +0 -3
- package/generators/migrate-to-query-v3/query-client-migration.js +97 -47
- package/generators/migrate-to-query-v3/rename-symbols.js +19 -6
- package/generators/migrate-to-query-v3/shared.js +6 -2
- package/generators/prep-for-query-v3/migration.js +68 -11
- package/package.json +5 -5
- package/types/ethlete-query-testing.d.ts +37 -5
- package/types/ethlete-query.d.ts +310 -119
|
@@ -8,6 +8,12 @@ const CLIENT_FEATURE = 'withEthleteApiErrors';
|
|
|
8
8
|
const AUTH_FEATURE = 'withBearerAuthMultiTabSync';
|
|
9
9
|
const isMigratableFile = (filePath) => /\.(ts|mts|cts)$/.test(filePath) && !filePath.endsWith('.d.ts');
|
|
10
10
|
const propertyNamed = (config, sourceFile, name) => config.properties.find((property) => ts.isPropertyAssignment(property) && property.name.getText(sourceFile) === name);
|
|
11
|
+
const hasFeature = (config, sourceFile, feature) => {
|
|
12
|
+
const features = propertyNamed(config, sourceFile, 'features')?.initializer;
|
|
13
|
+
return (!!features &&
|
|
14
|
+
ts.isArrayLiteralExpression(features) &&
|
|
15
|
+
features.elements.some((element) => ts.isCallExpression(element) && ts.isIdentifier(element.expression) && element.expression.text === feature));
|
|
16
|
+
};
|
|
11
17
|
/** `{ enabled: false, channelName: 'x' }` -> `withBearerAuthMultiTabSync({ channelName: 'x' })`, or `null` when off. */
|
|
12
18
|
const authFeatureCallFor = (property, sourceFile) => {
|
|
13
19
|
if (!property)
|
|
@@ -76,6 +82,9 @@ const collectEdits = (filePath, sourceFile, report) => {
|
|
|
76
82
|
dedupeKey: `spread-config:${callee}`,
|
|
77
83
|
});
|
|
78
84
|
}
|
|
85
|
+
else if (isClient && hasFeature(config, sourceFile, CLIENT_FEATURE)) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
79
88
|
else if (isClient) {
|
|
80
89
|
const feature = `${CLIENT_FEATURE}()`;
|
|
81
90
|
report.addFollowUp({
|
|
@@ -95,6 +104,9 @@ const collectEdits = (filePath, sourceFile, report) => {
|
|
|
95
104
|
}
|
|
96
105
|
else {
|
|
97
106
|
const syncProperty = propertyNamed(config, sourceFile, 'multiTabSync');
|
|
107
|
+
const alreadyAdded = hasFeature(config, sourceFile, AUTH_FEATURE);
|
|
108
|
+
if (alreadyAdded && !syncProperty)
|
|
109
|
+
return;
|
|
98
110
|
const value = syncProperty?.initializer;
|
|
99
111
|
if (value &&
|
|
100
112
|
value.kind !== ts.SyntaxKind.TrueKeyword &&
|
|
@@ -109,7 +121,7 @@ const collectEdits = (filePath, sourceFile, report) => {
|
|
|
109
121
|
dedupeKey: 'non-literal-multi-tab-sync',
|
|
110
122
|
});
|
|
111
123
|
}
|
|
112
|
-
const feature = authFeatureCallFor(syncProperty, sourceFile);
|
|
124
|
+
const feature = alreadyAdded ? null : authFeatureCallFor(syncProperty, sourceFile);
|
|
113
125
|
if (feature) {
|
|
114
126
|
report.addFollowUp({
|
|
115
127
|
title: 'Auth provider kept multi-tab sync',
|
|
@@ -47,17 +47,19 @@ const readConfig = (configArgument, sourceFile, filePath, node) => {
|
|
|
47
47
|
return config;
|
|
48
48
|
}
|
|
49
49
|
refreshConfig.initializer.properties.forEach((property) => {
|
|
50
|
-
|
|
50
|
+
const isShorthand = ts.isShorthandPropertyAssignment(property);
|
|
51
|
+
if ((!ts.isPropertyAssignment(property) && !isShorthand) || !ts.isIdentifier(property.name)) {
|
|
51
52
|
return;
|
|
52
53
|
}
|
|
53
54
|
const key = property.name.text;
|
|
54
55
|
if (!REFRESH_CONFIG_KEYS.has(key)) {
|
|
55
56
|
return;
|
|
56
57
|
}
|
|
57
|
-
const
|
|
58
|
+
const value = isShorthand ? property.name : property.initializer;
|
|
59
|
+
const text = value.getText(sourceFile);
|
|
58
60
|
if (key === 'queryCreator') {
|
|
59
|
-
if (ts.isIdentifier(
|
|
60
|
-
config.refreshCreatorName =
|
|
61
|
+
if (ts.isIdentifier(value)) {
|
|
62
|
+
config.refreshCreatorName = value.text;
|
|
61
63
|
}
|
|
62
64
|
return;
|
|
63
65
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import { createSourceFile, ensureImportFromQuery, ensureNamedImports
|
|
2
|
+
import { createSourceFile, ensureImportFromQuery, ensureNamedImports } from './shared.js';
|
|
3
3
|
/**
|
|
4
4
|
* Points existing devtools usage at the v3 components instead of deleting it.
|
|
5
5
|
*
|
|
@@ -10,7 +10,7 @@ import { createSourceFile, ensureImportFromQuery, ensureNamedImports, getLineNum
|
|
|
10
10
|
*/
|
|
11
11
|
export const migrateDevtoolsUsage = (tree, scope, report) => {
|
|
12
12
|
const updatedFiles = [];
|
|
13
|
-
const
|
|
13
|
+
const filesNeedingDevtoolsPackage = [];
|
|
14
14
|
scope.visit(tree, (filePath) => {
|
|
15
15
|
if (!filePath.endsWith('.ts')) {
|
|
16
16
|
return;
|
|
@@ -27,7 +27,7 @@ export const migrateDevtoolsUsage = (tree, scope, report) => {
|
|
|
27
27
|
tree.write(filePath, nextContent);
|
|
28
28
|
updatedFiles.push(filePath);
|
|
29
29
|
if (importsComponent) {
|
|
30
|
-
|
|
30
|
+
filesNeedingDevtoolsPackage.push(filePath);
|
|
31
31
|
}
|
|
32
32
|
});
|
|
33
33
|
if (updatedFiles.length === 0) {
|
|
@@ -35,16 +35,17 @@ export const migrateDevtoolsUsage = (tree, scope, report) => {
|
|
|
35
35
|
}
|
|
36
36
|
console.log('\n✅ Migrated query devtools usage to v3 in:');
|
|
37
37
|
updatedFiles.forEach((filePath) => console.log(` - ${filePath}`));
|
|
38
|
-
if (
|
|
38
|
+
if (filesNeedingDevtoolsPackage.length > 0) {
|
|
39
39
|
// The component moved packages, which is the one part of this rewrite that can fail outside the
|
|
40
|
-
// file being edited: an app that only ever depended on `@ethlete/query` now needs
|
|
40
|
+
// file being edited: an app that only ever depended on `@ethlete/query` now needs
|
|
41
|
+
// `@ethlete/query-devtools`.
|
|
41
42
|
report.addManualReview({
|
|
42
|
-
title: 'Add @ethlete/
|
|
43
|
-
summary: '`QueryDevtoolsComponent` now lives in `@ethlete/
|
|
44
|
-
action: 'Run `yarn add @ethlete/
|
|
45
|
-
locations:
|
|
43
|
+
title: 'Add @ethlete/query-devtools for the query devtools',
|
|
44
|
+
summary: '`QueryDevtoolsComponent` now lives in `@ethlete/query-devtools`. The imports were rewritten, but the package may not be a dependency of these projects yet.',
|
|
45
|
+
action: 'Run `yarn add @ethlete/query-devtools` where needed, then `yarn install` and re-lint the affected libs so the `@nx/dependency-checks` rule sees the new dependency.',
|
|
46
|
+
locations: filesNeedingDevtoolsPackage.map((filePath) => ({ filePath })),
|
|
46
47
|
source: 'cleanup-migration',
|
|
47
|
-
dedupeKey: 'devtools-
|
|
48
|
+
dedupeKey: 'devtools-package-dependency',
|
|
48
49
|
});
|
|
49
50
|
}
|
|
50
51
|
};
|
|
@@ -100,7 +101,7 @@ const migrateDevtoolsInFile = (content) => {
|
|
|
100
101
|
result = ensureNamedImports({
|
|
101
102
|
content: result,
|
|
102
103
|
importsNeeded: ['QueryDevtoolsComponent'],
|
|
103
|
-
moduleSpecifier: '@ethlete/
|
|
104
|
+
moduleSpecifier: '@ethlete/query-devtools',
|
|
104
105
|
});
|
|
105
106
|
}
|
|
106
107
|
return { content: result, importsComponent };
|
|
@@ -221,43 +222,7 @@ const replaceAnyQueryInFile = (content) => {
|
|
|
221
222
|
replacements.forEach(({ start, end, replacement }) => {
|
|
222
223
|
result = result.slice(0, start) + replacement + result.slice(end);
|
|
223
224
|
});
|
|
224
|
-
return
|
|
225
|
-
};
|
|
226
|
-
const removeAnyQueryFromImports = (content) => {
|
|
227
|
-
const sourceFile = createSourceFile(content);
|
|
228
|
-
let importNode;
|
|
229
|
-
ts.forEachChild(sourceFile, (node) => {
|
|
230
|
-
if (ts.isImportDeclaration(node) &&
|
|
231
|
-
ts.isStringLiteral(node.moduleSpecifier) &&
|
|
232
|
-
node.moduleSpecifier.text === '@ethlete/query') {
|
|
233
|
-
importNode = node;
|
|
234
|
-
}
|
|
235
|
-
});
|
|
236
|
-
if (!importNode?.importClause?.namedBindings || !ts.isNamedImports(importNode.importClause.namedBindings)) {
|
|
237
|
-
return content;
|
|
238
|
-
}
|
|
239
|
-
const replaced = new Map([
|
|
240
|
-
['AnyV2Query', 'AnyLegacyQuery'],
|
|
241
|
-
['AnyV2QueryCreator', 'AnyLegacyQueryCreator'],
|
|
242
|
-
]);
|
|
243
|
-
const names = new Set();
|
|
244
|
-
let changed = false;
|
|
245
|
-
importNode.importClause.namedBindings.elements.forEach((element) => {
|
|
246
|
-
const replacement = replaced.get(element.name.text);
|
|
247
|
-
if (!replacement) {
|
|
248
|
-
names.add(element.getText(sourceFile));
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
// Only add the alias that was actually imported. Adding both unconditionally is what put
|
|
252
|
-
// `AnyLegacyQuery` / `AnyLegacyQueryCreator` into hundreds of files that never referenced them.
|
|
253
|
-
changed = true;
|
|
254
|
-
names.add(replacement);
|
|
255
|
-
});
|
|
256
|
-
if (!changed) {
|
|
257
|
-
return content;
|
|
258
|
-
}
|
|
259
|
-
const nextImport = `import { ${Array.from(names).sort().join(', ')} } from '@ethlete/query';`;
|
|
260
|
-
return content.slice(0, importNode.getStart(sourceFile)) + nextImport + content.slice(importNode.getEnd());
|
|
225
|
+
return result;
|
|
261
226
|
};
|
|
262
227
|
const transformEmptyPrepareCalls = (content) => {
|
|
263
228
|
const sourceFile = createSourceFile(content);
|
|
@@ -286,8 +251,4 @@ const transformEmptyPrepareCalls = (content) => {
|
|
|
286
251
|
});
|
|
287
252
|
return result;
|
|
288
253
|
};
|
|
289
|
-
export const describeTemplateLine = (content, position, suffix) => {
|
|
290
|
-
const lineNumber = getLineNumberFromPosition(content, position);
|
|
291
|
-
return `${lineNumber} (${suffix})`;
|
|
292
|
-
};
|
|
293
254
|
//# sourceMappingURL=cleanup-migration.js.map
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
2
|
import { createSourceFile, ensureAngularCoreImports, getIndentation, getLineNumber, getLineNumberFromPosition, } from './shared.js';
|
|
3
|
+
/** `default` can never be a class name, so it cannot collide with a named class of the same file. */
|
|
4
|
+
const ANONYMOUS_DEFAULT_CLASS_KEY = 'default';
|
|
5
|
+
const getClassKey = (node) => node.name?.text ?? ANONYMOUS_DEFAULT_CLASS_KEY;
|
|
3
6
|
/** Contexts that already have an injector of their own, so the call site needs no rewrite. */
|
|
4
7
|
const CONTEXTS_WITH_INJECTOR = [
|
|
5
8
|
'queryComputed',
|
|
@@ -265,7 +268,7 @@ const findClassesNeedingInjector = (sourceFile, usages) => {
|
|
|
265
268
|
const filePath = usages[0]?.filePath ?? sourceFile.fileName;
|
|
266
269
|
const visit = (node) => {
|
|
267
270
|
if (ts.isClassDeclaration(node)) {
|
|
268
|
-
const className = node
|
|
271
|
+
const className = getClassKey(node);
|
|
269
272
|
const usagesInClass = usages.filter((usage) => {
|
|
270
273
|
return usage.position.start >= node.getStart(sourceFile) && usage.position.end <= node.getEnd();
|
|
271
274
|
});
|
|
@@ -331,7 +334,7 @@ const addInjectorMemberToClass = (content, classInfo) => {
|
|
|
331
334
|
const sourceFile = createSourceFile(content);
|
|
332
335
|
let targetClass;
|
|
333
336
|
const visit = (node) => {
|
|
334
|
-
if (ts.isClassDeclaration(node) && node
|
|
337
|
+
if (ts.isClassDeclaration(node) && getClassKey(node) === classInfo.className) {
|
|
335
338
|
targetClass = node;
|
|
336
339
|
}
|
|
337
340
|
ts.forEachChild(node, visit);
|
|
@@ -342,7 +345,7 @@ const addInjectorMemberToClass = (content, classInfo) => {
|
|
|
342
345
|
}
|
|
343
346
|
const classBodyStart = targetClass.members.length > 0 ? targetClass.members[0].getStart(sourceFile) : targetClass.getEnd() - 1;
|
|
344
347
|
const indentation = getIndentation(content, classBodyStart);
|
|
345
|
-
const injectorMember =
|
|
348
|
+
const injectorMember = `private ${classInfo.injectorMemberName} = inject(Injector);\n\n${indentation}`;
|
|
346
349
|
return content.slice(0, classBodyStart) + injectorMember + content.slice(classBodyStart);
|
|
347
350
|
};
|
|
348
351
|
const transformLegacyCreatorPrepareCalls = (tree, classInjectors, pollingInfo, scope) => {
|
|
@@ -412,12 +415,7 @@ const transformPrepareCallsInFile = (content, classes, pollingInfo) => {
|
|
|
412
415
|
const containingClass = findContainingClass(node);
|
|
413
416
|
const containingFunction = findContainingStandaloneFunction(node);
|
|
414
417
|
if (containingClass) {
|
|
415
|
-
const
|
|
416
|
-
if (!className) {
|
|
417
|
-
ts.forEachChild(node, visit);
|
|
418
|
-
return;
|
|
419
|
-
}
|
|
420
|
-
const injectorMember = classNameToInjector.get(className);
|
|
418
|
+
const injectorMember = classNameToInjector.get(getClassKey(containingClass));
|
|
421
419
|
if (!injectorMember) {
|
|
422
420
|
ts.forEachChild(node, visit);
|
|
423
421
|
return;
|
|
@@ -481,7 +479,7 @@ const transformPrepareCallsInFile = (content, classes, pollingInfo) => {
|
|
|
481
479
|
}
|
|
482
480
|
injectorDeclarations.push({
|
|
483
481
|
position: bodyStart,
|
|
484
|
-
text:
|
|
482
|
+
text: `const ${injectorName} = inject(Injector);\n${getIndentation(content, bodyStart)}`,
|
|
485
483
|
});
|
|
486
484
|
});
|
|
487
485
|
injectorDeclarations.sort((left, right) => right.position - left.position);
|
|
@@ -779,8 +777,7 @@ const getQueryVariableName = (expression) => {
|
|
|
779
777
|
return currentExpression.text;
|
|
780
778
|
}
|
|
781
779
|
if (ts.isPropertyAccessExpression(currentExpression)) {
|
|
782
|
-
|
|
783
|
-
continue;
|
|
780
|
+
return currentExpression.name.text;
|
|
784
781
|
}
|
|
785
782
|
if (ts.isCallExpression(currentExpression)) {
|
|
786
783
|
currentExpression = currentExpression.expression;
|
|
@@ -10,6 +10,7 @@ const LEGACY_HTTP_METHOD = {
|
|
|
10
10
|
PATCH: 'patch',
|
|
11
11
|
DELETE: 'delete',
|
|
12
12
|
};
|
|
13
|
+
const HTTP_OPTION_NAMES = ['reportProgress', 'responseType', 'transferCache', 'withCredentials'];
|
|
13
14
|
const isLegacyHttpMethod = (value) => {
|
|
14
15
|
return Object.values(LEGACY_HTTP_METHOD).includes(value);
|
|
15
16
|
};
|
|
@@ -85,6 +86,7 @@ export const updateLegacyCreatorImportsAndUsages = (tree, scope, report) => {
|
|
|
85
86
|
}
|
|
86
87
|
console.log(`\n✅ Found ${legacyCreators.size} legacy query creator wrappers`);
|
|
87
88
|
const moduleGraph = createModuleGraph(tree);
|
|
89
|
+
const pendingWrites = new Map();
|
|
88
90
|
scope.visit(tree, (filePath) => {
|
|
89
91
|
if (!filePath.endsWith('.ts') || filePath.endsWith('.spec.ts')) {
|
|
90
92
|
return;
|
|
@@ -101,10 +103,14 @@ export const updateLegacyCreatorImportsAndUsages = (tree, scope, report) => {
|
|
|
101
103
|
report,
|
|
102
104
|
});
|
|
103
105
|
if (nextContent !== content) {
|
|
104
|
-
|
|
105
|
-
updatedFiles.push(filePath);
|
|
106
|
+
pendingWrites.set(filePath, nextContent);
|
|
106
107
|
}
|
|
107
108
|
});
|
|
109
|
+
// Writing a barrel mid-pass would hide its original re-exports from consumers visited after it.
|
|
110
|
+
pendingWrites.forEach((nextContent, filePath) => {
|
|
111
|
+
tree.write(filePath, nextContent);
|
|
112
|
+
updatedFiles.push(filePath);
|
|
113
|
+
});
|
|
108
114
|
if (updatedFiles.length > 0) {
|
|
109
115
|
console.log('\n✅ Updated legacy creator imports and usages in:');
|
|
110
116
|
updatedFiles.forEach((filePath) => console.log(` - ${filePath}`));
|
|
@@ -226,17 +232,62 @@ const analyzeLegacyQueryCreators = (content, queryClientFiles, filePath, report)
|
|
|
226
232
|
secure: false,
|
|
227
233
|
httpOptions: new Map(),
|
|
228
234
|
};
|
|
235
|
+
const warnUnreadableConfigValue = (property, passedAs) => {
|
|
236
|
+
const propertyName = property.name.getText(sourceFile);
|
|
237
|
+
report.addWarning({
|
|
238
|
+
title: `Carry over \`${propertyName}\` of ${creatorName}`,
|
|
239
|
+
summary: `${creatorName} passes \`${propertyName}\` ${passedAs} in its v2 config. The migration cannot read its value, so it was not carried over to the v3 creator.`,
|
|
240
|
+
action: propertyName === 'secure'
|
|
241
|
+
? 'If the value is true, switch the generated creator to the matching *Secure creator of the client by hand.'
|
|
242
|
+
: 'Move what it configures onto the generated creator by hand.',
|
|
243
|
+
locations: [
|
|
244
|
+
{ filePath, line: sourceFile.getLineAndCharacterOfPosition(property.getStart(sourceFile)).line + 1 },
|
|
245
|
+
],
|
|
246
|
+
source: 'legacy-query-creator-migration',
|
|
247
|
+
dedupeKey: `unreadable-config:${filePath}:${creatorName}:${propertyName}`,
|
|
248
|
+
});
|
|
249
|
+
};
|
|
229
250
|
const configObject = node.initializer.arguments[0];
|
|
230
251
|
if (configObject && ts.isObjectLiteralExpression(configObject)) {
|
|
231
252
|
configObject.properties.forEach((property) => {
|
|
253
|
+
if (ts.isSpreadAssignment(property)) {
|
|
254
|
+
report.addWarning({
|
|
255
|
+
title: `Restore the spread config of ${creatorName}`,
|
|
256
|
+
summary: `${creatorName} spreads \`${property.expression.getText(sourceFile)}\` into its v2 config. The migration cannot see what it contains, so none of it was carried over to the v3 creator.`,
|
|
257
|
+
action: 'Move the route, secure flag, types and http options it provides onto the generated creator by hand.',
|
|
258
|
+
locations: [
|
|
259
|
+
{ filePath, line: sourceFile.getLineAndCharacterOfPosition(property.getStart(sourceFile)).line + 1 },
|
|
260
|
+
],
|
|
261
|
+
source: 'legacy-query-creator-migration',
|
|
262
|
+
dedupeKey: `spread-config:${filePath}:${creatorName}`,
|
|
263
|
+
});
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (ts.isShorthandPropertyAssignment(property)) {
|
|
267
|
+
if (property.name.text === 'route') {
|
|
268
|
+
info.route = property.name.text;
|
|
269
|
+
}
|
|
270
|
+
if (HTTP_OPTION_NAMES.includes(property.name.text)) {
|
|
271
|
+
info.httpOptions.set(property.name.text, property.name.text);
|
|
272
|
+
}
|
|
273
|
+
if (property.name.text !== 'route' && !HTTP_OPTION_NAMES.includes(property.name.text)) {
|
|
274
|
+
warnUnreadableConfigValue(property, 'as a shorthand property');
|
|
275
|
+
}
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
232
278
|
if (!ts.isPropertyAssignment(property) || !ts.isIdentifier(property.name)) {
|
|
233
279
|
return;
|
|
234
280
|
}
|
|
235
281
|
if (property.name.text === 'route') {
|
|
236
282
|
info.route = property.initializer.getText(sourceFile);
|
|
237
283
|
}
|
|
238
|
-
if (property.name.text === 'secure'
|
|
239
|
-
|
|
284
|
+
if (property.name.text === 'secure') {
|
|
285
|
+
if (property.initializer.kind === ts.SyntaxKind.TrueKeyword) {
|
|
286
|
+
info.secure = true;
|
|
287
|
+
}
|
|
288
|
+
else if (property.initializer.kind !== ts.SyntaxKind.FalseKeyword) {
|
|
289
|
+
warnUnreadableConfigValue(property, 'with a value that is not a literal');
|
|
290
|
+
}
|
|
240
291
|
}
|
|
241
292
|
if (property.name.text === 'types' && ts.isObjectLiteralExpression(property.initializer)) {
|
|
242
293
|
property.initializer.properties.forEach((typeProperty) => {
|
|
@@ -271,7 +322,7 @@ const analyzeLegacyQueryCreators = (content, queryClientFiles, filePath, report)
|
|
|
271
322
|
}
|
|
272
323
|
});
|
|
273
324
|
}
|
|
274
|
-
if (
|
|
325
|
+
if (HTTP_OPTION_NAMES.includes(property.name.text)) {
|
|
275
326
|
info.httpOptions.set(property.name.text, property.initializer.getText(sourceFile));
|
|
276
327
|
}
|
|
277
328
|
});
|
|
@@ -303,27 +354,42 @@ const transformLegacyQueryCreators = (content, legacyCreators) => {
|
|
|
303
354
|
}
|
|
304
355
|
creatorsByClient.get(creator.clientName).push(creator);
|
|
305
356
|
});
|
|
357
|
+
const wrappersByStatement = new Map();
|
|
306
358
|
legacyCreators.forEach((creator) => {
|
|
307
|
-
const nextCreator = generateNewQueryCreator(creator);
|
|
308
|
-
const legacyWrapper = generateLegacyWrapper(creator);
|
|
309
359
|
let variableStatement;
|
|
360
|
+
let declaration;
|
|
310
361
|
const visit = (node) => {
|
|
362
|
+
if (variableStatement)
|
|
363
|
+
return;
|
|
311
364
|
if (ts.isVariableStatement(node)) {
|
|
312
|
-
|
|
313
|
-
if (declaration
|
|
365
|
+
declaration = node.declarationList.declarations.find((candidate) => ts.isIdentifier(candidate.name) && candidate.name.text === creator.name);
|
|
366
|
+
if (declaration) {
|
|
314
367
|
variableStatement = node;
|
|
368
|
+
return;
|
|
315
369
|
}
|
|
316
370
|
}
|
|
317
371
|
ts.forEachChild(node, visit);
|
|
318
372
|
};
|
|
319
373
|
visit(sourceFile);
|
|
320
|
-
if (variableStatement)
|
|
374
|
+
if (!variableStatement || !declaration)
|
|
375
|
+
return;
|
|
376
|
+
const isTopLevel = ts.isSourceFile(variableStatement.parent);
|
|
377
|
+
const exportPrefix = isTopLevel ? 'export ' : '';
|
|
378
|
+
const nextCreator = `${creator.name} = ${generateNewQueryCreator(creator)}`;
|
|
379
|
+
const legacyWrapper = generateLegacyWrapper(creator, exportPrefix);
|
|
380
|
+
if (variableStatement.declarationList.declarations.length === 1) {
|
|
321
381
|
replacements.push({
|
|
322
382
|
start: variableStatement.getStart(sourceFile),
|
|
323
383
|
end: variableStatement.getEnd(),
|
|
324
|
-
replacement: `${nextCreator}
|
|
384
|
+
replacement: `${exportPrefix}const ${nextCreator};\n${legacyWrapper}`,
|
|
325
385
|
});
|
|
386
|
+
return;
|
|
326
387
|
}
|
|
388
|
+
replacements.push({ start: declaration.getStart(sourceFile), end: declaration.getEnd(), replacement: nextCreator });
|
|
389
|
+
wrappersByStatement.set(variableStatement, [...(wrappersByStatement.get(variableStatement) ?? []), legacyWrapper]);
|
|
390
|
+
});
|
|
391
|
+
wrappersByStatement.forEach((wrappers, statement) => {
|
|
392
|
+
replacements.push({ start: statement.getEnd(), end: statement.getEnd(), replacement: `\n${wrappers.join('\n')}` });
|
|
327
393
|
});
|
|
328
394
|
let result = content;
|
|
329
395
|
replacements.sort((left, right) => right.start - left.start);
|
|
@@ -334,7 +400,7 @@ const transformLegacyQueryCreators = (content, legacyCreators) => {
|
|
|
334
400
|
result = addCreatorImports(result, creatorsByClient);
|
|
335
401
|
// The rewritten creators no longer go through `def<T>()` or the v2 client object, so whatever is
|
|
336
402
|
// left over from those constructs would land as a lint error the moment `formatFiles` runs.
|
|
337
|
-
return pruneUnusedNamedImports(result
|
|
403
|
+
return pruneUnusedNamedImports(result);
|
|
338
404
|
};
|
|
339
405
|
const addCreatorImports = (content, creatorsByClient) => {
|
|
340
406
|
const sourceFile = createSourceFile(content);
|
|
@@ -412,7 +478,7 @@ const generateNewQueryCreator = (creator) => {
|
|
|
412
478
|
const typeParameter = getTypeParameter(creator);
|
|
413
479
|
const options = Array.from(creator.httpOptions.entries()).map(([key, value]) => `${key}: ${value}`);
|
|
414
480
|
const optionsBlock = options.length > 0 ? `, {\n ${options.join(',\n ')}\n}` : '';
|
|
415
|
-
return
|
|
481
|
+
return `${creatorFunction}${typeParameter}(${creator.route}${optionsBlock})`;
|
|
416
482
|
};
|
|
417
483
|
const getTypeParameter = (creator) => {
|
|
418
484
|
if (creator.typeArgs && (creator.typeBody || creator.typeResponse)) {
|
|
@@ -440,10 +506,10 @@ const getTypeParameter = (creator) => {
|
|
|
440
506
|
}
|
|
441
507
|
return '';
|
|
442
508
|
};
|
|
443
|
-
const generateLegacyWrapper = (creator) => {
|
|
509
|
+
const generateLegacyWrapper = (creator, exportPrefix) => {
|
|
444
510
|
// `name` is what lets a `prepare()` called outside an injection context name itself in the error
|
|
445
511
|
// instead of surfacing as a bare NG0203.
|
|
446
|
-
return `/**\n * ${legacyQueryDeprecationTag(creator.name)}\n */\
|
|
512
|
+
return `/**\n * ${legacyQueryDeprecationTag(creator.name)}\n */\n${exportPrefix}const ${toLegacyName(creator.name)} = createLegacyQueryCreator({ name: '${toLegacyName(creator.name)}', creator: ${creator.name} });`;
|
|
447
513
|
};
|
|
448
514
|
const createAuthProviders = ({ tree, authProvidersNeeded, report, v2Configs, creatorToClient, }) => {
|
|
449
515
|
const createdProviders = [];
|
|
@@ -23,18 +23,15 @@ export const createMigrationScope = (tree, options) => {
|
|
|
23
23
|
if (roots.length === 0) {
|
|
24
24
|
return {
|
|
25
25
|
visit: (targetTree, callback) => visitNotIgnoredFiles(targetTree, '', callback),
|
|
26
|
-
includes: () => true,
|
|
27
26
|
describe: () => 'the whole workspace',
|
|
28
27
|
};
|
|
29
28
|
}
|
|
30
|
-
const includes = (filePath) => roots.some((root) => filePath === root || filePath.startsWith(`${root}/`));
|
|
31
29
|
return {
|
|
32
30
|
visit: (targetTree, callback) => {
|
|
33
31
|
for (const root of roots) {
|
|
34
32
|
visitNotIgnoredFiles(targetTree, root, callback);
|
|
35
33
|
}
|
|
36
34
|
},
|
|
37
|
-
includes,
|
|
38
35
|
describe: () => roots.join(', '),
|
|
39
36
|
};
|
|
40
37
|
};
|