makeFileReader = (fileName, { fs, path }) => {
  const make = there => makeFileReader(there, { fs, path });

  // fs.promises.exists isn't implemented in Node.js apparently because it's pure
  // sugar.
  const exists = fn =>
    fs.promises.stat(fn).then(
      () => true,
      e => {
        if (e.code === 'ENOENT') {
          return false;
        }
        throw e;
      },
    );

  const readText = async () => {
    const promise = mutex;
    let release = Function.prototype;
    mutex = new Promise(resolve => {
      release = resolve;
    });
    await promise;
    try {
      return await fs.promises.readFile(fileName, 'utf-8');
    } finally {
      release(undefined);
    }
  };

  const maybeReadText = () =>
    readText().catch(error => {
      if (
        error.message.startsWith('ENOENT: ') ||
        error.message.startsWith('EISDIR: ')
      ) {
        return undefined;
      }
      throw error;
    });

  return harden({
    toString: () => fileName,
    readText,
    maybeReadText,
    neighbor: ref => make(path.resolve(fileName, ref)),
    stat: () => fs.promises.stat(fileName),
    absolute: () => path.normalize(fileName),
    relative: there => path.relative(fileName, there),
    exists: () => exists(fileName),
  });
}
@param {string} fileName
@param {{
  fs: {
    promises: Pick<import('fs/promises'),'readFile' | 'stat'>
  },
  path: Pick<import('path'), 'resolve' | 'relative' | 'normalize'>,
}} powers