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.
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)
Solutions
- Add outputRoot pointing at the destination directory for the migrated files
- If you only wanted a dry run, remove write:true instead of adding outputRoot
- 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
- Centralize migrateTestFiles option construction in one helper that always sets outputRoot when write is true
- Treat missing outputRoot as a dry-run-only configuration
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.
Related errors
- Output name must be a non-empty relative path: ${outputName
- Output name must identify a file below outputRoot: ${outputN
- Duplicate output path: ${outputLocalName}
- Migration result was refused for source: ${sourcePath}
- Output path must identify a file below outputRoot: ${outputP
AI-assisted analysis of ReactiveX/rxjs@54796b38a5 (2026-08-28).
Data as JSON: /api/errors/94e145dd83a9aa47.
Report an issue: GitHub.