Skip to content

Commit bf8514e

Browse files
authored
Reexport management example (#41)
* . * . * . * . * . * . * . * . * . * . * . * . * . * . * . * . * . * . * Automated pre-commit update --------- Co-authored-by: kopekC <28070492+kopekC@users.noreply.github.com>
1 parent 367a72e commit bf8514e

File tree

12 files changed

+364
-0
lines changed

12 files changed

+364
-0
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Transform Module Re-exports Organization
2+
3+
This example demonstrates how to use Codegen to automatically analyze and reorganize TypeScript module re-exports through shared directories. The script makes this process simple by handling all the tedious manual updates automatically.
4+
5+
> [!NOTE]
6+
> This codemod helps maintain clean module boundaries and improves code organization by centralizing shared exports.
7+
8+
## How the Migration Script Works
9+
10+
The script automates the entire reorganization process in a few key steps:
11+
12+
1. **Export Analysis**
13+
```python
14+
for export_stmt in file.export_statements:
15+
for export in export_stmt.exports:
16+
if export.is_reexport() and not export.is_external_export:
17+
all_reexports.append(export)
18+
```
19+
- Automatically identifies re-exports in shared directories
20+
- Analyzes export patterns and dependencies
21+
- Uses Codegen's intelligent code analysis engine
22+
23+
2. **Shared File Management**
24+
```python
25+
resolved_public_file = export.resolved_symbol.filepath.replace("src/", "src/shared/")
26+
if not codebase.has_file(resolved_public_file):
27+
target_file = codebase.create_file(resolved_public_file, sync=True)
28+
```
29+
- Creates or updates shared export files
30+
- Maintains proper file structure
31+
- Handles path resolution automatically
32+
33+
3. **Import Updates**
34+
```python
35+
# Updates imports to use new shared paths
36+
new_path = usage.file.ts_config.translate_import_path(resolved_public_file)
37+
new_import = f'import {{ {name} }} from "{new_path}"'
38+
```
39+
- Updates all import statements to use new paths
40+
- Maintains proper TypeScript path resolution
41+
- Handles different import types (normal, type)
42+
43+
## Why This Makes Organization Easy
44+
45+
1. **Zero Manual Updates**
46+
- Codegen SDK handles all file creation and updates
47+
- No tedious export management
48+
49+
2. **Consistent Structure**
50+
- Ensures all shared exports follow the same pattern
51+
- Maintains clean module boundaries
52+
53+
3. **Safe Transformations**
54+
- Validates changes before applying them
55+
- Preserves existing functionality
56+
57+
## Common Re-export Patterns
58+
59+
### Module to Shared Exports
60+
```typescript
61+
// Before: Direct module import
62+
import { validateEmail } from '../module_a/src/functions';
63+
64+
// After: Import through shared
65+
import { validateEmail } from '../module_a/src/shared';
66+
```
67+
68+
### Export Consolidation
69+
```typescript
70+
// Before: Multiple export files
71+
export { foo } from './foo';
72+
export { bar } from './bar';
73+
74+
// After: Consolidated in shared
75+
export * from '../functions';
76+
```
77+
78+
## Key Benefits to Note
79+
80+
1. **Better Module Boundaries**
81+
- Clear public API for each module
82+
- Centralized shared functionality
83+
84+
2. **Improved Maintainability**
85+
- Easier to track dependencies
86+
- Simplified import paths
87+
88+
3. **Code Organization**
89+
- Consistent export structure
90+
- Reduced import complexity
91+
92+
93+
The script will:
94+
1. 🎯 Start the reexport organization
95+
2. 📁 Analyze shared directories
96+
3. 🔄 Process and update exports
97+
4. ✨ Create shared export files
98+
5. 🧹 Clean up redundant exports
99+
100+
## Learn More
101+
102+
- [TypeScript Modules](https://www.typescriptlang.org/docs/handbook/modules.html)
103+
- [Export/Import Documentation](https://www.typescriptlang.org/docs/handbook/modules.html#export)
104+
- [Codegen Documentation](https://docs.codegen.com)
105+
- [Tutorial on Analyzing and Organizing Re-exports](https://docs.codegen.com/tutorials/managing-typescript-exports)
106+
- [More on exports ](https://docs.codegen.com/building-with-codegen/exports)
107+
## Contributing
108+
109+
Feel free to submit issues and enhancement requests!
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export const calculateSum = (a: number, b: number): number => {
2+
return a + b;
3+
};
4+
5+
export const formatName = (firstName: string, lastName: string): string => {
6+
return `${firstName} ${lastName}`;
7+
};
8+
9+
export const generateId = (): string => {
10+
return Math.random().toString(36).substring(7);
11+
};
12+
13+
export const validateEmail = (email: string): boolean => {
14+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
15+
return emailRegex.test(email);
16+
};
17+
18+
export const capitalize = (str: string): string => {
19+
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
20+
};

examples/reexport_management/input_repo/modules/module_a/src/shared/index.ts

Whitespace-only changes.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { calculateSum, formatName, capitalize } from '../module_a/src/functions';
2+
export { validateEmail } from '../module_c/src/shared/symbols/exports';
3+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { calculateSum, formatName, capitalize, validateEmail } from './shared/exports';
2+
3+
export const calculateAverage = (numbers: number[]): number => {
4+
const sum = numbers.reduce((acc, curr) => calculateSum(acc, curr), 0);
5+
return sum / numbers.length;
6+
};
7+
8+
export const createUserProfile = (firstName: string, lastName: string): string => {
9+
const formattedName = formatName(firstName, lastName);
10+
return `Profile: ${formattedName}`;
11+
};
12+
13+
export const formatText = (text: string): string => {
14+
return text.split(' ').map(capitalize).join(' ');
15+
};
16+
17+
export const multiply = (a: number, b: number): number => {
18+
return a * b;
19+
};
20+
21+
export const generateGreeting = (name: string): string => {
22+
const email = validateEmail(name);
23+
return `Hello, ${capitalize(name)}!`;
24+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { calculateSum, formatName, capitalize } from "../../imports";
2+
export {validateEmail} from "../../imports"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { validateEmail, generateId } from '../module_a/src/functions';
2+
export { calculateAverage, multiply, createUserProfile } from '../module_b/src/functions';
3+
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { validateEmail, generateId, calculateAverage, multiply, createUserProfile } from './shared/symbols/exports';
2+
3+
export const createUser = (email: string, firstName: string, lastName: string) => {
4+
if (!validateEmail(email)) {
5+
throw new Error('Invalid email');
6+
}
7+
8+
return {
9+
id: generateId(),
10+
profile: createUserProfile(firstName, lastName),
11+
email
12+
};
13+
};
14+
15+
export const calculateMetrics = (values: number[]): { average: number; scaled: number[] } => {
16+
const avg = calculateAverage(values);
17+
const scaled = values.map(v => multiply(v, 2));
18+
return { average: avg, scaled };
19+
};
20+
21+
export const validateAndFormatUser = (email: string, firstName: string, lastName: string) => {
22+
if (!validateEmail(email)) {
23+
return { success: false, message: 'Invalid email' };
24+
}
25+
26+
const profile = createUserProfile(firstName, lastName);
27+
return { success: true, profile };
28+
};
29+
30+
export const processNumbers = (numbers: number[]): number => {
31+
const { average } = calculateMetrics(numbers);
32+
return multiply(average, 100);
33+
};
34+
35+
export const generateReport = (userData: { email: string; name: string }): string => {
36+
const isValidEmail = validateEmail(userData.email);
37+
const id = generateId();
38+
return `Report ${id}: Email ${isValidEmail ? 'valid' : 'invalid'} - ${userData.name}`;
39+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { validateEmail, generateId } from '../../../imports'
2+
export { calculateAverage, multiply, createUserProfile } from '../../../imports'
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "default-exports-test",
3+
"version": "1.0.0",
4+
"description": "Test codebase for converting default exports",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"keywords": [],
10+
"author": "",
11+
"license": "ISC",
12+
"devDependencies": {
13+
"typescript": "^5.0.0"
14+
}
15+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"compilerOptions": {
3+
"baseUrl": "./",
4+
"paths": {
5+
"*": ["modules/*"]
6+
}
7+
},
8+
"include": ["modules/**/*"]
9+
}

examples/reexport_management/run.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import codegen
2+
from codegen import Codebase
3+
from codegen.sdk.typescript.file import TSImport
4+
from codegen.sdk.enums import ProgrammingLanguage
5+
6+
processed_imports = set()
7+
@codegen.function("reexport_management")
8+
def run(codebase: Codebase):
9+
print("🚀 Starting reexport analysis...")
10+
for file in codebase.files:
11+
# Only process files under /src/shared
12+
if "examples/analize_reexports" not in file.filepath or '/src/shared' not in file.filepath:
13+
continue
14+
15+
print(f"📁 Analyzing: {file.filepath}")
16+
17+
# Gather all reexports that are not external exports
18+
all_reexports = []
19+
for export_stmt in file.export_statements:
20+
for export in export_stmt.exports:
21+
if export.is_reexport() and not export.is_external_export:
22+
all_reexports.append(export)
23+
24+
if not all_reexports:
25+
continue
26+
27+
print(f"📦 Found {len(all_reexports)} reexports to process")
28+
29+
for export in all_reexports:
30+
has_wildcard = False
31+
32+
# Replace "src/" with "src/shared/"
33+
resolved_public_file = export.resolved_symbol.filepath.replace("src/", "src/shared/")
34+
print(f"🔄 Processing: {export.name} -> {resolved_public_file}")
35+
36+
# Get relative path from the "public" file back to the original file
37+
relative_path = codebase.get_relative_path(
38+
from_file=resolved_public_file,
39+
to_file=export.resolved_symbol.filepath
40+
)
41+
42+
# Ensure the "public" file exists
43+
if not codebase.has_file(resolved_public_file):
44+
print(f"✨ Creating new public file: {resolved_public_file}")
45+
target_file = codebase.create_file(resolved_public_file, sync=True)
46+
else:
47+
target_file = codebase.get_file(resolved_public_file)
48+
49+
# If target file already has a wildcard export for this relative path, skip
50+
if target_file.has_export_statement_for_path(relative_path, "WILDCARD"):
51+
has_wildcard = True
52+
continue
53+
54+
# Compare "public" path to the local file's export.filepath
55+
if codebase._remove_extension(resolved_public_file) != codebase._remove_extension(export.filepath):
56+
# A) Wildcard export
57+
if export.is_wildcard_export():
58+
target_file.insert_before(f'export * from "{relative_path}"')
59+
print(f"⭐ Added wildcard export for {relative_path}")
60+
61+
# B) Type export
62+
elif export.is_type_export():
63+
statement = file.get_export_statement_for_path(relative_path, "TYPE")
64+
if statement:
65+
if export.is_aliased():
66+
statement.insert(0, f"{export.resolved_symbol.name} as {export.name}")
67+
else:
68+
statement.insert(0, f"{export.name}")
69+
print(f"📝 Updated existing type export for {export.name}")
70+
else:
71+
if export.is_aliased():
72+
target_file.insert_before(
73+
f'export type {{ {export.resolved_symbol.name} as {export.name} }} '
74+
f'from "{relative_path}"'
75+
)
76+
else:
77+
target_file.insert_before(
78+
f'export type {{ {export.name} }} from "{relative_path}"'
79+
)
80+
print(f"✨ Added new type export for {export.name}")
81+
82+
# C) Normal export
83+
else:
84+
statement = file.get_export_statement_for_path(relative_path, "EXPORT")
85+
if statement:
86+
if export.is_aliased():
87+
statement.insert(0, f"{export.resolved_symbol.name} as {export.name}")
88+
else:
89+
statement.insert(0, f"{export.name}")
90+
print(f"📝 Updated existing export for {export.name}")
91+
else:
92+
if export.is_aliased():
93+
target_file.insert_before(
94+
f'export {{ {export.resolved_symbol.name} as {export.name} }} '
95+
f'from "{relative_path}"'
96+
)
97+
else:
98+
target_file.insert_before(
99+
f'export {{ {export.name} }} from "{relative_path}"'
100+
)
101+
print(f"✨ Added new export for {export.name}")
102+
103+
# Update import usages
104+
for usage in export.symbol_usages():
105+
if isinstance(usage, TSImport) and usage not in processed_imports:
106+
processed_imports.add(usage)
107+
108+
new_path = usage.file.ts_config.translate_import_path(resolved_public_file)
109+
110+
if has_wildcard and export.name != export.resolved_symbol.name:
111+
name = f"{export.resolved_symbol.name} as {export.name}"
112+
else:
113+
name = usage.name
114+
115+
if usage.is_type_import():
116+
new_import = f'import type {{ {name} }} from "{new_path}"'
117+
else:
118+
new_import = f'import {{ {name} }} from "{new_path}"'
119+
120+
usage.file.insert_before(new_import)
121+
usage.remove()
122+
print(f"🔄 Updated import in {usage.file.filepath}")
123+
124+
# Remove old export
125+
export.remove()
126+
print(f"🗑️ Removed old export from {export.filepath}")
127+
128+
# Clean up empty files
129+
if not file.export_statements and len(file.symbols) == 0:
130+
file.remove()
131+
print(f"🧹 Removed empty file: {file.filepath}")
132+
codebase.commit()
133+
134+
if __name__ == "__main__":
135+
print("🎯 Starting reexport organization...")
136+
codebase = Codebase("./", programming_language=ProgrammingLanguage.TYPESCRIPT)
137+
run(codebase)
138+
print("✅ Done! All reexports organized successfully!")

0 commit comments

Comments
 (0)