コンテンツにスキップ

$fs

$fsは、ファイルシステムへの読み書きを行うAPIです。

const exists = await $fs.exists(`${$env.DIR}\\targetFile.txt`);

操作方法は2種類あります。

方式概要
直接操作実ファイルへ即時に読み書きする非同期関数群です。
トランザクション操作仮想FS上に操作を積み、確認後に一括コミットします。

直接操作APIは、実行時に実ファイルシステムへ操作を反映します。すべて非同期関数であり、呼び出しにはawaitが必要です。

パスを引数に取るメソッドは、絶対パスを前提にします。相対パスを渡すと実行時エラーになります。

読み取り、作成、削除、リネームなどの処理は呼び出した時点で実ファイルシステムへ反映されます。変更内容を事前確認してから適用したい場合は、直接操作ではなくトランザクション操作を使います。

type FileStat = {
size: number;
isFile: boolean;
isDir: boolean;
createdAt?: number;
modifiedAt?: number;
};
メソッド説明
exists(path)指定パスが存在するか確認します。
glob(pattern)パターンに一致するパス配列を取得します。
stat(path)サイズ、作成日、ファイル/ディレクトリ判定などを取得します。
readDir(dir)ディレクトリ配下の要素一覧を取得します。
メソッド説明
readText(filePath, encoding?)テキストデータを読み込みます。encoding省略時はutf8です。
saveText(filePath, content)テキストファイルを作成または上書き保存します。
copyFile(src, dest)ファイルをバイナリコピーします。

ディレクトリ・削除・リネーム

Section titled “ディレクトリ・削除・リネーム”
メソッド説明
makeDir(dirPath)ディレクトリを作成します。既存ディレクトリの場合は何もしません。
deleteFile(filePath)ファイルを削除します。
deleteDir(dirPath)空ディレクトリを削除します。
renameFile(targetFilePath, newFileName)ファイル名を変更します。ディレクトリ移動はできません。
renameDir(targetDirPath, newDirName)ディレクトリ名を変更します。
const content = await $fs.readText(`${$env.ROOT}\\file.txt`, 'utf8');
const exists = await $fs.exists(`${$env.OUTPUT}\\result.csv`);
const entries = await $fs.readDir($env.SRC_DIR);
for (const entry of entries) {
$println(`${entry.name} (${entry.isDir ? 'DIR' : 'FILE'})`);
}

トランザクションAPIは、実ファイルへ直接書き込む代わりに、仮想FS上へ操作を積みます。

workの実行後にトランザクションダイアログが表示され、変更内容を確認してからコミットできます。

const tx = $fs.useTransaction();

useTransaction()は、トランザクションの開始ではなく、トランザクションを利用することの宣言です。1つのwork実行につき1回のみ呼び出せます。

useTransaction()で取得したハンドルに対して書き込み系APIを呼び出すと、実ファイルシステムではなく仮想FSへ操作オーダーが積まれます。書き込み系APIはオーダーを積むだけなので、awaitなしの同期処理として書けます。

一方、openText()は実ファイルを読み込むため非同期です。呼び出すと、対象パスはチェックアウト状態になり、更新、削除、リネームなどの変更操作は取得したtoken経由で行います。

関数説明
makeDir(dirPath)ディレクトリ作成を予約します。
deleteDir(dirPath)空ディレクトリ削除を予約します。
openText(filePath, encoding?)テキストを読み込み、チェックアウト状態にします。
updateText(token, content)openTextで得たtokenを使って更新オーダーを積みます。
saveText(filePath, content)新規テキストファイル作成を予約します。
copyFile(from, dest)ファイルコピーを予約します。
copyFileByToken(token, dest)チェックアウト中のファイルをtoken経由でコピーします。
deleteFile(filePath)ファイル削除を予約します。
deleteFileByToken(token)チェックアウト中のファイルをtoken経由で削除します。
renameFile(targetFilePath, newFileName)ファイル名変更を予約します。
renameFileByToken(token, newName)チェックアウト中のファイル名変更を予約します。

各メソッドは呼び出し時点でVFS上の論理検証を行います。不正な操作や競合が見つかった場合は、その場でエラーになります。

たとえば、openText()で開いたファイルに対して、ファイルパス指定のdeleteFile()renameFile()を直接呼び出すとランタイムエラーになります。チェックアウト済みのファイルは、updateText(token, ...)deleteFileByToken(token)renameFileByToken(token, ...)のようにtoken経由で操作してください。

エラーになる条件の詳細は、VFS上の論理チェックを参照してください。

const tx = $fs.useTransaction();
const { token, content } = await tx.openText(`${$env.DIR}\\target.txt`, 'utf8');
const newContent = content.replace(/old/g, 'new');
tx.updateText(token, newContent);
tx.saveText(`${$env.DIR}\\newFile.txt`, 'hello');

この段階では、実ファイルはまだ変更されません。workの実行が完了すると、積まれた操作を確認するためのトランザクションダイアログが表示されます。

トランザクション操作では、スクリプト実行中に仮想FS上へ操作を積みながら、操作同士の整合性を検証します。

ここでのチェックは、実ファイルシステムへの書き込み可否ではありません。実際のディレクトリ存在、権限、更新対象ファイルの有無などは、トランザクションダイアログのverifyで確認します。

VFS上の論理チェックは、同じトランザクション内で矛盾した操作を積まないためのものです。違反した場合は、work実行中にランタイムエラーになります。

useTransaction()は、1つのwork実行につき1回だけ呼び出せます。2回以上呼び出すとエラーになります。

トランザクション内で一度触れたパスには、履歴が残ります。

同じパスに対して、矛盾する操作を重ねることはできません。たとえば、同一パスに対して次のような操作はエラーになります。

  • 既に作成予定のファイルを、もう一度saveText()する
  • openText()済みのファイルに対して、saveText()で新規作成しようとする
  • 削除予定のファイルを、もう一度作成しようとする
  • 既にトランザクション内で履歴があるパスを、再度openText()する

openText()は既存ファイルをチェックアウトする操作です。チェックアウト済みファイルの変更は、ファイルパスではなくtoken経由で行います。

openText()は、ファイル本文とtokenを返します。

const { token, content } = await tx.openText(filePath, 'utf8');

このtokenは、チェックアウトしたファイルを変更するためのハンドルです。openText()したファイルに対して、パス指定のdeleteFile()renameFile()copyFile()を使うとエラーになります。

チェックアウト済みファイルは、次のようにtoken経由で操作します。

tx.updateText(token, nextContent);
tx.renameFileByToken(token, 'renamed.txt');
tx.deleteFileByToken(token);
tx.copyFileByToken(token, destPath);

ただし、変更予定があるtokenはcopyFileByToken()できません。コピーは、未変更のチェックアウトファイルに対してだけ行えます。

ファイル操作では、同じトランザクション内で矛盾する状態を作れません。

操作エラーになる例
saveText()同じパスが既に作成済み、open済み、削除予定、予約済みの場合
updateText()無効なtoken、本文を持たないtoken、削除済みファイルの場合
deleteFile()既に削除済み、または同一トランザクション内で新規作成したファイルの場合
deleteFileByToken()無効なtoken、既に削除済み、または新規作成ファイルの場合

新規作成したファイルを同じトランザクション内で削除する、削除予定のファイルを再作成する、といった操作はできません。必要な場合は、workの処理を見直し、最終的に必要な操作だけを積むようにしてください。

renameFile()renameFileByToken()は、同じディレクトリ内でのファイル名変更だけを扱います。新しい名前に/\を含めてディレクトリを移動することはできません。

リネームでは、次のような場合にエラーになります。

  • 新しい名前が現在の名前と同じ
  • 新しい名前にパス区切り文字が含まれる
  • 新規作成したファイルをリネームしようとする
  • 削除予定のファイルをリネームしようとする
  • 同じファイルを複数回リネームしようとする
  • リネーム先が既に予約済み
  • リネーム先が既にトランザクション内で触られている
  • リネーム先が別のリネーム操作で使われている

copyFile()copyFileByToken()では、コピー先パスが予約されます。

コピー先が既に予約済み、または同じトランザクション内で使用済みの場合はエラーになります。これにより、複数の操作が同じ出力先へ書き込むことを防ぎます。

チェックアウト済みファイルをコピーする場合は、copyFileByToken()を使います。ただし、そのtokenに更新や削除などの変更予定がある場合はコピーできません。

ディレクトリ操作も、ファイル操作との矛盾をチェックします。

makeDir()は冪等です。同じディレクトリが既に作成予定なら、追加操作はスキップされます。ただし、削除予定のディレクトリを作成し直すことはできません。

また、ファイルとして扱われているパスの配下にディレクトリを作ることはできません。

deleteDir()では、対象ディレクトリ配下にファイル操作やディレクトリ操作が積まれている場合にエラーになります。たとえば、あるディレクトリ配下のファイルを作成・更新・削除する予定がある状態で、その親ディレクトリを削除することはできません。

仮想ディレクトリ配下への操作

Section titled “仮想ディレクトリ配下への操作”

トランザクション内で作成予定のディレクトリ配下には、saveText()copyFile()でファイルを配置できます。この場合、実ファイルシステム上にはまだ存在しないディレクトリであっても、VFS上では作成予定のディレクトリ配下として扱われます。

一方、削除予定のディレクトリ配下にファイルを作成・コピーすることはできません。

トランザクションダイアログ

トランザクションAPIで書き込みオーダーを積んでworkが完了すると、トランザクションダイアログが表示されます。

フェーズ説明
confirm仮想FS上のオーダー内容を目視確認します。ファイル名をクリックすると詳細を確認できます。
verify実ファイルシステム上で変更可能か検証します。
commitすべての変更が実ファイルへ適用された状態です。

書き込み内容確認 更新内容確認 書き込み完了

confirmでは、仮想FSに積まれたオーダー内容を確認します。ファイル名をクリックすると、書き込み内容や更新差分を確認できます。問題がなければ、右下のコミットボタンでverifyへ進みます。

verifyでは、実ファイルシステムに対して変更可能かを検証します。たとえば、ファイル作成先のディレクトリが存在しない、更新対象ファイルが存在しない、書き込み権限がない、といった問題があればここでエラーになります。verifyでエラーが1件でも発生した場合、実ファイルへの変更処理は行われません。

エラーがなければ、実ファイルシステムへの変更が実行され、commitになります。

問題トランザクションによる解決
実装ミスで意図しないファイルが変更されるconfirmフェーズで目視確認できます。
途中でエラーが発生して中途半端な状態になるverifyで全チェック後に一括適用するため、部分的な変更を避けられます。
大量変更の内容を実行前に確認できないダイアログで変更一覧と詳細を確認できます。

ランタイム中は仮想FSで論理整合性を検証し、work完了後のトランザクションダイアログでは目視確認と実ファイルシステムへの変更可否を検証します。このワークフローにより、意図しないファイル変更や、途中まで変更されて中途半端な状態で止まる問題を避けやすくなります。