{"record":{"id":"9f1cfb89455ec04f","repo":"remix-run/remix","slug":"optionpath-must-use-x-x-y-or-x-y-z-vers","errorCode":null,"errorMessage":"${optionPath} must use \"X\", \"X.Y\", or \"X.Y.Z\" version format","messagePattern":"(.+?) must use \"X\", \"X\\.Y\", or \"X\\.Y\\.Z\" version format","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/assets/src/lib/target.ts","lineNumber":178,"sourceCode":"\n  if (!/^\\d{4}$/.test(normalizedValue) || Number(normalizedValue) < 2015) {\n    throw new TypeError(`${optionPath} must use a four-digit year of 2015 or higher`)\n  }\n\n  return `es${normalizedValue}`\n}\n\nfunction normalizeBrowserTargetVersion(value: unknown, optionPath: string): AssetTargetVersion {\n  if (typeof value !== 'string') {\n    throw new TypeError(`${optionPath} must be a string`)\n  }\n\n  if (value.trim().length === 0) {\n    throw new TypeError(`${optionPath} must be a non-empty string`)\n  }\n\n  if (!/^\\d+(\\.\\d+){0,2}$/.test(value)) {\n    throw new TypeError(`${optionPath} must use \"X\", \"X.Y\", or \"X.Y.Z\" version format`)\n  }\n\n  let segments = value.split('.').map(Number)\n  if (segments.some((segment) => segment > 255)) {\n    throw new TypeError(`${optionPath} must use version components between 0 and 255`)\n  }\n\n  return value as AssetTargetVersion\n}\n\nfunction toLightningCssTargetVersion(version: AssetTargetVersion): number {\n  let [major, minor = 0, patch = 0] = version.split('.').map(Number)\n  return major * 65536 + minor * 256 + patch\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n  return typeof value === 'object' && value !== null && !Array.isArray(value)\n}","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/assets/src/lib/target.ts#L160-L196","documentation":"Thrown when a browser target version does not match the X, X.Y, or X.Y.Z numeric format. The assets normalizer parses browser versions into up to three numeric components, so values like 'v90', '90.x', 'latest', '90.1.1.1', or '90-beta' fail this regex.","triggerScenarios":"`{ script: { chrome: 'v90' } }`, `{ style: { safari: 'latest' } }`, `{ script: { edge: '90.0.1.1' } }`, or any version with 4+ components or non-numeric characters.","commonSituations":"Copying version strings from User-Agent headers or release notes; using browserslist ranges like '>= 90'; prefixing versions with 'v' as is common in release tags.","solutions":["Strip prefixes and use plain dotted numbers: '90', '90.1', or '90.1.2'","Limit to at most three numeric components (drop the fourth)","Parse User-Agent or release strings down to major/minor before putting them in the target config"],"exampleFix":"// before\nlet target = { script: { chrome: 'v90.0.2200.3' } }\n// after\nlet target = { script: { chrome: '90.0' } }","handlingStrategy":"validation","validationCode":"let isValidVersion = (value: string) => /^\\d+(\\.\\d+){0,2}$/.test(value)\nfor (let [key, value] of Object.entries(browserTargets)) {\n  if (!isValidVersion(value)) throw new Error(`${key} version must be X, X.Y, or X.Y.Z`)\n}","typeGuard":"let isDottedVersion = (value: unknown): value is string =>\n  typeof value === 'string' && /^\\d+(\\.\\d+){0,2}$/.test(value)","tryCatchPattern":null,"preventionTips":["Normalize User-Agent-derived versions to at most 3 numeric components","Never pass 'latest', ranges, or prefixed versions into target config"],"tags":["config","validation","version-format"],"backgroundTag":"invalid-config-value","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}