{"record":{"id":"381fef90e214038d","repo":"jackwener/OpenCLI","slug":"from-and-to-must-differ-got-fromcity-381fef","errorCode":null,"errorMessage":"--from and --to must differ (got ${fromCity})","messagePattern":"--from and --to must differ \\(got (.+?)\\)","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"warning","filePath":"clis/ctrip/ferry.js","lineNumber":49,"sourceCode":"    args: [\n        { name: 'from', required: true, positional: true, help: 'Departure city name (e.g. 大连 / 海口)' },\n        { name: 'to', required: true, positional: true, help: 'Arrival city name (e.g. 烟台 / 海安)' },\n        { name: 'date', required: true, help: 'Departure date (YYYY-MM-DD)' },\n        { name: 'limit', default: 20, help: 'Number of sailings (1-50)' },\n    ],\n    columns: [\n        'rank',\n        'shipName',\n        'departureTime', 'fromPort',\n        'arrivalTime', 'toPort',\n        'duration', 'price', 'status',\n        'url',\n    ],\n    func: async (page, kwargs) => {\n        const fromCity = parsePlaceName('from', kwargs.from);\n        const toCity = parsePlaceName('to', kwargs.to);\n        if (fromCity === toCity) {\n            throw new ArgumentError(`--from and --to must differ (got ${fromCity})`);\n        }\n        const date = parseIsoDate('date', kwargs.date);\n        const limit = parseListLimit(kwargs.limit);\n\n        const searchUrl = buildFerryListUrl(fromCity, toCity, date);\n        await page.goto(searchUrl);\n        const waitResult = await page.evaluate(WAIT_FOR_FERRY_JS);\n        if (waitResult === 'captcha') {\n            throw new AuthRequiredError('ship.ctrip.com', 'Ctrip is asking for a captcha; complete it in your browser session and retry');\n        }\n        if (waitResult !== 'content') {\n            throw new CommandExecutionError(`Ctrip ferry page did not render sailing rows (state=${String(waitResult)})`);\n        }\n        const renderedCardCount = await page.evaluate(buildScrollUntilJs('.list-item-parent', limit));\n        const raw = await page.evaluate(buildFerryExtractJs());\n        if (!Array.isArray(raw)) {\n            throw new CommandExecutionError('Ctrip ferry DOM extraction returned malformed rows');\n        }","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/ctrip/ferry.js#L31-L67","documentation":"ArgumentError thrown before any navigation when the parsed departure and arrival city names are identical for the `ctrip ferry` command. A ferry route with the same origin and destination is meaningless and would produce an invalid ship.ctrip.com deep link, so the CLI validates up front via parsePlaceName and rejects it with the offending city name in the message. Pure input-validation error — no network or browser involved.","triggerScenarios":"Calling `ctrip ferry --from 大连 --to 大连 ...` or any invocation where the normalized from/to place names (after parsePlaceName) compare equal, including case/whitespace-equivalent inputs that normalize to the same city.","commonSituations":"Copy-pasting the same city into both flags; scripting where from/to variables are accidentally assigned the same value; typo in a loop over route pairs; assuming the CLI would silently return empty results instead of validating.","solutions":["Pass distinct --from and --to cities (e.g. 大连 to 烟台)","Fix the calling script so from/to are not assigned the same value","Normalize/compare route pairs in your own code before invoking to filter out degenerate routes"],"exampleFix":"// before\nawait run(['ctrip', 'ferry', '--from', city, '--to', city, '--date', date]);\n// after\nif (city !== dest) {\n  await run(['ctrip', 'ferry', '--from', city, '--to', dest, '--date', date]);\n}","handlingStrategy":"validation","validationCode":"function validateFerryArgs(from, to) {\n  const norm = s => String(s || '').trim();\n  if (!norm(from)) throw new Error('--from is required');\n  if (!norm(to)) throw new Error('--to is required');\n  if (norm(from) === norm(to)) throw new Error(`--from and --to must differ (got ${norm(from)})`);\n}\nvalidateFerryArgs(kwargs.from, kwargs.to);","typeGuard":null,"tryCatchPattern":"try {\n  return await run(['ctrip', 'ferry', '--from', f, '--to', t, '--date', d]);\n} catch (e) {\n  if (e instanceof Error && e.name === 'ArgumentError') {\n    console.error('Bad route arguments:', e.message);\n    process.exitCode = 2; // usage error, not a runtime failure\n    return null;\n  }\n  throw e;\n}","preventionTips":["Validate from !== to in your own script before shelling out to the CLI","Normalize place names (trim/case) before comparing or passing","Guard scripted route lists so the same city is never assigned to both ends","Surface ArgumentError as a usage error (exit code 2), distinct from network/data errors"],"tags":["argument-error","input-validation","cli","ferry"],"backgroundTag":"invalid-argument-value","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}