azdo-cli 0.22.0-develop.764 → 0.22.0-develop.782

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/README.md CHANGED
@@ -80,6 +80,8 @@ azdo pr comments --exclude-system --max-chars 500 # human comments only, trun
80
80
  azdo pr comments --thread 148 # a single thread, by id (selector: exit 1 if absent)
81
81
  azdo pr comments --contains '"kind":"review-plan"' # threads holding a literal substring
82
82
  azdo pr status # PR checks (status + branch policies + pipeline builds) + code-comment counts
83
+ azdo pr status --branch feature/x --json # same view for another branch, nothing checked out
84
+ azdo pr status --pr-number 64 # same view for one PR by number, any status
83
85
  azdo pr comment-resolve 17 --pr-number 64 # idempotent: exit 0 even when already resolved
84
86
  azdo pr comment-reopen 17 --pr-number 64
85
87
 
@@ -49,45 +49,56 @@ function parseCredentialStore(value, platform = process.platform) {
49
49
  }
50
50
 
51
51
  // src/services/config-store.ts
52
+ var CREDENTIAL_STORE_NOTES = {
53
+ keyring: "OS vault, default",
54
+ dpapi: "Windows only; DPAPI-encrypted files under ~/.azdo/credentials"
55
+ };
52
56
  var SETTINGS = [
53
57
  {
54
58
  key: "org",
55
59
  description: "Azure DevOps organization name",
56
60
  type: "string",
57
61
  example: "mycompany",
58
- required: true
62
+ required: true,
63
+ scoped: false
59
64
  },
60
65
  {
61
66
  key: "project",
62
67
  description: "Azure DevOps project name",
63
68
  type: "string",
64
69
  example: "MyProject",
65
- required: true
70
+ required: true,
71
+ scoped: true
66
72
  },
67
73
  {
68
74
  key: "fields",
69
75
  description: "Extra work item fields to include (comma-separated reference names)",
70
76
  type: "string[]",
71
77
  example: "System.Tags,Custom.Priority",
72
- required: false
78
+ required: false,
79
+ scoped: true
73
80
  },
74
81
  {
75
82
  key: "markdown",
76
83
  description: "Convert rich text fields to markdown on display",
77
84
  type: "boolean",
78
85
  example: "true",
79
- required: false
86
+ required: false,
87
+ scoped: true
80
88
  },
81
89
  {
82
90
  key: "credentialStore",
83
- description: 'Where credentials are stored: "keyring" (OS vault, default) or "dpapi" (Windows only, ~/.azdo/credentials)',
91
+ description: "Where credentials are stored (keyring or dpapi)",
84
92
  type: "string",
85
93
  example: "dpapi",
86
- required: false
94
+ required: false,
95
+ scoped: false,
96
+ values: CREDENTIAL_STORES.map((value) => ({ value, note: CREDENTIAL_STORE_NOTES[value] })),
97
+ env: "AZDO_CREDENTIAL_STORE"
87
98
  }
88
99
  ];
89
100
  var VALID_KEYS = SETTINGS.map((s) => s.key);
90
- var SCOPED_KEYS = ["project", "fields", "markdown"];
101
+ var SCOPED_KEYS = SETTINGS.filter((s) => s.scoped).map((s) => s.key);
91
102
  function getConfigPath() {
92
103
  return path.join(os.homedir(), ".azdo", "config.json");
93
104
  }
@@ -1178,7 +1189,7 @@ async function deletePat(org) {
1178
1189
  }
1179
1190
  try {
1180
1191
  const { unlinkSync: unlinkSync2 } = await import("fs");
1181
- const { lockPath: lockPath2 } = await import("./oauth-token-refresh-RUT4XIMM.js");
1192
+ const { lockPath: lockPath2 } = await import("./oauth-token-refresh-QPPOTES2.js");
1182
1193
  unlinkSync2(lockPath2(org));
1183
1194
  } catch {
1184
1195
  }
package/dist/index.js CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  tokenResponseToCredential,
40
40
  unsetConfigValue,
41
41
  unsetOrgScopedValue
42
- } from "./chunk-W4EVKARR.js";
42
+ } from "./chunk-MHJ6OBX3.js";
43
43
 
44
44
  // src/program.ts
45
45
  import { Command as Command19 } from "commander";
@@ -2777,9 +2777,43 @@ async function promptForSetting(cfg, setting, ask) {
2777
2777
 
2778
2778
  `);
2779
2779
  }
2780
+ var SETTING_TYPE_LABEL = {
2781
+ string: "string",
2782
+ "string[]": "comma-separated list",
2783
+ boolean: "true | false"
2784
+ };
2785
+ function describeSettingValues(setting) {
2786
+ if (!setting.values) return `type: ${SETTING_TYPE_LABEL[setting.type]}`;
2787
+ const list = setting.values.map((v) => v.note ? `${v.value} (${v.note})` : v.value).join(", ");
2788
+ return `values: ${list}`;
2789
+ }
2790
+ function renderSettingsHelp(settings = SETTINGS) {
2791
+ const keyWidth = Math.max(...settings.map((s) => s.key.length)) + 3;
2792
+ const indent = " ".repeat(2 + keyWidth);
2793
+ const lines = ["Settings (azdo config set <key> <value>):", ""];
2794
+ for (const setting of settings) {
2795
+ const facts = [
2796
+ describeSettingValues(setting),
2797
+ `scope: ${setting.scoped ? "global, or per organization with --org <org>" : "global only"}`,
2798
+ ...setting.required ? ["required"] : [],
2799
+ ...setting.env ? [`env: ${setting.env} overrides the stored value`] : [],
2800
+ `example: azdo config set ${setting.key} ${setting.example}`
2801
+ ];
2802
+ lines.push(
2803
+ ` ${setting.key.padEnd(keyWidth)}${setting.description}`,
2804
+ ...facts.map((fact) => `${indent}${fact}`),
2805
+ ""
2806
+ );
2807
+ }
2808
+ return lines.join("\n").trimEnd();
2809
+ }
2810
+ var KEY_ARGUMENT_HELP = `setting key (${SETTINGS.map((s) => s.key).join(", ")})`;
2811
+ var KEY_HELP_POINTER = "\nRun `azdo config --help` for each setting's meaning, accepted values and scope.\n";
2780
2812
  function createConfigCommand() {
2781
2813
  const config = new Command4("config");
2782
2814
  config.description("Manage CLI settings");
2815
+ config.addHelpText("after", `
2816
+ ${renderSettingsHelp()}`);
2783
2817
  config.addHelpText(
2784
2818
  "after",
2785
2819
  [
@@ -2797,7 +2831,7 @@ function createConfigCommand() {
2797
2831
  ].join("\n")
2798
2832
  );
2799
2833
  const set = new Command4("set");
2800
- set.description("Set a configuration value").argument("<key>", "setting key (org, project, fields, markdown, credentialStore)").argument("<value>", "setting value").option("--org <org>", "set value in an org-scoped configuration").option("--json", "output in JSON format").option("--copy-credentials", "with credentialStore dpapi: copy Credential Manager credentials without asking").option("--no-copy-credentials", "with credentialStore dpapi: do not offer to copy Credential Manager credentials").action((key, value, options) => {
2834
+ set.description("Set a configuration value").argument("<key>", KEY_ARGUMENT_HELP).argument("<value>", "setting value").option("--org <org>", "set value in an org-scoped configuration").option("--json", "output in JSON format").option("--copy-credentials", "with credentialStore dpapi: copy Credential Manager credentials without asking").option("--no-copy-credentials", "with credentialStore dpapi: do not offer to copy Credential Manager credentials").addHelpText("after", KEY_HELP_POINTER).action((key, value, options) => {
2801
2835
  try {
2802
2836
  if (options.org) {
2803
2837
  setOrgScopedValue(options.org, key, value);
@@ -2831,7 +2865,7 @@ function createConfigCommand() {
2831
2865
  writeJson();
2832
2866
  });
2833
2867
  const get = new Command4("get");
2834
- get.description("Get a configuration value").argument("<key>", "setting key (org, project, fields, markdown, credentialStore)").option("--org <org>", "read from an org-scoped configuration").option("--json", "output in JSON format").action((key, options) => {
2868
+ get.description("Get a configuration value").argument("<key>", KEY_ARGUMENT_HELP).option("--org <org>", "read from an org-scoped configuration").option("--json", "output in JSON format").addHelpText("after", KEY_HELP_POINTER).action((key, options) => {
2835
2869
  try {
2836
2870
  const value = options.org ? getOrgScopedValue(options.org, key) : getConfigValue(key);
2837
2871
  if (options.json) {
@@ -2864,7 +2898,7 @@ function createConfigCommand() {
2864
2898
  writeConfigList(cfg);
2865
2899
  });
2866
2900
  const unset = new Command4("unset");
2867
- unset.description("Remove a configuration value").argument("<key>", "setting key (org, project, fields, markdown, credentialStore)").option("--org <org>", "remove from an org-scoped configuration").option("--json", "output in JSON format").action((key, options) => {
2901
+ unset.description("Remove a configuration value").argument("<key>", KEY_ARGUMENT_HELP).option("--org <org>", "remove from an org-scoped configuration").option("--json", "output in JSON format").addHelpText("after", KEY_HELP_POINTER).action((key, options) => {
2868
2902
  try {
2869
2903
  if (options.org) {
2870
2904
  unsetOrgScopedValue(options.org, key);
@@ -4334,7 +4368,7 @@ function parsePositivePrNumber(raw) {
4334
4368
  return null;
4335
4369
  }
4336
4370
  const n = Number.parseInt(raw, 10);
4337
- return Number.isFinite(n) && n > 0 ? n : null;
4371
+ return Number.isSafeInteger(n) && n > 0 ? n : null;
4338
4372
  }
4339
4373
  function prNumberHelp(branchStatus = "active") {
4340
4374
  const matched = branchStatus === "abandoned" ? "abandoned" : "open";
@@ -4644,16 +4678,59 @@ async function resolvePrCommandContext(options, resolveOpts = {}) {
4644
4678
  pat: credential
4645
4679
  };
4646
4680
  }
4681
+ var STATUS_PR_NUMBER_HELP = "show only the pull request with this numeric id, instead of the current branch's pull requests; mutually exclusive with --branch";
4682
+ var STATUS_BRANCH_HELP = "show the pull requests whose source branch is this one (with or without the refs/heads/ prefix), instead of the current branch's; no checkout needed. Mutually exclusive with --pr-number";
4683
+ function parseStatusTarget(options) {
4684
+ if (options.prNumber !== void 0 && options.branch !== void 0) {
4685
+ writeError("Cannot specify both --pr-number and --branch.");
4686
+ return null;
4687
+ }
4688
+ if (options.branch !== void 0) {
4689
+ const branch = options.branch.trim().replace(/^refs\/heads\//, "");
4690
+ if (branch === "") {
4691
+ writeError("--branch must not be empty.");
4692
+ return null;
4693
+ }
4694
+ return { kind: "branch", branch };
4695
+ }
4696
+ const prId = parseTargetPrNumber(options);
4697
+ if (prId === "invalid") {
4698
+ return null;
4699
+ }
4700
+ return prId === "none" ? { kind: "current" } : { kind: "pr", prId };
4701
+ }
4702
+ async function findStatusPullRequests(resolved, target) {
4703
+ if (target.kind === "pr") {
4704
+ const pullRequest = await fetchTargetById(resolved, target.prId);
4705
+ return pullRequest === null ? null : { branch: formatBranchName(pullRequest.sourceRefName), pullRequests: [pullRequest] };
4706
+ }
4707
+ const branch = target.kind === "branch" ? target.branch : resolved.branch;
4708
+ const pullRequests = await listPullRequests(resolved.context, resolved.repo, resolved.pat, branch);
4709
+ if (target.kind === "branch" && pullRequests.length === 0) {
4710
+ writeError(
4711
+ `No pull requests found for branch ${branch} in ${resolved.context.org}/${resolved.context.project}/${resolved.repo}.`
4712
+ );
4713
+ return null;
4714
+ }
4715
+ return { branch, pullRequests };
4716
+ }
4647
4717
  function createPrStatusCommand() {
4648
4718
  const command = new Command12("status");
4649
- withCommonPrOptions(command).description("Check pull requests for the current branch").option("--json", "output JSON").action(async (options) => {
4719
+ withCommonPrOptions(configureUnwrappedHelp(command)).description("Check pull requests for the current branch, another branch, or a specific pull request").option("--pr-number <id>", STATUS_PR_NUMBER_HELP).option("--branch <name>", STATUS_BRANCH_HELP).option("--json", "output JSON").action(async (options) => {
4650
4720
  validateOrgProjectPair(options);
4721
+ const target = parseStatusTarget(options);
4722
+ if (target === null) {
4723
+ return;
4724
+ }
4651
4725
  let context;
4652
4726
  try {
4653
- const resolved = await resolvePrCommandContext(options);
4727
+ const resolved = await resolvePrCommandContext(options, { requireBranch: target.kind === "current" });
4654
4728
  context = resolved.context;
4655
- const branch = resolved.branch;
4656
- const pullRequests = await listPullRequests(resolved.context, resolved.repo, resolved.pat, branch);
4729
+ const found = await findStatusPullRequests(resolved, target);
4730
+ if (found === null) {
4731
+ return;
4732
+ }
4733
+ const { branch, pullRequests } = found;
4657
4734
  let projectId = null;
4658
4735
  try {
4659
4736
  projectId = await resolveProjectId(resolved.context, resolved.pat);
@@ -5,7 +5,7 @@ import {
5
5
  lockPath,
6
6
  locksDir,
7
7
  refreshIfNeeded
8
- } from "./chunk-W4EVKARR.js";
8
+ } from "./chunk-MHJ6OBX3.js";
9
9
  export {
10
10
  _resetInFlight,
11
11
  classifyRefreshFailure,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "azdo-cli",
3
- "version": "0.22.0-develop.764",
3
+ "version": "0.22.0-develop.782",
4
4
  "description": "Azure DevOps CLI tool",
5
5
  "type": "module",
6
6
  "bin": {