@ethlete/core 5.0.0-next.6 ā 5.0.0-next.61
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 +714 -0
- package/README.md +28 -4
- package/fesm2022/ethlete-core.mjs +8061 -3502
- package/fesm2022/ethlete-core.mjs.map +1 -1
- package/generators/devtools-about/generator.js +122 -0
- package/generators/devtools-about/schema.json +25 -0
- package/generators/generators.json +26 -5
- package/generators/migrate-provider-shape/migration-scope.js +35 -0
- package/generators/migrate-provider-shape/migration.js +50 -0
- package/generators/migrate-provider-shape/provider-shape.js +284 -0
- package/generators/migrate-provider-shape/schema.json +26 -0
- package/generators/migrate-surface-interaction-swatch/interaction-swatch.js +63 -0
- package/generators/migrate-surface-interaction-swatch/migration.js +36 -0
- package/generators/migrate-surface-interaction-swatch/schema.json +26 -0
- package/generators/migrate-to-v5/apply-replacements.js +14 -0
- package/generators/migrate-to-v5/color-naming.js +124 -0
- package/generators/migrate-to-v5/create-provider.js +67 -55
- package/generators/migrate-to-v5/migration-files.js +13 -0
- package/generators/migrate-to-v5/migration.js +33 -19
- package/generators/migrate-to-v5/removed-exports.js +73 -0
- package/generators/migrate-to-v5/router-state-service.js +123 -330
- package/generators/migrate-to-v5/schema.json +22 -0
- package/generators/migrate-to-v5/service-classes.js +108 -0
- package/generators/migrate-to-v5/viewport-service.js +224 -491
- package/generators/tailwind-4-color-theme/generator.js +594 -0
- package/generators/tailwind-4-color-theme/schema.json +44 -0
- package/generators/tailwind-4-surface-theme/generator.js +599 -0
- package/generators/tailwind-4-surface-theme/schema.json +48 -0
- package/migrations/seo-directive-removed.md +80 -0
- package/migrations/surface-theme-regenerate.md +20 -0
- package/migrations.json +43 -0
- package/package.json +25 -14
- package/types/ethlete-core.d.ts +3337 -1775
- package/generators/tailwind-4-theme/generator.js +0 -490
- package/generators/tailwind-4-theme/schema.json +0 -32
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { formatFiles } from '@nx/devkit';
|
|
2
|
+
import { createMigrationScope } from '../migrate-provider-shape/migration-scope.js';
|
|
3
|
+
import { migrateInteractionSwatchInFile } from './interaction-swatch.js';
|
|
4
|
+
export default async function migrateSurfaceInteractionSwatch(tree, schema) {
|
|
5
|
+
console.log('\nš Migrating surface interactionColor to the swatch shape...');
|
|
6
|
+
const scope = createMigrationScope(tree, schema);
|
|
7
|
+
console.log(` Scope: ${scope.describe()}`);
|
|
8
|
+
let filesChanged = 0;
|
|
9
|
+
let unresolvedCount = 0;
|
|
10
|
+
scope.visit(tree, (filePath) => {
|
|
11
|
+
if (!filePath.endsWith('.ts') || filePath.endsWith('.d.ts'))
|
|
12
|
+
return;
|
|
13
|
+
const before = tree.read(filePath, 'utf-8');
|
|
14
|
+
if (!before)
|
|
15
|
+
return;
|
|
16
|
+
const result = migrateInteractionSwatchInFile(filePath, before);
|
|
17
|
+
for (const expression of result.unresolved ?? []) {
|
|
18
|
+
unresolvedCount++;
|
|
19
|
+
console.warn(` ā ${filePath}: could not resolve interactionColor value ${expression}`);
|
|
20
|
+
}
|
|
21
|
+
if (!result.changed)
|
|
22
|
+
return;
|
|
23
|
+
tree.write(filePath, result.content);
|
|
24
|
+
filesChanged++;
|
|
25
|
+
console.log(` ā ${filePath}`);
|
|
26
|
+
});
|
|
27
|
+
console.log(filesChanged === 0
|
|
28
|
+
? ' Nothing to migrate.\n'
|
|
29
|
+
: `\nā
Migrated ${filesChanged} file(s). Regenerate your surface CSS with \`nx g @ethlete/core:tailwind-4-surface-theme\`.\n`);
|
|
30
|
+
if (unresolvedCount > 0) {
|
|
31
|
+
console.warn(` Review ${unresolvedCount} unresolved interactionColor value(s) manually.\n`);
|
|
32
|
+
}
|
|
33
|
+
if (!schema.skipFormat) {
|
|
34
|
+
await formatFiles(tree);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"$id": "migrate-surface-interaction-swatch",
|
|
4
|
+
"title": "Migrate surface interactionColor to the swatch shape",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"projects": {
|
|
8
|
+
"type": "array",
|
|
9
|
+
"items": { "type": "string" },
|
|
10
|
+
"description": "Nx projects to migrate. Defaults to every project in the workspace.",
|
|
11
|
+
"alias": "project"
|
|
12
|
+
},
|
|
13
|
+
"include": {
|
|
14
|
+
"type": "array",
|
|
15
|
+
"items": { "type": "string" },
|
|
16
|
+
"description": "Path prefixes to migrate, relative to the workspace root (e.g. 'apps/web'). Unioned with `projects`. Defaults to the whole workspace.",
|
|
17
|
+
"alias": "includePath"
|
|
18
|
+
},
|
|
19
|
+
"skipFormat": {
|
|
20
|
+
"type": "boolean",
|
|
21
|
+
"description": "Skip formatting files after migration",
|
|
22
|
+
"default": false
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"required": []
|
|
26
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const IDENTIFIER_CHAR = /[\w$]/;
|
|
2
|
+
export function applyReplacements(content, replacements) {
|
|
3
|
+
return [...replacements]
|
|
4
|
+
.sort(([a], [b]) => b.length - a.length)
|
|
5
|
+
.reduce((result, [original, replacement]) => result.replace(snippetPattern(original), () => replacement), content);
|
|
6
|
+
}
|
|
7
|
+
function snippetPattern(snippet) {
|
|
8
|
+
const start = IDENTIFIER_CHAR.test(snippet.charAt(0)) ? '(?<![\\w$])' : '';
|
|
9
|
+
const end = IDENTIFIER_CHAR.test(snippet.charAt(snippet.length - 1)) ? '(?![\\w$])' : '';
|
|
10
|
+
return new RegExp(`${start}${escapeRegExp(snippet)}${end}`, 'g');
|
|
11
|
+
}
|
|
12
|
+
function escapeRegExp(value) {
|
|
13
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
14
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { collectFiles } from './migration-files.js';
|
|
2
|
+
/**
|
|
3
|
+
* Symbol renames in TypeScript files (imports + usage).
|
|
4
|
+
*/
|
|
5
|
+
const TS_SYMBOL_RENAMES = {
|
|
6
|
+
ProvideThemeDirective: 'ProvideColorDirective',
|
|
7
|
+
THEME_PROVIDER: 'COLOR_PROVIDER',
|
|
8
|
+
ColorThemedDirective: 'ColorInteractiveDirective',
|
|
9
|
+
ColorThemedStylesComponent: 'ColorInteractiveStylesComponent',
|
|
10
|
+
SurfaceThemedDirective: 'SurfaceInteractiveDirective',
|
|
11
|
+
SurfaceThemedStylesComponent: 'SurfaceInteractiveStylesComponent',
|
|
12
|
+
injectThemesPrefix: 'injectColorThemesPrefix',
|
|
13
|
+
createCssThemeName: 'createCssColorThemeName',
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* String replacements applied to HTML templates and inline templates.
|
|
17
|
+
*/
|
|
18
|
+
const TEMPLATE_REPLACEMENTS = [
|
|
19
|
+
['[etProvideTheme]', '[etProvideColor]'],
|
|
20
|
+
['etProvideTheme', 'etProvideColor'],
|
|
21
|
+
['[etColorThemed]', '[etColorInteractive]'],
|
|
22
|
+
['etColorThemed', 'etColorInteractive'],
|
|
23
|
+
['[etSurfaceThemed]', '[etSurfaceInteractive]'],
|
|
24
|
+
['etSurfaceThemed', 'etSurfaceInteractive'],
|
|
25
|
+
['et-theme-alt--', 'et-color-alt--'],
|
|
26
|
+
['et-theme--', 'et-color--'],
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* CSS class name replacements.
|
|
30
|
+
*/
|
|
31
|
+
const CSS_REPLACEMENTS = [
|
|
32
|
+
['.et-color-themed', '.et-color-interactive'],
|
|
33
|
+
['.et-surface-themed', '.et-surface-interactive'],
|
|
34
|
+
['.et-theme--', '.et-color--'],
|
|
35
|
+
['.et-theme-alt--', '.et-color-alt--'],
|
|
36
|
+
];
|
|
37
|
+
/**
|
|
38
|
+
* Host directive input alias replacements in TypeScript files.
|
|
39
|
+
*/
|
|
40
|
+
const HOST_DIRECTIVE_REPLACEMENTS = [
|
|
41
|
+
["'etProvideTheme:theme'", "'etProvideColor:color'"],
|
|
42
|
+
["'etProvideTheme:color'", "'etProvideColor:color'"],
|
|
43
|
+
['"etProvideTheme:theme"', '"etProvideColor:color"'],
|
|
44
|
+
['"etProvideTheme"', '"etProvideColor"'],
|
|
45
|
+
["'etProvideTheme: theme'", "'etProvideColor: color'"],
|
|
46
|
+
['"etProvideTheme: theme"', '"etProvideColor: color"'],
|
|
47
|
+
];
|
|
48
|
+
export default async function migrateColorNaming(tree, scope) {
|
|
49
|
+
let filesModified = 0;
|
|
50
|
+
for (const filePath of collectFiles(tree, scope, ['.ts', '.html', '.css', '.scss'])) {
|
|
51
|
+
const wasModified = filePath.endsWith('.ts')
|
|
52
|
+
? migrateTypeScriptFile(tree, filePath)
|
|
53
|
+
: filePath.endsWith('.html')
|
|
54
|
+
? migrateTemplateFile(tree, filePath)
|
|
55
|
+
: migrateCssFile(tree, filePath);
|
|
56
|
+
if (wasModified)
|
|
57
|
+
filesModified++;
|
|
58
|
+
}
|
|
59
|
+
return { filesChanged: filesModified, review: [] };
|
|
60
|
+
}
|
|
61
|
+
function migrateTypeScriptFile(tree, filePath) {
|
|
62
|
+
let content = tree.read(filePath, 'utf-8');
|
|
63
|
+
if (!content)
|
|
64
|
+
return false;
|
|
65
|
+
const original = content;
|
|
66
|
+
if (!/from\s+['"]@ethlete\/(?:core|cdk|theming)['"]/.test(content))
|
|
67
|
+
return false;
|
|
68
|
+
// Rename symbols
|
|
69
|
+
for (const [oldName, newName] of Object.entries(TS_SYMBOL_RENAMES)) {
|
|
70
|
+
content = replaceWholeWord(content, oldName, newName);
|
|
71
|
+
}
|
|
72
|
+
// Rename host directive input aliases
|
|
73
|
+
for (const [oldStr, newStr] of HOST_DIRECTIVE_REPLACEMENTS) {
|
|
74
|
+
content = content.replaceAll(oldStr, newStr);
|
|
75
|
+
}
|
|
76
|
+
// Handle inline templates in TypeScript files
|
|
77
|
+
for (const [oldStr, newStr] of TEMPLATE_REPLACEMENTS) {
|
|
78
|
+
content = content.replaceAll(oldStr, newStr);
|
|
79
|
+
}
|
|
80
|
+
// Handle CSS in TypeScript files
|
|
81
|
+
for (const [oldStr, newStr] of CSS_REPLACEMENTS) {
|
|
82
|
+
content = content.replaceAll(oldStr, newStr);
|
|
83
|
+
}
|
|
84
|
+
if (content !== original) {
|
|
85
|
+
tree.write(filePath, content);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
function migrateTemplateFile(tree, filePath) {
|
|
91
|
+
let content = tree.read(filePath, 'utf-8');
|
|
92
|
+
if (!content)
|
|
93
|
+
return false;
|
|
94
|
+
const original = content;
|
|
95
|
+
for (const [oldStr, newStr] of TEMPLATE_REPLACEMENTS) {
|
|
96
|
+
content = content.replaceAll(oldStr, newStr);
|
|
97
|
+
}
|
|
98
|
+
if (content !== original) {
|
|
99
|
+
tree.write(filePath, content);
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
function migrateCssFile(tree, filePath) {
|
|
105
|
+
let content = tree.read(filePath, 'utf-8');
|
|
106
|
+
if (!content)
|
|
107
|
+
return false;
|
|
108
|
+
const original = content;
|
|
109
|
+
for (const [oldStr, newStr] of CSS_REPLACEMENTS) {
|
|
110
|
+
content = content.replaceAll(oldStr, newStr);
|
|
111
|
+
}
|
|
112
|
+
if (content !== original) {
|
|
113
|
+
tree.write(filePath, content);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
function replaceWholeWord(content, oldWord, newWord) {
|
|
119
|
+
const regex = new RegExp(`\\b${escapeRegex(oldWord)}\\b`, 'g');
|
|
120
|
+
return content.replace(regex, newWord);
|
|
121
|
+
}
|
|
122
|
+
function escapeRegex(str) {
|
|
123
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
124
|
+
}
|
|
@@ -1,38 +1,65 @@
|
|
|
1
|
-
import { logger } from '@nx/devkit';
|
|
2
1
|
import * as ts from 'typescript';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
// Recursively find all TypeScript files
|
|
7
|
-
function findTsFiles(dir) {
|
|
8
|
-
const children = tree.children(dir);
|
|
9
|
-
for (const child of children) {
|
|
10
|
-
const path = dir === '.' ? child : `${dir}/${child}`;
|
|
11
|
-
if (tree.isFile(path)) {
|
|
12
|
-
if (path.endsWith('.ts') && !path.includes('node_modules') && !path.endsWith('.spec.ts')) {
|
|
13
|
-
tsFiles.push(path);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
else {
|
|
17
|
-
findTsFiles(path);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
findTsFiles('.');
|
|
2
|
+
import { collectFiles } from './migration-files.js';
|
|
3
|
+
export default async function migrateCreateProvider(tree, scope) {
|
|
4
|
+
const review = [];
|
|
22
5
|
let filesModified = 0;
|
|
23
|
-
for (const filePath of
|
|
24
|
-
const
|
|
25
|
-
|
|
6
|
+
for (const filePath of collectFiles(tree, scope, ['.ts'])) {
|
|
7
|
+
const reexportsModified = migrateCreateProviderReexports(tree, filePath, review);
|
|
8
|
+
const wasModified = migrateCreateProviderInFile(tree, filePath) || reexportsModified;
|
|
9
|
+
if (wasModified)
|
|
26
10
|
filesModified++;
|
|
27
|
-
logger.log(` ā ${filePath}`);
|
|
28
|
-
}
|
|
29
11
|
}
|
|
30
|
-
|
|
31
|
-
|
|
12
|
+
return { filesChanged: filesModified, review };
|
|
13
|
+
}
|
|
14
|
+
function migrateCreateProviderReexports(tree, filePath, review) {
|
|
15
|
+
const content = tree.read(filePath, 'utf-8');
|
|
16
|
+
if (!content || !content.includes('createProvider'))
|
|
17
|
+
return false;
|
|
18
|
+
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
19
|
+
const edits = [];
|
|
20
|
+
for (const statement of sourceFile.statements) {
|
|
21
|
+
if (!ts.isImportDeclaration(statement) && !ts.isExportDeclaration(statement))
|
|
22
|
+
continue;
|
|
23
|
+
if (!statement.moduleSpecifier || !ts.isStringLiteral(statement.moduleSpecifier))
|
|
24
|
+
continue;
|
|
25
|
+
if (statement.moduleSpecifier.text !== '@ethlete/cdk')
|
|
26
|
+
continue;
|
|
27
|
+
if (ts.isImportDeclaration(statement)) {
|
|
28
|
+
const bindings = statement.importClause?.namedBindings;
|
|
29
|
+
if (bindings && ts.isNamespaceImport(bindings) && content.includes(`${bindings.name.text}.createProvider`)) {
|
|
30
|
+
review.push(`${filePath}: ${bindings.name.text}.createProvider is used through a namespace import of @ethlete/cdk. Switch it to createProvider from @ethlete/core manually.`);
|
|
31
|
+
}
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const clause = statement.exportClause;
|
|
35
|
+
if (!clause) {
|
|
36
|
+
review.push(`${filePath}: "export * from '@ethlete/cdk'" may re-export createProvider. Re-export it from @ethlete/core manually.`);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (ts.isNamespaceExport(clause))
|
|
40
|
+
continue;
|
|
41
|
+
const isCreateProvider = (element) => (element.propertyName?.text ?? element.name.text) === 'createProvider';
|
|
42
|
+
const moved = clause.elements.filter(isCreateProvider);
|
|
43
|
+
if (moved.length === 0)
|
|
44
|
+
continue;
|
|
45
|
+
const rest = clause.elements.filter((element) => !isCreateProvider(element));
|
|
46
|
+
const typeOnly = statement.isTypeOnly ? 'type ' : '';
|
|
47
|
+
const lines = [
|
|
48
|
+
...(rest.length > 0
|
|
49
|
+
? [`export ${typeOnly}{ ${rest.map((el) => el.getText(sourceFile)).join(', ')} } from '@ethlete/cdk';`]
|
|
50
|
+
: []),
|
|
51
|
+
`export ${typeOnly}{ ${moved.map((el) => el.getText(sourceFile)).join(', ')} } from '@ethlete/core';`,
|
|
52
|
+
];
|
|
53
|
+
edits.push({ start: statement.getStart(sourceFile), end: statement.getEnd(), text: lines.join('\n') });
|
|
32
54
|
}
|
|
33
|
-
|
|
34
|
-
|
|
55
|
+
if (edits.length === 0)
|
|
56
|
+
return false;
|
|
57
|
+
let updatedContent = content;
|
|
58
|
+
for (const edit of edits.reverse()) {
|
|
59
|
+
updatedContent = updatedContent.slice(0, edit.start) + edit.text + updatedContent.slice(edit.end);
|
|
35
60
|
}
|
|
61
|
+
tree.write(filePath, updatedContent);
|
|
62
|
+
return true;
|
|
36
63
|
}
|
|
37
64
|
function migrateCreateProviderInFile(tree, filePath) {
|
|
38
65
|
const content = tree.read(filePath, 'utf-8');
|
|
@@ -40,35 +67,30 @@ function migrateCreateProviderInFile(tree, filePath) {
|
|
|
40
67
|
return false;
|
|
41
68
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
42
69
|
let cdkImport;
|
|
43
|
-
let coreImport;
|
|
44
70
|
let hasCreateProviderInCdk = false;
|
|
45
|
-
|
|
71
|
+
let createProviderImport = 'createProvider';
|
|
46
72
|
for (const statement of sourceFile.statements) {
|
|
47
73
|
if (ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier)) {
|
|
48
74
|
const modulePath = statement.moduleSpecifier.text;
|
|
49
75
|
if (modulePath === '@ethlete/cdk') {
|
|
50
76
|
cdkImport = statement;
|
|
51
77
|
if (statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings)) {
|
|
52
|
-
|
|
78
|
+
const specifier = statement.importClause.namedBindings.elements.find((element) => (element.propertyName?.text ?? element.name.text) === 'createProvider');
|
|
79
|
+
hasCreateProviderInCdk = !!specifier;
|
|
80
|
+
createProviderImport = specifier?.getText(sourceFile) ?? createProviderImport;
|
|
53
81
|
}
|
|
54
82
|
}
|
|
55
|
-
else if (modulePath === '@ethlete/core') {
|
|
56
|
-
coreImport = statement;
|
|
57
|
-
}
|
|
58
83
|
}
|
|
59
84
|
}
|
|
60
|
-
// If createProvider is not imported from @ethlete/cdk, nothing to do
|
|
61
85
|
if (!hasCreateProviderInCdk)
|
|
62
86
|
return false;
|
|
63
87
|
let updatedContent = content;
|
|
64
|
-
// Remove createProvider from @ethlete/cdk import
|
|
65
88
|
if (cdkImport && cdkImport.importClause?.namedBindings && ts.isNamedImports(cdkImport.importClause.namedBindings)) {
|
|
66
89
|
const namedBindings = cdkImport.importClause.namedBindings;
|
|
67
|
-
const otherImports = namedBindings.elements.filter((element) => element.name.text !== 'createProvider');
|
|
90
|
+
const otherImports = namedBindings.elements.filter((element) => (element.propertyName?.text ?? element.name.text) !== 'createProvider');
|
|
68
91
|
const importStart = cdkImport.getStart(sourceFile);
|
|
69
92
|
const importEnd = cdkImport.getEnd();
|
|
70
93
|
if (otherImports.length === 0) {
|
|
71
|
-
// Remove entire import line including newline
|
|
72
94
|
let lineStart = importStart;
|
|
73
95
|
while (lineStart > 0 && content[lineStart - 1] !== '\n') {
|
|
74
96
|
lineStart--;
|
|
@@ -83,15 +105,12 @@ function migrateCreateProviderInFile(tree, filePath) {
|
|
|
83
105
|
updatedContent = content.slice(0, lineStart) + content.slice(lineEnd);
|
|
84
106
|
}
|
|
85
107
|
else {
|
|
86
|
-
|
|
87
|
-
const newImports = otherImports.map((el) => el.name.text).join(', ');
|
|
108
|
+
const newImports = otherImports.map((el) => el.getText(sourceFile)).join(', ');
|
|
88
109
|
const newImportText = `import { ${newImports} } from '@ethlete/cdk';`;
|
|
89
110
|
updatedContent = content.slice(0, importStart) + newImportText + content.slice(importEnd);
|
|
90
111
|
}
|
|
91
112
|
}
|
|
92
|
-
// Re-parse after first modification
|
|
93
113
|
const intermediateSourceFile = ts.createSourceFile(filePath, updatedContent, ts.ScriptTarget.Latest, true);
|
|
94
|
-
// Find @ethlete/core import in the updated content
|
|
95
114
|
let updatedCoreImport;
|
|
96
115
|
for (const statement of intermediateSourceFile.statements) {
|
|
97
116
|
if (ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier)) {
|
|
@@ -101,26 +120,22 @@ function migrateCreateProviderInFile(tree, filePath) {
|
|
|
101
120
|
}
|
|
102
121
|
}
|
|
103
122
|
}
|
|
104
|
-
// Add createProvider to @ethlete/core import
|
|
105
123
|
if (updatedCoreImport?.importClause?.namedBindings &&
|
|
106
124
|
ts.isNamedImports(updatedCoreImport.importClause.namedBindings)) {
|
|
107
125
|
const namedBindings = updatedCoreImport.importClause.namedBindings;
|
|
108
|
-
|
|
109
|
-
const hasCreateProvider = namedBindings.elements.some((element) => element.name.text === 'createProvider');
|
|
126
|
+
const hasCreateProvider = namedBindings.elements.some((element) => (element.propertyName?.text ?? element.name.text) === 'createProvider');
|
|
110
127
|
if (!hasCreateProvider) {
|
|
111
128
|
const importStart = updatedCoreImport.getStart(intermediateSourceFile);
|
|
112
129
|
const importEnd = updatedCoreImport.getEnd();
|
|
113
|
-
const existingImports = namedBindings.elements.map((el) => el.
|
|
114
|
-
const allImports = [...existingImports,
|
|
130
|
+
const existingImports = namedBindings.elements.map((el) => el.getText(intermediateSourceFile));
|
|
131
|
+
const allImports = [...existingImports, createProviderImport].sort();
|
|
115
132
|
const newImportText = `import { ${allImports.join(', ')} } from '@ethlete/core';`;
|
|
116
133
|
updatedContent = updatedContent.slice(0, importStart) + newImportText + updatedContent.slice(importEnd);
|
|
117
134
|
}
|
|
118
135
|
}
|
|
119
136
|
else {
|
|
120
|
-
// Add new import from @ethlete/core at the top
|
|
121
137
|
const firstImportIndex = updatedContent.indexOf('import');
|
|
122
138
|
if (firstImportIndex !== -1) {
|
|
123
|
-
// Add after other imports
|
|
124
139
|
let insertPosition;
|
|
125
140
|
let lastImportEnd = firstImportIndex;
|
|
126
141
|
for (const statement of intermediateSourceFile.statements) {
|
|
@@ -132,7 +147,6 @@ function migrateCreateProviderInFile(tree, filePath) {
|
|
|
132
147
|
}
|
|
133
148
|
}
|
|
134
149
|
insertPosition = lastImportEnd;
|
|
135
|
-
// Find end of line
|
|
136
150
|
while (insertPosition < updatedContent.length && updatedContent[insertPosition] !== '\n') {
|
|
137
151
|
insertPosition++;
|
|
138
152
|
}
|
|
@@ -141,12 +155,11 @@ function migrateCreateProviderInFile(tree, filePath) {
|
|
|
141
155
|
}
|
|
142
156
|
updatedContent =
|
|
143
157
|
updatedContent.slice(0, insertPosition) +
|
|
144
|
-
|
|
158
|
+
`import { ${createProviderImport} } from '@ethlete/core';\n` +
|
|
145
159
|
updatedContent.slice(insertPosition);
|
|
146
160
|
}
|
|
147
161
|
else {
|
|
148
|
-
|
|
149
|
-
updatedContent = "import { createProvider } from '@ethlete/core';\n\n" + updatedContent;
|
|
162
|
+
updatedContent = `import { ${createProviderImport} } from '@ethlete/core';\n\n` + updatedContent;
|
|
150
163
|
}
|
|
151
164
|
}
|
|
152
165
|
if (updatedContent !== content) {
|
|
@@ -155,4 +168,3 @@ function migrateCreateProviderInFile(tree, filePath) {
|
|
|
155
168
|
}
|
|
156
169
|
return false;
|
|
157
170
|
}
|
|
158
|
-
//# sourceMappingURL=create-provider.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createMigrationScope } from '../migrate-provider-shape/migration-scope.js';
|
|
2
|
+
const SKIPPED_DIRECTORIES = new Set(['node_modules', 'dist', '.git', '.angular', '.nx']);
|
|
3
|
+
export const collectFiles = (tree, scope, extensions) => {
|
|
4
|
+
const files = [];
|
|
5
|
+
(scope ?? createMigrationScope(tree, {})).visit(tree, (filePath) => {
|
|
6
|
+
if (filePath.split('/').some((segment) => SKIPPED_DIRECTORIES.has(segment)))
|
|
7
|
+
return;
|
|
8
|
+
if (!extensions.some((extension) => filePath.endsWith(extension)))
|
|
9
|
+
return;
|
|
10
|
+
files.push(filePath);
|
|
11
|
+
});
|
|
12
|
+
return files;
|
|
13
|
+
};
|
|
@@ -1,28 +1,42 @@
|
|
|
1
|
-
import { formatFiles } from '@nx/devkit';
|
|
1
|
+
import { formatFiles, logger } from '@nx/devkit';
|
|
2
|
+
import { createMigrationScope, } from '../migrate-provider-shape/migration-scope.js';
|
|
3
|
+
import migrateColorNaming from './color-naming.js';
|
|
2
4
|
import migrateCreateProvider from './create-provider.js';
|
|
5
|
+
import reportRemovedExports from './removed-exports.js';
|
|
3
6
|
import migrateRouterStateService from './router-state-service.js';
|
|
4
7
|
import migrateViewportService from './viewport-service.js';
|
|
5
8
|
export default async function migrate(tree, schema) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
9
|
+
const scope = createMigrationScope(tree, schema);
|
|
10
|
+
logger.info('\nš Starting core v5 migration...');
|
|
11
|
+
logger.info(` Scope: ${scope.describe()}`);
|
|
12
|
+
const transforms = [
|
|
13
|
+
{ label: 'ViewportService', enabled: schema.migrateViewportService !== false, run: migrateViewportService },
|
|
14
|
+
{ label: 'createProvider', enabled: schema.migrateCreateProvider !== false, run: migrateCreateProvider },
|
|
15
|
+
{
|
|
16
|
+
label: 'RouterStateService',
|
|
17
|
+
enabled: schema.migrateRouterStateService !== false,
|
|
18
|
+
run: migrateRouterStateService,
|
|
19
|
+
},
|
|
20
|
+
{ label: 'Theme ā color naming', enabled: schema.migrateColorNaming !== false, run: migrateColorNaming },
|
|
21
|
+
{ label: 'Removed exports', enabled: schema.reportRemovedExports !== false, run: reportRemovedExports },
|
|
22
|
+
];
|
|
23
|
+
const reports = [];
|
|
24
|
+
for (const transform of transforms) {
|
|
25
|
+
if (!transform.enabled)
|
|
26
|
+
continue;
|
|
27
|
+
logger.info(` ⢠${transform.label}...`);
|
|
28
|
+
reports.push([transform.label, await transform.run(tree, scope)]);
|
|
20
29
|
}
|
|
21
30
|
if (!schema.skipFormat) {
|
|
22
|
-
console.log(' ⢠Formatting files...');
|
|
23
31
|
await formatFiles(tree);
|
|
24
32
|
}
|
|
25
|
-
|
|
33
|
+
logger.info('\nSummary:');
|
|
34
|
+
for (const [label, report] of reports) {
|
|
35
|
+
const review = report.review.length > 0 ? `, ${report.review.length} to review manually` : '';
|
|
36
|
+
logger.info(` ⢠${label}: ${report.filesChanged} file(s) changed${review}`);
|
|
37
|
+
for (const message of report.review) {
|
|
38
|
+
logger.warn(` ā ${message}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
logger.info('\nā
Migration completed. Review the diff before committing.');
|
|
26
42
|
}
|
|
27
|
-
//#endregion
|
|
28
|
-
//# sourceMappingURL=migration.js.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { collectFiles } from './migration-files.js';
|
|
3
|
+
const REMOVED_EXPORTS = new Map([
|
|
4
|
+
...['Memo', 'MemoConfig', 'MemoResolver', 'MapLike'].map((name) => [name, 'the @Memo decorator']),
|
|
5
|
+
...[
|
|
6
|
+
'createProps',
|
|
7
|
+
'createHostProps',
|
|
8
|
+
'createPropHandlers',
|
|
9
|
+
'createSetup',
|
|
10
|
+
'createDependencyStash',
|
|
11
|
+
'createElementDictionary',
|
|
12
|
+
'bindProps',
|
|
13
|
+
'unbindProps',
|
|
14
|
+
'PropsDirective',
|
|
15
|
+
'templateComputed',
|
|
16
|
+
'ComponentType',
|
|
17
|
+
'AnyTemplateType',
|
|
18
|
+
'TemplateRefWithContext',
|
|
19
|
+
'ComponentTypeWithInputs',
|
|
20
|
+
'StringTemplate',
|
|
21
|
+
'NgTemplateTemplate',
|
|
22
|
+
'ComponentTemplate',
|
|
23
|
+
'Props',
|
|
24
|
+
'PropsAttachedElements',
|
|
25
|
+
'PropsAttachedElementsInternal',
|
|
26
|
+
'PropsInternal',
|
|
27
|
+
'CreatePropsOptions',
|
|
28
|
+
'HostProps',
|
|
29
|
+
'PropHandlers',
|
|
30
|
+
'BindPropsOptions',
|
|
31
|
+
'UnbindPropsOptions',
|
|
32
|
+
].map((name) => [name, 'the props module']),
|
|
33
|
+
]);
|
|
34
|
+
const ET_PROPS_ATTRIBUTE = /\betProps\b/;
|
|
35
|
+
export default async function reportRemovedExports(tree, scope) {
|
|
36
|
+
const review = [];
|
|
37
|
+
for (const filePath of collectFiles(tree, scope, ['.ts', '.html'])) {
|
|
38
|
+
const messages = filePath.endsWith('.ts') ? findRemovedImports(tree, filePath) : findEtPropsUsage(tree, filePath);
|
|
39
|
+
review.push(...messages.map((message) => `${filePath}: ${message}`));
|
|
40
|
+
}
|
|
41
|
+
return { filesChanged: 0, review };
|
|
42
|
+
}
|
|
43
|
+
function findRemovedImports(tree, filePath) {
|
|
44
|
+
const content = tree.read(filePath, 'utf-8');
|
|
45
|
+
if (!content || !content.includes('@ethlete/core'))
|
|
46
|
+
return [];
|
|
47
|
+
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
48
|
+
const messages = [];
|
|
49
|
+
for (const statement of sourceFile.statements) {
|
|
50
|
+
if (!ts.isImportDeclaration(statement) && !ts.isExportDeclaration(statement))
|
|
51
|
+
continue;
|
|
52
|
+
const specifier = statement.moduleSpecifier;
|
|
53
|
+
if (!specifier || !ts.isStringLiteral(specifier) || specifier.text !== '@ethlete/core')
|
|
54
|
+
continue;
|
|
55
|
+
const bindings = ts.isImportDeclaration(statement) ? statement.importClause?.namedBindings : statement.exportClause;
|
|
56
|
+
if (!bindings || ts.isNamespaceImport(bindings) || ts.isNamespaceExport(bindings))
|
|
57
|
+
continue;
|
|
58
|
+
for (const element of bindings.elements) {
|
|
59
|
+
const name = (element.propertyName ?? element.name).text;
|
|
60
|
+
const origin = REMOVED_EXPORTS.get(name);
|
|
61
|
+
if (origin) {
|
|
62
|
+
messages.push(`${name} was removed from @ethlete/core together with ${origin} and has no replacement.`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return messages;
|
|
67
|
+
}
|
|
68
|
+
function findEtPropsUsage(tree, filePath) {
|
|
69
|
+
const content = tree.read(filePath, 'utf-8');
|
|
70
|
+
if (!content || !ET_PROPS_ATTRIBUTE.test(content))
|
|
71
|
+
return [];
|
|
72
|
+
return ['[etProps] (PropsDirective) was removed from @ethlete/core together with the props module.'];
|
|
73
|
+
}
|