Skip to content

Commit 3d3f0a2

Browse files
Angular: Add deprecated configuration components migration tool (#993)
* Add angular nested components migration command * fix proper escaping issue * cli fixes according to the plan * Update packages/devextreme * Update packages/devextreme-schematics/src/collection.json * type import fix * Text updates after TW Review --------- Co-authored-by: Copilot <[email protected]>
1 parent 63b5450 commit 3d3f0a2

File tree

14 files changed

+1835
-10
lines changed

14 files changed

+1835
-10
lines changed

packages/devextreme-cli/index.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ if(args.help) {
3939
const run = async(commands, options) => {
4040
if(application.isApplicationCommand(commands[0])) {
4141
await application.run(commands, options, devextremeConfig.read());
42+
} else if(application.isMigrationCommand(commands[0])) {
43+
if(!commands[1]) {
44+
console.error('Please specify a change name for migration.');
45+
printHelp('migrate');
46+
return;
47+
}
48+
await application.run(['migrate', commands[1], ...commands.slice(2)], options, { applicationEngine: 'angular' });
49+
return;
4250
} else if(themeBuilder.isThemeBuilderCommand(commands[0])) {
4351
options.command = commands[0];
4452
themeBuilder.run(options);

packages/devextreme-cli/src/application.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,20 @@ const isApplicationCommand = (command) => {
99
return [ 'new', 'add' ].includes(command);
1010
};
1111

12+
const isMigrationCommand = (command) => {
13+
return [ 'migrate' ].includes(command);
14+
};
15+
1216
const handleWrongAppType = (appType, command) => {
1317
console.error(`The '${appType}' application type is not valid`);
1418
printHelp(command);
1519
};
1620

21+
const handleWrongChangeName = (changeName, command) => {
22+
console.error(`The '${changeName}' change name is not valid`);
23+
printHelp(command);
24+
};
25+
1726
const createReact = async(appName, options, command) => {
1827
const reactAppType = await getReactAppType(options['app-type']);
1928

@@ -30,12 +39,25 @@ const createReact = async(appName, options, command) => {
3039
};
3140

3241
const run = async(commands, options, devextremeConfig) => {
42+
3343
if(!commands[1]) {
3444
console.error('Command is incomplete. Please specify parameters.');
3545
printHelp(commands[0]);
3646
return;
3747
}
3848

49+
if(commands[0] === 'migrate') {
50+
const changeName = commands[1];
51+
switch(changeName) {
52+
case 'angular-config-components':
53+
await angularApplication.migrateConfigComponents(options);
54+
return;
55+
default:
56+
handleWrongChangeName(changeName, commands[0]);
57+
return;
58+
}
59+
}
60+
3961
if(commands[0] === 'new') {
4062
const app = commands[1];
4163
const appName = commands[2] || 'my-app';
@@ -104,5 +126,6 @@ const run = async(commands, options, devextremeConfig) => {
104126

105127
module.exports = {
106128
isApplicationCommand,
129+
isMigrationCommand,
107130
run
108131
};

packages/devextreme-cli/src/applications/application.angular.js

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,24 @@ async function runNgCommand(commandArguments, commandOptions, commandConfig) {
5353
}
5454

5555
function localPackageExists(packageName) {
56+
// Check local node_modules first
5657
const nodeModulesPath = path.join(process.cwd(), 'node_modules');
57-
if(!fs.existsSync(nodeModulesPath)) {
58-
return;
58+
if(fs.existsSync(nodeModulesPath)) {
59+
const packageJsonPath = path.join(nodeModulesPath, packageName, 'package.json');
60+
if(fs.existsSync(packageJsonPath)) {
61+
return true;
62+
}
5963
}
6064

61-
const packageJsonPath = path.join(nodeModulesPath, packageName, 'package.json');
62-
return fs.existsSync(packageJsonPath);
65+
// Check if globally installed by trying to resolve the package
66+
try {
67+
require.resolve(`${packageName}/package.json`);
68+
return true;
69+
} catch(e) {
70+
// Package not found globally
71+
}
72+
73+
return false;
6374
}
6475

6576
const hasSutableNgCli = async() => {
@@ -152,6 +163,70 @@ const addView = (viewName, options) => {
152163
runSchematicCommand('add-view', schematicOptions);
153164
};
154165

166+
const migrateConfigComponents = async(options = {}) => {
167+
const collectionName = 'devextreme-schematics';
168+
169+
// Check if devextreme-schematics is installed
170+
if(!localPackageExists(collectionName)) {
171+
const prompts = require('prompts');
172+
173+
console.log(`\nThe '${collectionName}' package is required to run this command.`);
174+
175+
const response = await prompts({
176+
type: 'confirm',
177+
name: 'install',
178+
message: `Would you like to install '${collectionName}' now?`,
179+
initial: true
180+
});
181+
182+
if(!response.install) {
183+
console.log('Migration cancelled. Please install devextreme-schematics manually:');
184+
console.log(`npm install -g ${collectionName}@${schematicsVersion}`);
185+
process.exit(1);
186+
}
187+
188+
console.log(`Installing ${collectionName}@${schematicsVersion}...`);
189+
try {
190+
await runCommand('npm', ['install', '-g', `${collectionName}@${schematicsVersion}`], { stdio: 'inherit' });
191+
console.log('Installation completed successfully.');
192+
} catch(error) {
193+
console.error('Failed to install devextreme-schematics. Please install manually:');
194+
console.error(`npm install -g ${collectionName}@${schematicsVersion}`);
195+
process.exit(1);
196+
}
197+
}
198+
199+
const schematicOptions = {
200+
...options
201+
};
202+
203+
if(schematicOptions.include && typeof schematicOptions.include === 'string') {
204+
schematicOptions.include = schematicOptions.include.split(',').map(s => s.trim());
205+
}
206+
if(schematicOptions.scriptInclude && typeof schematicOptions.scriptInclude === 'string') {
207+
schematicOptions.scriptInclude = schematicOptions.scriptInclude.split(',').map(s => s.trim());
208+
}
209+
210+
const commandArguments = ['schematics', `${collectionName}:migrate-config-components`];
211+
212+
const { [depsVersionTagOptionName]: _, ...optionsToArguments } = schematicOptions; // eslint-disable-line no-unused-vars
213+
for(let option in optionsToArguments) {
214+
const value = optionsToArguments[option];
215+
if(value !== undefined && value !== null && value !== '') {
216+
if(Array.isArray(value)) {
217+
if(value.length > 0) {
218+
commandArguments.push(`--${dasherize(option)}=${value.join(',')}`);
219+
}
220+
} else {
221+
commandArguments.push(`--${dasherize(option)}=${value}`);
222+
}
223+
}
224+
}
225+
226+
// Use runCommand directly with npx to work outside Angular workspace
227+
return runCommand('npx', commandArguments, { stdio: 'inherit' });
228+
};
229+
155230
const changeMainTs = (appPath) => {
156231
const filePath = path.join(appPath, 'src', 'main.ts');
157232

@@ -174,5 +249,6 @@ module.exports = {
174249
install,
175250
create,
176251
addTemplate,
177-
addView
252+
addView,
253+
migrateConfigComponents
178254
};

packages/devextreme-cli/src/commands.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,24 @@
4848
"name": "devextreme-angular",
4949
"description": "Add DevExtreme to an Angular application"
5050
}]
51+
}, {
52+
"name": "migrate",
53+
"description": "Migration commands for DevExtreme applications",
54+
"usage": "devextreme migrate <change name> [options]",
55+
"arguments": [{
56+
"name": "angular-config-components",
57+
"description": "Migrate to the latest DevExtreme configuration components.",
58+
"options": [{
59+
"name": "--include",
60+
"description": "Template file glob patterns to include (default: **/*.html). You can pass multiple patterns as a comma-separated string (e.g. \"**/a.html,**/b.html\") or as an array (e.g. [\"**/a.html\",\"**/b.html\"])."
61+
}, {
62+
"name": "--script-include",
63+
"description": "TypeScript/JavaScript file glob patterns to scan for inline templates (default: **/*.ts,**/*.js). You can pass multiple patterns as a comma-separated string or as an array. Pass an empty value ('' or []) to disable."
64+
}, {
65+
"name": "--dry",
66+
"description": "Run in dry mode to preview changes without applying them (default: false)."
67+
}]
68+
}]
5169
}, {
5270
"name": "build-theme",
5371
"description": "Build a custom color scheme",

packages/devextreme-schematics/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,30 @@ This package includes the following schematics:
1919

2020
- [add-view](src/add-view)
2121
Adds a view to a DevExtreme Angular application
22+
23+
## TypeScript Dependency & Global CLI Usage
24+
25+
Some DevExtreme migration schematics require TypeScript to process inline Angular templates. The CLI attempts to resolve TypeScript from multiple locations:
26+
27+
- The CLI's own node_modules
28+
- Your project's node_modules
29+
- The global node_modules
30+
31+
If TypeScript is not found, inline template migration will be skipped and a warning will be shown with resolution attempts and errors.
32+
33+
### How to Fix TypeScript Not Available
34+
35+
1. **Local Project:** Install TypeScript in your project root:
36+
```sh
37+
npm install typescript --save-dev
38+
```
39+
2. **Global CLI:** If you use the CLI globally, also install TypeScript globally:
40+
```sh
41+
npm install -g typescript
42+
```
43+
3. **npx Usage:** If you use npx, ensure TypeScript is available in your workspace or globally.
44+
4. **Troubleshooting:**
45+
- Some npm global installs may not link dependencies as expected. If you see repeated TypeScript resolution errors, try running the CLI from a project where TypeScript is installed locally.
46+
- You can also manually link TypeScript to your global node_modules if needed.
47+
48+
If you continue to see errors, review the warning output for resolution attempts and check your npm/node installation paths.

packages/devextreme-schematics/package-lock.json

Lines changed: 51 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/devextreme-schematics/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@
2424
"dependencies": {
2525
"@angular-devkit/core": "^17.3.17",
2626
"@angular-devkit/schematics": "^17.3.17",
27-
"@schematics/angular": "^17.3.17"
27+
"@schematics/angular": "^17.3.17",
28+
"parse5": "^7.1.2",
29+
"picomatch": "^4.0.2"
2830
},
2931
"devDependencies": {
3032
"@types/jasmine": "~3.10.18",
3133
"@types/node": "ts5.2",
34+
"@types/picomatch": "^4.0.2",
3235
"@types/semver": "^7.7.1",
3336
"jasmine": "^2.99.0",
3437
"rxjs": "^6.6.7",

packages/devextreme-schematics/src/collection.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
"description": "Add a new view to app-template.",
2525
"factory": "./add-view/index",
2626
"schema": "./add-view/schema.json"
27+
},
28+
"migrate-config-components": {
29+
"description": "Migrate to the latest DevExtreme configuration components.",
30+
"factory": "./migrate-config-components/index#migrateConfigComponents",
31+
"schema": "./migrate-config-components/schema.json"
2732
}
2833
}
2934
}

0 commit comments

Comments
 (0)