Skip to content

Instantly share code, notes, and snippets.

@toriwasa
Created September 13, 2022 13:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save toriwasa/8b57807c1ca630fde23d83a98f541280 to your computer and use it in GitHub Desktop.
Save toriwasa/8b57807c1ca630fde23d83a98f541280 to your computer and use it in GitHub Desktop.
Node.js + Typescriptのみで再帰コピーを実行するスクリプト
// 引数を元にコピー元ディレクトリからコピー先ディレクトリに再帰的にファイルをコピーする
// ビルド対象でない静的ファイルをビルド先ディレクトリに単純にコピーするためのスクリプト
// npm script で呼び出すことで利用する
// Usage: ts-node ./copy_assets.ts ./srcDirPath ./destDirPath
import {
readdirSync,
lstatSync,
existsSync,
mkdirSync,
copyFileSync,
} from 'fs';
import { basename } from 'path';
/**
* ファイル/ディレクトリのパスとディレクトリか否かを管理する型
*/
type fileStatus = {
filePath: string;
isDirectory: boolean;
};
/**
* コピー元ファイルパスとコピー先ファイルパス、ディレクトリか否かを管理する型
*/
type copyFileSpec = {
srcFilePath: string;
destFilePath: string;
isDirectory: boolean;
};
/**
* 指定したディレクトリパス配下のファイルリストを再帰的に取得する
* @param targetDirPath ファイルリストを取得したいディレクトリのパス
* @returns ファイルリスト
*/
function getFileListRecursively(targetDirPath: string): fileStatus[] {
// 指定したパスの ファイル or ディレクトリ のリストを生成する
const targetDirFileNames = readdirSync(targetDirPath);
// 指定したパスに ファイル or ディレクトリ が0の場合: 何もせずに空配列を返却する
if (!targetDirFileNames.length) {
return [];
}
// 指定したパスに ファイル or ディレクトリ がある場合: 指定したパス配下の ファイル or ディレクトリのパス配列を生成する
// -> 指定したパス配下の ファイル or ディレクトリのパス + ディレクトリフラグ の辞書オブジェクト配列 を生成する
const targetDirFileStatusList = targetDirFileNames
.map((fileName) => `${targetDirPath}/${fileName}`)
.map(
(filePath): fileStatus => ({
filePath: filePath,
isDirectory: lstatSync(filePath).isDirectory(),
})
);
// 指定したパスにディレクトリがある場合: 追加で再帰してスタックに積む。各ディレクトリの一番下の階層にたどり着いたらスタックを逆順処理していく
// スタックからファイルステータスリストが次々返却されて来るので、リストの中身を展開して1つのリストにする
const recursiveFileStatusList = targetDirFileStatusList
.filter((fileStatus) => fileStatus.isDirectory)
.flatMap((fileStatus) => getFileListRecursively(fileStatus.filePath));
// 指定したパス配下のファイルステータスリスト + スタックから返却されたファイルステータスリスト を返却する
return targetDirFileStatusList.concat(recursiveFileStatusList);
}
/**
* コピー元ディレクトリからコピー先ディレクトリにファイル/ディレクトリを再帰的にコピーする
* @param srcDirPath コピー元ディレクトリ
* @param destDirPath コピー先ディレクトリ
*/
function copyFilesRecursively(srcDirPath: string, destDirPath: string): void {
// コピー先のディレクトリが存在しない場合作成しておく
if (!existsSync(destDirPath)) {
mkdirSync(destDirPath, { recursive: true });
}
// コピー対象となるファイル・ディレクトリパス一覧を取得する
const targetFileStatusList = getFileListRecursively(srcDirPath);
// コピー元ファイルパスを加工してコピー先ファイルパスを作成後、新たなコピー用情報オブジェクトのリストを生成する
const copyFileSpecList = targetFileStatusList.map(
(fileStatus): copyFileSpec => ({
srcFilePath: fileStatus.filePath,
destFilePath:
`${destDirPath}` +
`${fileStatus.filePath.substring(srcDirPath.length)}`,
isDirectory: fileStatus.isDirectory,
})
);
// コピー対象がディレクトリの場合: 対象ディレクトリが存在しなければ作成する
copyFileSpecList
.filter((fileSpec) => fileSpec.isDirectory)
.filter((fileSpec) => !existsSync(fileSpec.destFilePath))
.map((fileSpec) => mkdirSync(fileSpec.destFilePath, { recursive: true }));
// コピー対象がファイルの場合: コピー元と同じディレクトリ階層構造でファイルをコピーする
copyFileSpecList
.filter((fileSpec) => !fileSpec.isDirectory)
.map((fileSpec) =>
copyFileSync(fileSpec.srcFilePath, fileSpec.destFilePath)
);
}
// 【ここからメイン処理】
// コマンドライン引数を受け取る
// 0: Node.jsのパス
// 1: 実行ファイルのパス
// 2: コピー元ディレクトリ
// 3: コピー先ディレクトリ
// 引数に過不足がある場合エラー終了する
if (process.argv.length !== 4) {
console.log('Usage: ts-node ./copy_assets.ts ./srcDirPath ./destDirPath');
process.exit(1);
}
const [srcDirPath, destDirPath] = process.argv.filter((_value, index) =>
[2, 3].includes(index)
);
// コピー元ディレクトリが存在しない場合エラー終了する
if (!existsSync(srcDirPath)) {
console.log(
`[${basename(__filename)}] ` +
`srcDirPath: ${srcDirPath} が存在しません。引数を確認してください`
);
process.exit(1);
}
console.log(`[${basename(__filename)}] srcDirPath: ${srcDirPath}`);
console.log(`[${basename(__filename)}] destDirPath: ${destDirPath}`);
copyFilesRecursively(srcDirPath, destDirPath);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment