@craft-ng/dev-tools 0.5.0-beta.4 → 0.5.1-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +132 -23
- package/package.json +6 -2
- package/src/bin/craft-migrate-primitives.d.ts +2 -0
- package/src/bin/craft-migrate-primitives.js +71 -0
- package/src/bin/craft-migrate-primitives.js.map +1 -0
- package/src/bin/craft-migrate-routes.d.ts +2 -0
- package/src/bin/craft-migrate-routes.js +77 -0
- package/src/bin/craft-migrate-routes.js.map +1 -0
- package/src/bin/craft-migrate-services.d.ts +2 -0
- package/src/bin/craft-migrate-services.js +75 -0
- package/src/bin/craft-migrate-services.js.map +1 -0
- package/src/bin/craft-migrate.d.ts +2 -0
- package/src/bin/craft-migrate.js +90 -0
- package/src/bin/craft-migrate.js.map +1 -0
- package/src/eslint-rules/global-exception-registry-match.cjs +4 -4
- package/src/eslint-rules/index.cjs +6 -0
- package/src/eslint-rules/require-craft-exception-handler.cjs +142 -0
- package/src/eslint-rules/require-exception-component-di-check.cjs +314 -0
- package/src/eslint-rules/require-lazy-load-with-retry.cjs +148 -0
- package/src/eslint-rules/require-pending-component-di-check.cjs +3 -3
- package/src/index.d.ts +8 -0
- package/src/index.js +8 -0
- package/src/index.js.map +1 -1
- package/src/scripts/angular-brand-codemod.d.ts +17 -0
- package/src/scripts/angular-brand-codemod.js +44 -8
- package/src/scripts/angular-brand-codemod.js.map +1 -1
- package/src/scripts/angular-brand-codemod.spec.ts +3 -3
- package/src/scripts/angular-brand-codemod.ts +78 -7
- package/src/scripts/migrate.d.ts +32 -0
- package/src/scripts/migrate.js +67 -0
- package/src/scripts/migrate.js.map +1 -0
- package/src/scripts/migrate.ts +128 -0
- package/src/scripts/migration-workspace.d.ts +2 -0
- package/src/scripts/migration-workspace.js +72 -0
- package/src/scripts/migration-workspace.js.map +1 -0
- package/src/scripts/migration-workspace.ts +93 -0
- package/src/scripts/primitives/migrate-primitives.d.ts +27 -0
- package/src/scripts/primitives/migrate-primitives.js +354 -0
- package/src/scripts/primitives/migrate-primitives.js.map +1 -0
- package/src/scripts/primitives/migrate-primitives.spec.ts +279 -0
- package/src/scripts/primitives/migrate-primitives.ts +543 -0
- package/src/scripts/primitives/migration-diagnostic.d.ts +8 -0
- package/src/scripts/primitives/migration-diagnostic.js +2 -0
- package/src/scripts/primitives/migration-diagnostic.js.map +1 -0
- package/src/scripts/primitives/migration-diagnostic.ts +13 -0
- package/src/scripts/routes/migrate-routes.d.ts +34 -0
- package/src/scripts/routes/migrate-routes.js +669 -0
- package/src/scripts/routes/migrate-routes.js.map +1 -0
- package/src/scripts/routes/migrate-routes.spec.ts +264 -0
- package/src/scripts/routes/migrate-routes.ts +897 -0
- package/src/scripts/routes/migration-diagnostic.d.ts +16 -0
- package/src/scripts/routes/migration-diagnostic.js +2 -0
- package/src/scripts/routes/migration-diagnostic.js.map +1 -0
- package/src/scripts/routes/migration-diagnostic.ts +25 -0
- package/src/scripts/services/config.d.ts +2 -0
- package/src/scripts/services/config.js +39 -0
- package/src/scripts/services/config.js.map +1 -0
- package/src/scripts/services/config.ts +60 -0
- package/src/scripts/services/migrate-services.d.ts +29 -0
- package/src/scripts/services/migrate-services.js +895 -0
- package/src/scripts/services/migrate-services.js.map +1 -0
- package/src/scripts/services/migrate-services.spec.ts +719 -0
- package/src/scripts/services/migrate-services.ts +1282 -0
- package/src/scripts/services/migration-diagnostic.d.ts +8 -0
- package/src/scripts/services/migration-diagnostic.js +2 -0
- package/src/scripts/services/migration-diagnostic.js.map +1 -0
- package/src/scripts/services/migration-diagnostic.ts +27 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { __awaiter } from "tslib";
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
6
|
+
import { promisify } from 'node:util';
|
|
7
|
+
import { Node, Project, QuoteKind, SyntaxKind, } from 'ts-morph';
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
export function runPrimitivesMigration() {
|
|
10
|
+
return __awaiter(this, arguments, void 0, function* (options = {}) {
|
|
11
|
+
var _a, _b, _c;
|
|
12
|
+
const rootDir = resolve((_a = options.rootDir) !== null && _a !== void 0 ? _a : process.cwd());
|
|
13
|
+
const tsConfigFilePath = options.tsConfigFilePath
|
|
14
|
+
? resolve(options.tsConfigFilePath)
|
|
15
|
+
: defaultTsConfig(rootDir);
|
|
16
|
+
const project = new Project(Object.assign(Object.assign({}, (existsSync(tsConfigFilePath) ? { tsConfigFilePath } : {})), { manipulationSettings: { quoteKind: QuoteKind.Single }, skipAddingFilesFromTsConfig: false }));
|
|
17
|
+
project.addSourceFilesAtPaths([
|
|
18
|
+
join(rootDir, '**/*.ts'),
|
|
19
|
+
`!${join(rootDir, '**/node_modules/**')}`,
|
|
20
|
+
`!${join(rootDir, '**/dist/**')}`,
|
|
21
|
+
`!${join(rootDir, '**/.angular/**')}`,
|
|
22
|
+
`!${join(rootDir, '**/*.d.ts')}`,
|
|
23
|
+
]);
|
|
24
|
+
const selected = ((_b = options.files) === null || _b === void 0 ? void 0 : _b.length)
|
|
25
|
+
? new Set(options.files.map((file) => resolve(rootDir, file)))
|
|
26
|
+
: undefined;
|
|
27
|
+
const sourceFiles = project.getSourceFiles().filter((file) => {
|
|
28
|
+
const path = resolve(file.getFilePath());
|
|
29
|
+
return isInside(path, rootDir) && (!selected || selected.has(path));
|
|
30
|
+
});
|
|
31
|
+
const files = new Map();
|
|
32
|
+
const touched = new Set();
|
|
33
|
+
const diagnostics = [];
|
|
34
|
+
for (const sourceFile of sourceFiles) {
|
|
35
|
+
const changedSignals = migrateSignalsToState(sourceFile);
|
|
36
|
+
const changedResources = migrateSingleEmissionRxResources(sourceFile, diagnostics);
|
|
37
|
+
const changedWorkflows = annotateImperativeWorkflows(sourceFile, diagnostics);
|
|
38
|
+
diagnoseSignalForms(sourceFile, diagnostics);
|
|
39
|
+
if (changedSignals || changedResources || changedWorkflows)
|
|
40
|
+
touched.add(sourceFile);
|
|
41
|
+
if (changedSignals || changedResources || changedWorkflows)
|
|
42
|
+
getFileReport(files, sourceFile.getFilePath()).changed = true;
|
|
43
|
+
}
|
|
44
|
+
for (const file of touched)
|
|
45
|
+
file.organizeImports();
|
|
46
|
+
if (options.write) {
|
|
47
|
+
yield Promise.all([...touched].map((file) => file.save()));
|
|
48
|
+
}
|
|
49
|
+
let eslintRan = false;
|
|
50
|
+
if (options.write && options.eslint !== false && touched.size > 0) {
|
|
51
|
+
yield runEslint([...touched].map((file) => file.getFilePath()), rootDir);
|
|
52
|
+
eslintRan = true;
|
|
53
|
+
}
|
|
54
|
+
const remainingAngularSignals = countAngularSignalImports(project, rootDir);
|
|
55
|
+
const remainingSignalForms = countSignalFormImports(project, rootDir);
|
|
56
|
+
const result = {
|
|
57
|
+
changedFiles: [...touched].map((file) => file.getFilePath()),
|
|
58
|
+
files: [...files.values()],
|
|
59
|
+
diagnostics,
|
|
60
|
+
remainingAngularSignals,
|
|
61
|
+
remainingSignalForms,
|
|
62
|
+
eslintRan,
|
|
63
|
+
exitCode: (options.check &&
|
|
64
|
+
(remainingAngularSignals > 0 || remainingSignalForms > 0)) ||
|
|
65
|
+
(options.failOnManual && diagnostics.length > 0)
|
|
66
|
+
? 1
|
|
67
|
+
: 0,
|
|
68
|
+
};
|
|
69
|
+
if (options.jsonFilePath) {
|
|
70
|
+
const path = resolve(options.jsonFilePath);
|
|
71
|
+
yield mkdir(dirname(path), { recursive: true });
|
|
72
|
+
yield writeFile(path, `${JSON.stringify(result, null, 2)}\n`, 'utf8');
|
|
73
|
+
}
|
|
74
|
+
const log = (_c = options.log) !== null && _c !== void 0 ? _c : console.log;
|
|
75
|
+
if (options.json)
|
|
76
|
+
log(JSON.stringify(result, null, 2));
|
|
77
|
+
else
|
|
78
|
+
logSummary(result, log, options.write === true);
|
|
79
|
+
return result;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function migrateSignalsToState(sourceFile) {
|
|
83
|
+
const angularCore = sourceFile.getImportDeclaration('@angular/core');
|
|
84
|
+
const signalImport = angularCore === null || angularCore === void 0 ? void 0 : angularCore.getNamedImports().find((item) => item.getName() === 'signal');
|
|
85
|
+
if (!signalImport)
|
|
86
|
+
return false;
|
|
87
|
+
let changed = false;
|
|
88
|
+
const declarationsToAnnotate = new Set();
|
|
89
|
+
for (const call of [
|
|
90
|
+
...sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression),
|
|
91
|
+
]) {
|
|
92
|
+
if (call.wasForgotten())
|
|
93
|
+
continue;
|
|
94
|
+
if (call.getExpression().getText() !== 'signal')
|
|
95
|
+
continue;
|
|
96
|
+
const typeArgument = call.getTypeArguments()[0];
|
|
97
|
+
const initialValue = call.getArguments()[0];
|
|
98
|
+
if (!initialValue)
|
|
99
|
+
continue;
|
|
100
|
+
const value = typeArgument
|
|
101
|
+
? `${initialValue.getText()} as ${typeArgument.getText()} satisfies ${typeArgument.getText()}`
|
|
102
|
+
: initialValue.getText();
|
|
103
|
+
const declaration = findImperativeStateDeclaration(call);
|
|
104
|
+
if (declaration)
|
|
105
|
+
declarationsToAnnotate.add(declaration);
|
|
106
|
+
call.replaceWithText(`state(${value}, ({ set, update }) => ({ set, update }))`);
|
|
107
|
+
changed = true;
|
|
108
|
+
}
|
|
109
|
+
for (const declaration of [...declarationsToAnnotate].sort((left, right) => right.getStart() - left.getStart())) {
|
|
110
|
+
if (declaration.wasForgotten())
|
|
111
|
+
continue;
|
|
112
|
+
declaration.replaceWithText(`${IMPERATIVE_STATE_COMMENT}\n${declaration.getText()}`);
|
|
113
|
+
}
|
|
114
|
+
if (!changed)
|
|
115
|
+
return false;
|
|
116
|
+
signalImport.remove();
|
|
117
|
+
if (angularCore &&
|
|
118
|
+
!angularCore.getDefaultImport() &&
|
|
119
|
+
!angularCore.getNamespaceImport() &&
|
|
120
|
+
angularCore.getNamedImports().length === 0)
|
|
121
|
+
angularCore.remove();
|
|
122
|
+
ensureCoreImports(sourceFile, ['state']);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
const IMPERATIVE_STATE_COMMENT = '// CRAFT_IMPERATIVE_CODE_DETECTED: imperative code detected, prefer a declarative approach.';
|
|
126
|
+
function findImperativeStateDeclaration(call) {
|
|
127
|
+
const declaration = call.getFirstAncestor((ancestor) => Node.isPropertyDeclaration(ancestor) ||
|
|
128
|
+
Node.isVariableStatement(ancestor));
|
|
129
|
+
if (!declaration ||
|
|
130
|
+
declaration.getFullText().includes('CRAFT_IMPERATIVE_CODE_DETECTED'))
|
|
131
|
+
return undefined;
|
|
132
|
+
return declaration;
|
|
133
|
+
}
|
|
134
|
+
const REACTIVE_WORKFLOW_COMMENT = '// CRAFT_REACTIVE_WORKFLOW_RECOMMENDED: workflow impératif détecté...';
|
|
135
|
+
const FIRST_VALUE_FROM_REVIEW_COMMENT = '// CRAFT_FIRST_VALUE_FROM_REVIEW: firstValueFrom bridges an Observable temporarily; prefer a Promise-native Craft API when possible.';
|
|
136
|
+
function migrateSingleEmissionRxResources(sourceFile, diagnostics) {
|
|
137
|
+
var _a, _b;
|
|
138
|
+
const rxInterop = sourceFile.getImportDeclaration('@angular/core/rxjs-interop');
|
|
139
|
+
if (!(rxInterop === null || rxInterop === void 0 ? void 0 : rxInterop.getNamedImports().some((item) => item.getName() === 'rxResource')))
|
|
140
|
+
return false;
|
|
141
|
+
let changed = false;
|
|
142
|
+
for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
143
|
+
if (call.wasForgotten() || call.getExpression().getText() !== 'rxResource')
|
|
144
|
+
continue;
|
|
145
|
+
const config = call.getArguments()[0];
|
|
146
|
+
if (!config || !Node.isObjectLiteralExpression(config))
|
|
147
|
+
continue;
|
|
148
|
+
const stream = config.getProperty('stream');
|
|
149
|
+
if (!stream || !Node.isPropertyAssignment(stream))
|
|
150
|
+
continue;
|
|
151
|
+
const converted = convertSingleEmissionStream((_b = (_a = stream.getInitializer()) === null || _a === void 0 ? void 0 : _a.getText()) !== null && _b !== void 0 ? _b : '');
|
|
152
|
+
if (!converted)
|
|
153
|
+
continue;
|
|
154
|
+
call.getExpression().replaceWithText('query');
|
|
155
|
+
stream.replaceWithText(`loader: ${converted}`);
|
|
156
|
+
changed = true;
|
|
157
|
+
}
|
|
158
|
+
if (!changed)
|
|
159
|
+
return false;
|
|
160
|
+
ensureCoreImports(sourceFile, ['query']);
|
|
161
|
+
ensureNamedImport(sourceFile, 'rxjs', 'firstValueFrom');
|
|
162
|
+
removeNamedImportIfUnused(sourceFile, '@angular/core/rxjs-interop', 'rxResource');
|
|
163
|
+
removeNamedImportIfUnused(sourceFile, 'rxjs', 'from');
|
|
164
|
+
removeNamedImportIfUnused(sourceFile, 'rxjs', 'of');
|
|
165
|
+
removeNamedImportIfUnused(sourceFile, 'rxjs/operators', 'switchMap');
|
|
166
|
+
if (!sourceFile
|
|
167
|
+
.getDescendantsOfKind(SyntaxKind.CallExpression)
|
|
168
|
+
.some((call) => call.getExpression().getText() === 'rxResource')) {
|
|
169
|
+
removeDiagnostic(diagnostics, sourceFile, 'RX_RESOURCE_REQUIRES_QUERY');
|
|
170
|
+
}
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
function convertSingleEmissionStream(initializer) {
|
|
174
|
+
const match = initializer.match(/^\s*\(([^)]*)\)\s*=>\s*\{([\s\S]*)\}\s*$/);
|
|
175
|
+
if (!match)
|
|
176
|
+
return undefined;
|
|
177
|
+
let body = match[2];
|
|
178
|
+
const bridge = body.match(/return\s+from\(([\s\S]*?)\)\.pipe\(\s*switchMap\(\s*\((\w+)\)\s*=>\s*([\s\S]*?)\s*\)\s*\)\s*;/);
|
|
179
|
+
if (!bridge)
|
|
180
|
+
return undefined;
|
|
181
|
+
body = body.replace(/return\s+of\(([\s\S]*?)\)\s*;/g, 'return $1;');
|
|
182
|
+
body = body.replace(bridge[0], `${FIRST_VALUE_FROM_REVIEW_COMMENT}\nreturn firstValueFrom((await ${bridge[1]}).${stripReceiver(bridge[3], bridge[2])});`);
|
|
183
|
+
return `async (${match[1]}) => {${body}}`;
|
|
184
|
+
}
|
|
185
|
+
function stripReceiver(expression, receiver) {
|
|
186
|
+
const trimmed = expression.trim();
|
|
187
|
+
const prefix = `${receiver}.`;
|
|
188
|
+
return trimmed.startsWith(prefix) ? trimmed.slice(prefix.length) : trimmed;
|
|
189
|
+
}
|
|
190
|
+
function ensureNamedImport(sourceFile, moduleSpecifier, name) {
|
|
191
|
+
let declaration = sourceFile.getImportDeclaration(moduleSpecifier);
|
|
192
|
+
if (!declaration)
|
|
193
|
+
declaration = sourceFile.addImportDeclaration({ moduleSpecifier });
|
|
194
|
+
if (!declaration
|
|
195
|
+
.getNamedImports()
|
|
196
|
+
.some((namedImport) => namedImport.getName() === name))
|
|
197
|
+
declaration.addNamedImport(name);
|
|
198
|
+
}
|
|
199
|
+
function removeNamedImportIfUnused(sourceFile, moduleSpecifier, name) {
|
|
200
|
+
const declaration = sourceFile.getImportDeclaration(moduleSpecifier);
|
|
201
|
+
const namedImport = declaration === null || declaration === void 0 ? void 0 : declaration.getNamedImports().find((item) => item.getName() === name);
|
|
202
|
+
if (!namedImport)
|
|
203
|
+
return;
|
|
204
|
+
const stillUsed = sourceFile
|
|
205
|
+
.getDescendantsOfKind(SyntaxKind.Identifier)
|
|
206
|
+
.some((identifier) => identifier.getText() === name &&
|
|
207
|
+
identifier.getFirstAncestorByKind(SyntaxKind.ImportDeclaration) !==
|
|
208
|
+
declaration);
|
|
209
|
+
if (stillUsed)
|
|
210
|
+
return;
|
|
211
|
+
namedImport.remove();
|
|
212
|
+
if (declaration &&
|
|
213
|
+
!declaration.getDefaultImport() &&
|
|
214
|
+
!declaration.getNamespaceImport() &&
|
|
215
|
+
declaration.getNamedImports().length === 0)
|
|
216
|
+
declaration.remove();
|
|
217
|
+
}
|
|
218
|
+
function removeDiagnostic(diagnostics, sourceFile, code) {
|
|
219
|
+
const index = diagnostics.findIndex((diagnostic) => diagnostic.code === code &&
|
|
220
|
+
diagnostic.filePath === sourceFile.getFilePath());
|
|
221
|
+
if (index >= 0)
|
|
222
|
+
diagnostics.splice(index, 1);
|
|
223
|
+
}
|
|
224
|
+
function annotateImperativeWorkflows(sourceFile, diagnostics) {
|
|
225
|
+
let changed = false;
|
|
226
|
+
const functions = [
|
|
227
|
+
...sourceFile.getDescendantsOfKind(SyntaxKind.FunctionDeclaration),
|
|
228
|
+
...sourceFile.getDescendantsOfKind(SyntaxKind.MethodDeclaration),
|
|
229
|
+
];
|
|
230
|
+
for (const functionLike of functions) {
|
|
231
|
+
if (functionLike.getFullText().includes('CRAFT_REACTIVE_WORKFLOW_RECOMMENDED'))
|
|
232
|
+
continue;
|
|
233
|
+
const calls = functionLike.getDescendantsOfKind(SyntaxKind.CallExpression);
|
|
234
|
+
const hasSubmit = calls.some((call) => {
|
|
235
|
+
const expression = call.getExpression();
|
|
236
|
+
return (expression.getText() === 'submit' ||
|
|
237
|
+
(Node.isPropertyAccessExpression(expression) &&
|
|
238
|
+
expression.getName() === 'submit'));
|
|
239
|
+
});
|
|
240
|
+
if (!hasSubmit)
|
|
241
|
+
continue;
|
|
242
|
+
const stateWrites = calls.filter((call) => {
|
|
243
|
+
const expression = call.getExpression();
|
|
244
|
+
return (Node.isPropertyAccessExpression(expression) &&
|
|
245
|
+
['set', 'update'].includes(expression.getName()));
|
|
246
|
+
}).length;
|
|
247
|
+
const hasNavigation = calls.some((call) => {
|
|
248
|
+
const expression = call.getExpression();
|
|
249
|
+
return (Node.isPropertyAccessExpression(expression) &&
|
|
250
|
+
['navigate', 'navigateByUrl'].includes(expression.getName()));
|
|
251
|
+
});
|
|
252
|
+
if (stateWrites < 2 && !(stateWrites >= 1 && hasNavigation))
|
|
253
|
+
continue;
|
|
254
|
+
functionLike.replaceWithText(`${REACTIVE_WORKFLOW_COMMENT}\n${functionLike.getText()}`);
|
|
255
|
+
diagnose(diagnostics, 'IMPERATIVE_WORKFLOW_REQUIRES_REVIEW', sourceFile, 'Workflow impératif détecté: préférer insertFormSubmit avec une réaction au statut du formulaire, ou source$ avec on$/effect.');
|
|
256
|
+
changed = true;
|
|
257
|
+
}
|
|
258
|
+
return changed;
|
|
259
|
+
}
|
|
260
|
+
function diagnoseSignalForms(sourceFile, diagnostics) {
|
|
261
|
+
var _a;
|
|
262
|
+
const formsImport = sourceFile.getImportDeclaration('@angular/forms/signals');
|
|
263
|
+
if (!formsImport)
|
|
264
|
+
return;
|
|
265
|
+
const names = new Set(formsImport.getNamedImports().map((item) => item.getName()));
|
|
266
|
+
if (names.has('form')) {
|
|
267
|
+
diagnose(diagnostics, 'SIGNAL_FORM_REQUIRES_INSERT_FORM', sourceFile, 'Angular signal form `form(...)` doit être migré vers `state(..., insertForm(...))`; les chemins de champs doivent être remappés vers le form tree craft.');
|
|
268
|
+
}
|
|
269
|
+
if (names.has('validateAsync')) {
|
|
270
|
+
for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
271
|
+
if (!isValidateAsyncRxResourceCall(call))
|
|
272
|
+
continue;
|
|
273
|
+
diagnose(diagnostics, 'ASYNC_VALIDATOR_REQUIRES_QUERY', sourceFile, '`validateAsync(...)` basé sur `rxResource(...)` doit devenir une `query(...)` locale déclenchée par la valeur du champ, puis `cAsyncValidate(queryRef, ...)`. Dans un craftService, créer la query avec `yield* track(query(...))`.');
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if ((_a = sourceFile.getImportDeclaration('@angular/core/rxjs-interop')) === null || _a === void 0 ? void 0 : _a.getNamedImports().some((item) => item.getName() === 'rxResource')) {
|
|
277
|
+
diagnose(diagnostics, 'RX_RESOURCE_REQUIRES_QUERY', sourceFile, '`rxResource(...)` doit être remplacé par `query(...)` ou `mutation(...)` selon l’intention; dans un validateur async, préférer `query(...) + cAsyncValidate(...)`. Dans un craftService, englober toute primitive dépendante avec `yield* track(...)`.');
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function isValidateAsyncRxResourceCall(call) {
|
|
281
|
+
if (call.getExpression().getText() !== 'validateAsync')
|
|
282
|
+
return false;
|
|
283
|
+
return call
|
|
284
|
+
.getDescendantsOfKind(SyntaxKind.CallExpression)
|
|
285
|
+
.some((inner) => inner.getExpression().getText() === 'rxResource');
|
|
286
|
+
}
|
|
287
|
+
function ensureCoreImports(file, names) {
|
|
288
|
+
let declaration = file.getImportDeclaration('@craft-ng/core');
|
|
289
|
+
if (!declaration)
|
|
290
|
+
declaration = file.addImportDeclaration({
|
|
291
|
+
moduleSpecifier: '@craft-ng/core',
|
|
292
|
+
});
|
|
293
|
+
const existing = new Set(declaration.getNamedImports().map((item) => item.getName()));
|
|
294
|
+
declaration.addNamedImports(names.filter((name) => !existing.has(name)));
|
|
295
|
+
}
|
|
296
|
+
function diagnose(diagnostics, code, sourceFile, message) {
|
|
297
|
+
if (diagnostics.some((item) => item.code === code && item.filePath === sourceFile.getFilePath()))
|
|
298
|
+
return;
|
|
299
|
+
diagnostics.push({
|
|
300
|
+
code,
|
|
301
|
+
filePath: sourceFile.getFilePath(),
|
|
302
|
+
message,
|
|
303
|
+
manual: true,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
function countAngularSignalImports(project, rootDir) {
|
|
307
|
+
return project
|
|
308
|
+
.getSourceFiles()
|
|
309
|
+
.filter((file) => isInside(resolve(file.getFilePath()), rootDir))
|
|
310
|
+
.filter((file) => {
|
|
311
|
+
var _a;
|
|
312
|
+
return (_a = file
|
|
313
|
+
.getImportDeclaration('@angular/core')) === null || _a === void 0 ? void 0 : _a.getNamedImports().some((item) => item.getName() === 'signal');
|
|
314
|
+
}).length;
|
|
315
|
+
}
|
|
316
|
+
function countSignalFormImports(project, rootDir) {
|
|
317
|
+
return project
|
|
318
|
+
.getSourceFiles()
|
|
319
|
+
.filter((file) => isInside(resolve(file.getFilePath()), rootDir))
|
|
320
|
+
.filter((file) => Boolean(file.getImportDeclaration('@angular/forms/signals')))
|
|
321
|
+
.length;
|
|
322
|
+
}
|
|
323
|
+
function getFileReport(map, filePath) {
|
|
324
|
+
let report = map.get(filePath);
|
|
325
|
+
if (!report) {
|
|
326
|
+
report = { filePath, changed: false };
|
|
327
|
+
map.set(filePath, report);
|
|
328
|
+
}
|
|
329
|
+
return report;
|
|
330
|
+
}
|
|
331
|
+
function isInside(filePath, rootDir) {
|
|
332
|
+
const path = relative(rootDir, filePath);
|
|
333
|
+
return path === '' || (!path.startsWith('..') && !isAbsolute(path));
|
|
334
|
+
}
|
|
335
|
+
function defaultTsConfig(rootDir) {
|
|
336
|
+
for (const name of ['tsconfig.app.json', 'tsconfig.json']) {
|
|
337
|
+
const candidate = join(rootDir, name);
|
|
338
|
+
if (existsSync(candidate))
|
|
339
|
+
return candidate;
|
|
340
|
+
}
|
|
341
|
+
return join(rootDir, 'tsconfig.json');
|
|
342
|
+
}
|
|
343
|
+
function runEslint(files, cwd) {
|
|
344
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
345
|
+
const local = join(cwd, 'node_modules', '.bin', 'eslint');
|
|
346
|
+
yield execFileAsync(existsSync(local) ? local : 'eslint', ['--fix', ...files], { cwd });
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
function logSummary(result, log, wrote) {
|
|
350
|
+
log(`${wrote ? 'Migrated' : 'Would migrate'} ${result.changedFiles.length} file(s); ${result.diagnostics.length} manual diagnostic(s).`);
|
|
351
|
+
for (const diagnostic of result.diagnostics)
|
|
352
|
+
log(`[${diagnostic.code}] ${diagnostic.filePath}: ${diagnostic.message}`);
|
|
353
|
+
}
|
|
354
|
+
//# sourceMappingURL=migrate-primitives.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate-primitives.js","sourceRoot":"","sources":["../../../../../../libs/dev-tools/src/scripts/primitives/migrate-primitives.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { runPrimitivesMigration } from './migrate-primitives';
|
|
6
|
+
|
|
7
|
+
const temporaryDirectories: string[] = [];
|
|
8
|
+
|
|
9
|
+
afterEach(async () => {
|
|
10
|
+
await Promise.all(
|
|
11
|
+
temporaryDirectories
|
|
12
|
+
.splice(0)
|
|
13
|
+
.map((directory) => rm(directory, { recursive: true, force: true })),
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
async function fixture(files: Record<string, string>): Promise<string> {
|
|
18
|
+
const root = await mkdtemp(join(tmpdir(), 'craft-primitives-'));
|
|
19
|
+
temporaryDirectories.push(root);
|
|
20
|
+
await Promise.all(
|
|
21
|
+
Object.entries(files).map(async ([path, contents]) => {
|
|
22
|
+
const fullPath = join(root, path);
|
|
23
|
+
const { mkdir } = await import('node:fs/promises');
|
|
24
|
+
await mkdir(join(fullPath, '..'), { recursive: true });
|
|
25
|
+
await writeFile(fullPath, contents, 'utf8');
|
|
26
|
+
}),
|
|
27
|
+
);
|
|
28
|
+
return root;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('primitives migration', () => {
|
|
32
|
+
it('converts Angular signal calls to craft state', async () => {
|
|
33
|
+
const root = await fixture({
|
|
34
|
+
'tsconfig.json': JSON.stringify({
|
|
35
|
+
compilerOptions: { experimentalDecorators: true },
|
|
36
|
+
}),
|
|
37
|
+
'wizard.ts': `
|
|
38
|
+
import { computed, signal } from '@angular/core';
|
|
39
|
+
export class Wizard {
|
|
40
|
+
readonly activeStep = signal('delivery');
|
|
41
|
+
readonly total = computed(() => this.activeStep());
|
|
42
|
+
}
|
|
43
|
+
`,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const result = await runPrimitivesMigration({
|
|
47
|
+
rootDir: root,
|
|
48
|
+
write: true,
|
|
49
|
+
eslint: false,
|
|
50
|
+
log: () => undefined,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const output = await readFile(join(root, 'wizard.ts'), 'utf8');
|
|
54
|
+
expect(result.diagnostics).toEqual([]);
|
|
55
|
+
expect(output).toContain("import { computed } from '@angular/core'");
|
|
56
|
+
expect(output).toContain("import { state } from '@craft-ng/core'");
|
|
57
|
+
expect(output).toContain(
|
|
58
|
+
"activeStep = state('delivery', ({ set, update }) => ({ set, update }))",
|
|
59
|
+
);
|
|
60
|
+
expect(output).toContain(
|
|
61
|
+
'// CRAFT_IMPERATIVE_CODE_DETECTED: imperative code detected, prefer a declarative approach.',
|
|
62
|
+
);
|
|
63
|
+
expect(output).not.toContain('signal(');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('replaces explicit signal generics with satisfies on the state value', async () => {
|
|
67
|
+
const root = await fixture({
|
|
68
|
+
'tsconfig.json': '{}',
|
|
69
|
+
'wizard.ts': `
|
|
70
|
+
import { signal } from '@angular/core';
|
|
71
|
+
type WizardStep = 'delivery' | 'schedule' | 'review';
|
|
72
|
+
export const activeStep = signal<WizardStep>('delivery');
|
|
73
|
+
export const stepStatus = signal<Record<WizardStep, 'success' | 'error' | null>>({
|
|
74
|
+
delivery: null,
|
|
75
|
+
schedule: null,
|
|
76
|
+
review: null,
|
|
77
|
+
});
|
|
78
|
+
`,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
await runPrimitivesMigration({
|
|
82
|
+
rootDir: root,
|
|
83
|
+
write: true,
|
|
84
|
+
eslint: false,
|
|
85
|
+
log: () => undefined,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const output = await readFile(join(root, 'wizard.ts'), 'utf8');
|
|
89
|
+
expect(output).toContain(
|
|
90
|
+
"state('delivery' as WizardStep satisfies WizardStep, ({ set, update }) => ({ set, update }))",
|
|
91
|
+
);
|
|
92
|
+
expect(output).toContain(
|
|
93
|
+
"} as Record<WizardStep, 'success' | 'error' | null> satisfies Record<WizardStep, 'success' | 'error' | null>, ({ set, update }) => ({ set, update }))",
|
|
94
|
+
);
|
|
95
|
+
expect(output).not.toContain('state<WizardStep>');
|
|
96
|
+
expect(output).not.toContain('state<Record<');
|
|
97
|
+
expect(
|
|
98
|
+
output.match(/CRAFT_IMPERATIVE_CODE_DETECTED/g),
|
|
99
|
+
).toHaveLength(2);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('annotates an imperative workflow in an already migrated craftService', async () => {
|
|
103
|
+
const root = await fixture({
|
|
104
|
+
'tsconfig.json': '{}',
|
|
105
|
+
'wizard.ts': `
|
|
106
|
+
import { craftService, state } from '@craft-ng/core';
|
|
107
|
+
export const { injectWizard } = craftService(
|
|
108
|
+
{ name: 'Wizard', scope: 'function' },
|
|
109
|
+
() => {
|
|
110
|
+
const activeStep = state('delivery');
|
|
111
|
+
const stepStatus = state({ delivery: null });
|
|
112
|
+
const checkoutForm = { form: { delivery: { submit: async () => true } } };
|
|
113
|
+
const router = { navigate: async (_commands: unknown[]) => true };
|
|
114
|
+
|
|
115
|
+
async function validateStep(step: 'delivery') {
|
|
116
|
+
const success = await checkoutForm.form[step].submit();
|
|
117
|
+
if (!success) return;
|
|
118
|
+
stepStatus.update((status) => ({ ...status, [step]: 'success' }));
|
|
119
|
+
activeStep.set(step);
|
|
120
|
+
await router.navigate(['/checkout', step]);
|
|
121
|
+
}
|
|
122
|
+
return { validateStep };
|
|
123
|
+
},
|
|
124
|
+
);
|
|
125
|
+
`,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const first = await runPrimitivesMigration({
|
|
129
|
+
rootDir: root,
|
|
130
|
+
write: true,
|
|
131
|
+
eslint: false,
|
|
132
|
+
log: () => undefined,
|
|
133
|
+
});
|
|
134
|
+
const output = await readFile(join(root, 'wizard.ts'), 'utf8');
|
|
135
|
+
expect(output).toContain(
|
|
136
|
+
'// CRAFT_REACTIVE_WORKFLOW_RECOMMENDED: workflow impératif détecté...',
|
|
137
|
+
);
|
|
138
|
+
expect(first.diagnostics.map((diagnostic) => diagnostic.code)).toContain(
|
|
139
|
+
'IMPERATIVE_WORKFLOW_REQUIRES_REVIEW',
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
await runPrimitivesMigration({
|
|
143
|
+
rootDir: root,
|
|
144
|
+
write: true,
|
|
145
|
+
eslint: false,
|
|
146
|
+
log: () => undefined,
|
|
147
|
+
});
|
|
148
|
+
const secondOutput = await readFile(join(root, 'wizard.ts'), 'utf8');
|
|
149
|
+
expect(
|
|
150
|
+
secondOutput.match(/CRAFT_REACTIVE_WORKFLOW_RECOMMENDED/g),
|
|
151
|
+
).toHaveLength(1);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('reports signal forms and rxResource async validators for insertForm rewrite', async () => {
|
|
155
|
+
const root = await fixture({
|
|
156
|
+
'tsconfig.json': JSON.stringify({
|
|
157
|
+
compilerOptions: { experimentalDecorators: true },
|
|
158
|
+
}),
|
|
159
|
+
'checkout-wizard.ts': `
|
|
160
|
+
import { signal } from '@angular/core';
|
|
161
|
+
import { rxResource } from '@angular/core/rxjs-interop';
|
|
162
|
+
import { form, required, validateAsync } from '@angular/forms/signals';
|
|
163
|
+
export class CheckoutWizard {
|
|
164
|
+
private readonly discount = signal(0);
|
|
165
|
+
readonly checkoutForm = form(signal({ coupon: { code: '' } }), (schema) => {
|
|
166
|
+
required(schema.coupon.code);
|
|
167
|
+
validateAsync(schema.coupon.code, {
|
|
168
|
+
params: ({ fieldTreeOf }) => ({ fieldTreeOf }),
|
|
169
|
+
factory: (params) => rxResource({
|
|
170
|
+
params,
|
|
171
|
+
stream: ({ params }) => params.fieldTreeOf(schema.coupon.code)().value(),
|
|
172
|
+
}),
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
`,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const result = await runPrimitivesMigration({
|
|
180
|
+
rootDir: root,
|
|
181
|
+
write: true,
|
|
182
|
+
eslint: false,
|
|
183
|
+
log: () => undefined,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const output = await readFile(join(root, 'checkout-wizard.ts'), 'utf8');
|
|
187
|
+
expect(output).toContain(
|
|
188
|
+
'state(0, ({ set, update }) => ({ set, update }))',
|
|
189
|
+
);
|
|
190
|
+
expect(output).toContain(
|
|
191
|
+
'state({ coupon: { code:',
|
|
192
|
+
);
|
|
193
|
+
expect(result.diagnostics.map((diagnostic) => diagnostic.code)).toEqual([
|
|
194
|
+
'SIGNAL_FORM_REQUIRES_INSERT_FORM',
|
|
195
|
+
'ASYNC_VALIDATOR_REQUIRES_QUERY',
|
|
196
|
+
'RX_RESOURCE_REQUIRES_QUERY',
|
|
197
|
+
]);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('converts a debounced single-emission rxResource to query', async () => {
|
|
201
|
+
const root = await fixture({
|
|
202
|
+
'tsconfig.json': '{}',
|
|
203
|
+
'location-field.ts': `
|
|
204
|
+
import { signal } from '@angular/core';
|
|
205
|
+
import { rxResource, toObservable, toSignal } from '@angular/core/rxjs-interop';
|
|
206
|
+
import { debounceTime, distinctUntilChanged, from, map, of } from 'rxjs';
|
|
207
|
+
import { switchMap } from 'rxjs/operators';
|
|
208
|
+
|
|
209
|
+
export class LocationField {
|
|
210
|
+
private readonly api = Promise.resolve({
|
|
211
|
+
search: (params: { q: string }) => of([{ label: params.q }]),
|
|
212
|
+
});
|
|
213
|
+
private readonly searchInput = signal('');
|
|
214
|
+
private readonly debouncedSearch = toSignal(
|
|
215
|
+
toObservable(this.searchInput).pipe(
|
|
216
|
+
debounceTime(280),
|
|
217
|
+
map((value) => value.trim()),
|
|
218
|
+
distinctUntilChanged(),
|
|
219
|
+
),
|
|
220
|
+
{ initialValue: '' },
|
|
221
|
+
);
|
|
222
|
+
readonly suggestions = rxResource({
|
|
223
|
+
params: () => {
|
|
224
|
+
const query = this.debouncedSearch();
|
|
225
|
+
return query.length < 2 ? undefined : { q: query };
|
|
226
|
+
},
|
|
227
|
+
stream: ({ params }) => {
|
|
228
|
+
if (!params) return of([]);
|
|
229
|
+
return from(this.api).pipe(switchMap((service) => service.search(params)));
|
|
230
|
+
},
|
|
231
|
+
defaultValue: [],
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
`,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const result = await runPrimitivesMigration({
|
|
238
|
+
rootDir: root,
|
|
239
|
+
write: true,
|
|
240
|
+
eslint: false,
|
|
241
|
+
log: () => undefined,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const output = await readFile(join(root, 'location-field.ts'), 'utf8');
|
|
245
|
+
expect(output).toContain('readonly suggestions = query({');
|
|
246
|
+
expect(output).toContain('loader: async ({ params }) =>');
|
|
247
|
+
expect(output).toContain('return [];');
|
|
248
|
+
expect(output).toContain(
|
|
249
|
+
'// CRAFT_FIRST_VALUE_FROM_REVIEW: firstValueFrom bridges an Observable temporarily; prefer a Promise-native Craft API when possible.',
|
|
250
|
+
);
|
|
251
|
+
expect(output).toContain(
|
|
252
|
+
'return firstValueFrom((await this.api).search(params));',
|
|
253
|
+
);
|
|
254
|
+
expect(output).not.toContain('rxResource');
|
|
255
|
+
expect(output).not.toContain("from 'rxjs/operators'");
|
|
256
|
+
expect(result.diagnostics).toEqual([]);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('supports check mode for remaining signal forms', async () => {
|
|
260
|
+
const root = await fixture({
|
|
261
|
+
'tsconfig.json': '{}',
|
|
262
|
+
'form.ts': `
|
|
263
|
+
import { form } from '@angular/forms/signals';
|
|
264
|
+
export const x = form;
|
|
265
|
+
`,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const result = await runPrimitivesMigration({
|
|
269
|
+
rootDir: root,
|
|
270
|
+
write: false,
|
|
271
|
+
check: true,
|
|
272
|
+
eslint: false,
|
|
273
|
+
log: () => undefined,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
expect(result.exitCode).toBe(1);
|
|
277
|
+
expect(result.remainingSignalForms).toBe(1);
|
|
278
|
+
});
|
|
279
|
+
});
|