@kolatts/pncli 1.23.0 → 1.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  setRepoConfigValue,
9
9
  writeGlobalConfig,
10
10
  writeRepoConfig
11
- } from "./chunk-KRDKHGYS.js";
11
+ } from "./chunk-JOAWUILY.js";
12
12
  import {
13
13
  ExitCode,
14
14
  PncliError,
@@ -20,13 +20,29 @@ import {
20
20
  success,
21
21
  warn,
22
22
  writeRawOutput
23
- } from "./chunk-XYHZMXJU.js";
23
+ } from "./chunk-ABHLES5M.js";
24
24
  import {
25
25
  AdoCoreClient
26
26
  } from "./chunk-VDL32PSG.js";
27
27
 
28
28
  // src/cli.ts
29
29
  import { Command as Command3 } from "commander";
30
+
31
+ // src/lib/proxyFetch.ts
32
+ async function configureProxy() {
33
+ try {
34
+ const { setGlobalDispatcher, EnvHttpProxyAgent } = await import("undici");
35
+ setGlobalDispatcher(new EnvHttpProxyAgent());
36
+ } catch (err) {
37
+ const isModuleNotFound = err instanceof Error && "code" in err && err.code === "ERR_MODULE_NOT_FOUND";
38
+ if (!isModuleNotFound) {
39
+ process.stderr.write(`[pncli] Warning: failed to configure proxy: ${String(err)}
40
+ `);
41
+ }
42
+ }
43
+ }
44
+
45
+ // src/cli.ts
30
46
  import { createRequire } from "module";
31
47
 
32
48
  // src/services/git/client.ts
@@ -778,6 +794,17 @@ var BitbucketClient = class {
778
794
  }
779
795
  );
780
796
  }
797
+ /**
798
+ * Returns true if `from` has commits not yet in `to` (i.e. there is something
799
+ * to promote), false if the branches are at the same point.
800
+ */
801
+ async compareCommits(project, repo, from, to) {
802
+ const result = await this.http.bitbucket(
803
+ `${API}/projects/${project}/repos/${repo}/commits`,
804
+ { params: { until: from, since: to, limit: 1 } }
805
+ );
806
+ return (result.values?.length ?? 0) > 0;
807
+ }
781
808
  };
782
809
 
783
810
  // src/services/git/commands.ts
@@ -1830,6 +1857,50 @@ function registerBitbucketCommands(program2) {
1830
1857
  fail(err, "bitbucket", "create-pr", start);
1831
1858
  }
1832
1859
  });
1860
+ bb.command("create-prs").description("Create pull requests for multiple repositories in a project").requiredOption("--from <branch>", "Source branch").requiredOption("--to <branch>", "Target branch").requiredOption("--repos <slugs>", "Comma-separated repository slugs").option("--title <title>", 'PR title (defaults to "Promote <from> to <to>")').option("--description <desc>", "PR description").option("--reviewers <users>", "Comma-separated reviewer usernames").option("--project <key>", "Bitbucket project key (overrides parent --project)").action(async (opts) => {
1861
+ const start = Date.now();
1862
+ try {
1863
+ const globalOpts = bb.optsWithGlobals();
1864
+ const config = loadConfig({ configPath: globalOpts.config });
1865
+ const http = createHttpClient(config, Boolean(globalOpts.dryRun));
1866
+ const client = new BitbucketClient(http);
1867
+ const ctx = getGitContext(config);
1868
+ const project = opts.project ?? globalOpts.project ?? ctx?.project ?? config.defaults.bitbucket?.project ?? "";
1869
+ if (!project) {
1870
+ throw new PncliError("Could not determine Bitbucket project. Pass --project, or run pncli config init.", 1);
1871
+ }
1872
+ const repos = opts.repos.split(",").map((s) => s.trim()).filter(Boolean);
1873
+ if (repos.length === 0) {
1874
+ throw new PncliError("--repos must contain at least one repository slug", 1);
1875
+ }
1876
+ const title = opts.title ?? `Promote ${opts.from} to ${opts.to}`;
1877
+ const reviewers = opts.reviewers ? opts.reviewers.split(",").map((s) => s.trim()) : [];
1878
+ const results = [];
1879
+ let created = 0;
1880
+ let nothingToPromote = 0;
1881
+ let errors = 0;
1882
+ for (const repo of repos) {
1883
+ try {
1884
+ const hasCommits = await client.compareCommits(project, repo, opts.from, opts.to);
1885
+ if (!hasCommits) {
1886
+ results.push({ repo, status: "nothing_to_promote" });
1887
+ nothingToPromote++;
1888
+ continue;
1889
+ }
1890
+ const pr = await client.createPR({ project, repo, title, source: opts.from, target: opts.to, description: opts.description, reviewers });
1891
+ results.push({ repo, status: "created", pr });
1892
+ created++;
1893
+ } catch (err) {
1894
+ const message = err instanceof Error ? err.message : String(err);
1895
+ results.push({ repo, status: "error", error: message });
1896
+ errors++;
1897
+ }
1898
+ }
1899
+ success({ results, summary: { created, nothing_to_promote: nothingToPromote, error: errors } }, "bitbucket", "create-prs", start);
1900
+ } catch (err) {
1901
+ fail(err, "bitbucket", "create-prs", start);
1902
+ }
1903
+ });
1833
1904
  bb.command("update-pr").description("Update a pull request").requiredOption("--id <pr-id>", "Pull request ID").option("--title <title>", "New title").option("--description <desc>", "New description").option("--reviewers <users>", "Comma-separated reviewer usernames").action(async (opts) => {
1834
1905
  const start = Date.now();
1835
1906
  try {
@@ -2235,12 +2306,84 @@ var GitHubClient = class {
2235
2306
  }
2236
2307
  });
2237
2308
  }
2309
+ async listReviewThreads(owner, repo, pullNumber) {
2310
+ const query = `
2311
+ query($owner: String!, $repo: String!, $number: Int!) {
2312
+ repository(owner: $owner, name: $repo) {
2313
+ pullRequest(number: $number) {
2314
+ reviewThreads(first: 100) {
2315
+ pageInfo { hasNextPage }
2316
+ nodes {
2317
+ id
2318
+ isResolved
2319
+ isOutdated
2320
+ path
2321
+ line
2322
+ comments(first: 10) {
2323
+ nodes {
2324
+ id
2325
+ databaseId
2326
+ body
2327
+ author { login }
2328
+ createdAt
2329
+ }
2330
+ }
2331
+ }
2332
+ }
2333
+ }
2334
+ }
2335
+ }
2336
+ `;
2337
+ const data = await this.http.githubGraphQL(query, { owner, repo, number: pullNumber });
2338
+ const threads = data.repository.pullRequest.reviewThreads;
2339
+ if (threads.pageInfo?.hasNextPage) {
2340
+ process.stderr.write(`warning: more than ${threads.nodes.length} review threads on PR #${pullNumber}; only showing the first ${threads.nodes.length}
2341
+ `);
2342
+ }
2343
+ return threads.nodes;
2344
+ }
2345
+ async resolveReviewThread(threadId) {
2346
+ const mutation = `
2347
+ mutation($threadId: ID!) {
2348
+ resolveReviewThread(input: { threadId: $threadId }) {
2349
+ thread {
2350
+ id
2351
+ isResolved
2352
+ isOutdated
2353
+ path
2354
+ line
2355
+ comments(first: 10) {
2356
+ nodes {
2357
+ id
2358
+ databaseId
2359
+ body
2360
+ author { login }
2361
+ createdAt
2362
+ }
2363
+ }
2364
+ }
2365
+ }
2366
+ }
2367
+ `;
2368
+ const data = await this.http.githubGraphQL(mutation, { threadId });
2369
+ return data.resolveReviewThread.thread;
2370
+ }
2238
2371
  };
2239
2372
 
2240
2373
  // src/services/github/commands.ts
2241
2374
  function getClient3(program2, overrides) {
2242
2375
  const opts = program2.optsWithGlobals();
2243
2376
  const config = loadConfig({ configPath: opts.config });
2377
+ const missing = [];
2378
+ if (!config.github.baseUrl) missing.push("github.baseUrl (e.g. pncli config set github.baseUrl https://api.github.com)");
2379
+ if (!config.github.token) missing.push("github.token (e.g. pncli config set github.token <your-PAT>)");
2380
+ if (missing.length) {
2381
+ throw new PncliError(
2382
+ `GitHub not configured. Missing required settings:
2383
+ ${missing.join("\n ")}`,
2384
+ 1
2385
+ );
2386
+ }
2244
2387
  const http = createHttpClient(config, Boolean(opts.dryRun));
2245
2388
  const client = new GitHubClient(http);
2246
2389
  const ctx = getGitContext(config);
@@ -2541,6 +2684,26 @@ function registerGitHubCommands(program2) {
2541
2684
  fail(err, "github", "list-reviewers", start);
2542
2685
  }
2543
2686
  });
2687
+ gh.command("list-review-threads").description("List review threads on a pull request (includes thread node IDs for resolve-thread)").requiredOption("--number <n>", "Pull request number").action(async (opts) => {
2688
+ const start = Date.now();
2689
+ try {
2690
+ const { client, owner, repo } = getClient3(gh);
2691
+ const data = await client.listReviewThreads(owner, repo, parseInt(opts.number, 10));
2692
+ success(data, "github", "list-review-threads", start);
2693
+ } catch (err) {
2694
+ fail(err, "github", "list-review-threads", start);
2695
+ }
2696
+ });
2697
+ gh.command("resolve-thread").description("Mark a review thread as resolved (use list-review-threads to get the thread id)").requiredOption("--thread-id <id>", "GraphQL node ID of the review thread (e.g. PRT_kwDO...)").action(async (opts) => {
2698
+ const start = Date.now();
2699
+ try {
2700
+ const { client } = getClient3(gh);
2701
+ const data = await client.resolveReviewThread(opts.threadId);
2702
+ success(data, "github", "resolve-thread", start);
2703
+ } catch (err) {
2704
+ fail(err, "github", "resolve-thread", start);
2705
+ }
2706
+ });
2544
2707
  gh.command("get-status").description("Get combined commit status for a ref (branch, tag, or SHA)").requiredOption("--ref <ref>", "Git ref (branch, tag, or commit SHA)").action(async (opts) => {
2545
2708
  const start = Date.now();
2546
2709
  try {
@@ -2778,6 +2941,28 @@ function xmlParseHint(err, bodyContent, convertedFromMarkdown = false) {
2778
2941
  return `Offending line ${row}${suffix}: ${line}
2779
2942
  ${pointer}`;
2780
2943
  }
2944
+ function storageFormatHint(err, bodyContent) {
2945
+ if (!(err instanceof PncliError) || err.status !== 500) return null;
2946
+ const hints = [];
2947
+ if (/<ac:parameter[^>]*(?<![:\w])name\s*=/i.test(bodyContent)) {
2948
+ hints.push(
2949
+ '<ac:parameter name="..."> found \u2014 use ac:name="..." instead (e.g. <ac:parameter ac:name="language">text</ac:parameter>).'
2950
+ );
2951
+ }
2952
+ const macroTagRe = /<ac:structured-macro(?:\s[^>]*)?\s*>/gi;
2953
+ let macroMatch;
2954
+ while ((macroMatch = macroTagRe.exec(bodyContent)) !== null) {
2955
+ if (!/\bac:macro-id\b/.test(macroMatch[0])) {
2956
+ hints.push(
2957
+ '<ac:structured-macro> without ac:macro-id found \u2014 add a unique UUID: ac:macro-id="<uuid>" (e.g. ac:macro-id="a1b2c3d4-...").'
2958
+ );
2959
+ break;
2960
+ }
2961
+ }
2962
+ if (hints.length === 0) return null;
2963
+ return `HTTP 500 \u2014 possible storage-format causes:
2964
+ ${hints.map((h) => ` \u2022 ${h}`).join("\n")}`;
2965
+ }
2781
2966
  function getClient4(program2) {
2782
2967
  const opts = program2.optsWithGlobals();
2783
2968
  const config = loadConfig({ configPath: opts.config });
@@ -2873,7 +3058,9 @@ function registerConfluenceCommands(program2) {
2873
3058
  });
2874
3059
  success(data, "confluence", "create-page", start);
2875
3060
  } catch (err) {
2876
- const hint = bodyContent ? xmlParseHint(err, bodyContent, Boolean(opts.markdown)) : null;
3061
+ const xmlHint = bodyContent ? xmlParseHint(err, bodyContent, Boolean(opts.markdown)) : null;
3062
+ const sfHint = bodyContent ? storageFormatHint(err, bodyContent) : null;
3063
+ const hint = [xmlHint, sfHint].filter(Boolean).join("\n\n") || null;
2877
3064
  if (hint) {
2878
3065
  const msg = err instanceof Error ? err.message : String(err);
2879
3066
  const status = err instanceof PncliError ? err.status : 1;
@@ -2905,7 +3092,9 @@ ${hint}`, status, url), "confluence", "create-page", start);
2905
3092
  });
2906
3093
  success(data, "confluence", "update-page", start);
2907
3094
  } catch (err) {
2908
- const hint = bodyContent ? xmlParseHint(err, bodyContent, Boolean(opts.markdown)) : null;
3095
+ const xmlHint = bodyContent ? xmlParseHint(err, bodyContent, Boolean(opts.markdown)) : null;
3096
+ const sfHint = bodyContent ? storageFormatHint(err, bodyContent) : null;
3097
+ const hint = [xmlHint, sfHint].filter(Boolean).join("\n\n") || null;
2909
3098
  if (hint) {
2910
3099
  const msg = err instanceof Error ? err.message : String(err);
2911
3100
  const status = err instanceof PncliError ? err.status : 1;
@@ -5724,7 +5913,7 @@ function registerConfigCommands(program2) {
5724
5913
  }
5725
5914
  if (cfg.checkmarx.baseUrl && cfg.checkmarx.tenantName && (cfg.checkmarx.apiKey || cfg.checkmarx.clientId && cfg.checkmarx.clientSecret)) {
5726
5915
  try {
5727
- await http.checkmarx("/api/projects", { params: { limit: 1 } });
5916
+ await http.checkmarx("projects", { params: { limit: 1 } });
5728
5917
  results.checkmarx = { ok: true, message: "connected" };
5729
5918
  } catch (err) {
5730
5919
  results.checkmarx = { ok: false, message: err instanceof Error ? err.message : String(err) };
@@ -5966,7 +6155,7 @@ function registerConfigCommands(program2) {
5966
6155
  results.checkmarx = { status: "error", message: "baseUrl not configured" };
5967
6156
  } else {
5968
6157
  try {
5969
- await http.checkmarx("/api/projects", { params: { limit: 1 }, timeoutMs: 1e4 });
6158
+ await http.checkmarx("projects", { params: { limit: 1 }, timeoutMs: 1e4 });
5970
6159
  results.checkmarx = { status: "valid", message: "ok" };
5971
6160
  } catch (err) {
5972
6161
  results.checkmarx = categorize2(err);
@@ -7979,11 +8168,11 @@ function registerAdoRepoCommands(ado) {
7979
8168
  const start = Date.now();
7980
8169
  try {
7981
8170
  const globalOpts = ado.optsWithGlobals();
7982
- const { loadConfig: lc } = await import("./config-QN5FFAZY.js");
8171
+ const { loadConfig: lc } = await import("./config-SQ3YHJHI.js");
7983
8172
  const config = lc({ configPath: globalOpts.config });
7984
8173
  const { collection, project, repo: repo2, gitClient } = getAdoContext(ado, true);
7985
8174
  const { AdoCoreClient: AdoCoreClient2 } = await import("./core-SWYVXOQ4.js");
7986
- const { createHttpClient: createHttpClient2 } = await import("./http-MCIU2IV6.js");
8175
+ const { createHttpClient: createHttpClient2 } = await import("./http-WCK6AZW6.js");
7987
8176
  const http = createHttpClient2(config, Boolean(globalOpts.dryRun));
7988
8177
  const coreClient = new AdoCoreClient2(http);
7989
8178
  const me = await coreClient.getConnectionData(collection);
@@ -10752,6 +10941,7 @@ function registerLogscaleCommands(program2) {
10752
10941
  if (!process.env.PNCLI_VERIFY_TLS) {
10753
10942
  process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
10754
10943
  }
10944
+ await configureProxy();
10755
10945
  var require2 = createRequire(import.meta.url);
10756
10946
  var pkg = require2("../package.json");
10757
10947
  var TAGLINE = "One command does what three meetings couldn't.";