ngx-devkit-builders 1.3.3 → 2.0.1
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/LICENSE +1 -1
- package/README.md +5 -3
- package/builders.json +5 -0
- package/copy-environment/index.js +2 -14
- package/copy-environment/schema.json +1 -1
- package/package.json +8 -7
- package/robots/schema.json +1 -1
- package/sort-imports/index.js +215 -0
- package/sort-imports/schema.json +21 -0
- package/version/schema.json +1 -1
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
This package contains Architect builders used to build and test Angular applications and libraries.
|
|
15
15
|
|
|
16
|
-
> ✓ _Angular
|
|
16
|
+
> ✓ _Angular 21 compatible_
|
|
17
17
|
|
|
18
18
|
## 🚀 Builders
|
|
19
19
|
|
|
@@ -22,6 +22,7 @@ This package contains Architect builders used to build and test Angular applicat
|
|
|
22
22
|
| [version](./src/version/README.md) | Create build information file |
|
|
23
23
|
| [robots](./src/robots/README.md) | Create simplified robots.txt file |
|
|
24
24
|
| [copy-environment](./src/copy-environment/README.md) | Copy environment file |
|
|
25
|
+
| [sort-imports](./src/sort-imports/README.md) | Sort imports of components |
|
|
25
26
|
|
|
26
27
|
_More builders can be added in the future._
|
|
27
28
|
|
|
@@ -29,12 +30,13 @@ _More builders can be added in the future._
|
|
|
29
30
|
|
|
30
31
|
| Angular | ngx-devkit-builders | Install |
|
|
31
32
|
| ------- | ------------------- | -------------------------------- |
|
|
32
|
-
| >=
|
|
33
|
+
| >= 21 | 2.x | `yarn add ngx-devkit-builders` |
|
|
34
|
+
| >= 17 | 1.x | `yarn add ngx-devkit-builders@1` |
|
|
33
35
|
| >= 16 | 0.x | `yarn add ngx-devkit-builders@0` |
|
|
34
36
|
|
|
35
37
|
## 🪪 License
|
|
36
38
|
|
|
37
|
-
Copyright © 2022 -
|
|
39
|
+
Copyright © 2022 - 2026 [Dominik Hladik](https://github.com/Celtian)
|
|
38
40
|
|
|
39
41
|
All contents are licensed under the [MIT license].
|
|
40
42
|
|
package/builders.json
CHANGED
|
@@ -14,6 +14,11 @@
|
|
|
14
14
|
"description": "Create build information file",
|
|
15
15
|
"implementation": "./version",
|
|
16
16
|
"schema": "./version/schema.json"
|
|
17
|
+
},
|
|
18
|
+
"sort-imports": {
|
|
19
|
+
"description": "Sorts imports in Angular components and directives using AST analysis",
|
|
20
|
+
"implementation": "./dist/sort-imports",
|
|
21
|
+
"schema": "./dist/sort-imports/schema.json"
|
|
17
22
|
}
|
|
18
23
|
}
|
|
19
24
|
}
|
|
@@ -3,16 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const architect_1 = require("@angular-devkit/architect");
|
|
4
4
|
const core_1 = require("@angular-devkit/core");
|
|
5
5
|
const fs_extra_1 = require("fs-extra");
|
|
6
|
-
async function copyFiles({ sourceFile, targetFile, overwrite }) {
|
|
7
|
-
return new Promise((resolve, reject) => {
|
|
8
|
-
(0, fs_extra_1.copy)(sourceFile, targetFile, { overwrite }, () => {
|
|
9
|
-
reject();
|
|
10
|
-
});
|
|
11
|
-
resolve();
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
6
|
exports.default = (0, architect_1.createBuilder)(async ({ verbose, source, target, overwrite }, ctx) => {
|
|
15
|
-
ctx.logger.info('🚧
|
|
7
|
+
ctx.logger.info('🚧 Copying environment…');
|
|
16
8
|
const projectMetadata = await ctx.getProjectMetadata(ctx.target.project);
|
|
17
9
|
if (projectMetadata.projectType !== 'application') {
|
|
18
10
|
ctx.logger.error('❌ Project must be type of application');
|
|
@@ -30,11 +22,7 @@ exports.default = (0, architect_1.createBuilder)(async ({ verbose, source, targe
|
|
|
30
22
|
const sourceFile = `${environmentsFolder}/${source}`;
|
|
31
23
|
const targetFile = `${environmentsFolder}/${target}`;
|
|
32
24
|
try {
|
|
33
|
-
await
|
|
34
|
-
sourceFile,
|
|
35
|
-
targetFile,
|
|
36
|
-
overwrite,
|
|
37
|
-
});
|
|
25
|
+
await (0, fs_extra_1.copy)(sourceFile, targetFile, { overwrite });
|
|
38
26
|
if (overwrite) {
|
|
39
27
|
ctx.logger.info(`✔️ Environment replaced in "${targetFile}"`);
|
|
40
28
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ngx-devkit-builders",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"author": {
|
|
5
5
|
"name": "Dominik Hladík",
|
|
6
6
|
"email": "dominik.hladik@seznam.cz",
|
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
"builders": "builders.json",
|
|
13
13
|
"scripts": {},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@angular-devkit/architect": "^0.
|
|
16
|
-
"@angular-devkit/core": "^
|
|
17
|
-
"
|
|
15
|
+
"@angular-devkit/architect": "^0.2100.4",
|
|
16
|
+
"@angular-devkit/core": "^21.0.4",
|
|
17
|
+
"@schematics/angular": "^21.0.4",
|
|
18
|
+
"fs-extra": "^11.3.3"
|
|
18
19
|
},
|
|
19
20
|
"devDependencies": {},
|
|
20
21
|
"homepage": "https://github.com/Celtian/ngx-devkit-builders#readme",
|
|
@@ -32,14 +33,14 @@
|
|
|
32
33
|
"url": "https://github.com/Celtian/ngx-devkit-builders/issues"
|
|
33
34
|
},
|
|
34
35
|
"engines": {
|
|
35
|
-
"node": ">=
|
|
36
|
+
"node": ">=20",
|
|
36
37
|
"npm": "please-use-yarn"
|
|
37
38
|
},
|
|
38
39
|
"publishConfig": {
|
|
39
40
|
"registry": "https://registry.npmjs.org"
|
|
40
41
|
},
|
|
41
42
|
"peerDependencies": {
|
|
42
|
-
"@angular/core": ">=
|
|
43
|
-
"@angular/cli": ">=
|
|
43
|
+
"@angular/core": ">=21",
|
|
44
|
+
"@angular/cli": ">=21"
|
|
44
45
|
}
|
|
45
46
|
}
|
package/robots/schema.json
CHANGED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const architect_1 = require("@angular-devkit/architect");
|
|
4
|
+
const ast_utils_1 = require("@schematics/angular/utility/ast-utils");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
const ts = require("typescript");
|
|
8
|
+
const walkDirectory = (dir, files = []) => {
|
|
9
|
+
const items = (0, fs_1.readdirSync)(dir, { withFileTypes: true });
|
|
10
|
+
for (const item of items) {
|
|
11
|
+
const fullPath = (0, path_1.join)(dir, item.name);
|
|
12
|
+
if (item.isDirectory()) {
|
|
13
|
+
walkDirectory(fullPath, files);
|
|
14
|
+
}
|
|
15
|
+
else if (item.isFile() &&
|
|
16
|
+
item.name.endsWith('.ts') &&
|
|
17
|
+
!item.name.endsWith('.spec.ts') &&
|
|
18
|
+
!item.name.endsWith('.d.ts')) {
|
|
19
|
+
files.push(fullPath);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return files;
|
|
23
|
+
};
|
|
24
|
+
const isAngularComponentOrDirective = (filePath, includeDirectives) => {
|
|
25
|
+
try {
|
|
26
|
+
const content = (0, fs_1.readFileSync)(filePath, 'utf8');
|
|
27
|
+
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
28
|
+
const componentDecorators = (0, ast_utils_1.getDecoratorMetadata)(sourceFile, 'Component', '@angular/core');
|
|
29
|
+
if (componentDecorators.length > 0) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
if (includeDirectives) {
|
|
33
|
+
const directiveDecorators = (0, ast_utils_1.getDecoratorMetadata)(sourceFile, 'Directive', '@angular/core');
|
|
34
|
+
return directiveDecorators.length > 0;
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const analyzeComponentImports = (sourceFile) => {
|
|
43
|
+
const componentImports = [];
|
|
44
|
+
const visitNode = (node) => {
|
|
45
|
+
if (ts.isDecorator(node) && ts.isCallExpression(node.expression)) {
|
|
46
|
+
const callExpr = node.expression;
|
|
47
|
+
// Check if this is a @Component decorator
|
|
48
|
+
if (ts.isIdentifier(callExpr.expression) && callExpr.expression.text === 'Component') {
|
|
49
|
+
if (callExpr.arguments.length > 0) {
|
|
50
|
+
const arg = callExpr.arguments[0];
|
|
51
|
+
if (ts.isObjectLiteralExpression(arg)) {
|
|
52
|
+
// Look for the imports property
|
|
53
|
+
for (const property of arg.properties) {
|
|
54
|
+
if (ts.isPropertyAssignment(property) &&
|
|
55
|
+
ts.isIdentifier(property.name) &&
|
|
56
|
+
property.name.text === 'imports') {
|
|
57
|
+
if (ts.isArrayLiteralExpression(property.initializer)) {
|
|
58
|
+
// Extract import names from the array
|
|
59
|
+
for (const element of property.initializer.elements) {
|
|
60
|
+
if (ts.isIdentifier(element)) {
|
|
61
|
+
componentImports.push(element.text);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
ts.forEachChild(node, visitNode);
|
|
72
|
+
};
|
|
73
|
+
visitNode(sourceFile);
|
|
74
|
+
return componentImports;
|
|
75
|
+
};
|
|
76
|
+
const sortComponentImports = (imports) => {
|
|
77
|
+
// Sort imports alphabetically
|
|
78
|
+
return [...imports].sort();
|
|
79
|
+
};
|
|
80
|
+
const processFile = (filePath, options) => {
|
|
81
|
+
const content = (0, fs_1.readFileSync)(filePath, 'utf8');
|
|
82
|
+
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
83
|
+
const componentName = (0, path_1.basename)(filePath, '.ts');
|
|
84
|
+
const importsArray = analyzeComponentImports(sourceFile);
|
|
85
|
+
const sortedImports = sortComponentImports(importsArray);
|
|
86
|
+
// Check if imports need to be reordered
|
|
87
|
+
const hasChanges = JSON.stringify(importsArray) !== JSON.stringify(sortedImports);
|
|
88
|
+
if (hasChanges && !options.dryRun) {
|
|
89
|
+
// Find and replace the imports array in the component decorator
|
|
90
|
+
const updatedContent = updateComponentImports(content, sortedImports);
|
|
91
|
+
(0, fs_1.writeFileSync)(filePath, updatedContent, 'utf8');
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
filePath,
|
|
95
|
+
componentName,
|
|
96
|
+
importsArray,
|
|
97
|
+
sortedImports,
|
|
98
|
+
hasChanges,
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
const updateComponentImports = (content, sortedImports) => {
|
|
102
|
+
const sourceFile = ts.createSourceFile('temp.ts', content, ts.ScriptTarget.Latest, true);
|
|
103
|
+
let updatedContent = content;
|
|
104
|
+
const visitNode = (node) => {
|
|
105
|
+
if (ts.isDecorator(node) && ts.isCallExpression(node.expression)) {
|
|
106
|
+
const callExpr = node.expression;
|
|
107
|
+
if (ts.isIdentifier(callExpr.expression) && callExpr.expression.text === 'Component') {
|
|
108
|
+
if (callExpr.arguments.length > 0) {
|
|
109
|
+
const arg = callExpr.arguments[0];
|
|
110
|
+
if (ts.isObjectLiteralExpression(arg)) {
|
|
111
|
+
for (const property of arg.properties) {
|
|
112
|
+
if (ts.isPropertyAssignment(property) &&
|
|
113
|
+
ts.isIdentifier(property.name) &&
|
|
114
|
+
property.name.text === 'imports') {
|
|
115
|
+
if (ts.isArrayLiteralExpression(property.initializer)) {
|
|
116
|
+
// Replace the array content
|
|
117
|
+
const start = property.initializer.getStart(sourceFile);
|
|
118
|
+
const end = property.initializer.getEnd();
|
|
119
|
+
const newImportsArray = `[${sortedImports.join(', ')}]`;
|
|
120
|
+
updatedContent = content.substring(0, start) + newImportsArray + content.substring(end);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
ts.forEachChild(node, visitNode);
|
|
129
|
+
};
|
|
130
|
+
visitNode(sourceFile);
|
|
131
|
+
return updatedContent;
|
|
132
|
+
};
|
|
133
|
+
const outputResults = (analyses, options, context) => {
|
|
134
|
+
const changedFiles = analyses.filter((a) => a.hasChanges);
|
|
135
|
+
const unchangedFiles = analyses.filter((a) => !a.hasChanges);
|
|
136
|
+
context.logger.info('\n📋 COMPONENT IMPORTS SORTING ANALYSIS');
|
|
137
|
+
context.logger.info('━'.repeat(80));
|
|
138
|
+
if (options.dryRun) {
|
|
139
|
+
context.logger.info('🔍 DRY RUN MODE - No files were modified');
|
|
140
|
+
}
|
|
141
|
+
context.logger.info(`📊 Summary: ${analyses.length} files analyzed`);
|
|
142
|
+
context.logger.info(`✅ ${unchangedFiles.length} files already have sorted imports`);
|
|
143
|
+
context.logger.info(`🔄 ${changedFiles.length} files ${options.dryRun ? 'would be' : 'were'} updated`);
|
|
144
|
+
if (options.verbose && changedFiles.length > 0) {
|
|
145
|
+
context.logger.info(`📝 Files ${options.dryRun ? 'that would be' : 'that were'} updated:`);
|
|
146
|
+
context.logger.info('─'.repeat(80));
|
|
147
|
+
context.logger.info('Component Name'.padEnd(35) + 'Sorted Imports');
|
|
148
|
+
context.logger.info('─'.repeat(80));
|
|
149
|
+
changedFiles.forEach((analysis) => {
|
|
150
|
+
const componentName = analysis.componentName.length > 32 ? analysis.componentName.substring(0, 29) + '...' : analysis.componentName;
|
|
151
|
+
const sortedImports = `[${analysis.sortedImports.join(', ')}]`;
|
|
152
|
+
context.logger.info(componentName.padEnd(35) + sortedImports);
|
|
153
|
+
});
|
|
154
|
+
context.logger.info('─'.repeat(80));
|
|
155
|
+
}
|
|
156
|
+
if (options.verbose && unchangedFiles.length > 0) {
|
|
157
|
+
context.logger.info('✨ Files with already sorted imports:');
|
|
158
|
+
context.logger.info('─'.repeat(80));
|
|
159
|
+
context.logger.info('Component Name'.padEnd(35) + 'Current Imports');
|
|
160
|
+
context.logger.info('─'.repeat(80));
|
|
161
|
+
unchangedFiles.forEach((analysis) => {
|
|
162
|
+
const componentName = analysis.componentName.length > 32 ? analysis.componentName.substring(0, 29) + '...' : analysis.componentName;
|
|
163
|
+
if (analysis.importsArray.length > 0) {
|
|
164
|
+
const imports = `[${analysis.importsArray.join(', ')}]`;
|
|
165
|
+
context.logger.info(componentName.padEnd(35) + imports);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
const noImportsText = 'No imports array found';
|
|
169
|
+
context.logger.info(componentName.padEnd(35) + noImportsText);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
context.logger.info('─'.repeat(80));
|
|
173
|
+
}
|
|
174
|
+
context.logger.info('━'.repeat(80));
|
|
175
|
+
};
|
|
176
|
+
exports.default = (0, architect_1.createBuilder)(async (options, context) => {
|
|
177
|
+
try {
|
|
178
|
+
const projectRoot = context.target?.project
|
|
179
|
+
? (0, path_1.join)(context.workspaceRoot, 'projects', context.target.project)
|
|
180
|
+
: context.workspaceRoot;
|
|
181
|
+
const srcDir = (0, path_1.join)(projectRoot, 'src');
|
|
182
|
+
if (!(0, fs_1.statSync)(srcDir).isDirectory()) {
|
|
183
|
+
context.logger.error(`Source directory not found: ${srcDir}`);
|
|
184
|
+
return { success: false };
|
|
185
|
+
}
|
|
186
|
+
// Find all TypeScript files recursively
|
|
187
|
+
const allTsFiles = walkDirectory(srcDir);
|
|
188
|
+
// Filter to only component and directive files
|
|
189
|
+
const targetFiles = allTsFiles.filter((file) => isAngularComponentOrDirective(file, options.includeDirectives));
|
|
190
|
+
if (targetFiles.length === 0) {
|
|
191
|
+
context.logger.info('No Angular components or directives found.');
|
|
192
|
+
return { success: true };
|
|
193
|
+
}
|
|
194
|
+
// Process each file
|
|
195
|
+
const analyses = [];
|
|
196
|
+
for (const filePath of targetFiles) {
|
|
197
|
+
try {
|
|
198
|
+
const analysis = processFile(filePath, options);
|
|
199
|
+
analyses.push(analysis);
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
203
|
+
context.logger.warn(`Failed to process ${filePath}: ${errorMessage}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Output results
|
|
207
|
+
outputResults(analyses, options, context);
|
|
208
|
+
return { success: true };
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
|
|
212
|
+
context.logger.error('Error sorting imports: ' + errorMessage);
|
|
213
|
+
return { success: false };
|
|
214
|
+
}
|
|
215
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"dryRun": {
|
|
6
|
+
"description": "Run the builder without making any changes to files",
|
|
7
|
+
"type": "boolean",
|
|
8
|
+
"default": false
|
|
9
|
+
},
|
|
10
|
+
"verbose": {
|
|
11
|
+
"description": "Extended logging output",
|
|
12
|
+
"type": "boolean",
|
|
13
|
+
"default": false
|
|
14
|
+
},
|
|
15
|
+
"includeDirectives": {
|
|
16
|
+
"description": "Include Angular directives in addition to components",
|
|
17
|
+
"type": "boolean",
|
|
18
|
+
"default": true
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
package/version/schema.json
CHANGED