Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"lint-sandbox": "eslint --config .github/eslint-security.config.mjs"
},
"devDependencies": {
"@angular-eslint/schematics": "0.2.0-beta.1",
"@angular-eslint/schematics": "^20.7.0",
"@devexpress/eslint-plugin-dx-security": "^0.2.8",
"@typescript-eslint/parser": "^8.63.0",
"eslint": "^9.39.4",
Expand Down
2 changes: 1 addition & 1 deletion packages/devextreme-cli/src/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ const run = async(commands, options, devextremeConfig) => {

if(app) {
if(commands[1] === 'view') {
app.addView(commands[2], options);
await app.addView(commands[2], options);
} else {
console.error('Invalid command');
printHelp(commands[0]);
Expand Down
32 changes: 20 additions & 12 deletions packages/devextreme-cli/src/applications/application.angular.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const { getPackageJsonPath } = require('../utility/package-json-utils');
const modifyJson = require('../utility/modify-json-file');
const schematicsVersion = latestVersions['devextreme-schematics'] || 'latest';

const minNgCliVersion = new semver('17.0.0');
const minNgCliVersion = new semver('20.0.0');
const ngCliWithZoneless = new semver('20.0.0');

async function runSchematicCommand(schematicCommand, options, evaluatingOptions) {
Expand All @@ -35,18 +35,16 @@ async function runSchematicCommand(schematicCommand, options, evaluatingOptions)
commandArguments.push(`--${dasherize(option)}=${options[option]}`);
}

runNgCommand(commandArguments, options, evaluatingOptions);
return runNgCommand(commandArguments, options, evaluatingOptions);
}

async function runNgCommand(commandArguments, commandOptions, commandConfig) {
const hasNg = await hasSutableNgCli();
const depsVersionTag = extractDepsVersionTag(commandOptions);
const npmCommandName = hasNg && !depsVersionTag ? 'ng' : 'npx';
const [minCliLtsVersion] = minNgCliVersion.version.split('.');

const ngCommandArguments = hasNg && !depsVersionTag
? []
: ['-p', `@angular/cli@${depsVersionTag || `v${minCliLtsVersion}-lts`}`, 'ng'];
: ['-p', `@angular/cli@${depsVersionTag || 'latest'}`, 'ng'];

ngCommandArguments.push(...commandArguments);
return runCommand(npmCommandName, ngCommandArguments, commandConfig);
Expand Down Expand Up @@ -74,8 +72,16 @@ const hasSutableNgCli = async() => {
return isSupportVersion;
};

const getTargetNgCliVersion = () => {
const localVersion = ngVersion.getLocalNgVersion();

return localVersion && localVersion.compare(minNgCliVersion) >= 0
? localVersion
: ngVersion.getPublicNgVersion();
};

const install = async(options) => {
runSchematicCommand('install', {
return runSchematicCommand('install', {
...options
});
};
Expand All @@ -100,7 +106,7 @@ const bumpAngular = (appPath, versionTag) => {

const create = async(appName, options) => {
const layout = await getLayoutInfo(options.layout);
const currentNgVersion = ngVersion.getNgCliVersion().version;
const currentNgVersion = getTargetNgCliVersion().version;
const depsVersionTag = extractDepsVersionTag(options);

const commandArguments = [
Expand All @@ -111,7 +117,8 @@ const create = async(appName, options) => {
'--skip-tests=true',
'--skip-install=true',
'--standalone=true',
'--ssr=false'
'--ssr=false',
'--ai-config=none'
];

if(ngCliWithZoneless.compare(currentNgVersion) <= 0) {
Expand All @@ -129,7 +136,8 @@ const create = async(appName, options) => {
options.resolveConflicts = 'override';
options.updateBudgets = true;
options.layout = layout;
addTemplate(appName, options, {
options.globalNgCliVersion = currentNgVersion;
await addTemplate(appName, options, {
cwd: appPath
});

Expand All @@ -140,17 +148,17 @@ const addTemplate = async(appName, options, evaluatingOptions) => {
const schematicOptions = {
...(appName && { project: appName }),
...options,
globalNgCliVersion: ngVersion.getNgCliVersion().version
globalNgCliVersion: options.globalNgCliVersion || getTargetNgCliVersion().version
};
runSchematicCommand('add-app-template', schematicOptions, evaluatingOptions);
return runSchematicCommand('add-app-template', schematicOptions, evaluatingOptions);
};

const addView = (viewName, options) => {
const schematicOptions = Object.assign({
name: viewName
}, options);
schematicOptions.name = viewName;
runSchematicCommand('add-view', schematicOptions);
return runSchematicCommand('add-view', schematicOptions);
};

const migrateConfigComponents = async(options = {}) => {
Expand Down
3 changes: 2 additions & 1 deletion packages/devextreme-cli/src/utility/ng-version.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const getLocalNgVersion = () => {
};

const getPublicNgVersion = () => {
const version = execSync('npm view @angular/cli@v16-lts version').toString();
const versions = JSON.parse(execSync('npm view @angular/cli@latest version --json').toString());
const version = Array.isArray(versions) ? versions[versions.length - 1] : versions;
return new semver(version);
};

Expand Down
13 changes: 5 additions & 8 deletions packages/devextreme-cli/testing/creating.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,8 @@ const args = minimist(process.argv.slice(2), {
: envs.filter(e => e.engine === args.e);

const depsVersionTag = args[depsVersionTagOptionName];
filteredEnvs.forEach(async env => {
try {
await env.createApp(depsVersionTag);
} catch(e) {
process.exit(1);
}
});
})().catch(reject => console.error(reject));
await Promise.all(filteredEnvs.map(env => env.createApp(depsVersionTag)));
})().catch(reject => {
console.error(reject);
process.exitCode = 1;
});
4 changes: 2 additions & 2 deletions packages/devextreme-cli/testing/env.angular.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ const sandboxPath = path.join(process.cwd(), './testing/sandbox/angular');
const appPath = path.join(sandboxPath, appName);
const schematicsDirectory = '../../../../devextreme-schematics';
const schematicsPath = path.join(sandboxPath, schematicsDirectory);
const appComponentTemplatePath = path.join(appPath, 'src/app/app.component.html');
const appComponentPath = path.join(appPath, 'src/app/app.component.ts');
const appComponentTemplatePath = path.join(appPath, 'src/app/app.html');
const appComponentPath = path.join(appPath, 'src/app/app.ts');

async function prepareSchematics() {
await packageManager.runInstall({
Expand Down
8 changes: 4 additions & 4 deletions packages/devextreme-schematics/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
],
"schematics": "./src/collection.json",
"dependencies": {
"@angular-devkit/core": "^19.2.27",
"@angular-devkit/schematics": "^19.2.27",
"@schematics/angular": "^19.2.27",
"@angular-devkit/core": "^20.3.36",
"@angular-devkit/schematics": "^20.3.36",
"@schematics/angular": "^20.3.36",
"parse5": "^7.3.0",
"picomatch": "^4.0.5",
"semver": "^7.8.5"
Expand All @@ -37,6 +37,6 @@
"jasmine": "^2.99.0",
"rxjs": "^7.8.2",
"tslint": "^5.20.1",
"typescript": "~5.6.3"
"typescript": "~5.8.3"
}
}
28 changes: 14 additions & 14 deletions packages/devextreme-schematics/src/add-layout/index_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ describe('layout', () => {
expect(devextremeConfigContent).toContain('"inputFile": "src/themes/metadata.additional.json"');
expect(devextremeConfigContent).toMatch(/\n\s{2}\S/g);

const componentContent = tree.readContent('/src/app/app.component.html');
const componentContent = tree.readContent('/src/app/app.html');
expect(componentContent).toContain('app-side-nav-outer-toolbar title="{{appInfo.title}}"');

const stylesContent = tree.readContent('/src/dx-styles.scss');
Expand All @@ -101,16 +101,16 @@ describe('layout', () => {
expect(styles[3]).toBe('src/themes/generated/theme.additional.dark.css');
expect(styles[4]).toBe('src/themes/generated/theme.additional.css');

const appContent = tree.readContent('/src/app/app.component.ts');
const appContent = tree.readContent('/src/app/app.ts');
expect(appContent).toContain('import { DxHttpModule }');
expect(appContent)
.toContain('import { SideNavOuterToolbarComponent as SideNavToolbarComponent }');
expect(appContent)
.toContain(`import { AuthService, ScreenService, AppInfoService } from './shared/services';`);
expect(appContent)
.toContain('import { FooterComponent }');
expect(appContent).toContain('templateUrl: \'./app.component.html\',');
expect(appContent).toContain('styleUrls: [\'./app.component.scss\']');
expect(appContent).toContain('templateUrl: \'./app.html\',');
expect(appContent).toContain('styleUrls: [\'./app.scss\']');
expect(appContent).toContain('selector: \'app-root\',');

const navigationMenu = tree.readContent(
Expand Down Expand Up @@ -217,18 +217,18 @@ describe('layout', () => {
options.resolveConflicts = 'createNew';
const tree = await runner.runSchematic('add-layout', options, appTree);

expect(tree.files).toContain('/src/app/app1.component.ts');
expect(tree.files).toContain('/src/app/app1.ts');

const componentContent = tree.readContent('/src/app/app1.component.html');
const componentContent = tree.readContent('/src/app/app1.html');
expect(componentContent).toContain('app-side-nav-outer-toolbar title="{{appInfo.title}}"');

const appContent = tree.readContent('/src/app/app.component.ts');
expect(appContent).toMatch(/templateUrl: '.\/app.component.html',/);
expect(appContent).toMatch(/styleUrl: '.\/app.component.scss'/);
const appContent = tree.readContent('/src/app/app.ts');
expect(appContent).toMatch(/templateUrl: '.\/app.html',/);
expect(appContent).toMatch(/styleUrl: '.\/app.scss'/);

const newAppContent = tree.readContent('/src/app/app1.component.ts');
expect(newAppContent).toMatch(/templateUrl: '.\/app1.component.html',/);
expect(newAppContent).toMatch(/styleUrls: \['.\/app1.component.scss'\]/);
const newAppContent = tree.readContent('/src/app/app1.ts');
expect(newAppContent).toMatch(/templateUrl: '.\/app1.html',/);
expect(newAppContent).toMatch(/styleUrls: \['.\/app1.scss'\]/);
expect(newAppContent).toContain(`import { AuthService, ScreenService, AppInfoService } from './shared/services';`);

const appInfo = tree.readContent('/src/app/shared/services/app-info.service.ts');
Expand Down Expand Up @@ -267,7 +267,7 @@ describe('layout', () => {
options.layout = 'side-nav-inner-toolbar';
options.resolveConflicts = 'override';
const tree = await runner.runSchematic('add-layout', options, appTree);
const content = tree.readContent('/src/app/app.component.html');
const content = tree.readContent('/src/app/app.html');

expect(content).toContain('app-side-nav-inner-toolbar title="{{appInfo.title}}"');
});
Expand Down Expand Up @@ -308,7 +308,7 @@ describe('layout', () => {
project: 'testApp2'
}, appTree);

const appContent = tree.readContent('projects/testApp2/src/app/app.component.ts');
const appContent = tree.readContent('projects/testApp2/src/app/app.ts');
expect(appContent).toContain('selector: \'app2-root\',');

const content = tree.readContent('/devextreme.json');
Expand Down
11 changes: 11 additions & 0 deletions packages/devextreme-schematics/src/add-view/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ import { humanize } from '../utility/string';

async function getPathToFile(host: Tree, projectName: string, moduleName: string) {
const rootPath = await getApplicationPath(host, projectName);
const directCandidates = [
`/${rootPath}/${moduleName}.ts`,
`/${rootPath}/${moduleName}.module.ts`,
`/${rootPath}/${moduleName}-module.ts`,
];

const directMatch = directCandidates.find(candidate => host.exists(candidate));
if (directMatch) {
return directMatch;
}

try {
return findModuleFromOptions(host, { name: moduleName, path: rootPath, module: moduleName });
Expand Down Expand Up @@ -185,6 +195,7 @@ export default function(options: any): Rule {
skipTests: options.skipTests,
inlineStyle: options.inlineStyle,
prefix: options.prefix,
type: 'component',
standalone: true
}),
addContentToView({ name, project }) as any,
Expand Down
2 changes: 1 addition & 1 deletion packages/devextreme-schematics/src/add-view/index_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ describe('view', () => {
}, tree);
tree = await runner.runSchematic('add-view', options, tree);

const moduleContent = tree.readContent('/src/app/test/test-routing.module.ts');
const moduleContent = tree.readContent('/src/app/test/test-routing-module.ts');

expect(moduleContent).toMatch(/component: TestComponent/);
expect(moduleContent).toMatch(/path: 'pages\/test'/);
Expand Down
6 changes: 4 additions & 2 deletions packages/devextreme-schematics/src/utility/change.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ const newLine = `
export function applyChanges(host: Tree, changes: Change[], filePath: string) {
const recorder = host.beginUpdate(filePath);

changes.forEach((change: InsertChange) => {
recorder.insertLeft(change.pos, change.toAdd);
changes.forEach((change: Change) => {
if (change instanceof InsertChange) {
recorder.insertLeft(change.pos, change.toAdd);
}
});

host.commitUpdate(recorder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ export interface TypeScriptResolutionResult {
errors: string[];
}

function getErrorMessage(err: unknown): string {
return err instanceof Error ? err.message : String(err);
}

/**
* Resolves TypeScript with a 3-level fallback strategy:
* 1. Project search - look in the user's project node_modules
Expand All @@ -27,7 +31,7 @@ export function resolveTypeScript(): TypeScriptResolutionResult {
};
}
} catch (err) {
errors.push(`Failed to import TypeScript from project: ${err?.message || err}`);
errors.push(`Failed to import TypeScript from project: ${getErrorMessage(err)}`);
}

try {
Expand All @@ -40,7 +44,7 @@ export function resolveTypeScript(): TypeScriptResolutionResult {
};
}
} catch (err) {
errors.push(`Failed to import TypeScript from global node_modules: ${err?.message || err}`);
errors.push(`Failed to import TypeScript from global node_modules: ${getErrorMessage(err)}`);
}

try {
Expand All @@ -53,7 +57,7 @@ export function resolveTypeScript(): TypeScriptResolutionResult {
};
}
} catch (err) {
errors.push(`Failed to import TypeScript from temporary installation (npm cache): ${err?.message || err}`);
errors.push(`Failed to import TypeScript from temporary installation (npm cache): ${getErrorMessage(err)}`);
}

return {
Expand Down
Loading
Loading