migrate-barrel-imports 1.5.1 → 1.7.0

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/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare function main(): Promise<void>;
2
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAIA,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAmD1C"}
package/dist/cli.js ADDED
@@ -0,0 +1,35 @@
1
+ import { Command } from 'commander';
2
+ import { migrateBarrelImports } from './migrate-barrel-imports';
3
+ import { defaultOptions } from './options';
4
+ export async function main() {
5
+ const program = new Command();
6
+ program
7
+ .name('migrate-barrel-imports')
8
+ .description('CLI tool to migrate barrel files imports to direct imports')
9
+ .argument('<source-path>', 'Glob pattern for source packages containing barrel files (e.g. "libs/*")')
10
+ .argument('[target-path]', 'Path to the directory where imports should be migrated (default: current directory)')
11
+ .option('--ignore-source-files <patterns>', 'Comma-separated list of file patterns to ignore in source directory')
12
+ .option('--ignore-target-files <patterns>', 'Comma-separated list of file patterns to ignore in target directory')
13
+ .option('--no-extension', 'Exclude js|jsx|ts|tsx|mjs|cjs file extensions from import statements')
14
+ .allowUnknownOption(false)
15
+ .parse(process.argv);
16
+ const args = program.args;
17
+ if (!args[0]) {
18
+ console.error('Error: source-path is required');
19
+ process.exit(1);
20
+ }
21
+ const sourcePath = args[0];
22
+ const targetPath = args[1] || defaultOptions.targetPath;
23
+ const options = program.opts();
24
+ await migrateBarrelImports({
25
+ sourcePath,
26
+ targetPath,
27
+ ignoreSourceFiles: options.ignoreSourceFiles
28
+ ? options.ignoreSourceFiles.split(',')
29
+ : defaultOptions.ignoreSourceFiles,
30
+ ignoreTargetFiles: options.ignoreTargetFiles
31
+ ? options.ignoreTargetFiles.split(',')
32
+ : defaultOptions.ignoreTargetFiles,
33
+ includeExtension: options.extension !== false ? true : defaultOptions.includeExtension
34
+ });
35
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js CHANGED
@@ -1,7 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{Command as e}from"commander";import o,{readFileSync as s,writeFileSync as r}from"node:fs";import t from"node:path";import i from"@babel/generator";import{parse as n}from"@babel/parser";import l from"@babel/traverse";import{isVariableDeclaration as a,isIdentifier as c,isFunctionDeclaration as p,isTSEnumDeclaration as g,isTSInterfaceDeclaration as d,isTSTypeAliasDeclaration as u,isClassDeclaration as f,isExportSpecifier as m,isImportSpecifier as h,importDeclaration as $,stringLiteral as x,importSpecifier as S}from"@babel/types";import F from"fast-glob";import k from"micromatch";let w=i.default||i,y=l.default||l,b={sourceType:"module",plugins:["typescript","jsx","decorators-legacy","classProperties","classPrivateProperties","classPrivateMethods","exportDefaultFrom","exportNamespaceFrom","functionBind","functionSent","dynamicImport","nullishCoalescingOperator","optionalChaining","objectRestSpread","asyncGenerators","doExpressions","importMeta","logicalAssignment","moduleBlocks","moduleStringNames","numericSeparator","partialApplication","privateIn","throwExpressions","topLevelAwait"]};async function E(e){return JSON.parse(s(t.join(e,"package.json"),"utf-8"))}async function P({packagePath:e,ignoreSourceFiles:o=[],stats:r}){let i=[];console.log(`Scanning for TypeScript and JavaScript files in: ${e}`);let l=await F("**/*.{ts,tsx,js,jsx}",{cwd:e,ignore:["**/node_modules/**","**/dist/**","**/build/**"]});for(let h of(console.log(`Found ${l.length} files`),l)){let l=o.some(e=>k.isMatch(h,e));l&&(console.log(`File matches ignore pattern but will be preserved: ${h}`),r&&r.sourceFilesSkipped++);let $=t.join(e,h);console.log(`
3
- Processing file: ${h}`);let x=s($,"utf-8");try{let e=n(x,b);y(e,{ExportNamedDeclaration(e){if(console.log(`Found named export in ${h}`),e.node.source){let o=e.node.source.value;if(o.includes("node_modules")||!o.startsWith(".")){console.log(`Skipping re-export from external package: ${o}`);return}}if(e.node.declaration){var o;let s=(o=e.node.declaration)?a(o)?o.declarations.map(e=>c(e.id)?e.id.name:null).filter(e=>null!==e):p(o)&&o.id||g(o)||d(o)||u(o)||f(o)&&o.id?[o.id.name]:[]:[];s.length>0&&(console.log(`Named exports found: ${s.join(", ")}`),i.push({source:h,exports:s,isIgnored:l}))}let s=e.node.specifiers.map(e=>{if(m(e)){let o=e.exported;return c(o)?o.name:o.value}return null}).filter(e=>null!==e);s.length>0&&(console.log(`Named exports found: ${s.join(", ")}`),i.push({source:h,exports:s,isIgnored:l}))},ExportDefaultDeclaration(e){console.log(`Found default export in ${h}`);let o=e.node.declaration,s=c(o)?o.name:p(o)&&o.id?o.id.name:null;s?(console.log(`Default export name: ${s}`),i.push({source:h,exports:[s],isIgnored:l})):console.log("Default export is not an identifier or named function")}})}catch(e){console.error(`Error parsing ${h}:`,e)}}return console.log(`
4
- Total exports found: ${i.length}`),i}async function j({packageName:e,monorepoRoot:o}){try{let r=new Set,t=await F(["**/*.{ts,tsx,js,jsx}"],{cwd:o,absolute:!0,ignore:["**/node_modules/**","**/dist/**","**/build/**"],followSymbolicLinks:!1});for(let o of(console.log(`Found ${t.length} files to scan`),t))try{let t=s(o,"utf-8"),i=n(t,b);y(i,{ImportDeclaration(s){let t=s.node.source.value;(t===e||t.startsWith(`${e}/`))&&r.add(o)}})}catch(e){console.error(`Error processing file ${o}:`,e)}let i=Array.from(r);if(i.length>0)for(let o of(console.log(`Found total of ${i.length} files with imports from ${e}`),console.log("Files found:"),i))console.log(` ${o}`);else console.log(`No files found importing from ${e}`);return i}catch(e){return console.error("Error finding imports:",e),[]}}async function T({filePath:e,packageName:o,exports:t,includeExtension:i=!0,warnings:l,stats:a}){console.log(`
5
- Processing file: ${e}`);let p=s(e,"utf-8");try{let s=n(p,b),a=!1,g=0,d=new Set;if(y(s,{ImportDeclaration(s){let r=s.node.source.value;if(!d.has(r)&&r===o){g++,console.log(`Found import from ${o}`);let n=s.node.specifiers,p=new Map,u=[],f=!1;for(let s of n)if(h(s)){let r=s.imported,n=c(r)?r.name:r.value,g=t.find(e=>e.exports.includes(n));if(g){if(console.log(` Found export ${n} in ${g.source}`),g.isIgnored){console.log(` Keeping original import for ignored file: ${g.source}`),u.push(s);continue}let e=i?`${o}/${g.source}`:`${o}/${g.source.replace(/\.(js|jsx|ts|tsx|mjs|cjs)$/,"")}`;p.has(e)||p.set(e,[]),p.get(e)?.push({local:s.local,imported:s.imported}),a=!0,f=!0}else{if(t.some(e=>e.source.includes("node_modules")||!e.source.startsWith("."))){let o=`Skipping re-export from external package: ${n} in "${e}"`;console.log(` ${o}`),l&&l.push(o),u.push(s);continue}let o=`Could not find export ${n} in ${e}`;console.log(` Warning: ${o}`),l&&l.push(o),u.push(s)}}else u.push(s);if(!f){console.log(" Keeping original import due to no direct exports to migrate"),d.add(r);return}let m=[];for(let[e,s]of(u.length>0&&m.push($(u,x(o))),p.entries()))m.push($(s.map(e=>S(e.local,e.imported)),x(e)));s.replaceWithMultiple(m),console.log(" Replaced import with direct imports from source files"),d.add(r)}}}),a){console.log(`Writing changes to ${e}`);let o=w(s,{retainLines:!0,retainFunctionParens:!0},p);r(e,o.code)}else g>0?console.log(`No changes needed for ${g} imports`):console.log("No imports found to update")}catch(o){a&&a.errors++,console.error(`Error processing ${e}:`,o)}}async function N(e){let{sourcePath:r,targetPath:i,ignoreSourceFiles:n,ignoreTargetFiles:l}=e,a={totalFiles:0,filesProcessed:0,filesSkipped:0,importsUpdated:0,filesWithNoUpdates:0,errors:0,totalExports:0,sourceFilesScanned:0,sourceFilesWithExports:0,sourceFilesSkipped:0,targetFilesFound:[],packagesProcessed:0,packagesSkipped:0,warnings:[]},c=await F(r,{cwd:i,absolute:!0,ignore:["**/node_modules/**","**/dist/**","**/build/**","**/target-app/**"],followSymbolicLinks:!1,onlyDirectories:!0}),p=[];for(let e of c){let s=t.join(e,"package.json");try{await o.promises.access(s),p.push(s)}catch{console.log(`No package.json found in ${e}, skipping`)}}for(let o of(console.log(`Found ${p.length} source packages to process`),p))try{let r=t.dirname(o);console.log(`
6
- Processing package: ${r}`);let c=await E(r),p=await P({packagePath:r,ignoreSourceFiles:n,stats:a});a.totalExports+=p.reduce((e,o)=>e+o.exports.length,0),a.sourceFilesWithExports+=new Set(p.map(e=>e.source)).size,a.sourceFilesScanned+=(await F("**/*.{ts,tsx}",{cwd:r,ignore:["**/node_modules/**","**/dist/**","**/build/**"]})).length;let g=await j({packageName:c.name,monorepoRoot:i});for(let o of(a.totalFiles+=g.length,a.targetFilesFound.push(...g),g)){let r=t.relative(i,o);if(l.some(e=>k.isMatch(r,e))){console.log(`Skipping ignored file: ${o} (matches pattern in ${l.join(", ")})`),a.filesSkipped++;continue}try{let r=s(o,"utf-8");await T({filePath:o,packageName:c.name,exports:p,includeExtension:e.includeExtension,warnings:a.warnings,stats:a});let t=s(o,"utf-8");r!==t?a.importsUpdated++:a.filesWithNoUpdates++,a.filesProcessed++}catch(e){a.errors++,console.error(`Error processing ${o}:`,e)}}a.packagesProcessed++}catch(e){a.packagesSkipped++,console.error(`Error processing package ${o}:`,e)}if(console.log("\nMigration Summary"),console.log(`Source packages found: ${p.length}`),console.log(`Source packages processed: ${a.packagesProcessed}`),console.log(`Source packages skipped: ${a.packagesSkipped}`),console.log(`Source files found: ${a.sourceFilesScanned}`),console.log(`Source files with exports: ${a.sourceFilesWithExports}`),console.log(`Source files skipped: ${a.sourceFilesSkipped}`),console.log(`Exports found: ${a.totalExports}`),console.log(`Target files found: ${a.totalFiles}`),console.log(`Target files processed: ${a.filesProcessed}`),console.log(`Target files with imports updated: ${a.importsUpdated}`),console.log(`Target files with no changes needed: ${a.filesWithNoUpdates}`),console.log(`Target files skipped: ${a.filesSkipped}`),a.warnings.length>0)for(let e of(console.log("\nWarnings:"),a.warnings))console.log(` - ${e}`);a.errors>0&&console.log(`
7
- Warning: ${a.errors} errors encountered during processing`)}let v={targetPath:".",ignoreSourceFiles:[],ignoreTargetFiles:[],includeExtension:!1};async function D(){let o=new e;o.name("migrate-barrel-imports").description("CLI tool to migrate barrel files imports to direct imports").argument("<source-path>",'Glob pattern for source packages containing barrel files (e.g. "libs/*")').argument("[target-path]","Path to the directory where imports should be migrated (default: current directory)").option("--ignore-source-files <patterns>","Comma-separated list of file patterns to ignore in source directory").option("--ignore-target-files <patterns>","Comma-separated list of file patterns to ignore in target directory").option("--no-extension","Exclude js|jsx|ts|tsx|mjs|cjs file extensions from import statements").allowUnknownOption(!1).parse(process.argv);let s=o.args;s[0]||(console.error("Error: source-path is required"),process.exit(1));let r=s[0],t=s[1]||v.targetPath,i=o.opts();await N({sourcePath:r,targetPath:t,ignoreSourceFiles:i.ignoreSourceFiles?i.ignoreSourceFiles.split(","):v.ignoreSourceFiles,ignoreTargetFiles:i.ignoreTargetFiles?i.ignoreTargetFiles.split(","):v.ignoreTargetFiles,includeExtension:!1!==i.extension||v.includeExtension})}(async()=>{await D()})();
2
+ import { main } from './cli';
3
+ void (async () => {
4
+ await main();
5
+ })();
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @fileoverview Tool for migrating TypeScript projects from barrel file exports to direct file imports
3
+ *
4
+ * This tool helps migrate TypeScript projects that use barrel files (index.ts files that re-export)
5
+ * to use direct imports from source files instead. This improves:
6
+ * - Tree-shaking efficiency
7
+ * - Build performance
8
+ * - Code maintainability
9
+ * - TypeScript compilation speed
10
+ *
11
+ * The migration process:
12
+ * 1. Scans source package for all exports
13
+ * 2. Finds all files importing from the package
14
+ * 3. Updates imports to point directly to source files
15
+ * 4. Preserves original import names and types
16
+ */
17
+ import type { Options as MigrationOptions } from './options';
18
+ /**
19
+ * Main migration function that orchestrates the barrel file migration process
20
+ *
21
+ * Process flow:
22
+ * 1. Finds all source packages matching the glob pattern
23
+ * 2. For each package:
24
+ * - Reads package.json to get package name and configuration
25
+ * - Scans source package for all exports (named and default)
26
+ * - Finds all files in the monorepo that import from the package
27
+ * - Updates each import to point directly to source files
28
+ *
29
+ * @param {Options} options - Migration configuration options
30
+ * @returns {Promise<void>}
31
+ */
32
+ export declare function migrateBarrelImports(options: MigrationOptions): Promise<void>;
33
+ //# sourceMappingURL=migrate-barrel-imports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate-barrel-imports.d.ts","sourceRoot":"","sources":["../src/migrate-barrel-imports.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAkCH,OAAO,KAAK,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,WAAW,CAAA;AA2tB5D;;;;;;;;;;;;;GAaG;AACH,wBAAsB,oBAAoB,CACzC,OAAO,EAAE,gBAAgB,GACvB,OAAO,CAAC,IAAI,CAAC,CAoFf"}
@@ -0,0 +1,681 @@
1
+ /**
2
+ * @fileoverview Tool for migrating TypeScript projects from barrel file exports to direct file imports
3
+ *
4
+ * This tool helps migrate TypeScript projects that use barrel files (index.ts files that re-export)
5
+ * to use direct imports from source files instead. This improves:
6
+ * - Tree-shaking efficiency
7
+ * - Build performance
8
+ * - Code maintainability
9
+ * - TypeScript compilation speed
10
+ *
11
+ * The migration process:
12
+ * 1. Scans source package for all exports
13
+ * 2. Finds all files importing from the package
14
+ * 3. Updates imports to point directly to source files
15
+ * 4. Preserves original import names and types
16
+ */
17
+ import { readFileSync, writeFileSync } from 'node:fs';
18
+ import path from 'node:path';
19
+ import _generate from '@babel/generator';
20
+ import { parse } from '@babel/parser';
21
+ import _traverse from '@babel/traverse';
22
+ import { importDeclaration, importSpecifier, isClassDeclaration, isExportSpecifier, isFunctionDeclaration, isIdentifier, isImportDefaultSpecifier, isImportNamespaceSpecifier, isImportSpecifier, isTSEnumDeclaration, isTSInterfaceDeclaration, isTSTypeAliasDeclaration, isVariableDeclaration, stringLiteral } from '@babel/types';
23
+ import fg from 'fast-glob';
24
+ import micromatch from 'micromatch';
25
+ // @ts-expect-error
26
+ const generate = _generate.default || _generate;
27
+ // @ts-expect-error
28
+ const traverse = _traverse.default || _traverse;
29
+ // Common Babel configuration for parsing TypeScript files
30
+ const BABEL_CONFIG = {
31
+ sourceType: 'module',
32
+ plugins: [
33
+ 'typescript',
34
+ 'jsx',
35
+ 'decorators-legacy',
36
+ 'classProperties',
37
+ 'classPrivateProperties',
38
+ 'classPrivateMethods',
39
+ 'exportDefaultFrom',
40
+ 'exportNamespaceFrom',
41
+ 'functionBind',
42
+ 'functionSent',
43
+ 'dynamicImport',
44
+ 'nullishCoalescingOperator',
45
+ 'optionalChaining',
46
+ 'objectRestSpread',
47
+ 'asyncGenerators',
48
+ 'doExpressions',
49
+ 'importMeta',
50
+ 'logicalAssignment',
51
+ 'moduleBlocks',
52
+ 'moduleStringNames',
53
+ 'numericSeparator',
54
+ 'partialApplication',
55
+ 'privateIn',
56
+ 'throwExpressions',
57
+ 'topLevelAwait'
58
+ ]
59
+ };
60
+ /**
61
+ * Reads and parses the package.json file for a given package path
62
+ *
63
+ * @param {string} packagePath - The path to the package directory
64
+ * @returns {Promise<PackageJson>} The parsed package.json contents
65
+ * @throws {Error} If package.json cannot be read or parsed
66
+ */
67
+ async function _getPackageInfo(packagePath) {
68
+ const packageJsonPath = path.join(packagePath, 'package.json');
69
+ const content = readFileSync(packageJsonPath, 'utf-8');
70
+ return JSON.parse(content);
71
+ }
72
+ /**
73
+ * Extracts export names from a declaration node
74
+ */
75
+ function getExportNames(declaration) {
76
+ if (!declaration)
77
+ return [];
78
+ if (isVariableDeclaration(declaration)) {
79
+ return declaration.declarations
80
+ .map((d) => (isIdentifier(d.id) ? d.id.name : null))
81
+ .filter((name) => name !== null);
82
+ }
83
+ if (isFunctionDeclaration(declaration) && declaration.id) {
84
+ return [declaration.id.name];
85
+ }
86
+ if (isTSEnumDeclaration(declaration)) {
87
+ return [declaration.id.name];
88
+ }
89
+ if (isTSInterfaceDeclaration(declaration)) {
90
+ return [declaration.id.name];
91
+ }
92
+ if (isTSTypeAliasDeclaration(declaration)) {
93
+ return [declaration.id.name];
94
+ }
95
+ if (isClassDeclaration(declaration) && declaration.id) {
96
+ return [declaration.id.name];
97
+ }
98
+ return [];
99
+ }
100
+ /**
101
+ * Checks if a file is a barrel file by analyzing its exports
102
+ *
103
+ * @param {string} filePath - Path to the file to check
104
+ * @returns {Promise<boolean>} Whether the file is a barrel file
105
+ */
106
+ async function isBarrelFile(filePath) {
107
+ try {
108
+ const content = readFileSync(filePath, 'utf-8');
109
+ const ast = parse(content, BABEL_CONFIG);
110
+ let hasReExports = false;
111
+ let _hasDirectExports = false;
112
+ traverse(ast, {
113
+ ExportNamedDeclaration(path) {
114
+ if (path.node.source) {
115
+ hasReExports = true;
116
+ }
117
+ else {
118
+ _hasDirectExports = true;
119
+ }
120
+ },
121
+ ExportDefaultDeclaration() {
122
+ _hasDirectExports = true;
123
+ }
124
+ });
125
+ // A barrel file typically has re-exports and may or may not have direct exports
126
+ return hasReExports;
127
+ }
128
+ catch (error) {
129
+ console.error(`Error checking if ${filePath} is a barrel file:`, error);
130
+ return false;
131
+ }
132
+ }
133
+ /**
134
+ * Recursively finds all exports in a package by scanning all TypeScript files
135
+ *
136
+ * This function:
137
+ * 1. Scans all .ts and .tsx files in the package
138
+ * 2. Identifies both named exports and default exports
139
+ * 3. Skips re-exports to avoid circular dependencies
140
+ * 4. Filters out ignored files based on patterns
141
+ * 5. Handles barrel files by tracking their re-exports
142
+ *
143
+ * @param {FindExportsParams} params - Parameters for finding exports
144
+ * @returns {Promise<ExportInfo[]>} Array of export information, including source file and exported names
145
+ */
146
+ async function findExports({ packagePath, ignoreSourceFiles = [], stats }) {
147
+ const exports = [];
148
+ const barrelFiles = new Set();
149
+ const _processedFiles = new Set();
150
+ const _exportSources = {};
151
+ const exportFiles = {};
152
+ console.log(`Scanning for TypeScript and JavaScript files in: ${packagePath}`);
153
+ const allFiles = await fg('**/*.{ts,tsx,js,jsx}', {
154
+ cwd: packagePath,
155
+ ignore: ['**/node_modules/**', '**/dist/**', '**/build/**']
156
+ });
157
+ console.log(`Found ${allFiles.length} files`);
158
+ // First pass: identify barrel files
159
+ for (const file of allFiles) {
160
+ const fullPath = path.join(packagePath, file);
161
+ if (await isBarrelFile(fullPath)) {
162
+ barrelFiles.add(file);
163
+ console.log(`Identified barrel file: ${file}`);
164
+ }
165
+ }
166
+ // Second pass: process all files
167
+ for (const file of allFiles) {
168
+ // Mark files that match ignore patterns but still process them
169
+ const isIgnored = ignoreSourceFiles.some((pattern) => micromatch.isMatch(file, pattern));
170
+ if (isIgnored) {
171
+ console.log(`File matches ignore pattern but will be preserved: ${file}`);
172
+ if (stats) {
173
+ stats.sourceFilesSkipped++;
174
+ }
175
+ }
176
+ const fullPath = path.join(packagePath, file);
177
+ console.log(`\nProcessing file: ${file}`);
178
+ const content = readFileSync(fullPath, 'utf-8');
179
+ try {
180
+ const ast = parse(content, BABEL_CONFIG);
181
+ const fileExports = [];
182
+ const reExports = {};
183
+ const fileExportSources = {};
184
+ const defaultExportNames = [];
185
+ traverse(ast, {
186
+ ExportNamedDeclaration(nodePath) {
187
+ // Handle re-exports from external packages
188
+ if (nodePath.node.source) {
189
+ const sourceValue = nodePath.node.source.value;
190
+ if (sourceValue.includes('node_modules') ||
191
+ !sourceValue.startsWith('.')) {
192
+ // Extract export names and their original source
193
+ nodePath.node.specifiers.forEach((specifier) => {
194
+ if (isExportSpecifier(specifier)) {
195
+ const exported = specifier.exported;
196
+ const exportName = isIdentifier(exported)
197
+ ? exported.name
198
+ : exported.value;
199
+ reExports[exportName] = sourceValue;
200
+ fileExports.push(exportName);
201
+ fileExportSources[exportName] = file;
202
+ // Track all files that export this symbol
203
+ if (!exportFiles[exportName]) {
204
+ exportFiles[exportName] = [];
205
+ }
206
+ exportFiles[exportName].push(file);
207
+ }
208
+ });
209
+ return;
210
+ }
211
+ }
212
+ // Handle variable declarations with exports
213
+ if (nodePath.node.declaration) {
214
+ const exportNames = getExportNames(nodePath.node.declaration);
215
+ if (exportNames.length > 0) {
216
+ fileExports.push(...exportNames);
217
+ exportNames.forEach((name) => {
218
+ fileExportSources[name] = file;
219
+ // Track all files that export this symbol
220
+ if (!exportFiles[name]) {
221
+ exportFiles[name] = [];
222
+ }
223
+ exportFiles[name].push(file);
224
+ });
225
+ }
226
+ }
227
+ // Handle export specifiers
228
+ const exportNames = nodePath.node.specifiers
229
+ .map((s) => {
230
+ if (isExportSpecifier(s)) {
231
+ const exported = s.exported;
232
+ const exportName = isIdentifier(exported)
233
+ ? exported.name
234
+ : exported.value;
235
+ if (nodePath.node.source) {
236
+ // If it's a re-export from another file, track the source
237
+ const sourceValue = nodePath.node.source.value;
238
+ if (sourceValue.startsWith('.')) {
239
+ const resolvedPath = path.join(path.dirname(file), sourceValue);
240
+ fileExportSources[exportName] = resolvedPath.replace(/\.[^/.]+$/, '');
241
+ // Track all files that export this symbol
242
+ if (!exportFiles[exportName]) {
243
+ exportFiles[exportName] = [];
244
+ }
245
+ exportFiles[exportName].push(file);
246
+ }
247
+ }
248
+ return exportName;
249
+ }
250
+ return null;
251
+ })
252
+ .filter((name) => name !== null);
253
+ if (exportNames.length > 0) {
254
+ fileExports.push(...exportNames);
255
+ exportNames.forEach((name) => {
256
+ if (!fileExportSources[name]) {
257
+ fileExportSources[name] = file;
258
+ }
259
+ });
260
+ }
261
+ },
262
+ ExportDefaultDeclaration(path) {
263
+ const exported = path.node.declaration;
264
+ const exportName = isIdentifier(exported)
265
+ ? exported.name
266
+ : isFunctionDeclaration(exported) && exported.id
267
+ ? exported.id.name
268
+ : isClassDeclaration(exported) && exported.id
269
+ ? exported.id.name
270
+ : 'default';
271
+ fileExports.push(exportName);
272
+ fileExportSources[exportName] = file;
273
+ // If this is a named entity (class, function) being exported as default, track its name
274
+ if (exportName !== 'default') {
275
+ defaultExportNames.push(exportName);
276
+ }
277
+ }
278
+ });
279
+ if (fileExports.length > 0 || Object.keys(reExports).length > 0) {
280
+ exports.push({
281
+ source: file,
282
+ exports: fileExports,
283
+ isIgnored,
284
+ ...(Object.keys(reExports).length > 0 && { reExports }),
285
+ ...(Object.keys(fileExportSources).length > 0 && {
286
+ exportSources: fileExportSources
287
+ }),
288
+ ...(defaultExportNames.length > 0 && { defaultExportNames }),
289
+ ...((await isBarrelFile(fullPath)) && { isBarrelFile: true }),
290
+ ...(Object.keys(exportFiles).length > 0 && { exportFiles })
291
+ });
292
+ // Print exports in a single line
293
+ if (fileExports.length > 0) {
294
+ console.log(`Found exports ${fileExports.join(', ')} in ${file}`);
295
+ }
296
+ }
297
+ }
298
+ catch (error) {
299
+ console.error(`Error parsing ${file}:`, error);
300
+ }
301
+ }
302
+ console.log(`\nTotal exports found: ${exports.length}`);
303
+ console.log(`Barrel files found: ${barrelFiles.size}`);
304
+ return exports;
305
+ }
306
+ /**
307
+ * Finds all files in the monorepo that import from a specific package
308
+ *
309
+ * This function:
310
+ * 1. Uses fast-glob to find all TypeScript files
311
+ * 2. Parses each file's AST to find imports
312
+ * 3. Handles both direct package imports and subpath imports
313
+ * 4. Excludes node_modules, dist, and build directories
314
+ *
315
+ * @param {FindImportsParams} params - Parameters for finding imports
316
+ * @returns {Promise<string[]>} Array of file paths that import from the package
317
+ */
318
+ async function findImports({ packageName, targetPath, stats: _stats }) {
319
+ try {
320
+ const allFiles = new Set();
321
+ // Find all TypeScript and JavaScript files in the monorepo
322
+ const files = await fg(['**/*.{ts,tsx,js,jsx}'], {
323
+ cwd: targetPath,
324
+ absolute: true,
325
+ ignore: ['**/node_modules/**', '**/dist/**', '**/build/**'],
326
+ followSymbolicLinks: false
327
+ });
328
+ console.log(`Found ${files.length} files to scan`);
329
+ // Scan each file for imports
330
+ for (const file of files) {
331
+ try {
332
+ const content = readFileSync(file, 'utf-8');
333
+ const ast = parse(content, BABEL_CONFIG);
334
+ traverse(ast, {
335
+ ImportDeclaration(path) {
336
+ const source = path.node.source.value;
337
+ // Check for exact package import or subpath import
338
+ if (source === packageName ||
339
+ source.startsWith(`${packageName}/`)) {
340
+ allFiles.add(file);
341
+ }
342
+ }
343
+ });
344
+ }
345
+ catch (error) {
346
+ console.error(`Error processing file ${file}:`, error);
347
+ }
348
+ }
349
+ const uniqueFiles = Array.from(allFiles);
350
+ if (uniqueFiles.length > 0) {
351
+ console.log(`Found total of ${uniqueFiles.length} files with imports from ${packageName}`);
352
+ console.log('Files found:');
353
+ for (const file of uniqueFiles) {
354
+ console.log(` ${file}`);
355
+ }
356
+ }
357
+ else {
358
+ console.log(`No files found importing from ${packageName}`);
359
+ }
360
+ return uniqueFiles;
361
+ }
362
+ catch (error) {
363
+ console.error('Error finding imports:', error);
364
+ return [];
365
+ }
366
+ }
367
+ /**
368
+ * Updates imports in a file to point directly to source files instead of using barrel files
369
+ *
370
+ * This function:
371
+ * 1. Parses the file's AST to find imports from the package
372
+ * 2. For each import, finds the source file containing the export
373
+ * 3. Updates the import to point directly to the source file
374
+ * 4. Preserves original import names and types
375
+ * 5. Only modifies the file if changes are needed
376
+ *
377
+ * @param {UpdateImportsParams} params - Parameters for updating imports
378
+ * @returns {Promise<void>}
379
+ */
380
+ async function updateImports({ filePath, packageName, exports, includeExtension = true, warnings: _warnings, stats: _stats }) {
381
+ console.log(`\nProcessing file: ${filePath}`);
382
+ const content = readFileSync(filePath, 'utf-8');
383
+ let modified = false;
384
+ try {
385
+ const ast = parse(content, BABEL_CONFIG);
386
+ const importDeclarations = [];
387
+ // First pass: collect all import declarations
388
+ traverse(ast, {
389
+ ImportDeclaration(path) {
390
+ const importSource = path.node.source.value;
391
+ if (importSource.startsWith(packageName)) {
392
+ importDeclarations.push(path.node);
393
+ }
394
+ }
395
+ });
396
+ const importsBySource = new Map();
397
+ const remainingSpecifiers = [];
398
+ for (const declaration of importDeclarations) {
399
+ const _importSource = declaration.source.value;
400
+ const specifiers = declaration.specifiers;
401
+ for (const specifier of specifiers) {
402
+ if (isImportSpecifier(specifier)) {
403
+ const imported = specifier.imported;
404
+ const importName = isIdentifier(imported)
405
+ ? imported.name
406
+ : imported.value;
407
+ const exportInfo = exports.find((e) => e.exports.includes(importName));
408
+ if (exportInfo) {
409
+ if (exportInfo.isIgnored) {
410
+ remainingSpecifiers.push(specifier);
411
+ continue;
412
+ }
413
+ // Check if this is a re-export from an external package
414
+ if (exportInfo.reExports?.[importName]) {
415
+ const reExportSource = exportInfo.reExports[importName];
416
+ if (!reExportSource.startsWith('.')) {
417
+ // Keep the original import from the external package
418
+ const sourcePath = reExportSource;
419
+ if (!importsBySource.has(sourcePath)) {
420
+ importsBySource.set(sourcePath, []);
421
+ }
422
+ importsBySource.get(sourcePath)?.push({
423
+ local: specifier.local,
424
+ imported: specifier.imported
425
+ });
426
+ modified = true;
427
+ continue;
428
+ }
429
+ }
430
+ // Then check if it's a direct export from index.ts
431
+ if (exportInfo.source === 'src/index.ts' &&
432
+ exportInfo.exports.includes(importName) &&
433
+ !exportInfo.reExports?.[importName]) {
434
+ // Handle entities that are exported as default
435
+ const isDefaultExportedEntity = exportInfo.defaultExportNames?.includes(importName);
436
+ if (isDefaultExportedEntity) {
437
+ // For entities that are exported as default, import directly from the package
438
+ const sourcePath = packageName;
439
+ if (!importsBySource.has(sourcePath)) {
440
+ importsBySource.set(sourcePath, []);
441
+ }
442
+ importsBySource.get(sourcePath)?.push({
443
+ local: specifier.local,
444
+ imported: specifier.imported
445
+ });
446
+ modified = true;
447
+ continue;
448
+ }
449
+ // Check if this is a named export or default export
450
+ if (importName !== 'default') {
451
+ // For each named export from index.ts, create a separate import source path
452
+ const sourcePath = includeExtension
453
+ ? `${packageName}/${exportInfo.source}`
454
+ : `${packageName}/${exportInfo.source.replace(/\.[^/.]+$/, '')}`;
455
+ if (!importsBySource.has(sourcePath)) {
456
+ importsBySource.set(sourcePath, []);
457
+ }
458
+ importsBySource.get(sourcePath)?.push({
459
+ local: specifier.local,
460
+ imported: specifier.imported
461
+ });
462
+ modified = true;
463
+ continue;
464
+ }
465
+ }
466
+ // Find the best source file for this export
467
+ const exportFiles = exportInfo.exportFiles?.[importName] || [];
468
+ let bestSourceFile = exportFiles[0]; // Default to first file if no better option
469
+ // Prefer main source files over auxiliary files
470
+ if (exportFiles.length > 1) {
471
+ // Remove story files, test files, and other auxiliary files from consideration
472
+ const mainFiles = exportFiles.filter((file) => !file.includes('.stories.') &&
473
+ !file.includes('.test.') &&
474
+ !file.includes('.spec.') &&
475
+ !file.includes('.stories/') &&
476
+ !file.includes('.test/') &&
477
+ !file.includes('.spec/'));
478
+ if (mainFiles.length > 0) {
479
+ bestSourceFile = mainFiles[0];
480
+ }
481
+ }
482
+ if (bestSourceFile) {
483
+ const sourcePath = includeExtension
484
+ ? `${packageName}/${bestSourceFile}`
485
+ : `${packageName}/${bestSourceFile.replace(/\.[^/.]+$/, '')}`;
486
+ // Check if this import is aliased and if we already have the original import
487
+ const isAliased = specifier.local.name !== importName;
488
+ const hasOriginalImport = Array.from(importsBySource.values()).some((specs) => specs.some((spec) => spec.imported &&
489
+ isIdentifier(spec.imported) &&
490
+ spec.imported.name === importName));
491
+ // Only add the import if it's not aliased or if we don't have the original import yet
492
+ if (!isAliased || !hasOriginalImport) {
493
+ if (!importsBySource.has(sourcePath)) {
494
+ importsBySource.set(sourcePath, []);
495
+ }
496
+ importsBySource.get(sourcePath)?.push({
497
+ local: specifier.local,
498
+ imported: specifier.imported
499
+ });
500
+ modified = true;
501
+ }
502
+ continue;
503
+ }
504
+ remainingSpecifiers.push(specifier);
505
+ }
506
+ else if (isImportDefaultSpecifier(specifier) ||
507
+ isImportNamespaceSpecifier(specifier)) {
508
+ remainingSpecifiers.push(specifier);
509
+ }
510
+ }
511
+ }
512
+ }
513
+ // Second pass: update the AST with new imports
514
+ traverse(ast, {
515
+ ImportDeclaration(path) {
516
+ const importSource = path.node.source.value;
517
+ if (importSource.startsWith(packageName)) {
518
+ // Remove the original import declaration
519
+ path.remove();
520
+ }
521
+ }
522
+ });
523
+ // Add new import declarations
524
+ const newImports = [];
525
+ for (const [source, specifiers] of importsBySource) {
526
+ if (specifiers.length > 0) {
527
+ newImports.push(importDeclaration(specifiers.map(({ local, imported }) => importSpecifier(local, imported)), stringLiteral(source)));
528
+ if (_stats) {
529
+ _stats.importsMigrated += specifiers.length;
530
+ }
531
+ }
532
+ }
533
+ // Add remaining specifiers if any
534
+ if (remainingSpecifiers.length > 0) {
535
+ newImports.push(importDeclaration(remainingSpecifiers, stringLiteral(packageName)));
536
+ }
537
+ // Add all new imports at the top of the file
538
+ if (newImports.length > 0) {
539
+ ast.program.body.unshift(...newImports);
540
+ modified = true;
541
+ }
542
+ if (modified) {
543
+ // Write changes back to file
544
+ const output = generate(ast, {
545
+ // To avoid removing spaces in code
546
+ retainLines: true,
547
+ retainFunctionParens: true
548
+ }, content).code;
549
+ writeFileSync(filePath, output);
550
+ console.log(`Writing changes to ${filePath}`);
551
+ if (_stats) {
552
+ _stats.importsUpdated++;
553
+ }
554
+ }
555
+ else if (_stats) {
556
+ _stats.noChangesNeeded++;
557
+ }
558
+ }
559
+ catch (error) {
560
+ console.error(`Error updating imports in ${filePath}:`, error);
561
+ }
562
+ }
563
+ /**
564
+ * Main migration function that orchestrates the barrel file migration process
565
+ *
566
+ * Process flow:
567
+ * 1. Finds all source packages matching the glob pattern
568
+ * 2. For each package:
569
+ * - Reads package.json to get package name and configuration
570
+ * - Scans source package for all exports (named and default)
571
+ * - Finds all files in the monorepo that import from the package
572
+ * - Updates each import to point directly to source files
573
+ *
574
+ * @param {Options} options - Migration configuration options
575
+ * @returns {Promise<void>}
576
+ */
577
+ export async function migrateBarrelImports(options) {
578
+ const { sourcePath, targetPath, includeExtension = true } = options;
579
+ // Track migration statistics
580
+ const stats = {
581
+ sourcePackagesFound: 0,
582
+ sourcePackagesProcessed: 0,
583
+ sourcePackagesSkipped: 0,
584
+ sourceFilesFound: 0,
585
+ sourceFilesWithExports: 0,
586
+ sourceFilesSkipped: 0,
587
+ exportsFound: 0,
588
+ targetFilesFound: 0,
589
+ targetFilesProcessed: 0,
590
+ importsUpdated: 0,
591
+ noChangesNeeded: 0,
592
+ targetFilesSkipped: 0,
593
+ importsMigrated: 0
594
+ };
595
+ // Track warnings
596
+ const warnings = [];
597
+ try {
598
+ // Find source packages
599
+ const sourcePackages = await findSourcePackages(sourcePath);
600
+ stats.sourcePackagesFound = sourcePackages.length;
601
+ for (const packagePath of sourcePackages) {
602
+ console.log(`\nProcessing package: ${packagePath}`);
603
+ // Find exports in source package
604
+ const exports = await findExports({ packagePath, stats });
605
+ stats.exportsFound = exports.reduce((total, info) => total + info.exports.length, 0);
606
+ stats.sourceFilesWithExports = exports.length;
607
+ // Find files that import from this package
608
+ const packageName = await getPackageName(packagePath);
609
+ const targetFiles = await findImports({ packageName, targetPath, stats });
610
+ stats.targetFilesFound = targetFiles.length;
611
+ // Update imports in target files
612
+ for (const filePath of targetFiles) {
613
+ stats.targetFilesProcessed++;
614
+ await updateImports({
615
+ filePath,
616
+ packageName,
617
+ exports,
618
+ includeExtension,
619
+ warnings,
620
+ stats
621
+ });
622
+ }
623
+ stats.sourcePackagesProcessed++;
624
+ }
625
+ // Print migration summary
626
+ console.log('\nMigration Summary');
627
+ console.log(`Source packages found: ${stats.sourcePackagesFound}`);
628
+ console.log(`Source packages processed: ${stats.sourcePackagesProcessed}`);
629
+ console.log(`Source packages skipped: ${stats.sourcePackagesSkipped}`);
630
+ console.log(`Source files found: ${stats.sourceFilesFound}`);
631
+ console.log(`Source files with exports: ${stats.sourceFilesWithExports}`);
632
+ console.log(`Source files skipped: ${stats.sourceFilesSkipped}`);
633
+ console.log(`Exports found: ${stats.exportsFound}`);
634
+ console.log(`Target files found: ${stats.targetFilesFound}`);
635
+ console.log(`Target files processed: ${stats.targetFilesProcessed}`);
636
+ console.log(`Target files with imports updated: ${stats.importsUpdated}`);
637
+ console.log(`Target files with no changes needed: ${stats.noChangesNeeded}`);
638
+ console.log(`Target files skipped: ${stats.targetFilesSkipped}`);
639
+ console.log(`Total imports migrated: ${stats.importsMigrated}`);
640
+ if (warnings.length > 0) {
641
+ console.log('\nWarnings:');
642
+ warnings.forEach((warning) => console.log(` - ${warning}`));
643
+ }
644
+ }
645
+ catch (error) {
646
+ console.error('Error during migration:', error);
647
+ throw error;
648
+ }
649
+ }
650
+ /**
651
+ * Gets the package name from package.json
652
+ *
653
+ * @param {string} packagePath - Path to the package directory
654
+ * @returns {Promise<string>} Package name
655
+ */
656
+ async function getPackageName(packagePath) {
657
+ const packageJsonPath = path.join(packagePath, 'package.json');
658
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
659
+ return packageJson.name;
660
+ }
661
+ /**
662
+ * Finds all source packages in the given path
663
+ *
664
+ * @param {string} sourcePath - Path to search for source packages
665
+ * @returns {Promise<string[]>} Array of package paths
666
+ */
667
+ async function findSourcePackages(sourcePath) {
668
+ // Use a local variable instead of reassigning the parameter
669
+ const resolvedPath = path.isAbsolute(sourcePath)
670
+ ? path.resolve(sourcePath)
671
+ : path.join(process.cwd(), sourcePath);
672
+ console.log(`Looking for source packages in: ${resolvedPath}`);
673
+ const packageJsonFiles = await fg('{package.json,**/package.json}', {
674
+ cwd: resolvedPath,
675
+ ignore: ['**/node_modules/**', '**/dist/**', '**/build/**'],
676
+ absolute: true
677
+ });
678
+ console.log(`Found ${packageJsonFiles.length} package.json files:`);
679
+ packageJsonFiles.forEach((file) => console.log(` - ${file}`));
680
+ return packageJsonFiles.map(path.dirname);
681
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Configuration options for the migration process
3
+ * @property {string} sourcePath - Glob pattern for source packages to migrate
4
+ * @property {string} targetPath - Path to the monorepo root to search for imports
5
+ * @property {string[]} ignoreSourceFiles - Patterns to ignore when scanning source files
6
+ * @property {string[]} ignoreTargetFiles - Patterns to ignore when scanning target files
7
+ * @property {boolean} [includeExtension] - Whether to include file extensions in imports
8
+ */
9
+ export type Options = {
10
+ sourcePath: string;
11
+ targetPath: string;
12
+ ignoreSourceFiles: string[];
13
+ ignoreTargetFiles: string[];
14
+ includeExtension?: boolean;
15
+ };
16
+ export declare const defaultOptions: Omit<Options, 'sourcePath'>;
17
+ //# sourceMappingURL=options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,MAAM,OAAO,GAAG;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,iBAAiB,EAAE,MAAM,EAAE,CAAA;IAC3B,iBAAiB,EAAE,MAAM,EAAE,CAAA;IAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC1B,CAAA;AAED,eAAO,MAAM,cAAc,EAAE,IAAI,CAAC,OAAO,EAAE,YAAY,CAKtD,CAAA"}
@@ -0,0 +1,6 @@
1
+ export const defaultOptions = {
2
+ targetPath: '.',
3
+ ignoreSourceFiles: [],
4
+ ignoreTargetFiles: [],
5
+ includeExtension: false
6
+ };
package/package.json CHANGED
@@ -1,88 +1,90 @@
1
1
  {
2
- "name": "migrate-barrel-imports",
3
- "version": "1.5.1",
4
- "description": "A CLI tool to migrate barrel imports in JavaScript/TypeScript monorepos",
5
- "files": [
6
- "dist/"
7
- ],
8
- "main": "./dist/index.js",
9
- "targets": {
10
- "main": {
11
- "optimize": true
12
- }
13
- },
14
- "keywords": [
15
- "typescript",
16
- "javascript",
17
- "monorepo",
18
- "cli",
19
- "tool",
20
- "migrate",
21
- "barrel",
22
- "import",
23
- "imports"
24
- ],
25
- "repository": {
26
- "type": "git",
27
- "url": "git+https://github.com/brandhaug/migrate-barrel-imports.git"
28
- },
29
- "bugs": {
30
- "url": "https://github.com/brandhaug/migrate-barrel-imports/issues"
31
- },
32
- "homepage": "https://github.com/brandhaug/migrate-barrel-imports#readme",
33
- "type": "module",
34
- "bin": {
35
- "migrate-barrel-imports": "dist/index.js"
36
- },
37
- "scripts": {
38
- "build": "parcel build src/index.ts --no-source-maps",
39
- "start": "node dist/index.js",
40
- "lint": "biome lint",
41
- "check-write": "biome check --write",
42
- "typecheck": "tsc",
43
- "prepare": "husky",
44
- "test": "vitest",
45
- "validate": "npm run lint && npm run typecheck && npm run test"
46
- },
47
- "dependencies": {
48
- "@babel/generator": "7.26.10",
49
- "@babel/parser": "7.26.10",
50
- "@babel/traverse": "7.26.10",
51
- "@babel/types": "7.26.10",
52
- "commander": "13.1.0",
53
- "fast-glob": "3.3.3",
54
- "micromatch": "4.0.8",
55
- "ts-morph": "25.0.1"
56
- },
57
- "devDependencies": {
58
- "@biomejs/biome": "1.9.4",
59
- "@commitlint/cli": "19.8.0",
60
- "@commitlint/config-conventional": "19.8.0",
61
- "@semantic-release/changelog": "6.0.3",
62
- "@types/babel__generator": "7.6.8",
63
- "@types/babel__traverse": "7.20.6",
64
- "@types/micromatch": "4.0.9",
65
- "execa": "9.5.2",
66
- "husky": "9.1.7",
67
- "lint-staged": "15.5.0",
68
- "parcel": "2.14.1",
69
- "semantic-release": "24.2.3",
70
- "tsc-files": "1.1.4",
71
- "tsx": "4.19.3",
72
- "typescript": "5.8.2",
73
- "vitest": "3.0.9"
74
- },
75
- "author": "Martin Brandhaug",
76
- "license": "MIT",
77
- "lint-staged": {
78
- "*.{ts,json}": [
79
- "biome check --write"
80
- ],
81
- "*.ts": [
82
- "tsc-files"
83
- ]
84
- },
85
- "engines": {
86
- "node": ">=20"
87
- }
2
+ "name": "migrate-barrel-imports",
3
+ "version": "1.7.0",
4
+ "description": "A CLI tool to migrate barrel imports in JavaScript/TypeScript monorepos",
5
+ "files": [
6
+ "dist/"
7
+ ],
8
+ "main": "./dist/index.js",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "keywords": [
18
+ "typescript",
19
+ "javascript",
20
+ "monorepo",
21
+ "cli",
22
+ "tool",
23
+ "migrate",
24
+ "barrel",
25
+ "import",
26
+ "imports"
27
+ ],
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/brandhaug/migrate-barrel-imports.git"
31
+ },
32
+ "bugs": {
33
+ "url": "https://github.com/brandhaug/migrate-barrel-imports/issues"
34
+ },
35
+ "homepage": "https://github.com/brandhaug/migrate-barrel-imports#readme",
36
+ "type": "module",
37
+ "bin": {
38
+ "migrate-barrel-imports": "dist/index.js"
39
+ },
40
+ "scripts": {
41
+ "build": "tsc",
42
+ "start": "node dist/index.js",
43
+ "lint": "biome lint",
44
+ "check-write": "biome check --write",
45
+ "typecheck": "tsc",
46
+ "prepare": "husky",
47
+ "test": "vitest",
48
+ "validate": "npm run lint && npm run typecheck && npm run test"
49
+ },
50
+ "dependencies": {
51
+ "@babel/generator": "7.28.0",
52
+ "@babel/parser": "7.28.0",
53
+ "@babel/traverse": "7.28.0",
54
+ "@babel/types": "7.28.2",
55
+ "commander": "14.0.0",
56
+ "fast-glob": "3.3.3",
57
+ "micromatch": "4.0.8",
58
+ "ts-morph": "26.0.0"
59
+ },
60
+ "devDependencies": {
61
+ "@biomejs/biome": "2.1.4",
62
+ "@commitlint/cli": "19.8.1",
63
+ "@commitlint/config-conventional": "19.8.1",
64
+ "@semantic-release/changelog": "6.0.3",
65
+ "@types/babel__generator": "7.27.0",
66
+ "@types/babel__traverse": "7.28.0",
67
+ "@types/micromatch": "4.0.9",
68
+ "execa": "9.6.0",
69
+ "husky": "9.1.7",
70
+ "lint-staged": "16.1.5",
71
+ "semantic-release": "24.2.7",
72
+ "tsc-files": "1.1.4",
73
+ "tsx": "4.20.3",
74
+ "typescript": "5.9.2",
75
+ "vitest": "3.2.4"
76
+ },
77
+ "author": "Martin Brandhaug",
78
+ "license": "MIT",
79
+ "lint-staged": {
80
+ "*.{ts,json}": [
81
+ "biome check --write"
82
+ ],
83
+ "*.ts": [
84
+ "tsc-files"
85
+ ]
86
+ },
87
+ "engines": {
88
+ "node": ">=20"
89
+ }
88
90
  }