{"record":{"id":"0a8ce22421a4d7ce","repo":"ReactiveX/rxjs","slug":"output-name-must-be-a-non-empty-relative-path-o","errorCode":null,"errorMessage":"Output name must be a non-empty relative path: ${outputName || '<empty>'}","messagePattern":"Output name must be a non-empty relative path: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/migrate/src/node.ts","lineNumber":176,"sourceCode":"    if (outputStats.isSymbolicLink()) throw new Error(`Refusing to overwrite a symbolic link: ${outputPath}`);\n    if (!outputStats.isFile()) throw new Error(`Output path is not a regular file: ${outputPath}`);\n    if (!overwrite) throw new Error(`Output path already exists; enable overwrite explicitly: ${outputPath}`);\n  } catch (error: unknown) {\n    if (!isMissingPathError(error)) throw error;\n  }\n}\n\nexport async function migrateTestFiles(options: MigrateFilesOptions): Promise<readonly MigratedFile[]> {\n  if (options.write && !options.outputRoot) {\n    throw new Error('outputRoot is required when write is enabled.');\n  }\n  const plan = await planMigrationFiles(options);\n  return options.write ? applyMigrationPlan(plan, { overwrite: options.overwrite }) : plan.files;\n}\n\nfunction safeOutputPath(outputRoot: string, outputName: string): string {\n  if (!outputName || outputName === '.' || isAbsolute(outputName)) {\n    throw new Error(`Output name must be a non-empty relative path: ${outputName || '<empty>'}`);\n  }\n  const outputPath = resolve(outputRoot, outputName);\n  if (outputPath === outputRoot) throw new Error(`Output name must identify a file below outputRoot: ${outputName}`);\n  assertContained(outputRoot, outputPath, `Output path is outside outputRoot: ${outputName}`);\n  return outputPath;\n}\n\nfunction assertContained(root: string, candidate: string, message: string): void {\n  const localPath = relative(root, candidate);\n  if (localPath === '' || (localPath !== '..' && !localPath.startsWith(`..${sep}`) && !isAbsolute(localPath))) return;\n  throw new Error(message);\n}\n\nasync function canonicalDirectory(path: string, label: string): Promise<string> {\n  const canonicalPath = await realpath(path);\n  const pathStats = await stat(canonicalPath);\n  if (!pathStats.isDirectory()) throw new Error(`${label} is not a directory: ${path}`);\n  return canonicalPath;","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/ReactiveX/rxjs/blob/54796b38a57e6309f9861e174737479bb3f63f61/packages/migrate/src/node.ts#L158-L194","documentation":"safeOutputPath builds each output path by resolving a relative name against outputRoot, and rejects names that are empty, '.', or absolute. Absolute or degenerate names would either escape the outputRoot contract or fail to identify a file, so they are treated as programming errors in the outputName configuration.","triggerScenarios":"A custom options.outputName function returning '', '.', or an absolute path like '/tmp/out.spec.ts' (or a value starting with '/' or a Windows drive letter); also triggered when a string outputName is empty.","commonSituations":"A name-mapping callback that returns undefined/null coerced to string on edge cases, or one that resolves names against the wrong root and returns an absolute path.","solutions":["Make outputName always return a non-empty relative path with a file extension","If the callback computes absolute paths, strip the outputRoot prefix first (path.relative(outputRoot, p))","Guard the callback's fallback branch so it never returns undefined for unmatched files"],"exampleFix":"// before\noutputName: ({ sourcePath }) => absoluteNames.get(sourcePath),\n\n// after\noutputName: ({ sourcePath, sourceRoot }) =>\n  path.relative(sourceRoot, sourcePath).replaceAll('\\\\', '/'),","handlingStrategy":"validation","validationCode":"import { isAbsolute } from 'node:path';\nfunction validOutputName(name: string): boolean {\n  return Boolean(name) && name !== '.' && !isAbsolute(name);\n}","typeGuard":"function isRelativeOutputName(name: unknown): name is string {\n  return typeof name === 'string' && name.length > 0 && name !== '.' && !isAbsolute(name);\n}","tryCatchPattern":null,"preventionTips":["Unit-test your outputName callback over all input filenames including edge cases","Return path.relative(...) from name callbacks that internally compute absolute paths"],"tags":["migrate","filesystem","path-validation","configuration"],"backgroundTag":"invalid-relative-path","analyzedSha":"54796b38a57e6309f9861e174737479bb3f63f61","analyzedAt":"2026-08-28T10:21:27.410Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}