fix: Jira error deserialization, Connection header, and exit codes#8
fix: Jira error deserialization, Connection header, and exit codes#8
Conversation
- Parse Jira 400 responses correctly: read errorMessages (string[]) and errors (Record<string,string>) instead of broken array indexing - Add Connection: close header to Jira and Bitbucket requests - Introduce src/lib/exitCodes.ts with sysexits-style codes (69, 77, 78) - fail() now maps HTTP 401/403 → 77, network failures → 69, general → 1 - Replace all hardcoded exit code literals with named constants Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR improves pncli’s HTTP error reporting (notably for Jira 400 responses), standardizes process exit codes across the CLI, and tweaks default HTTP headers for Jira/Bitbucket requests.
Changes:
- Improve Jira error deserialization by extracting messages from
errorMessagesand field-levelerrors. - Add
Connection: closeto Jira/Bitbucket request headers. - Introduce
ExitCodeconstants + HTTP-status→exit-code mapping, and replace hardcodedprocess.exit(0|1)calls.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/services/config/commands.ts | Replaces literal exit codes with ExitCode constants for abort/cancel paths. |
| src/lib/output.ts | Routes fail() exits through exitCodeFromStatus() for auth/network failures. |
| src/lib/http.ts | Enhances non-OK response parsing (Jira formats) and adds Connection: close headers; updates dry-run exit code. |
| src/lib/exitCodes.ts | Adds shared exit code constants and status→exit mapping helper. |
| src/cli.ts | Replaces fatal error exit literal with ExitCode.GENERAL_ERROR. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Jira: errors is Record<string, string> | ||
| if (parsed.errors && typeof parsed.errors === 'object' && !Array.isArray(parsed.errors)) { | ||
| for (const [field, msg] of Object.entries(parsed.errors as Record<string, string>)) { | ||
| parts.push(`${field}: ${msg}`); | ||
| } |
There was a problem hiding this comment.
The new error-message extraction handles Jira’s errors as an object, but it no longer handles the previously-supported shape where errors is an array of objects with a message field (common in several APIs, and this file previously checked parsed.errors?.[0]?.message). This can regress error detail for those responses. Consider adding a branch for Array.isArray(parsed.errors) that pulls message (and/or joins multiple messages) before falling back to parsed.message.
|
|
||
| export type ExitCode = (typeof ExitCode)[keyof typeof ExitCode]; | ||
|
|
||
| export function exitCodeFromStatus(httpStatus: number): number { |
There was a problem hiding this comment.
exitCodeFromStatus is currently typed to return number, which loses the stronger ExitCode union and makes it easier to accidentally return an out-of-range value later. Consider changing the return type to ExitCode (and optionally constraining the parameter to number) so callers like fail() stay type-safe.
| export function exitCodeFromStatus(httpStatus: number): number { | |
| export function exitCodeFromStatus(httpStatus: number): ExitCode { |
- Handle errors-as-array (other APIs) alongside errors-as-object (Jira) - Tighten exitCodeFromStatus return type to ExitCode union Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: Jira error deserialization, Connection header, and exit codes - Parse Jira 400 responses correctly: read errorMessages (string[]) and errors (Record<string,string>) instead of broken array indexing - Add Connection: close header to Jira and Bitbucket requests - Introduce src/lib/exitCodes.ts with sysexits-style codes (69, 77, 78) - fail() now maps HTTP 401/403 → 77, network failures → 69, general → 1 - Replace all hardcoded exit code literals with named constants Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address PR feedback — array errors shape and ExitCode return type - Handle errors-as-array (other APIs) alongside errors-as-object (Jira) - Tighten exitCodeFromStatus return type to ExitCode union Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Sunny Kolattukudy <sunny@imagile.dev> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add deps command group with CVE scanning and dependency auditing Implements pncli deps with frisk as the primary command and scan, diff, outdated, license-check, and connectivity as auxiliary commands. Replaces the artifactory stub. - deps frisk: scans all deps for CVEs via OSV.dev querybatch, returns structured remediation paths in JSON for agent consumption (Tier 3) - deps scan: local-only dependency inventory across npm, NuGet, Maven - deps diff: dep changes between two git refs using git show - deps outdated: latest versions via Artifactory REST (Tier 2) - deps license-check: license data per package via Artifactory (Tier 2) - deps connectivity: diagnoses which tier is available Parsers handle package-lock.json (v2/v3), yarn.lock, pnpm-lock.yaml, .csproj/packages.lock.json/Directory.Packages.props/packages.config, pom.xml, build.gradle, and gradle.lockfile. Artifactory config uses flat npmRepo/nugetRepo/mavenRepo fields. Each ecosystem repo is independently optional — missing repos are skipped silently. config init updated with opt-in Artifactory wizard section. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: update copilot-instructions for deps command group Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore: auto-stage copilot-instructions.md in pre-commit hook The build step regenerates copilot-instructions.md — stage it automatically so it's never left out of a commit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address PR #7 review comments Security: - Replace execSync string interpolation with execFileSync + arg arrays in readFileAtRef and scanRepoAtRef to prevent shell injection from user-controlled --from/--to refs Parser fixes: - yarn.lock: fix blockRegex to match scoped packages (@scope/name); classify direct vs transitive using package.json dep lists and respect opts.includeTransitive - pnpm-lock.yaml: same direct/transitive classification fix; pass packageJsonContent through for cross-referencing - nuget: remove dead propsFiles map that was populated but never read - maven: resolveProperty now returns null when a ${placeholder} remains unresolved, so callers correctly fall through to dependencyManagement versions instead of emitting invalid version strings Diff fixes: - Key on ecosystem:name:source instead of ecosystem:name to preserve multiple versions of the same transitive dep (common in npm) - summary.unchanged now computed directly from the key set rather than mixing collapsed/uncollapsed counts Directory.Packages.props: walk up parent directories to repo root instead of only checking the manifest's own directory (props files are typically at repo root in central package management) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(main): release 1.1.0 (#5) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix: Jira error deserialization, Connection header, and exit codes (#8) * fix: Jira error deserialization, Connection header, and exit codes - Parse Jira 400 responses correctly: read errorMessages (string[]) and errors (Record<string,string>) instead of broken array indexing - Add Connection: close header to Jira and Bitbucket requests - Introduce src/lib/exitCodes.ts with sysexits-style codes (69, 77, 78) - fail() now maps HTTP 401/403 → 77, network failures → 69, general → 1 - Replace all hardcoded exit code literals with named constants Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address PR feedback — array errors shape and ExitCode return type - Handle errors-as-array (other APIs) alongside errors-as-object (Jira) - Tighten exitCodeFromStatus return type to ExitCode union Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Sunny Kolattukudy <sunny@imagile.dev> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address second round of PR #7 review comments - Add shared semver utility (semver.ts) with pre-release/build-metadata aware comparison, replacing lossy strip-non-numeric logic in diff and artifactory client - Rename remediation snake_case fields to camelCase (fixAvailable, fixedVersions) to match rest of CLI output shape - Remove always-true availableInArtifactory field; add uncheckedEcosystems to OutdatedData so callers know which ecosystems had no repo configured - Drop unused repoRoot parameter from parseManifests - Implement NuGet manifest deduplication: when multiple project files share the same packages.lock.json, keep only one representative to avoid inflated package counts - Add Kotlin DSL / Groovy parenthesised form support to parseBuildGradle (handles both implementation("g:a:v") and implementation 'g:a:v') - Fix Artifactory config init prompt — deps frisk uses OSV.dev, not Artifactory; prompt now only mentions outdated and license-check Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: use write callback before process.exit to avoid libuv assertion on Windows process.exit() called synchronously after process.stdout/stderr.write() triggers a libuv assertion on Windows because the write handle is torn down before the kernel flushes the buffer. Move process.exit() into the write callback in output.ts fail() and http.ts dry-run paths. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Sunny Kolattukudy <sunny@imagile.dev> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Summary
parsed.errors?.[0]?.message(array indexing) but Jira returnserrorsas aRecord<string, string>anderrorMessagesasstring[]— users were only ever seeing "HTTP 400 Bad Request". Now both fields are read and joined into a readable message.Connection: closeheader: Added to bothjiraHeaders()andbitbucketHeaders()insrc/lib/http.ts.src/lib/exitCodes.tswith sysexits-style constants (GENERAL_ERROR=1,NETWORK_ERROR=69,AUTH_ERROR=77,CONFIG_ERROR=78).fail()inoutput.tsnow maps HTTP 401/403 → 77 and network failures (status 0) → 69. All hardcodedprocess.exit(0)/process.exit(1)literals replaced with named constants.Test plan
npm run typecheckpassesnpm run lintpassesnpm run buildpassespncli jira issue get TEST-1; echo $?🤖 Generated with Claude Code