{"record":{"id":"a16f2d17bbb200a9","repo":"jackwener/OpenCLI","slug":"failed-to-parse-12306-station-name-js-no-station","errorCode":null,"errorMessage":"Failed to parse 12306 station_name.js: no station records found","messagePattern":"Failed to parse 12306 station_name\\.js: no station records found","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/12306/utils.js","lineNumber":62,"sourceCode":"        throw new CommandExecutionError('Failed to parse 12306 station_name.js: source string not found');\n    }\n    const raw = match[1];\n    const records = raw.split('@').filter(Boolean);\n    const stations = [];\n    for (const r of records) {\n        const parts = r.split('|');\n        if (parts.length < 8 || !parts[2]) continue;\n        stations.push({\n            short: parts[0] || '',\n            name: parts[1] || '',\n            code: parts[2] || '',\n            pinyin: parts[3] || '',\n            abbr: parts[4] || '',\n            city: parts[7] || '',\n        });\n    }\n    if (stations.length === 0) {\n        throw new CommandExecutionError('Failed to parse 12306 station_name.js: no station records found');\n    }\n    return stations;\n}\n\n/**\n * Resolve a user-supplied station identifier to a telecode.\n *\n * Accepts Chinese name (`上海虹桥`), telecode (`AOH`), pinyin\n * (`shanghaihongqiao`), short alias (`shh`), or city name with a\n * preference for the city's main station.\n */\nexport function resolveStation(stations, input) {\n    const trimmed = String(input ?? '').trim();\n    if (!trimmed) throw new ArgumentError('station must not be empty');\n    if (STATION_CODE_RE.test(trimmed)) {\n        const exact = stations.find((s) => s.code === trimmed);\n        if (exact) return exact;\n        throw new ArgumentError(`Unknown 12306 station telecode \"${trimmed}\"`);","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/12306/utils.js#L44-L80","documentation":"CommandExecutionError from parseStationBundle when the extracted quoted string exists but splits into zero usable station records. Each record must have at least 8 `|`-separated fields including a telecode at position 2; if the string is present but empty or malformed, the stations array stays empty and this error is thrown.","triggerScenarios":"station_name.js served a quoted but empty or structurally changed string — e.g. a stub file with `var station_names ='';`, a truncated download, or a 12306 format change that alters field positions/count so every record fails the `parts.length < 8 || !parts[2]` guard.","commonSituations":"CDN serving a truncated/cached placeholder file; 12306 mid-deploy serving a stub bundle; a protocol change in the bundle format (fewer fields per record); a proxy compressing/altering the body such that the quoted literal is mangled.","solutions":["Re-fetch — a truncated or stub response is often transient; clear any intermediary cache/proxy.","Dump the matched string (first ~200 chars) and verify records are `@`-separated with 8+ pipe fields.","If 12306 changed the bundle layout, update the field positions and the parts.length/telecode guards in parseStationBundle.","Pin or cache a known-good station bundle as a fallback."],"exampleFix":"// before\nif (stations.length === 0) {\n    throw new CommandExecutionError('Failed to parse 12306 station_name.js: no station records found');\n}\n// after\nif (stations.length === 0) {\n    throw new CommandExecutionError(`Failed to parse 12306 station_name.js: no station records found (extracted ${raw.length} chars, sample: ${raw.slice(0, 100)})`);\n}","handlingStrategy":"type-guard","validationCode":"const match = text.match(/'([^']+)'/);\nconst records = match ? match[1].split('@').filter(Boolean) : [];\nconst ok = records.some((r) => r.split('|').length >= 8 && r.split('|')[2]);\nif (!ok) console.warn('station bundle present but structurally empty/malformed');","typeGuard":"function hasValidStationRecords(text) {\n  const m = typeof text === 'string' && text.match(/'([^']+)'/);\n  if (!m) return false;\n  return m[1].split('@').some((r) => {\n    const p = r.split('|');\n    return p.length >= 8 && Boolean(p[2]);\n  });\n}","tryCatchPattern":"try {\n  const stations = await fetchStationBundle();\n} catch (e) {\n  if (/no station records found/.test(e.message)) {\n    console.error('Bundle malformed/truncated — refetching or falling back to cached bundle');\n    const stations = parseStationBundle(await fs.readFile('./station_name.cache.js', 'utf8'));\n  } else throw e;\n}","preventionTips":["Cache a known-good station bundle as a fallback.","Spot-check the extracted string format (`@`-separated, 8+ pipe fields) when errors occur.","Watch for 12306 bundle-format changes and update field indices in parseStationBundle.","Re-fetch on failure — truncated/stub responses are often transient."],"tags":["parsing","schema","truncated-response","api-change"],"backgroundTag":"station-data-parse-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}