@wavyx/pdcli 0.10.0 → 0.11.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +7 -1
  3. package/oclif.manifest.json +6369 -1598
  4. package/package.json +29 -2
  5. package/src/commands/activity/type/list.js +36 -0
  6. package/src/commands/deal/convert.js +112 -0
  7. package/src/commands/deal/follower/add.js +31 -0
  8. package/src/commands/deal/follower/list.js +38 -0
  9. package/src/commands/deal/follower/remove.js +44 -0
  10. package/src/commands/deal/list.js +7 -4
  11. package/src/commands/deal/participant/add.js +31 -0
  12. package/src/commands/deal/participant/list.js +46 -0
  13. package/src/commands/deal/participant/remove.js +53 -0
  14. package/src/commands/deal/summary.js +69 -0
  15. package/src/commands/field/create.js +72 -0
  16. package/src/commands/field/delete.js +56 -0
  17. package/src/commands/field/get.js +10 -1
  18. package/src/commands/field/list.js +10 -1
  19. package/src/commands/field/option/add.js +49 -0
  20. package/src/commands/field/option/remove.js +72 -0
  21. package/src/commands/field/update.js +53 -0
  22. package/src/commands/file/delete.js +41 -0
  23. package/src/commands/file/update.js +39 -0
  24. package/src/commands/filter/delete.js +41 -0
  25. package/src/commands/lead/convert.js +103 -0
  26. package/src/commands/note/comment/add.js +31 -0
  27. package/src/commands/note/comment/delete.js +51 -0
  28. package/src/commands/note/comment/list.js +43 -0
  29. package/src/commands/note/comment/update.js +37 -0
  30. package/src/commands/org/follower/add.js +31 -0
  31. package/src/commands/org/follower/list.js +41 -0
  32. package/src/commands/org/follower/remove.js +50 -0
  33. package/src/commands/org/relationship/add.js +44 -0
  34. package/src/commands/org/relationship/list.js +52 -0
  35. package/src/commands/org/relationship/remove.js +46 -0
  36. package/src/commands/person/follower/add.js +31 -0
  37. package/src/commands/person/follower/list.js +38 -0
  38. package/src/commands/person/follower/remove.js +48 -0
  39. package/src/commands/project/list.js +10 -4
  40. package/src/commands/search.js +66 -9
  41. package/src/commands/task/create.js +44 -0
  42. package/src/commands/task/delete.js +41 -0
  43. package/src/commands/task/get.js +26 -0
  44. package/src/commands/task/list.js +60 -0
  45. package/src/commands/task/update.js +72 -0
  46. package/src/lib/client.js +19 -0
  47. package/src/lib/fields.js +43 -12
@@ -0,0 +1,72 @@
1
+ import { Args, Flags } from '@oclif/core'
2
+ import BaseCommand from '../../base-command.js'
3
+ import { buildWriteBody } from '../../lib/input.js'
4
+ import { outputRecord } from '../../lib/entity-view.js'
5
+ import { CliError } from '../../lib/errors.js'
6
+
7
+ export default class TaskUpdateCommand extends BaseCommand {
8
+ static description = 'Update a task (v2 PATCH — only provided fields change)'
9
+
10
+ static examples = [
11
+ '<%= config.bin %> task update 7 --title "Renamed"',
12
+ '<%= config.bin %> task update 7 --done',
13
+ '<%= config.bin %> task update 7 --assignee 9',
14
+ ]
15
+
16
+ static args = {
17
+ id: Args.integer({ required: true, description: 'Task ID' }),
18
+ }
19
+
20
+ static flags = {
21
+ ...BaseCommand.baseFlags,
22
+ title: Flags.string({ description: 'Task title' }),
23
+ project: Flags.integer({ description: 'Project ID' }),
24
+ description: Flags.string({ description: 'Task description' }),
25
+ assignee: Flags.integer({ description: 'Assignee (user) ID' }),
26
+ 'due-date': Flags.string({ description: 'Due date (YYYY-MM-DD)' }),
27
+ parent: Flags.integer({ description: 'Parent task ID' }),
28
+ done: Flags.boolean({
29
+ description: 'Mark the task as done',
30
+ exclusive: ['undone'],
31
+ }),
32
+ undone: Flags.boolean({
33
+ description: 'Mark the task as not done',
34
+ exclusive: ['done'],
35
+ }),
36
+ body: Flags.string({ description: 'Raw JSON body to merge (flags win)' }),
37
+ }
38
+
39
+ async run() {
40
+ const { args, flags } = await this.parse(TaskUpdateCommand)
41
+
42
+ // The v2 task body takes `done` as an integer enum (0 = not done, 1 = done).
43
+ let done
44
+ if (flags.done) done = 1
45
+ else if (flags.undone) done = 0
46
+
47
+ const body = buildWriteBody({
48
+ typed: {
49
+ title: flags.title,
50
+ project_id: flags.project,
51
+ description: flags.description,
52
+ assignee_id: flags.assignee,
53
+ due_date: flags['due-date'],
54
+ parent_task_id: flags.parent,
55
+ done,
56
+ },
57
+ rawBody: flags.body,
58
+ })
59
+
60
+ if (Object.keys(body).length === 0) {
61
+ throw new CliError(
62
+ 'Nothing to update — pass at least one field flag or --body',
63
+ { exitCode: 64 },
64
+ )
65
+ }
66
+
67
+ const res = await this.apiClient.patch(`/api/v2/tasks/${args.id}`, {
68
+ body,
69
+ })
70
+ await outputRecord(this, res.data)
71
+ }
72
+ }
package/src/lib/client.js CHANGED
@@ -307,6 +307,24 @@ export function createClient({
307
307
  })
308
308
  }
309
309
 
310
+ /**
311
+ * PUT application/x-www-form-urlencoded (v1 form endpoints, e.g.
312
+ * /api/v1/files/:id — JSON is not accepted there).
313
+ * @param {string} path
314
+ * @param {Record<string, unknown>} fields Null/undefined values are omitted.
315
+ */
316
+ async function putForm(path, fields = {}) {
317
+ const params = new URLSearchParams()
318
+ for (const [k, v] of Object.entries(fields)) {
319
+ if (v != null) params.set(k, String(v))
320
+ }
321
+ return transport('PUT', lockedUrl(path), {
322
+ path,
323
+ makeBody: () => params.toString(),
324
+ extraHeaders: { 'content-type': 'application/x-www-form-urlencoded' },
325
+ })
326
+ }
327
+
310
328
  return {
311
329
  get: (path, opts) => request('GET', path, opts),
312
330
  post: (path, opts) => request('POST', path, opts),
@@ -316,6 +334,7 @@ export function createClient({
316
334
  download,
317
335
  postMultipart,
318
336
  postForm,
337
+ putForm,
319
338
  pageV1,
320
339
  pageV2,
321
340
  }
package/src/lib/fields.js CHANGED
@@ -13,19 +13,35 @@ const ENTITY_FIELDS = {
13
13
  activity: 'activityFields',
14
14
  }
15
15
 
16
+ /**
17
+ * Entities whose fields live ONLY on v1 — offset-paginated and returning the
18
+ * legacy key/name shape that getFields normalizes to field_code/field_name.
19
+ */
20
+ const V1_ENTITY_FIELDS = {
21
+ lead: 'leadFields',
22
+ note: 'noteFields',
23
+ }
24
+
16
25
  /**
17
26
  * @param {string} entity deal | person | org(anization) | product | activity
18
- * @returns {string} v2 fields endpoint path
27
+ * | lead | note
28
+ * @returns {string} fields endpoint path (v2 for core entities, v1 for
29
+ * lead/note)
19
30
  */
20
31
  export function entityToFieldsPath(entity) {
21
- const resource = ENTITY_FIELDS[entity]
22
- if (!resource) {
23
- throw new CliError(
24
- `Unknown entity "${entity}". Use one of: ${Object.keys(ENTITY_FIELDS).join(', ')}`,
25
- { exitCode: 64 },
26
- )
27
- }
28
- return `/api/v2/${resource}`
32
+ const v2 = ENTITY_FIELDS[entity]
33
+ if (v2) return `/api/v2/${v2}`
34
+
35
+ const v1 = V1_ENTITY_FIELDS[entity]
36
+ if (v1) return `/api/v1/${v1}`
37
+
38
+ throw new CliError(
39
+ `Unknown entity "${entity}". Use one of: ${[
40
+ ...Object.keys(ENTITY_FIELDS),
41
+ ...Object.keys(V1_ENTITY_FIELDS),
42
+ ].join(', ')}`,
43
+ { exitCode: 64 },
44
+ )
29
45
  }
30
46
 
31
47
  /** @type {Map<string, object[]>} per-run field-definition cache */
@@ -35,9 +51,22 @@ export function clearFieldsCache() {
35
51
  cache.clear()
36
52
  }
37
53
 
54
+ /**
55
+ * Normalize a v1 field definition (key/name) to the v2 shape
56
+ * (field_code/field_name) so callers can treat both alike.
57
+ * @param {object} def
58
+ */
59
+ function normalizeV1Field(def) {
60
+ const { key, name, ...rest } = def
61
+ return { ...rest, field_code: key, field_name: name }
62
+ }
63
+
38
64
  /**
39
65
  * Fetch (and memoize for this run) all field definitions for an entity.
40
- * @param {{ pageV2: (path: string) => AsyncGenerator<object> }} client
66
+ * Core entities use the v2 cursor pager; lead/note use the v1 offset pager
67
+ * and are normalized to the v2 field_code/field_name shape.
68
+ * @param {{ pageV2: (path: string) => AsyncGenerator<object>,
69
+ * pageV1: (path: string) => AsyncGenerator<object> }} client
41
70
  * @param {string} entity
42
71
  * @returns {Promise<object[]>}
43
72
  */
@@ -45,10 +74,12 @@ export async function getFields(client, entity) {
45
74
  const path = entityToFieldsPath(entity)
46
75
  if (cache.has(path)) return cache.get(path)
47
76
 
77
+ const isV1 = path.startsWith('/api/v1/')
48
78
  debug('fetching field definitions: %s', path)
49
79
  const defs = []
50
- for await (const def of client.pageV2(path)) {
51
- defs.push(def)
80
+ const pager = isV1 ? client.pageV1(path) : client.pageV2(path)
81
+ for await (const def of pager) {
82
+ defs.push(isV1 ? normalizeV1Field(def) : def)
52
83
  }
53
84
  cache.set(path, defs)
54
85
  return defs