ReactiveX/rxjs · error · Error

outputRoot is required when write is enabled.

Error message

outputRoot is required when write is enabled.

What it means

migrateTestFiles is the one-call convenience wrapper: it plans and (when options.write is true) applies the plan. Writing requires a concrete destination, so the very first thing the function checks is that outputRoot is provided whenever write is enabled; without it the migrator would have to write files alongside their sources with no way to control the destination.

Solutions

  1. Add outputRoot pointing at the destination directory for the migrated files
  2. If you only wanted a dry run, remove write:true instead of adding outputRoot
  3. Derive outputRoot from sourceRoot explicitly (e.g. path.join(sourceRoot, 'migrated')) if you want a predictable default

Example fix

// before
await migrateTestFiles({ files, sourceRoot, write: true });

// after
await migrateTestFiles({ files, sourceRoot, outputRoot: path.join(sourceRoot, 'migrated'), write: true });
Defensive patterns

Strategy: type-guard

Validate before calling

if (options.write && !options.outputRoot) {
  throw new Error('outputRoot is required when write is enabled.'); // fail fast with your own message
}

Type guard

interface WriteOptions { write?: boolean; outputRoot?: string }
function isWritableConfig(o: WriteOptions): o is WriteOptions & { write: true; outputRoot: string } {
  return o.write === true && typeof o.outputRoot === 'string' && o.outputRoot.length > 0;
}

Prevention

When it happens

Trigger: Calling migrateTestFiles({ write: true }) without an outputRoot property (or with it set to undefined/empty).

Common situations: Adapting a dry-run script (which legitimately omits outputRoot) to actually write by adding write:true; typoing the option name; assuming outputRoot defaults to sourceRoot when writing.

Understand the failure class

Background: "Must pass :limit option" / "Missing required option" — required option errors explained — this error's family across 41 libraries.

Related errors


AI-assisted analysis of ReactiveX/rxjs@54796b38a5 (2026-08-28). Data as JSON: /api/errors/94e145dd83a9aa47. Report an issue: GitHub.

Appendix: source

Thrown at packages/migrate/src/node.ts:168

    canonicalOutputs.add(canonicalOutputPath);
    await assertWritableOutput(resolvedOutputPath, options.overwrite ?? false);
  }
}

async function assertWritableOutput(outputPath: string, overwrite: boolean): Promise<void> {
  try {
    const outputStats = await lstat(outputPath);
    if (outputStats.isSymbolicLink()) throw new Error(`Refusing to overwrite a symbolic link: ${outputPath}`);
    if (!outputStats.isFile()) throw new Error(`Output path is not a regular file: ${outputPath}`);
    if (!overwrite) throw new Error(`Output path already exists; enable overwrite explicitly: ${outputPath}`);
  } catch (error: unknown) {
    if (!isMissingPathError(error)) throw error;
  }
}

export async function migrateTestFiles(options: MigrateFilesOptions): Promise<readonly MigratedFile[]> {
  if (options.write && !options.outputRoot) {
    throw new Error('outputRoot is required when write is enabled.');
  }
  const plan = await planMigrationFiles(options);
  return options.write ? applyMigrationPlan(plan, { overwrite: options.overwrite }) : plan.files;
}

function safeOutputPath(outputRoot: string, outputName: string): string {
  if (!outputName || outputName === '.' || isAbsolute(outputName)) {
    throw new Error(`Output name must be a non-empty relative path: ${outputName || '<empty>'}`);
  }
  const outputPath = resolve(outputRoot, outputName);
  if (outputPath === outputRoot) throw new Error(`Output name must identify a file below outputRoot: ${outputName}`);
  assertContained(outputRoot, outputPath, `Output path is outside outputRoot: ${outputName}`);
  return outputPath;
}

function assertContained(root: string, candidate: string, message: string): void {
  const localPath = relative(root, candidate);
  if (localPath === '' || (localPath !== '..' && !localPath.startsWith(`..${sep}`) && !isAbsolute(localPath))) return;

View on GitHub (pinned to 54796b38a5)