{"record":{"id":"c11815dc7e26244b","repo":"jackwener/OpenCLI","slug":"npm-downloads-period-start-start-is-after-end","errorCode":null,"errorMessage":"npm downloads period start ${start} is after end ${end}","messagePattern":"npm downloads period start (.+?) is after end (.+?)","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/npm/downloads.js","lineNumber":20,"sourceCode":"//\n// Hits `api.npmjs.org/downloads/range/<period>/<pkg>`. Default window is the\n// last 7 days (one row per day). Use `--period last-month` for 30 days, or\n// pass a custom `YYYY-MM-DD:YYYY-MM-DD` range.\nimport { cli, Strategy } from '@jackwener/opencli/registry';\nimport { ArgumentError, EmptyResultError } from '@jackwener/opencli/errors';\nimport { NPM_API, npmFetch, requirePackageName } from './utils.js';\n\nconst FIXED_PERIODS = new Set(['last-day', 'last-week', 'last-month', 'last-year']);\nconst RANGE_PATTERN = /^(\\d{4}-\\d{2}-\\d{2}):(\\d{4}-\\d{2}-\\d{2})$/;\n\nfunction requirePeriod(value) {\n    const s = String(value ?? 'last-week').trim();\n    if (FIXED_PERIODS.has(s)) return s;\n    const m = RANGE_PATTERN.exec(s);\n    if (m) {\n        const [, start, end] = m;\n        if (new Date(start) > new Date(end)) {\n            throw new ArgumentError(`npm downloads period start ${start} is after end ${end}`);\n        }\n        return `${start}:${end}`;\n    }\n    throw new ArgumentError(\n        `npm downloads period \"${value}\" is invalid`,\n        'Use last-day / last-week (default) / last-month / last-year, or YYYY-MM-DD:YYYY-MM-DD.',\n    );\n}\n\ncli({\n    site: 'npm',\n    name: 'downloads',\n    access: 'read',\n    description: 'Daily download counts for an npm package over a window',\n    domain: 'api.npmjs.org',\n    strategy: Strategy.PUBLIC,\n    browser: false,\n    args: [","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/npm/downloads.js#L2-L38","documentation":"npm downloads accepts a period of a fixed keyword (last-day/week/month/year) or a YYYY-MM-DD:YYYY-MM-DD range. requirePeriod parses the value; when the range's start date is chronologically after its end date it throws ArgumentError with both dates interpolated. This is pure input validation done before any API call.","triggerScenarios":"Calling npm downloads with a period like 2024-05-01:2024-01-01 (start after end), swapping the dates, or constructing the range programmatically with variables in the wrong order.","commonSituations":"Scripted date ranges computed as [from, to] but passed reversed; copy-paste of a range where someone edited only one date; timezone/mental-model mistakes about which date is earlier.","solutions":["Swap the two dates so the earlier date comes first: period=YYYY-MM-DD:YYYY-MM-DD with start <= end","If the range is computed, sort or assert start <= end before invoking the CLI","Use a fixed period (last-week, last-month) when an exact range is not required"],"exampleFix":"// before\nconst period = `${to}:${from}`;\n// after\nconst period = new Date(from) <= new Date(to) ? `${from}:${to}` : `${to}:${from}`;","handlingStrategy":"validation","validationCode":"function validPeriod(from, to){\n  const a = new Date(from), b = new Date(to);\n  if (isNaN(a) || isNaN(b)) throw new Error('invalid date');\n  if (a > b) throw new Error('start after end');\n  return `${from}:${to}`;\n}","typeGuard":"function isOrderedDateRange(p){ const m = /^(\\d{4}-\\d{2}-\\d{2}):(\\d{4}-\\d{2}-\\d{2})$/.exec(p); return !!m && new Date(m[1]) <= new Date(m[2]); }","tryCatchPattern":"try { await run(['npm-downloads', '--period', period]); }\ncatch (e) {\n  if (e instanceof ArgumentError && /is after end/.test(e.message)) {\n    const [a, b] = period.split(':');\n    return run(['npm-downloads', '--period', `${b}:${a}`]); // swap and retry\n  }\n  throw e;\n}","preventionTips":["Always build ranges as earlierDate:laterDate","Sort or assert start <= end when dates come from variables","Prefer fixed keywords (last-week, last-month) when an exact range is unnecessary","Use ISO YYYY-MM-DD formatting everywhere"],"tags":["argument-validation","date-range","npm"],"backgroundTag":"invalid-date-range","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}