-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6f38a85
commit 2131f7f
Showing
4 changed files
with
409 additions
and
172 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,87 +1,65 @@ | ||
const { readFile, writeFile, access } = require('fs').promises; | ||
const { join, dirname, resolve } = require('path'); | ||
const glob = require('glob-promise'); | ||
const process = require('process'); | ||
const { access } = require('fs').promises; | ||
const { dirname, join, resolve } = require('path'); | ||
|
||
/** | ||
* 非同期の置換処理を行う関数 | ||
* @param {string} str - 置換対象の文字列 | ||
* @param {RegExp} regex - 正規表現 | ||
* @param {(match: string, ...args: any[]) => Promise<string>} asyncFn - 非同期関数 | ||
* @return {Promise<string>} | ||
*/ | ||
async function replaceAsync(str, regex, asyncFn) { | ||
const promises = []; | ||
str.replace(regex, (match, ...args) => { | ||
const promise = asyncFn(match, ...args); | ||
promises.push(promise); | ||
return match; | ||
}); | ||
const data = await Promise.all(promises); | ||
return str.replace(regex, () => data.shift()); | ||
async function checkFileExists(filePath) { | ||
try { | ||
await access(filePath); | ||
return true; | ||
} catch { | ||
return false; | ||
} | ||
} | ||
|
||
const shouldReplaceExtension = (value) => | ||
(value.startsWith('./') || value.startsWith('../')) && | ||
!value.endsWith('.mjs'); | ||
|
||
/** | ||
* 指定されたファイルのimport文を置換する | ||
* @param {string} filePath - ファイルのパス | ||
* @return {Promise<void>} | ||
* | ||
* @param {import('jscodeshift').FileInfo} file | ||
* @param {import('jscodeshift').API} api | ||
*/ | ||
async function replaceImportsInFile(filePath) { | ||
try { | ||
const data = await readFile(filePath, 'utf8'); | ||
const importRegex = | ||
/from\s+['"](\.\/|\.\.\/)(?!.*\.mjs['"])([^'"]+)(['"])/g; | ||
const result = await replaceAsync( | ||
data, | ||
importRegex, | ||
async (_match, p1, p2, p3) => { | ||
const importPath = resolve(dirname(filePath), p1 + p2); | ||
const mjsPath = importPath + '.mjs'; | ||
const indexPath = join(importPath, 'index.mjs'); | ||
return await Promise.allSettled([ | ||
access(mjsPath), | ||
access(indexPath), | ||
]).then((results) => { | ||
const mjsPathResult = results[0]; | ||
const indexPathResult = results[1]; | ||
if (mjsPathResult.status === 'fulfilled') { | ||
return `from '${p1}${p2}.mjs${p3}`; | ||
} else if (indexPathResult.status === 'fulfilled') { | ||
return `from '${p1}${p2}/index.mjs${p3}`; | ||
module.exports = async function transformer(file, api) { | ||
const j = api.jscodeshift; | ||
const root = j(file.source); | ||
await Promise.all( | ||
[ | ||
j.ImportDeclaration, | ||
j.ExportNamedDeclaration, | ||
j.ExportAllDeclaration, | ||
j.ExportDefaultDeclaration, | ||
] | ||
.flatMap((declaration) => root.find(declaration).nodes()) | ||
.flatMap(async (node) => { | ||
const source = node.source; | ||
if (!source) { | ||
return; | ||
} | ||
const value = source.value; | ||
|
||
if (shouldReplaceExtension(value)) { | ||
const importPath = resolve(dirname(file.path), value); | ||
const mjsPath = `${importPath}.mjs`; | ||
const indexPath = join(importPath, 'index.mjs'); | ||
|
||
const mjsExists = await checkFileExists(mjsPath); | ||
const indexExists = await checkFileExists(indexPath); | ||
|
||
if (mjsExists) { | ||
node.source.value = `${value}.mjs`; | ||
} else if (indexExists) { | ||
node.source.value = `${value}/index.mjs`; | ||
} else { | ||
console.error( | ||
`.mjs または /index.mjs が存在しません: ${importPath}`, | ||
); | ||
process.exit(1); | ||
} | ||
}); | ||
}, | ||
); | ||
await writeFile(filePath, result, 'utf8'); | ||
if (data !== result) { | ||
console.log(`置換処理が完了しました: ${filePath}`); | ||
} | ||
} catch (err) { | ||
console.error(`エラーが発生しました: ${filePath}`, err); | ||
process.exit(1); | ||
} | ||
} | ||
} | ||
}), | ||
); | ||
|
||
async function main() { | ||
try { | ||
const directories = await glob(join(__dirname, '../packages/*/dist/**/*.mjs')); | ||
await Promise.all(directories.map(replaceImportsInFile)); | ||
} catch (err) { | ||
console.error('エラーが発生しました', err); | ||
process.exit(1); | ||
} | ||
} | ||
|
||
if (require.main === module) { | ||
main(); | ||
} | ||
|
||
module.exports = { | ||
replaceAsync, | ||
replaceImportsInFile, | ||
return root.toSource(); | ||
}; | ||
|
||
module.exports.checkFileExists = checkFileExists; | ||
module.exports.shouldReplaceExtension = shouldReplaceExtension; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,98 +1,36 @@ | ||
const fs = require('fs').promises; | ||
const { | ||
replaceImportsInFile, | ||
replaceAsync, | ||
checkFileExists, | ||
shouldReplaceExtension, | ||
} = require('./outputExtensionReplace'); | ||
|
||
jest.mock('fs', () => { | ||
const originalModule = jest.requireActual('fs'); | ||
return { | ||
...originalModule, | ||
promises: { | ||
readdir: jest.fn(), | ||
readFile: jest.fn(), | ||
writeFile: jest.fn(), | ||
stat: jest.fn(), | ||
access: jest.fn(), | ||
}, | ||
}; | ||
}); | ||
|
||
describe('replaceImportsInFile', () => { | ||
let originalExit; | ||
let originalConsoleError; | ||
let originalConsoleLog; | ||
|
||
beforeAll(() => { | ||
originalExit = process.exit; | ||
process.exit = jest.fn(); | ||
originalConsoleError = console.error; | ||
console.error = jest.fn(); | ||
originalConsoleLog = console.log; | ||
console.log = jest.fn(); | ||
describe('checkFileExists', () => { | ||
it('should return true if the file exists', async () => { | ||
const result = await checkFileExists(__filename); | ||
expect(result).toBe(true); | ||
}); | ||
|
||
afterAll(() => { | ||
process.exit = originalExit; | ||
console.error = originalConsoleError; | ||
console.log = originalConsoleLog; | ||
}); | ||
|
||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it('should replace imports in a file', async () => { | ||
const filePath = '/file.mjs'; | ||
const fileContent = ` | ||
import React, { forwardRef } from 'react'; | ||
import module from './module'; | ||
import hasIndexDirectory from '../hasIndexDirectory'; | ||
`; | ||
const expectedContent = ` | ||
import React, { forwardRef } from 'react'; | ||
import module from './module.mjs'; | ||
import hasIndexDirectory from '../hasIndexDirectory/index.mjs'; | ||
`; | ||
fs.readFile.mockResolvedValue(fileContent); | ||
fs.writeFile.mockResolvedValue(); | ||
fs.access.mockImplementation((path) => { | ||
if (path.endsWith('module.mjs')) { | ||
return Promise.resolve(); | ||
} else if (path.endsWith('hasIndexDirectory/index.mjs')) { | ||
return Promise.resolve(); | ||
} | ||
return Promise.reject(); | ||
}); | ||
await replaceImportsInFile(filePath); | ||
expect(fs.readFile).toHaveBeenCalledWith(filePath, 'utf8'); | ||
expect(fs.writeFile).toHaveBeenCalledWith( | ||
filePath, | ||
expectedContent, | ||
'utf8', | ||
); | ||
expect(console.log).toHaveBeenCalledWith(`置換処理が完了しました: ${filePath}`); | ||
}); | ||
|
||
it('should exit the process if an error occurs', async () => { | ||
const filePath = '/file.mjs'; | ||
fs.readFile.mockRejectedValue(new Error('error')); | ||
await replaceImportsInFile(filePath); | ||
expect(process.exit).toHaveBeenCalledWith(1); | ||
expect(console.error).toHaveBeenCalledWith( | ||
`エラーが発生しました: ${filePath}`, | ||
new Error('error'), | ||
); | ||
it('should return false if the file does not exist', async () => { | ||
const filePath = 'non_existent_file.dummy'; | ||
const result = await checkFileExists(filePath); | ||
expect(result).toBe(false); | ||
}); | ||
}); | ||
|
||
describe('replaceAsync', () => { | ||
it('should replace a string asynchronously', async () => { | ||
const result = await replaceAsync( | ||
"import module from './module", | ||
/.\/module/g, | ||
async () => './module.mjs', | ||
); | ||
expect(result).toBe("import module from './module.mjs"); | ||
describe('shouldReplaceExtension', () => { | ||
it('should return true if the value starts with ./ or ../ and does not end with .mjs', () => { | ||
[ | ||
{ value: './foo', expected: true }, | ||
{ value: '../foo', expected: true }, | ||
{ value: './foo/bar', expected: true }, | ||
{ value: '../foo/bar', expected: true }, | ||
{ value: './foo.mjs', expected: false }, | ||
{ value: '../foo.mjs', expected: false }, | ||
{ value: './foo/bar.mjs', expected: false }, | ||
{ value: '../foo/bar.mjs', expected: false }, | ||
{ value: 'react', expected: false }, | ||
].forEach(({ value, expected }) => { | ||
const result = shouldReplaceExtension(value); | ||
expect(result).toBe(expected); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.