@metaphorli/pingcode-cli 0.3.2 → 0.3.3

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.
@@ -0,0 +1,236 @@
1
+ 'use strict';
2
+
3
+ const core = require('../core');
4
+ const shared = require('./shared');
5
+
6
+ // ── Help ───────────────────────────────────────────────────────────────
7
+
8
+ function printHelp() {
9
+ console.log([
10
+ 'PingCode product — Manage products',
11
+ '',
12
+ 'Usage: pingcode product <subcommand> [options]',
13
+ '',
14
+ 'Subcommands:',
15
+ ' list [options] List products',
16
+ ' --keywords TEXT Filter by keywords (matches name and identifier)',
17
+ ' --limit N Max results per page',
18
+ '',
19
+ ' get <id|name> Show a single product by raw id or name',
20
+ '',
21
+ 'Global options:',
22
+ ' --base-url URL PingCode base URL',
23
+ ' --client-id ID OAuth client ID',
24
+ ' --client-secret SECRET OAuth client secret',
25
+ ' --token TOKEN Bearer token (skip OAuth)',
26
+ ' --user-id ID Current user ID',
27
+ ' --user-name NAME Current user name',
28
+ ' --workspace-cache PATH Workspace cache file path',
29
+ ' --no-workspace-cache Disable workspace cache',
30
+ ' --no-token-cache Disable token cache',
31
+ ' --dry-run Show API request without executing',
32
+ ' --compact Compact output',
33
+ ' --grant-type TYPE OAuth grant type: client_credentials, authorization_code, or auto (default; uses cached token type)',
34
+ ' --help Show this help',
35
+ ].join('\n'));
36
+ }
37
+
38
+ function printSubcommandHelp(subcommand) {
39
+ switch (subcommand) {
40
+ case 'list':
41
+ console.log([
42
+ 'Usage: pingcode product list [options]',
43
+ '',
44
+ 'List products.',
45
+ '',
46
+ 'Options:',
47
+ ' --keywords TEXT Filter by keywords (matches name and identifier)',
48
+ ' --limit N Max results per page',
49
+ ].join('\n'));
50
+ break;
51
+ case 'get':
52
+ console.log([
53
+ 'Usage: pingcode product get <id|name>',
54
+ '',
55
+ 'Show a single product by raw id or name.',
56
+ '',
57
+ 'If given a raw id (24-32 hex characters), fetches the product directly.',
58
+ 'Otherwise searches products by keywords and returns the first match.',
59
+ ].join('\n'));
60
+ break;
61
+ default:
62
+ printHelp();
63
+ }
64
+ }
65
+
66
+ // ── List subcommand ───────────────────────────────────────────────────
67
+
68
+ function parseListArgs(tokens) {
69
+ const args = {
70
+ keywords: null,
71
+ limit: null,
72
+ };
73
+ const stringFlags = {
74
+ '--keywords': 'keywords',
75
+ '--limit': 'limit',
76
+ };
77
+
78
+ for (let i = 0; i < tokens.length; i++) {
79
+ const arg = tokens[i];
80
+ if (arg in stringFlags) {
81
+ if (i + 1 >= tokens.length) {
82
+ throw new core.PingCodeError(`Flag ${arg} requires a value`);
83
+ }
84
+ args[stringFlags[arg]] = tokens[i + 1];
85
+ i += 1;
86
+ } else if (arg.startsWith('--')) {
87
+ const eqIndex = arg.indexOf('=');
88
+ if (eqIndex !== -1) {
89
+ const flag = arg.slice(0, eqIndex);
90
+ const value = arg.slice(eqIndex + 1);
91
+ if (flag in stringFlags) {
92
+ args[stringFlags[flag]] = value;
93
+ } else {
94
+ throw new core.PingCodeError(`Unknown option: ${flag}`);
95
+ }
96
+ } else {
97
+ throw new core.PingCodeError(`Unknown option: ${arg}. Use product list --help for usage.`);
98
+ }
99
+ } else {
100
+ throw new core.PingCodeError(`Unexpected argument: ${arg}. Use product list --help for usage.`);
101
+ }
102
+ }
103
+ return args;
104
+ }
105
+
106
+ async function runList(client, opts, args) {
107
+ const params = {};
108
+ if (args.keywords) {
109
+ params.keywords = args.keywords;
110
+ }
111
+ if (args.limit) {
112
+ params.page_size = String(args.limit);
113
+ }
114
+
115
+ return await client.request(
116
+ 'GET',
117
+ '/v1/ship/products',
118
+ params,
119
+ null,
120
+ { dry_run: opts.dry_run, use_workspace_cache: true },
121
+ );
122
+ }
123
+
124
+ // ── Get subcommand ────────────────────────────────────────────────────
125
+
126
+ function parseGetArgs(tokens) {
127
+ let productId = null;
128
+ for (let i = 0; i < tokens.length; i++) {
129
+ const arg = tokens[i];
130
+ if (!arg.startsWith('--')) {
131
+ if (productId === null) {
132
+ productId = arg;
133
+ continue;
134
+ }
135
+ throw new core.PingCodeError(`Unexpected argument: ${arg}. Use product get --help for usage.`);
136
+ }
137
+ throw new core.PingCodeError(`Unknown option: ${arg}. Use product get --help for usage.`);
138
+ }
139
+ if (!productId) {
140
+ throw new core.PingCodeError('A product id or name is required. Use product get --help for usage.');
141
+ }
142
+ return { product_id: productId };
143
+ }
144
+
145
+ const PRODUCT_RAW_ID_RE = /^[0-9a-fA-F]{24,32}$/;
146
+
147
+ async function runGet(client, opts, args) {
148
+ const input = args.product_id;
149
+ let productId;
150
+
151
+ if (PRODUCT_RAW_ID_RE.test(input)) {
152
+ productId = input;
153
+ } else {
154
+ // Search by keywords, resolve to id
155
+ const listResp = await client.request(
156
+ 'GET',
157
+ '/v1/ship/products',
158
+ { keywords: input },
159
+ null,
160
+ { dry_run: false, use_workspace_cache: true },
161
+ );
162
+ const values = core.pageValues(listResp);
163
+ if (values.length === 0) {
164
+ throw new core.PingCodeError(`No product found matching "${input}"`);
165
+ }
166
+ productId = values[0].id;
167
+ }
168
+
169
+ return await client.request(
170
+ 'GET',
171
+ `/v1/ship/products/${productId}`,
172
+ null,
173
+ null,
174
+ { dry_run: opts.dry_run, use_workspace_cache: true },
175
+ );
176
+ }
177
+
178
+ // ── Main dispatcher ───────────────────────────────────────────────────────
179
+
180
+ async function run(argv) {
181
+ const tokens = argv || [];
182
+
183
+ if (tokens.length === 0 || tokens[0] === '--help' || tokens[0] === '-h') {
184
+ printHelp();
185
+ return;
186
+ }
187
+
188
+ const subcommand = tokens[0];
189
+ const remaining = tokens.slice(1);
190
+
191
+ if (remaining.includes('--help') || remaining.includes('-h')) {
192
+ printSubcommandHelp(subcommand);
193
+ return;
194
+ }
195
+
196
+ const { opts, remaining: subArgs } = shared.parseGlobalOptions(remaining);
197
+ const client = shared.clientFromOpts(opts);
198
+
199
+ try {
200
+ let result;
201
+ switch (subcommand) {
202
+ case 'list': {
203
+ const listArgs = parseListArgs(subArgs);
204
+ result = await runList(client, opts, listArgs);
205
+ break;
206
+ }
207
+ case 'get': {
208
+ const getArgs = parseGetArgs(subArgs);
209
+ result = await runGet(client, opts, getArgs);
210
+ break;
211
+ }
212
+ default:
213
+ throw new core.PingCodeError(`Unknown product subcommand: ${subcommand}. Use product --help for usage.`);
214
+ }
215
+
216
+ if (opts.dry_run) {
217
+ core.printJson(result);
218
+ } else if (result !== null && result !== undefined) {
219
+ if (opts.compact) {
220
+ core.printJson(core.compactResponse(result));
221
+ } else {
222
+ core.printJson(result);
223
+ }
224
+ }
225
+ } catch (exc) {
226
+ throw exc;
227
+ }
228
+ }
229
+
230
+ shared.registerModule('product', {
231
+ name: 'product',
232
+ description: 'Manage PingCode products',
233
+ run,
234
+ });
235
+
236
+ module.exports = { run, printHelp };
@@ -1,9 +1,11 @@
1
- 'use strict';
1
+ 'use strict';
2
2
 
3
3
  // Module registry shared by the dispatcher and command modules.
4
4
  // Command modules register themselves here; the dispatcher discovers
5
5
  // them without importing command files directly (avoids circular deps).
6
6
 
7
+ const core = require('../core');
8
+
7
9
  const registry = new Map();
8
10
 
9
11
  function registerModule(name, config) {
@@ -38,4 +40,107 @@ function printModulesHelp() {
38
40
  console.log(lines.join('\n'));
39
41
  }
40
42
 
41
- module.exports = { registerModule, getModule, listModules, printModulesHelp };
43
+ const BASE_GLOBAL_BOOLEAN_FLAGS = new Set([
44
+ '--dry-run', '--compact', '--no-token-cache', '--no-workspace-cache',
45
+ ]);
46
+
47
+ const BASE_GLOBAL_STRING_FLAGS = {
48
+ '--base-url': 'base_url',
49
+ '--client-id': 'client_id',
50
+ '--client-secret': 'client_secret',
51
+ '--token': 'token',
52
+ '--user-id': 'user_id',
53
+ '--user-name': 'user_name',
54
+ '--workspace-cache': 'workspace_cache',
55
+ '--grant-type': 'grant_type',
56
+ };
57
+
58
+ function defaultGlobalOpts(extraBooleanFlags = []) {
59
+ const opts = {
60
+ base_url: process.env.PINGCODE_BASE_URL || core.DEFAULT_BASE_URL,
61
+ client_id: process.env.PINGCODE_CLIENT_ID || null,
62
+ client_secret: process.env.PINGCODE_CLIENT_SECRET || null,
63
+ token: process.env.PINGCODE_ACCESS_TOKEN || null,
64
+ user_id: process.env.PINGCODE_USER_ID || null,
65
+ user_name: process.env.PINGCODE_USER_NAME || null,
66
+ no_token_cache: false,
67
+ workspace_cache: process.env.PINGCODE_WORKSPACE_CACHE || core.DEFAULT_WORKSPACE_CACHE,
68
+ no_workspace_cache: false,
69
+ dry_run: false,
70
+ compact: false,
71
+ grant_type: 'auto',
72
+ };
73
+ for (const flag of extraBooleanFlags) {
74
+ const key = flag.replace(/^--/, '').replace(/-/g, '_');
75
+ opts[key] = false;
76
+ }
77
+ return opts;
78
+ }
79
+
80
+ function parseGlobalOptions(tokens, extraBooleanFlags = []) {
81
+ const booleanFlags = new Set([...BASE_GLOBAL_BOOLEAN_FLAGS, ...extraBooleanFlags]);
82
+ const opts = defaultGlobalOpts(extraBooleanFlags);
83
+ const remaining = [];
84
+
85
+ for (let i = 0; i < tokens.length; i++) {
86
+ const arg = tokens[i];
87
+ if (arg === '--help' || arg === '-h') {
88
+ remaining.push(arg);
89
+ continue;
90
+ }
91
+ if (booleanFlags.has(arg)) {
92
+ const key = arg.replace(/^--/, '').replace(/-/g, '_');
93
+ opts[key] = true;
94
+ continue;
95
+ }
96
+ const eqIndex = arg.indexOf('=');
97
+ let flag, value, consumedNext = false;
98
+ if (eqIndex !== -1) {
99
+ flag = arg.slice(0, eqIndex);
100
+ value = arg.slice(eqIndex + 1);
101
+ } else if (arg.startsWith('--')) {
102
+ flag = arg;
103
+ if (i + 1 < tokens.length && !tokens[i + 1].startsWith('--')) {
104
+ value = tokens[i + 1];
105
+ consumedNext = true;
106
+ } else {
107
+ remaining.push(arg);
108
+ continue;
109
+ }
110
+ } else {
111
+ remaining.push(arg);
112
+ continue;
113
+ }
114
+ if (flag in BASE_GLOBAL_STRING_FLAGS) {
115
+ opts[BASE_GLOBAL_STRING_FLAGS[flag]] = value;
116
+ if (consumedNext) i += 1;
117
+ } else {
118
+ remaining.push(arg);
119
+ if (consumedNext) remaining.push(value);
120
+ if (consumedNext) i += 1;
121
+ }
122
+ }
123
+ return { opts, remaining };
124
+ }
125
+
126
+ function clientFromOpts(opts) {
127
+ const tokenCache = opts.no_token_cache
128
+ ? null
129
+ : (process.env.PINGCODE_TOKEN_CACHE || core.DEFAULT_TOKEN_CACHE);
130
+ const workspaceCache = opts.no_workspace_cache ? null : opts.workspace_cache;
131
+ return new core.PingCodeClient({
132
+ base_url: opts.base_url,
133
+ client_id: opts.client_id,
134
+ client_secret: opts.client_secret,
135
+ token: opts.token,
136
+ token_cache: tokenCache,
137
+ workspace_cache: workspaceCache,
138
+ grant_type: opts.grant_type,
139
+ });
140
+ }
141
+
142
+ module.exports = {
143
+ registerModule, getModule, listModules, printModulesHelp,
144
+ BASE_GLOBAL_BOOLEAN_FLAGS, BASE_GLOBAL_STRING_FLAGS,
145
+ defaultGlobalOpts, parseGlobalOptions, clientFromOpts,
146
+ };
@@ -3,102 +3,14 @@
3
3
  const core = require('../core');
4
4
  const shared = require('./shared');
5
5
 
6
- // ── Global option defaults ────────────────────────────────────────────
7
- const GLOBAL_BOOLEAN_FLAGS = new Set([
8
- '--dry-run', '--compact', '--no-token-cache', '--no-workspace-cache',
9
- '--all-users', '--all-projects', '--all-sprints',
10
- ]);
6
+ // ── Extra global boolean flags ──────────────────────────────────────
7
+ const EXTRA_BOOLEAN_FLAGS = ['--all-users', '--all-projects', '--all-sprints'];
11
8
 
12
- const GLOBAL_STRING_FLAGS = {
13
- '--base-url': 'base_url',
14
- '--client-id': 'client_id',
15
- '--client-secret': 'client_secret',
16
- '--token': 'token',
17
- '--user-id': 'user_id',
18
- '--user-name': 'user_name',
19
- '--workspace-cache': 'workspace_cache',
20
- '--grant-type': 'grant_type',
21
- };
22
-
23
- function defaultGlobalOpts() {
24
- return {
25
- base_url: process.env.PINGCODE_BASE_URL || core.DEFAULT_BASE_URL,
26
- client_id: process.env.PINGCODE_CLIENT_ID || null,
27
- client_secret: process.env.PINGCODE_CLIENT_SECRET || null,
28
- token: process.env.PINGCODE_ACCESS_TOKEN || null,
29
- user_id: process.env.PINGCODE_USER_ID || null,
30
- user_name: process.env.PINGCODE_USER_NAME || null,
31
- no_token_cache: false,
32
- workspace_cache: process.env.PINGCODE_WORKSPACE_CACHE || core.DEFAULT_WORKSPACE_CACHE,
33
- no_workspace_cache: false,
34
- dry_run: false,
35
- compact: false,
36
- all_users: false,
37
- all_projects: false,
38
- all_sprints: false,
39
- grant_type: 'auto',
40
- };
41
- }
42
-
43
- function parseGlobalOptions(tokens) {
44
- const opts = defaultGlobalOpts();
45
- const remaining = [];
46
- for (let i = 0; i < tokens.length; i++) {
47
- const arg = tokens[i];
48
- if (arg === '--help' || arg === '-h') {
49
- remaining.push(arg);
50
- continue;
51
- }
52
- if (GLOBAL_BOOLEAN_FLAGS.has(arg)) {
53
- const key = arg.replace(/^--/, '').replace(/-/g, '_');
54
- opts[key] = true;
55
- continue;
56
- }
57
- const eqIndex = arg.indexOf('=');
58
- let flag, value, consumedNext = false;
59
- if (eqIndex !== -1) {
60
- flag = arg.slice(0, eqIndex);
61
- value = arg.slice(eqIndex + 1);
62
- } else if (arg.startsWith('--')) {
63
- flag = arg;
64
- if (i + 1 < tokens.length && !tokens[i + 1].startsWith('--')) {
65
- value = tokens[i + 1];
66
- consumedNext = true;
67
- } else {
68
- // Boolean-like flag with no value — could be a subcommand flag
69
- remaining.push(arg);
70
- continue;
71
- }
72
- } else {
73
- remaining.push(arg);
74
- continue;
75
- }
76
- if (flag in GLOBAL_STRING_FLAGS) {
77
- opts[GLOBAL_STRING_FLAGS[flag]] = value;
78
- if (consumedNext) i += 1;
79
- } else {
80
- // Not a recognized global flag — pass through as subcommand arg
81
- remaining.push(arg);
82
- if (consumedNext) remaining.push(value);
83
- if (consumedNext) i += 1;
84
- }
85
- }
86
- return { opts, remaining };
87
- }
88
-
89
- function clientFromOpts(opts) {
90
- const tokenCache = opts.no_token_cache ? null : (process.env.PINGCODE_TOKEN_CACHE || core.DEFAULT_TOKEN_CACHE);
91
- const workspaceCache = opts.no_workspace_cache ? null : opts.workspace_cache;
92
- return new core.PingCodeClient({
93
- base_url: opts.base_url,
94
- client_id: opts.client_id,
95
- client_secret: opts.client_secret,
96
- token: opts.token,
97
- token_cache: tokenCache,
98
- workspace_cache: workspaceCache,
99
- grant_type: opts.grant_type,
100
- });
101
- }
9
+ // Combined set used by sub-parsers to skip globally consumed flags
10
+ const ALL_BOOLEAN_FLAGS = new Set([
11
+ ...shared.BASE_GLOBAL_BOOLEAN_FLAGS,
12
+ ...EXTRA_BOOLEAN_FLAGS,
13
+ ]);
102
14
 
103
15
  // ── Cache lookup helpers ──────────────────────────────────────────────
104
16
 
@@ -234,7 +146,7 @@ function parseListArgs(tokens) {
234
146
  } else {
235
147
  throw new core.PingCodeError(`Unknown option: ${flag}`);
236
148
  }
237
- } else if (!(arg in GLOBAL_BOOLEAN_FLAGS)) {
149
+ } else if (!ALL_BOOLEAN_FLAGS.has(arg)) {
238
150
  throw new core.PingCodeError(`Unknown option: ${arg}. Use workitem list --help for usage.`);
239
151
  }
240
152
  } else {
@@ -341,7 +253,7 @@ function parseCreateArgs(tokens) {
341
253
  } else {
342
254
  throw new core.PingCodeError(`Unknown option: ${flag}`);
343
255
  }
344
- } else if (!(arg in GLOBAL_BOOLEAN_FLAGS)) {
256
+ } else if (!ALL_BOOLEAN_FLAGS.has(arg)) {
345
257
  throw new core.PingCodeError(`Unknown option: ${arg}. Use workitem create --help for usage.`);
346
258
  }
347
259
  } else {
@@ -441,61 +353,6 @@ async function runCreate(client, opts, args) {
441
353
  );
442
354
  }
443
355
 
444
- // ── Show subcommand ──────────────────────────────────────────────────
445
-
446
- function parseShowArgs(tokens) {
447
- // First non-flag token is the id/identifier
448
- let target = null;
449
- for (let i = 0; i < tokens.length; i++) {
450
- const arg = tokens[i];
451
- if (!arg.startsWith('--')) {
452
- if (target === null) {
453
- target = arg;
454
- continue;
455
- }
456
- throw new core.PingCodeError(`Unexpected argument: ${arg}. Use workitem show --help for usage.`);
457
- }
458
- // Consume flag+value for global options (already parsed)
459
- if (GLOBAL_BOOLEAN_FLAGS.has(arg)) continue;
460
- if (GLOBAL_STRING_FLAGS[arg]) {
461
- i += 1;
462
- continue;
463
- }
464
- // For --help (already handled in parseGlobalOptions)
465
- }
466
- if (!target) {
467
- throw new core.PingCodeError('A work item id or identifier is required. Use workitem show --help for usage.');
468
- }
469
- return { target };
470
- }
471
-
472
- async function runShow(client, opts, args) {
473
- const params = {};
474
- // Detect if target is an identifier (uppercase letters + dash + number)
475
- const isIdFormat = /^[A-Z]{3,6}-\d+$/.test(args.target);
476
- if (isIdFormat) {
477
- params.identifier = args.target;
478
- } else {
479
- params.id = args.target;
480
- }
481
-
482
- const result = await client.request(
483
- 'GET',
484
- '/v1/project/work_items',
485
- params,
486
- null,
487
- { dry_run: opts.dry_run, use_workspace_cache: true },
488
- );
489
-
490
- if (opts.dry_run) return result;
491
-
492
- // For real results, compact if requested, otherwise return first match
493
- if (opts.compact) {
494
- return core.compactResponse(result);
495
- }
496
- return result;
497
- }
498
-
499
356
  // ── Get subcommand ───────────────────────────────────────────────────
500
357
 
501
358
  function parseGetArgs(tokens) {
@@ -509,8 +366,8 @@ function parseGetArgs(tokens) {
509
366
  }
510
367
  throw new core.PingCodeError(`Unexpected argument: ${arg}. Use workitem get --help for usage.`);
511
368
  }
512
- if (GLOBAL_BOOLEAN_FLAGS.has(arg)) continue;
513
- if (GLOBAL_STRING_FLAGS[arg]) {
369
+ if (ALL_BOOLEAN_FLAGS.has(arg)) continue;
370
+ if (shared.BASE_GLOBAL_STRING_FLAGS[arg]) {
514
371
  i += 1;
515
372
  continue;
516
373
  }
@@ -521,6 +378,12 @@ function parseGetArgs(tokens) {
521
378
  return { work_item_id: workItemId };
522
379
  }
523
380
 
381
+ function isIdentifier(arg) {
382
+ // PingCode identifiers look like: PROJECT_KEY-NUMBER (e.g., SCR-1, TASK-42)
383
+ // Project keys are typically 3-6 uppercase letters.
384
+ return /^[A-Z]{3,6}-\d+$/.test(arg);
385
+ }
386
+
524
387
  async function runGet(client, opts, args) {
525
388
  if (isIdentifier(args.work_item_id)) {
526
389
  const resolutionParams = { identifier: args.work_item_id };
@@ -642,11 +505,11 @@ function parseUpdateArgs(tokens) {
642
505
  if (flag in stringFlags) {
643
506
  args[stringFlags[flag]] = value;
644
507
  } else {
645
- if (!(flag in GLOBAL_BOOLEAN_FLAGS) && !(flag in GLOBAL_STRING_FLAGS)) {
508
+ if (!ALL_BOOLEAN_FLAGS.has(flag) && !(flag in shared.BASE_GLOBAL_STRING_FLAGS)) {
646
509
  throw new core.PingCodeError(`Unknown option: ${flag}`);
647
510
  }
648
511
  }
649
- } else if (!(arg in GLOBAL_BOOLEAN_FLAGS)) {
512
+ } else if (!ALL_BOOLEAN_FLAGS.has(arg)) {
650
513
  throw new core.PingCodeError(`Unknown option: ${arg}. Use workitem update --help for usage.`);
651
514
  }
652
515
  }
@@ -666,12 +529,6 @@ function parseUpdateArgs(tokens) {
666
529
  return args;
667
530
  }
668
531
 
669
- function isIdentifier(arg) {
670
- // PingCode identifiers look like: PROJECT_KEY-NUMBER (e.g., SCR-1, TASK-42)
671
- // Project keys are typically 3-6 uppercase letters.
672
- return /^[A-Z]{3,6}-\d+$/.test(arg);
673
- }
674
-
675
532
  function parseNumber(value, label) {
676
533
  const parsed = Number(value);
677
534
  if (Number.isNaN(parsed)) {
@@ -831,8 +688,6 @@ function printHelp() {
831
688
  ' --description TEXT Description text',
832
689
  ' --parent <id|identifier> Parent work item',
833
690
  '',
834
- ' show <id|identifier> Show a single work item',
835
- '',
836
691
  ' get <id|identifier> Get a single work item by id or identifier',
837
692
  '',
838
693
  ' update <id|identifier> Update a work item',
@@ -913,18 +768,13 @@ function printSubcommandHelp(subcommand) {
913
768
  ' --parent <id|identifier> Parent work item',
914
769
  ].join('\n'));
915
770
  break;
916
- case 'show':
917
- console.log([
918
- 'Usage: pingcode workitem show <id|identifier>',
919
- '',
920
- 'Show a single work item by id or identifier.',
921
- ].join('\n'));
922
- break;
923
771
  case 'get':
924
772
  console.log([
925
773
  'Usage: pingcode workitem get <id|identifier>',
926
774
  '',
927
775
  'Get a single work item by id or identifier.',
776
+ '',
777
+ 'Identifiers (e.g. SCR-123) are resolved to an id, then the single item is fetched.',
928
778
  ].join('\n'));
929
779
  break;
930
780
  case 'update':
@@ -979,10 +829,10 @@ async function run(argv) {
979
829
  return;
980
830
  }
981
831
 
982
- // Parse global options from remaining tokens
983
- const { opts, remaining: subArgs } = parseGlobalOptions(remaining);
832
+ // Parse global options from remaining tokens, passing workitem-specific boolean flags
833
+ const { opts, remaining: subArgs } = shared.parseGlobalOptions(remaining, EXTRA_BOOLEAN_FLAGS);
984
834
 
985
- const client = clientFromOpts(opts);
835
+ const client = shared.clientFromOpts(opts);
986
836
 
987
837
  try {
988
838
  let result;
@@ -997,11 +847,6 @@ async function run(argv) {
997
847
  result = await runCreate(client, opts, args);
998
848
  break;
999
849
  }
1000
- case 'show': {
1001
- const args = parseShowArgs(subArgs);
1002
- result = await runShow(client, opts, args);
1003
- break;
1004
- }
1005
850
  case 'get': {
1006
851
  const args = parseGetArgs(subArgs);
1007
852
  result = await runGet(client, opts, args);
package/scripts/core.js CHANGED
@@ -1185,6 +1185,22 @@ function pathIsListWorkItems(rawPath, baseUrl = DEFAULT_BASE_URL) {
1185
1185
  return normalizePath(rawPath, baseUrl) === '/v1/project/work_items';
1186
1186
  }
1187
1187
 
1188
+ async function resolveWorkItemIdentifier(client, identifier) {
1189
+ const params = { identifier };
1190
+ const response = await client.request(
1191
+ 'GET',
1192
+ '/v1/project/work_items',
1193
+ params,
1194
+ null,
1195
+ { dry_run: false, use_workspace_cache: true },
1196
+ );
1197
+ const values = pageValues(response);
1198
+ if (values.length === 0) {
1199
+ throw new PingCodeError(`No work item found with identifier ${identifier}`);
1200
+ }
1201
+ return values[0].id;
1202
+ }
1203
+
1188
1204
  function printJson(data) {
1189
1205
  const sorted = sortKeys(data);
1190
1206
  console.log(JSON.stringify(sorted, null, 2));
@@ -1368,5 +1384,6 @@ module.exports = {
1368
1384
  applyDefaultWorkItemFilters,
1369
1385
  ensureWorkItemWorkspaceContext,
1370
1386
  applyDefaultWorkItemCreateBody,
1387
+ resolveWorkItemIdentifier,
1371
1388
  printJson,
1372
1389
  };
@@ -13,7 +13,10 @@ module.exports = { ...core };
13
13
  // Import command modules so they self-register with the shared registry.
14
14
  // (Side-effect imports; no circular dependency because these import from
15
15
  // ./shared and ../core, never from ../pingcode.)
16
+ require('./commands/comment');
16
17
  require('./commands/context');
18
+ require('./commands/idea');
19
+ require('./commands/product');
17
20
  require('./commands/workitem');
18
21
  require('./commands/auth');
19
22