@ethlete/core 5.0.0-next.6 → 5.0.0-next.60

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.
Files changed (32) hide show
  1. package/CHANGELOG.md +686 -0
  2. package/README.md +28 -4
  3. package/fesm2022/ethlete-core.mjs +8020 -3527
  4. package/fesm2022/ethlete-core.mjs.map +1 -1
  5. package/generators/devtools-about/generator.js +122 -0
  6. package/generators/devtools-about/schema.json +25 -0
  7. package/generators/generators.json +26 -5
  8. package/generators/migrate-provider-shape/migration-scope.js +35 -0
  9. package/generators/migrate-provider-shape/migration.js +50 -0
  10. package/generators/migrate-provider-shape/provider-shape.js +284 -0
  11. package/generators/migrate-provider-shape/schema.json +26 -0
  12. package/generators/migrate-surface-interaction-swatch/interaction-swatch.js +63 -0
  13. package/generators/migrate-surface-interaction-swatch/migration.js +36 -0
  14. package/generators/migrate-surface-interaction-swatch/schema.json +26 -0
  15. package/generators/migrate-to-v5/color-naming.js +170 -0
  16. package/generators/migrate-to-v5/create-provider.js +14 -14
  17. package/generators/migrate-to-v5/migration.js +11 -1
  18. package/generators/migrate-to-v5/removed-exports.js +93 -0
  19. package/generators/migrate-to-v5/router-state-service.js +33 -8
  20. package/generators/migrate-to-v5/schema.json +10 -0
  21. package/generators/migrate-to-v5/viewport-service.js +41 -60
  22. package/generators/tailwind-4-color-theme/generator.js +608 -0
  23. package/generators/tailwind-4-color-theme/schema.json +44 -0
  24. package/generators/tailwind-4-surface-theme/generator.js +599 -0
  25. package/generators/tailwind-4-surface-theme/schema.json +48 -0
  26. package/migrations/seo-directive-removed.md +80 -0
  27. package/migrations/surface-theme-regenerate.md +20 -0
  28. package/migrations.json +43 -0
  29. package/package.json +25 -14
  30. package/types/ethlete-core.d.ts +3329 -1774
  31. package/generators/tailwind-4-theme/generator.js +0 -490
  32. 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,170 @@
1
+ import { logger } from '@nx/devkit';
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) {
49
+ logger.log('\nšŸ”„ Migrating theme → color naming...\n');
50
+ const tsFiles = [];
51
+ const htmlFiles = [];
52
+ const cssFiles = [];
53
+ findFiles('.', tree, tsFiles, htmlFiles, cssFiles);
54
+ let filesModified = 0;
55
+ for (const filePath of tsFiles) {
56
+ const wasModified = migrateTypeScriptFile(tree, filePath);
57
+ if (wasModified) {
58
+ filesModified++;
59
+ logger.log(` āœ“ ${filePath}`);
60
+ }
61
+ }
62
+ for (const filePath of htmlFiles) {
63
+ const wasModified = migrateTemplateFile(tree, filePath);
64
+ if (wasModified) {
65
+ filesModified++;
66
+ logger.log(` āœ“ ${filePath}`);
67
+ }
68
+ }
69
+ for (const filePath of cssFiles) {
70
+ const wasModified = migrateCssFile(tree, filePath);
71
+ if (wasModified) {
72
+ filesModified++;
73
+ logger.log(` āœ“ ${filePath}`);
74
+ }
75
+ }
76
+ if (filesModified > 0) {
77
+ logger.log(`\nāœ… Successfully migrated color naming in ${filesModified} file(s)\n`);
78
+ }
79
+ else {
80
+ logger.log('\nā„¹ļø No files needed migration\n');
81
+ }
82
+ }
83
+ function findFiles(dir, tree, tsFiles, htmlFiles, cssFiles) {
84
+ const children = tree.children(dir);
85
+ for (const child of children) {
86
+ const path = dir === '.' ? child : `${dir}/${child}`;
87
+ if (tree.isFile(path)) {
88
+ if (path.includes('node_modules'))
89
+ continue;
90
+ if (path.endsWith('.ts')) {
91
+ tsFiles.push(path);
92
+ }
93
+ else if (path.endsWith('.html')) {
94
+ htmlFiles.push(path);
95
+ }
96
+ else if (path.endsWith('.css') || path.endsWith('.scss')) {
97
+ cssFiles.push(path);
98
+ }
99
+ }
100
+ else {
101
+ if (child === 'node_modules')
102
+ continue;
103
+ findFiles(path, tree, tsFiles, htmlFiles, cssFiles);
104
+ }
105
+ }
106
+ }
107
+ function migrateTypeScriptFile(tree, filePath) {
108
+ let content = tree.read(filePath, 'utf-8');
109
+ if (!content)
110
+ return false;
111
+ const original = content;
112
+ if (!/from\s+['"]@ethlete\/(?:core|cdk)['"]/.test(content))
113
+ return false;
114
+ // Rename symbols
115
+ for (const [oldName, newName] of Object.entries(TS_SYMBOL_RENAMES)) {
116
+ content = replaceWholeWord(content, oldName, newName);
117
+ }
118
+ // Rename host directive input aliases
119
+ for (const [oldStr, newStr] of HOST_DIRECTIVE_REPLACEMENTS) {
120
+ content = content.replaceAll(oldStr, newStr);
121
+ }
122
+ // Handle inline templates in TypeScript files
123
+ for (const [oldStr, newStr] of TEMPLATE_REPLACEMENTS) {
124
+ content = content.replaceAll(oldStr, newStr);
125
+ }
126
+ // Handle CSS in TypeScript files
127
+ for (const [oldStr, newStr] of CSS_REPLACEMENTS) {
128
+ content = content.replaceAll(oldStr, newStr);
129
+ }
130
+ if (content !== original) {
131
+ tree.write(filePath, content);
132
+ return true;
133
+ }
134
+ return false;
135
+ }
136
+ function migrateTemplateFile(tree, filePath) {
137
+ let content = tree.read(filePath, 'utf-8');
138
+ if (!content)
139
+ return false;
140
+ const original = content;
141
+ for (const [oldStr, newStr] of TEMPLATE_REPLACEMENTS) {
142
+ content = content.replaceAll(oldStr, newStr);
143
+ }
144
+ if (content !== original) {
145
+ tree.write(filePath, content);
146
+ return true;
147
+ }
148
+ return false;
149
+ }
150
+ function migrateCssFile(tree, filePath) {
151
+ let content = tree.read(filePath, 'utf-8');
152
+ if (!content)
153
+ return false;
154
+ const original = content;
155
+ for (const [oldStr, newStr] of CSS_REPLACEMENTS) {
156
+ content = content.replaceAll(oldStr, newStr);
157
+ }
158
+ if (content !== original) {
159
+ tree.write(filePath, content);
160
+ return true;
161
+ }
162
+ return false;
163
+ }
164
+ function replaceWholeWord(content, oldWord, newWord) {
165
+ const regex = new RegExp(`\\b${escapeRegex(oldWord)}\\b`, 'g');
166
+ return content.replace(regex, newWord);
167
+ }
168
+ function escapeRegex(str) {
169
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
170
+ }
@@ -9,11 +9,13 @@ export default async function migrateCreateProvider(tree) {
9
9
  for (const child of children) {
10
10
  const path = dir === '.' ? child : `${dir}/${child}`;
11
11
  if (tree.isFile(path)) {
12
- if (path.endsWith('.ts') && !path.includes('node_modules') && !path.endsWith('.spec.ts')) {
12
+ if (path.endsWith('.ts') && !path.includes('node_modules')) {
13
13
  tsFiles.push(path);
14
14
  }
15
15
  }
16
16
  else {
17
+ if (child === 'node_modules')
18
+ continue;
17
19
  findTsFiles(path);
18
20
  }
19
21
  }
@@ -40,8 +42,8 @@ function migrateCreateProviderInFile(tree, filePath) {
40
42
  return false;
41
43
  const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
42
44
  let cdkImport;
43
- let coreImport;
44
45
  let hasCreateProviderInCdk = false;
46
+ let createProviderImport = 'createProvider';
45
47
  // Find relevant imports
46
48
  for (const statement of sourceFile.statements) {
47
49
  if (ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier)) {
@@ -49,12 +51,11 @@ function migrateCreateProviderInFile(tree, filePath) {
49
51
  if (modulePath === '@ethlete/cdk') {
50
52
  cdkImport = statement;
51
53
  if (statement.importClause?.namedBindings && ts.isNamedImports(statement.importClause.namedBindings)) {
52
- hasCreateProviderInCdk = statement.importClause.namedBindings.elements.some((element) => element.name.text === 'createProvider');
54
+ const specifier = statement.importClause.namedBindings.elements.find((element) => (element.propertyName?.text ?? element.name.text) === 'createProvider');
55
+ hasCreateProviderInCdk = !!specifier;
56
+ createProviderImport = specifier?.getText(sourceFile) ?? createProviderImport;
53
57
  }
54
58
  }
55
- else if (modulePath === '@ethlete/core') {
56
- coreImport = statement;
57
- }
58
59
  }
59
60
  }
60
61
  // If createProvider is not imported from @ethlete/cdk, nothing to do
@@ -64,7 +65,7 @@ function migrateCreateProviderInFile(tree, filePath) {
64
65
  // Remove createProvider from @ethlete/cdk import
65
66
  if (cdkImport && cdkImport.importClause?.namedBindings && ts.isNamedImports(cdkImport.importClause.namedBindings)) {
66
67
  const namedBindings = cdkImport.importClause.namedBindings;
67
- const otherImports = namedBindings.elements.filter((element) => element.name.text !== 'createProvider');
68
+ const otherImports = namedBindings.elements.filter((element) => (element.propertyName?.text ?? element.name.text) !== 'createProvider');
68
69
  const importStart = cdkImport.getStart(sourceFile);
69
70
  const importEnd = cdkImport.getEnd();
70
71
  if (otherImports.length === 0) {
@@ -84,7 +85,7 @@ function migrateCreateProviderInFile(tree, filePath) {
84
85
  }
85
86
  else {
86
87
  // Keep other imports
87
- const newImports = otherImports.map((el) => el.name.text).join(', ');
88
+ const newImports = otherImports.map((el) => el.getText(sourceFile)).join(', ');
88
89
  const newImportText = `import { ${newImports} } from '@ethlete/cdk';`;
89
90
  updatedContent = content.slice(0, importStart) + newImportText + content.slice(importEnd);
90
91
  }
@@ -106,12 +107,12 @@ function migrateCreateProviderInFile(tree, filePath) {
106
107
  ts.isNamedImports(updatedCoreImport.importClause.namedBindings)) {
107
108
  const namedBindings = updatedCoreImport.importClause.namedBindings;
108
109
  // Check if createProvider already exists
109
- const hasCreateProvider = namedBindings.elements.some((element) => element.name.text === 'createProvider');
110
+ const hasCreateProvider = namedBindings.elements.some((element) => (element.propertyName?.text ?? element.name.text) === 'createProvider');
110
111
  if (!hasCreateProvider) {
111
112
  const importStart = updatedCoreImport.getStart(intermediateSourceFile);
112
113
  const importEnd = updatedCoreImport.getEnd();
113
- const existingImports = namedBindings.elements.map((el) => el.name.text);
114
- const allImports = [...existingImports, 'createProvider'].sort();
114
+ const existingImports = namedBindings.elements.map((el) => el.getText(intermediateSourceFile));
115
+ const allImports = [...existingImports, createProviderImport].sort();
115
116
  const newImportText = `import { ${allImports.join(', ')} } from '@ethlete/core';`;
116
117
  updatedContent = updatedContent.slice(0, importStart) + newImportText + updatedContent.slice(importEnd);
117
118
  }
@@ -141,12 +142,12 @@ function migrateCreateProviderInFile(tree, filePath) {
141
142
  }
142
143
  updatedContent =
143
144
  updatedContent.slice(0, insertPosition) +
144
- "import { createProvider } from '@ethlete/core';\n" +
145
+ `import { ${createProviderImport} } from '@ethlete/core';\n` +
145
146
  updatedContent.slice(insertPosition);
146
147
  }
147
148
  else {
148
149
  // No imports, add at the beginning
149
- updatedContent = "import { createProvider } from '@ethlete/core';\n\n" + updatedContent;
150
+ updatedContent = `import { ${createProviderImport} } from '@ethlete/core';\n\n` + updatedContent;
150
151
  }
151
152
  }
152
153
  if (updatedContent !== content) {
@@ -155,4 +156,3 @@ function migrateCreateProviderInFile(tree, filePath) {
155
156
  }
156
157
  return false;
157
158
  }
158
- //# sourceMappingURL=create-provider.js.map
@@ -1,5 +1,7 @@
1
1
  import { formatFiles } from '@nx/devkit';
2
+ import migrateColorNaming from './color-naming.js';
2
3
  import migrateCreateProvider from './create-provider.js';
4
+ import reportRemovedExports from './removed-exports.js';
3
5
  import migrateRouterStateService from './router-state-service.js';
4
6
  import migrateViewportService from './viewport-service.js';
5
7
  export default async function migrate(tree, schema) {
@@ -18,6 +20,15 @@ export default async function migrate(tree, schema) {
18
20
  if (shouldMigrateRouterStateService) {
19
21
  await migrateRouterStateService(tree);
20
22
  }
23
+ const shouldMigrateColorNaming = schema.migrateColorNaming !== false;
24
+ if (shouldMigrateColorNaming) {
25
+ console.log(' • Migrating theme → color naming...');
26
+ await migrateColorNaming(tree);
27
+ }
28
+ if (schema.reportRemovedExports !== false) {
29
+ console.log(' • Reporting removed exports...');
30
+ await reportRemovedExports(tree);
31
+ }
21
32
  if (!schema.skipFormat) {
22
33
  console.log(' • Formatting files...');
23
34
  await formatFiles(tree);
@@ -25,4 +36,3 @@ export default async function migrate(tree, schema) {
25
36
  console.log('\nāœ… Migration completed successfully!');
26
37
  }
27
38
  //#endregion
28
- //# sourceMappingURL=migration.js.map
@@ -0,0 +1,93 @@
1
+ import * as ts from 'typescript';
2
+ const REMOVED_EXPORTS = new Map([
3
+ ...['Memo', 'MemoConfig', 'MemoResolver', 'MapLike'].map((name) => [name, 'the @Memo decorator']),
4
+ ...[
5
+ 'createProps',
6
+ 'createHostProps',
7
+ 'createPropHandlers',
8
+ 'createSetup',
9
+ 'createDependencyStash',
10
+ 'createElementDictionary',
11
+ 'bindProps',
12
+ 'unbindProps',
13
+ 'PropsDirective',
14
+ 'templateComputed',
15
+ 'ComponentType',
16
+ 'AnyTemplateType',
17
+ 'TemplateRefWithContext',
18
+ 'ComponentTypeWithInputs',
19
+ 'StringTemplate',
20
+ 'NgTemplateTemplate',
21
+ 'ComponentTemplate',
22
+ 'Props',
23
+ 'PropsAttachedElements',
24
+ 'PropsAttachedElementsInternal',
25
+ 'PropsInternal',
26
+ 'CreatePropsOptions',
27
+ 'HostProps',
28
+ 'PropHandlers',
29
+ 'BindPropsOptions',
30
+ 'UnbindPropsOptions',
31
+ ].map((name) => [name, 'the props module']),
32
+ ]);
33
+ const ET_PROPS_ATTRIBUTE = /\betProps\b/;
34
+ export default async function reportRemovedExports(tree) {
35
+ const findings = new Map();
36
+ function walkFiles(dir) {
37
+ for (const child of tree.children(dir)) {
38
+ if (child === 'node_modules')
39
+ continue;
40
+ const path = dir === '.' ? child : `${dir}/${child}`;
41
+ if (!tree.isFile(path)) {
42
+ walkFiles(path);
43
+ continue;
44
+ }
45
+ const messages = path.endsWith('.ts')
46
+ ? findRemovedImports(tree, path)
47
+ : path.endsWith('.html')
48
+ ? findEtPropsUsage(tree, path)
49
+ : [];
50
+ if (messages.length > 0)
51
+ findings.set(path, messages);
52
+ }
53
+ }
54
+ walkFiles('.');
55
+ if (findings.size === 0)
56
+ return;
57
+ console.log('\nāš ļø Manual migration required for the following files:');
58
+ findings.forEach((messages, file) => {
59
+ console.log(`\nšŸ“„ ${file}:`);
60
+ messages.forEach((message) => console.log(` - ${message}`));
61
+ });
62
+ }
63
+ function findRemovedImports(tree, filePath) {
64
+ const content = tree.read(filePath, 'utf-8');
65
+ if (!content || !content.includes('@ethlete/core'))
66
+ return [];
67
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
68
+ const messages = [];
69
+ for (const statement of sourceFile.statements) {
70
+ if (!ts.isImportDeclaration(statement) && !ts.isExportDeclaration(statement))
71
+ continue;
72
+ const specifier = statement.moduleSpecifier;
73
+ if (!specifier || !ts.isStringLiteral(specifier) || specifier.text !== '@ethlete/core')
74
+ continue;
75
+ const bindings = ts.isImportDeclaration(statement) ? statement.importClause?.namedBindings : statement.exportClause;
76
+ if (!bindings || ts.isNamespaceImport(bindings) || ts.isNamespaceExport(bindings))
77
+ continue;
78
+ for (const element of bindings.elements) {
79
+ const name = (element.propertyName ?? element.name).text;
80
+ const origin = REMOVED_EXPORTS.get(name);
81
+ if (origin) {
82
+ messages.push(`${name} was removed from @ethlete/core together with ${origin} and has no replacement.`);
83
+ }
84
+ }
85
+ }
86
+ return messages;
87
+ }
88
+ function findEtPropsUsage(tree, filePath) {
89
+ const content = tree.read(filePath, 'utf-8');
90
+ if (!content || !ET_PROPS_ATTRIBUTE.test(content))
91
+ return [];
92
+ return ['[etProps] (PropsDirective) was removed from @ethlete/core together with the props module.'];
93
+ }
@@ -67,6 +67,8 @@ function findRouterStateServiceVariables(sourceFile) {
67
67
  function findClassForRouterStateService(sourceFile, routerStateServiceVar) {
68
68
  let classNode = null;
69
69
  function visit(node) {
70
+ if (classNode)
71
+ return;
70
72
  if (ts.isClassDeclaration(node)) {
71
73
  const hasRouterStateService = node.members.some((member) => {
72
74
  if (ts.isPropertyDeclaration(member) && ts.isIdentifier(member.name)) {
@@ -750,9 +752,10 @@ function addImportsToPackage(sourceFile, content, imports, packageName) {
750
752
  }
751
753
  });
752
754
  if (existingImport?.importClause?.namedBindings && ts.isNamedImports(existingImport.importClause.namedBindings)) {
753
- // Add to existing import
754
- const existingImports = existingImport.importClause.namedBindings.elements.map((el) => el.name.text);
755
- const newImports = importsList.filter((imp) => !existingImports.includes(imp));
755
+ const existingElements = existingImport.importClause.namedBindings.elements;
756
+ const existingImportedNames = existingElements.map((el) => el.propertyName?.text ?? el.name.text);
757
+ const existingImports = existingElements.map((el) => el.getText(sourceFile));
758
+ const newImports = importsList.filter((imp) => !existingImportedNames.includes(imp));
756
759
  if (newImports.length === 0)
757
760
  return content;
758
761
  const allImports = [...existingImports, ...newImports].sort();
@@ -782,7 +785,16 @@ function removeRouterStateServiceInjection(sourceFile, content, routerStateServi
782
785
  node.members.forEach((member) => {
783
786
  if (ts.isPropertyDeclaration(member) && ts.isIdentifier(member.name)) {
784
787
  const memberName = member.name.text;
785
- if (routerStateServiceVars.includes(memberName)) {
788
+ const injectsRouterState = member.initializer &&
789
+ ts.isCallExpression(member.initializer) &&
790
+ ts.isIdentifier(member.initializer.expression) &&
791
+ member.initializer.expression.text === 'inject' &&
792
+ member.initializer.arguments.some((argument) => ts.isIdentifier(argument) && argument.text === 'RouterStateService');
793
+ const hasRouterStateType = member.type &&
794
+ ts.isTypeReferenceNode(member.type) &&
795
+ ts.isIdentifier(member.type.typeName) &&
796
+ member.type.typeName.text === 'RouterStateService';
797
+ if (routerStateServiceVars.includes(memberName) && (injectsRouterState || hasRouterStateType)) {
786
798
  const memberStart = member.getStart(sourceFile, true);
787
799
  const memberEnd = member.getEnd();
788
800
  let lineStart = memberStart;
@@ -807,7 +819,11 @@ function removeRouterStateServiceInjection(sourceFile, content, routerStateServi
807
819
  member.parameters.forEach((param) => {
808
820
  if (ts.isIdentifier(param.name)) {
809
821
  const paramName = param.name.text;
810
- if (routerStateServiceVars.includes(paramName)) {
822
+ const hasRouterStateType = param.type &&
823
+ ts.isTypeReferenceNode(param.type) &&
824
+ ts.isIdentifier(param.type.typeName) &&
825
+ param.type.typeName.text === 'RouterStateService';
826
+ if (routerStateServiceVars.includes(paramName) && hasRouterStateType) {
811
827
  return;
812
828
  }
813
829
  }
@@ -951,11 +967,13 @@ export default async function migrateRouterStateService(tree) {
951
967
  for (const child of children) {
952
968
  const path = dir === '.' ? child : `${dir}/${child}`;
953
969
  if (tree.isFile(path)) {
954
- if (path.endsWith('.ts') && !path.includes('node_modules') && !path.includes('.spec.ts')) {
970
+ if (path.endsWith('.ts') && !path.includes('node_modules')) {
955
971
  tsFiles.push(path);
956
972
  }
957
973
  }
958
974
  else {
975
+ if (child === 'node_modules')
976
+ continue;
959
977
  findFiles(path);
960
978
  }
961
979
  }
@@ -971,6 +989,14 @@ export default async function migrateRouterStateService(tree) {
971
989
  continue;
972
990
  console.log(`Processing: ${filePath}`);
973
991
  const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
992
+ const hasEthleteImport = sourceFile.statements.some((statement) => ts.isImportDeclaration(statement) &&
993
+ ts.isStringLiteral(statement.moduleSpecifier) &&
994
+ statement.moduleSpecifier.text.startsWith('@ethlete/') &&
995
+ !!statement.importClause?.namedBindings &&
996
+ ts.isNamedImports(statement.importClause.namedBindings) &&
997
+ statement.importClause.namedBindings.elements.some((element) => (element.propertyName?.text ?? element.name.text) === 'RouterStateService'));
998
+ if (!hasEthleteImport)
999
+ continue;
974
1000
  const inlineResult = handleInlineInjectPatterns(sourceFile, content);
975
1001
  let updatedContent = inlineResult.content;
976
1002
  const updatedSourceFile = updatedContent !== content
@@ -1005,7 +1031,7 @@ export default async function migrateRouterStateService(tree) {
1005
1031
  allImportsNeeded['@angular/core/rxjs-interop'].add('toObservable');
1006
1032
  }
1007
1033
  });
1008
- for (const [original, replacement] of context.replacements) {
1034
+ for (const [original, replacement] of [...context.replacements].sort((a, b) => b[0].length - a[0].length)) {
1009
1035
  if (replacement.includes('toObservable(')) {
1010
1036
  allImportsNeeded['@angular/core/rxjs-interop'].add('toObservable');
1011
1037
  }
@@ -1061,4 +1087,3 @@ export default async function migrateRouterStateService(tree) {
1061
1087
  console.log('\nā„¹ļø No RouterStateService usage found\n');
1062
1088
  }
1063
1089
  }
1064
- //# sourceMappingURL=router-state-service.js.map
@@ -23,6 +23,16 @@
23
23
  "type": "boolean",
24
24
  "description": "Migrate router state service to v5",
25
25
  "default": true
26
+ },
27
+ "migrateColorNaming": {
28
+ "type": "boolean",
29
+ "description": "Migrate theme naming to color naming (ProvideThemeDirective -> ProvideColorDirective, etc.)",
30
+ "default": true
31
+ },
32
+ "reportRemovedExports": {
33
+ "type": "boolean",
34
+ "description": "List imports of exports removed in v5 without a replacement (Memo, the props module)",
35
+ "default": true
26
36
  }
27
37
  },
28
38
  "required": []