{"record":{"id":"e56dadd4700ede9b","repo":"vercel/turborepo","slug":"invalid-version-version","errorCode":null,"errorMessage":"Invalid version: ${version}","messagePattern":"Invalid version: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/turbo-codemod/src/transforms/update-versioned-schema-json.ts","lineNumber":104,"sourceCode":" * Replaces dots with hyphens to create a valid DNS subdomain.\n * e.g., \"2.7.5\" -> \"v2-7-5\", \"2.9.4-canary.5\" -> \"v2-9-4-canary-5\"\n */\nfunction versionToSubdomain(version: string): string {\n  return `v${version.replaceAll(\".\", \"-\")}`;\n}\n\n/**\n * Generates the new versioned schema URL.\n * Uses semver.parse to preserve prerelease identifiers (e.g., canary.5),\n * falling back to semver.coerce for non-standard version strings.\n */\nexport function getVersionedSchemaUrl(version: string): string {\n  // parse preserves prerelease: \"2.9.4-canary.5\" -> \"2.9.4-canary.5\"\n  // coerce strips it: \"2.9.4-canary.5\" -> \"2.9.4\"\n  const parsed = parse(version);\n  const resolved = parsed ? parsed.version : coerce(version)?.version;\n  if (!resolved) {\n    throw new Error(`Invalid version: ${version}`);\n  }\n  const subdomain = versionToSubdomain(resolved);\n  return `https://${subdomain}.turborepo.dev/schema.json`;\n}\n\n/**\n * Updates any old schema URLs in file content to the new versioned URL\n */\nfunction updateSchemaUrls(content: string, newUrl: string): string {\n  let updated = content;\n  // Replace static old URLs\n  for (const oldUrl of OLD_SCHEMA_URLS) {\n    updated = updated.replaceAll(oldUrl, newUrl);\n  }\n  // Replace outdated versioned URLs (e.g., v2-7-4 -> v2-7-5)\n  updated = updated.replaceAll(VERSIONED_SCHEMA_URL_REGEX, newUrl);\n  return updated;\n}","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/vercel/turborepo/blob/9f94a7d215b3881942527dd526afa3fc650869d5/packages/turbo-codemod/src/transforms/update-versioned-schema-json.ts#L86-L122","documentation":"The update-versioned-schema-json codemod rewrites turbo.json `$schema` URLs to versioned ones (https://<major-minor-patch>.turborepo.dev/schema.json). getVersionedSchemaUrl first tries semver.parse (preserving prerelease) then semver.coerce (lenient); when both return null the input contains no extractable version and it throws `Invalid version: <version>`.","triggerScenarios":"A version string with no semver-shaped digits at all: `workspace:*`, `latest`, `file:../turbo`, `github:vercel/turbo`, `link:...`, or an empty string — anything semver.coerce cannot find numbers in.","commonSituations":"Monorepos aliasing turbo to a local workspace build (`workspace:*`, common in pnpm/Yarn setups); tarball or git dependencies for turbo; overridden resolutions replacing the real version.","solutions":["Set devDependencies.turbo to a concrete published version before running the codemod (e.g. `^2.3.0`), then retry","If you must keep `workspace:*` day-to-day, temporarily pin a real version, run the codemod, then restore your alias — the schema URL it writes stays valid","If invoking the transform programmatically, pass a parseable version string (semver.parse it first) instead of a range/alias"],"exampleFix":"// package.json (before)\n\"devDependencies\": { \"turbo\": \"workspace:*\" }\n// after\n\"devDependencies\": { \"turbo\": \"^2.3.0\" }","handlingStrategy":"type-guard","validationCode":"import { parse, coerce } from 'semver';\n\nfunction isResolvableVersion(v: string | undefined): boolean {\n  return Boolean(v) && Boolean(parse(v!) ?? coerce(v));\n}\n// before running the codemod: read devDependencies.turbo and require isResolvableVersion","typeGuard":"import { parse, coerce } from 'semver';\n\nfunction isSemverResolvable(version: string): boolean {\n  return Boolean(parse(version) ?? coerce(version));\n}","tryCatchPattern":"try {\n  getVersionedSchemaUrl(turboVersion);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Invalid version:')) {\n    // fall back to the concrete version from the lockfile/registry instead of an alias\n    return getVersionedSchemaUrl(await resolveInstalledTurboVersion());\n  }\n  throw err;\n}","preventionTips":["Pin turbo to concrete semver versions (`^2.3.0`), not aliases (`workspace:*`, `latest`, `file:...`, `github:...`)","If you must alias locally, temporarily pin a real version around codemod runs","Guard any wrapper script with semver.parse/coerce before passing versions in"],"tags":["turbo-codemod","semver","schema","migrate","package-json"],"backgroundTag":"invalid-semver-version","analyzedSha":"9f94a7d215b3881942527dd526afa3fc650869d5","analyzedAt":"2026-08-16T19:47:29.531Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}