Skip to content

feat: Confluence integration + fail() exit fix + TLS bypass#10

Merged
kolatts merged 3 commits intomainfrom
feat/confluence-integration
Apr 6, 2026
Merged

feat: Confluence integration + fail() exit fix + TLS bypass#10
kolatts merged 3 commits intomainfrom
feat/confluence-integration

Conversation

@kolatts
Copy link
Copy Markdown
Owner

@kolatts kolatts commented Apr 6, 2026

Summary

  • Confluence service — full implementation replacing the stub: 16 typed client methods and 17 CLI commands covering read, search, write, comments, labels, spaces, and attachments against the Confluence REST API v1 (Data Center). Auth token falls back to the Jira token automatically since they're shared on DC installs.
  • Fix fail() race on Windows — replaced the async stdout.write(output, callback) + throw pattern with fs.writeSync + process.exit. The old code scheduled exit via a write callback then threw, which raced with the top-level .catch() in cli.ts calling process.exit() synchronously — truncating JSON output on Windows (e.g. bad JQL responses).
  • TLS bypass — set NODE_TLS_REJECT_UNAUTHORIZED=0 at startup to handle corporate MITM/SSL inspection proxies that break deps commands.
  • README — trimmed Quick Start to just config init with a pointer to copilot-instructions.md; updated Confluence status to Active.

Test plan

  • pncli confluence --help lists all 17 subcommands
  • pncli confluence get-page --id 123 --dry-run prints dry-run output and exits 0
  • pncli config show includes confluence section with masked token
  • pncli jira search --jql "INVALID!!!" returns full JSON error envelope without truncation on Windows
  • pncli config init prompts for Confluence URL and token between Bitbucket and Artifactory sections

🤖 Generated with Claude Code

…pass

- Add full Confluence service (16 client methods, 17 CLI commands) covering
  read, search, write, comments, labels, spaces, and attachments via REST API v1
- Confluence apiToken falls back to Jira token (shared on Data Center installs)
- Fix race condition in fail() — replace async stdout callback with fs.writeSync
  so JSON output is guaranteed flushed before process.exit(), fixing truncation
  on Windows when errors like bad JQL are returned
- Set NODE_TLS_REJECT_UNAUTHORIZED=0 at startup for corporate MITM proxies
- Update README quick start to point to copilot-instructions.md
- Update copilot-instructions.md with all Confluence command docs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 6, 2026 02:09
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a full Confluence (Data Center) integration to pncli, expands configuration to support Confluence credentials, and adjusts CLI behavior/output handling to improve reliability and usability.

Changes:

  • Implement Confluence client + CLI command suite (read/search/write/comments/labels/spaces/attachments).
  • Extend config resolution/init/masking for Confluence (including env vars and Jira-token fallback).
  • Change error output exit behavior and update startup/docs (including TLS behavior and Confluence “Active” status).

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/types/confluence.ts Adds typed Confluence REST v1 response/request shapes used by the new client.
src/types/config.ts Extends config types to include confluence in global/resolved config.
src/services/confluence/client.ts Implements Confluence REST API wrapper methods on top of HttpClient.
src/services/confluence/commands.ts Registers Confluence CLI subcommands and wires them to the client + output envelopes.
src/services/config/commands.ts Adds Confluence prompts and persists Confluence config during config init.
src/lib/http.ts Adds Confluence-specific HTTP request + pagination helpers to HttpClient.
src/lib/config.ts Adds Confluence env vars, resolves Confluence config, and masks Confluence token output.
src/lib/output.ts Updates fail() to use synchronous stdout write + process.exit.
src/cli.ts Sets TLS env behavior at startup and updates help text to reflect active services.
README.md Updates messaging/quick start and marks Confluence as Active.
copilot-instructions.md Documents Confluence CLI subcommands and options.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/cli.ts Outdated
Comment on lines +1 to +2
// Disable TLS verification to handle corporate MITM/SSL inspection proxies
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TLS verification is disabled unconditionally for the entire CLI by setting NODE_TLS_REJECT_UNAUTHORIZED=0 at startup. This weakens HTTPS security for all commands (not just deps) and can allow silent MITM. Make this opt-in (e.g. a --insecure-tls flag / PNCLI_INSECURE_TLS env var) and/or scope it only to the specific commands that need it, with a clear warning in output/docs when enabled.

Copilot uses AI. Check for mistakes.
Comment thread src/lib/http.ts Outdated
while (true) {
const page = await fetchPage(start, limit);
results.push(...page.results);
if (!page._links.next || page.size < limit) break;
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

confluencePaginate uses a fixed limit = 25 and stops when page.size < limit. If callers override the per-request limit (e.g. list-pages --limit 10), page.size will be < 25 on the first page and pagination will terminate early even when _links.next is present. Adjust the loop termination to rely on the API’s pagination signal (e.g. _links.next), or compare against the requested/page limit from the response rather than the hard-coded 25.

Suggested change
if (!page._links.next || page.size < limit) break;
if (!page._links.next) break;

Copilot uses AI. Check for mistakes.
Comment thread src/services/confluence/client.ts Outdated
Comment on lines +65 to +66
return this.http.confluence<ConfluencePageResponse<ConfluencePage>>(`${API}/content`, {
params: { spaceKey, type: 'page', expand: 'version', start, limit: opts.limit ?? limit }
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

listPages() accepts opts.start but never uses it; pagination always begins at start=0. Either pass the initial offset through to the pagination helper (and to the first request), or remove the option to avoid misleading behavior.

Suggested change
return this.http.confluence<ConfluencePageResponse<ConfluencePage>>(`${API}/content`, {
params: { spaceKey, type: 'page', expand: 'version', start, limit: opts.limit ?? limit }
const pageStart = (opts.start ?? 0) + start;
return this.http.confluence<ConfluencePageResponse<ConfluencePage>>(`${API}/content`, {
params: { spaceKey, type: 'page', expand: 'version', start: pageStart, limit: opts.limit ?? limit }

Copilot uses AI. Check for mistakes.
Comment on lines +52 to +59
.option('--start <n>', 'Offset for first result')
.action(async (opts: { space: string; limit?: string; start?: string }) => {
const start = Date.now();
try {
const client = getClient(program);
const data = await client.listPages(opts.space, {
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
start: opts.start ? parseInt(opts.start, 10) : undefined
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The --start option is parsed and passed through, but the Confluence client’s listPages() currently ignores the start offset (it always begins at 0). This makes --start misleading; implement offset support end-to-end or remove this option from the command.

Suggested change
.option('--start <n>', 'Offset for first result')
.action(async (opts: { space: string; limit?: string; start?: string }) => {
const start = Date.now();
try {
const client = getClient(program);
const data = await client.listPages(opts.space, {
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
start: opts.start ? parseInt(opts.start, 10) : undefined
.action(async (opts: { space: string; limit?: string }) => {
const start = Date.now();
try {
const client = getClient(program);
const data = await client.listPages(opts.space, {
limit: opts.limit ? parseInt(opts.limit, 10) : undefined

Copilot uses AI. Check for mistakes.
Comment thread src/lib/output.ts Outdated
const exitCode = err instanceof PncliError ? exitCodeFromStatus(err.status) : ExitCode.GENERAL_ERROR;
process.stdout.write(output, () => process.exit(exitCode));
throw new PncliError(errorDetail.message, errorDetail.status);
fs.writeSync(process.stdout.fd, output);
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fs.writeSync(process.stdout.fd, output) can throw (commonly EPIPE) when stdout is closed by the consumer (e.g. piping to head). Since fail() now uses a synchronous write, consider catching write errors (especially EPIPE) and exiting cleanly to avoid an unhandled exception and stack trace.

Suggested change
fs.writeSync(process.stdout.fd, output);
try {
fs.writeSync(process.stdout.fd, output);
} catch (writeErr) {
const code =
typeof writeErr === 'object' && writeErr !== null && 'code' in writeErr
? String((writeErr as { code?: unknown }).code)
: undefined;
if (code !== 'EPIPE') {
throw writeErr;
}
}

Copilot uses AI. Check for mistakes.
Comment thread copilot-instructions.md
Comment on lines 236 to +245
### Confluence

```
# confluence — no subcommands implemented yet
pncli confluence get-page
--id <page-id> Page ID
--expand <fields> Comma-separated fields to expand (default:
"body.storage,version,space,ancestors")

pncli confluence get-page-by-title
--space <key> Space key
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description says there are 17 Confluence CLI subcommands, but the docs here list 16 (and the implementation appears to register 16). Please reconcile the count in the PR description/docs so users aren’t expecting a missing command.

Copilot uses AI. Check for mistakes.
Sunny Kolattukudy and others added 2 commits April 5, 2026 22:14
- updatePage: make title required in UpdatePageOpts; command falls back to
  current page title when --title not passed, preventing guaranteed 400s
- confluencePaginate: rely solely on _links.next for pagination termination;
  page.size < limit check was incorrectly stopping early on filtered spaces
- listPages/listSpaces: wire opts.start as initial offset into pagination loop
  so --start flag actually affects which page results begin from
- deps parsers: set maxBuffer to 10MB on all git execSync/execFileSync calls
  to prevent ENOBUFS crashes on large monorepos

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TLS verification is now disabled by default (enterprise Data Center installs
commonly sit behind SSL inspection proxies). Set PNCLI_VERIFY_TLS=1 to opt
back in. The env var is intentionally undocumented in copilot-instructions.md
since the vast majority of users need TLS disabled.

Also catch EPIPE in fs.writeSync within fail() so piping output to head/grep
doesn't produce an unhandled exception and stack trace on top of the JSON.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@kolatts kolatts merged commit 95a2084 into main Apr 6, 2026
1 check passed
@kolatts kolatts deleted the feat/confluence-integration branch April 6, 2026 02:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants