@ethlete/core 5.0.0-next.60 → 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.
@@ -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
+ }
@@ -1,4 +1,4 @@
1
- import { logger } from '@nx/devkit';
1
+ import { collectFiles } from './migration-files.js';
2
2
  /**
3
3
  * Symbol renames in TypeScript files (imports + usage).
4
4
  */
@@ -45,71 +45,25 @@ const HOST_DIRECTIVE_REPLACEMENTS = [
45
45
  ["'etProvideTheme: theme'", "'etProvideColor: color'"],
46
46
  ['"etProvideTheme: theme"', '"etProvideColor: color"'],
47
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);
48
+ export default async function migrateColorNaming(tree, scope) {
54
49
  let filesModified = 0;
55
- for (const filePath of tsFiles) {
56
- const wasModified = migrateTypeScriptFile(tree, filePath);
57
- if (wasModified) {
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)
58
57
  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
58
  }
59
+ return { filesChanged: filesModified, review: [] };
106
60
  }
107
61
  function migrateTypeScriptFile(tree, filePath) {
108
62
  let content = tree.read(filePath, 'utf-8');
109
63
  if (!content)
110
64
  return false;
111
65
  const original = content;
112
- if (!/from\s+['"]@ethlete\/(?:core|cdk)['"]/.test(content))
66
+ if (!/from\s+['"]@ethlete\/(?:core|cdk|theming)['"]/.test(content))
113
67
  return false;
114
68
  // Rename symbols
115
69
  for (const [oldName, newName] of Object.entries(TS_SYMBOL_RENAMES)) {
@@ -1,40 +1,65 @@
1
- import { logger } from '@nx/devkit';
2
1
  import * as ts from 'typescript';
3
- export default async function migrateCreateProvider(tree) {
4
- logger.log('\nšŸ”„ Migrating createProvider imports from @ethlete/cdk to @ethlete/core...\n');
5
- const tsFiles = [];
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')) {
13
- tsFiles.push(path);
14
- }
15
- }
16
- else {
17
- if (child === 'node_modules')
18
- continue;
19
- findTsFiles(path);
20
- }
21
- }
22
- }
23
- findTsFiles('.');
2
+ import { collectFiles } from './migration-files.js';
3
+ export default async function migrateCreateProvider(tree, scope) {
4
+ const review = [];
24
5
  let filesModified = 0;
25
- for (const filePath of tsFiles) {
26
- const wasModified = migrateCreateProviderInFile(tree, filePath);
27
- if (wasModified) {
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)
28
10
  filesModified++;
29
- logger.log(` āœ“ ${filePath}`);
30
- }
31
11
  }
32
- if (filesModified > 0) {
33
- logger.log(`\nāœ… Successfully migrated createProvider in ${filesModified} file(s)\n`);
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') });
34
54
  }
35
- else {
36
- logger.log('\nā„¹ļø No files needed migration\n');
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);
37
60
  }
61
+ tree.write(filePath, updatedContent);
62
+ return true;
38
63
  }
39
64
  function migrateCreateProviderInFile(tree, filePath) {
40
65
  const content = tree.read(filePath, 'utf-8');
@@ -44,7 +69,6 @@ function migrateCreateProviderInFile(tree, filePath) {
44
69
  let cdkImport;
45
70
  let hasCreateProviderInCdk = false;
46
71
  let createProviderImport = 'createProvider';
47
- // Find relevant imports
48
72
  for (const statement of sourceFile.statements) {
49
73
  if (ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier)) {
50
74
  const modulePath = statement.moduleSpecifier.text;
@@ -58,18 +82,15 @@ function migrateCreateProviderInFile(tree, filePath) {
58
82
  }
59
83
  }
60
84
  }
61
- // If createProvider is not imported from @ethlete/cdk, nothing to do
62
85
  if (!hasCreateProviderInCdk)
63
86
  return false;
64
87
  let updatedContent = content;
65
- // Remove createProvider from @ethlete/cdk import
66
88
  if (cdkImport && cdkImport.importClause?.namedBindings && ts.isNamedImports(cdkImport.importClause.namedBindings)) {
67
89
  const namedBindings = cdkImport.importClause.namedBindings;
68
90
  const otherImports = namedBindings.elements.filter((element) => (element.propertyName?.text ?? element.name.text) !== 'createProvider');
69
91
  const importStart = cdkImport.getStart(sourceFile);
70
92
  const importEnd = cdkImport.getEnd();
71
93
  if (otherImports.length === 0) {
72
- // Remove entire import line including newline
73
94
  let lineStart = importStart;
74
95
  while (lineStart > 0 && content[lineStart - 1] !== '\n') {
75
96
  lineStart--;
@@ -84,15 +105,12 @@ function migrateCreateProviderInFile(tree, filePath) {
84
105
  updatedContent = content.slice(0, lineStart) + content.slice(lineEnd);
85
106
  }
86
107
  else {
87
- // Keep other imports
88
108
  const newImports = otherImports.map((el) => el.getText(sourceFile)).join(', ');
89
109
  const newImportText = `import { ${newImports} } from '@ethlete/cdk';`;
90
110
  updatedContent = content.slice(0, importStart) + newImportText + content.slice(importEnd);
91
111
  }
92
112
  }
93
- // Re-parse after first modification
94
113
  const intermediateSourceFile = ts.createSourceFile(filePath, updatedContent, ts.ScriptTarget.Latest, true);
95
- // Find @ethlete/core import in the updated content
96
114
  let updatedCoreImport;
97
115
  for (const statement of intermediateSourceFile.statements) {
98
116
  if (ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier)) {
@@ -102,11 +120,9 @@ function migrateCreateProviderInFile(tree, filePath) {
102
120
  }
103
121
  }
104
122
  }
105
- // Add createProvider to @ethlete/core import
106
123
  if (updatedCoreImport?.importClause?.namedBindings &&
107
124
  ts.isNamedImports(updatedCoreImport.importClause.namedBindings)) {
108
125
  const namedBindings = updatedCoreImport.importClause.namedBindings;
109
- // Check if createProvider already exists
110
126
  const hasCreateProvider = namedBindings.elements.some((element) => (element.propertyName?.text ?? element.name.text) === 'createProvider');
111
127
  if (!hasCreateProvider) {
112
128
  const importStart = updatedCoreImport.getStart(intermediateSourceFile);
@@ -118,10 +134,8 @@ function migrateCreateProviderInFile(tree, filePath) {
118
134
  }
119
135
  }
120
136
  else {
121
- // Add new import from @ethlete/core at the top
122
137
  const firstImportIndex = updatedContent.indexOf('import');
123
138
  if (firstImportIndex !== -1) {
124
- // Add after other imports
125
139
  let insertPosition;
126
140
  let lastImportEnd = firstImportIndex;
127
141
  for (const statement of intermediateSourceFile.statements) {
@@ -133,7 +147,6 @@ function migrateCreateProviderInFile(tree, filePath) {
133
147
  }
134
148
  }
135
149
  insertPosition = lastImportEnd;
136
- // Find end of line
137
150
  while (insertPosition < updatedContent.length && updatedContent[insertPosition] !== '\n') {
138
151
  insertPosition++;
139
152
  }
@@ -146,7 +159,6 @@ function migrateCreateProviderInFile(tree, filePath) {
146
159
  updatedContent.slice(insertPosition);
147
160
  }
148
161
  else {
149
- // No imports, add at the beginning
150
162
  updatedContent = `import { ${createProviderImport} } from '@ethlete/core';\n\n` + updatedContent;
151
163
  }
152
164
  }
@@ -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,38 +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';
2
3
  import migrateColorNaming from './color-naming.js';
3
4
  import migrateCreateProvider from './create-provider.js';
4
5
  import reportRemovedExports from './removed-exports.js';
5
6
  import migrateRouterStateService from './router-state-service.js';
6
7
  import migrateViewportService from './viewport-service.js';
7
8
  export default async function migrate(tree, schema) {
8
- console.log('\nšŸ”„ Starting core v5 migration...');
9
- const shouldMigrateViewportService = schema.migrateViewportService !== false;
10
- const shouldMigrateCreateProvider = schema.migrateCreateProvider !== false;
11
- const shouldMigrateRouterStateService = schema.migrateRouterStateService !== false;
12
- if (shouldMigrateViewportService) {
13
- console.log(' • Migrating viewport service...');
14
- await migrateViewportService(tree);
15
- }
16
- if (shouldMigrateCreateProvider) {
17
- console.log(' • Migrating create provider...');
18
- await migrateCreateProvider(tree);
19
- }
20
- if (shouldMigrateRouterStateService) {
21
- await migrateRouterStateService(tree);
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);
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)]);
31
29
  }
32
30
  if (!schema.skipFormat) {
33
- console.log(' • Formatting files...');
34
31
  await formatFiles(tree);
35
32
  }
36
- console.log('\nāœ… Migration completed successfully!');
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.');
37
42
  }
38
- //#endregion
@@ -1,4 +1,5 @@
1
1
  import * as ts from 'typescript';
2
+ import { collectFiles } from './migration-files.js';
2
3
  const REMOVED_EXPORTS = new Map([
3
4
  ...['Memo', 'MemoConfig', 'MemoResolver', 'MapLike'].map((name) => [name, 'the @Memo decorator']),
4
5
  ...[
@@ -31,34 +32,13 @@ const REMOVED_EXPORTS = new Map([
31
32
  ].map((name) => [name, 'the props module']),
32
33
  ]);
33
34
  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
- }
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}`));
53
40
  }
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
- });
41
+ return { filesChanged: 0, review };
62
42
  }
63
43
  function findRemovedImports(tree, filePath) {
64
44
  const content = tree.read(filePath, 'utf-8');