@wavyx/pdcli 0.1.0 → 0.3.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/CHANGELOG.md +36 -1
- package/README.md +60 -11
- package/oclif.manifest.json +7101 -354
- package/package.json +4 -1
- package/src/base-command.js +65 -7
- package/src/commands/activity/create.js +63 -0
- package/src/commands/activity/delete.js +39 -0
- package/src/commands/activity/get.js +2 -17
- package/src/commands/activity/update.js +89 -0
- package/src/commands/api.js +6 -2
- package/src/commands/auth/login.js +102 -7
- package/src/commands/auth/logout.js +2 -1
- package/src/commands/auth/status.js +61 -13
- package/src/commands/backup.js +53 -0
- package/src/commands/deal/create.js +65 -0
- package/src/commands/deal/delete.js +39 -0
- package/src/commands/deal/get.js +2 -19
- package/src/commands/deal/update.js +79 -0
- package/src/commands/file/download.js +35 -0
- package/src/commands/file/get.js +26 -0
- package/src/commands/file/list.js +40 -0
- package/src/commands/file/upload.js +42 -0
- package/src/commands/filter/get.js +26 -0
- package/src/commands/filter/list.js +43 -0
- package/src/commands/goal/list.js +37 -0
- package/src/commands/lead/create.js +58 -0
- package/src/commands/lead/delete.js +39 -0
- package/src/commands/lead/get.js +26 -0
- package/src/commands/lead/list.js +50 -0
- package/src/commands/lead/update.js +71 -0
- package/src/commands/note/create.js +42 -0
- package/src/commands/note/delete.js +39 -0
- package/src/commands/note/get.js +26 -0
- package/src/commands/note/list.js +49 -0
- package/src/commands/note/update.js +45 -0
- package/src/commands/org/create.js +42 -0
- package/src/commands/org/delete.js +39 -0
- package/src/commands/org/get.js +2 -17
- package/src/commands/org/update.js +57 -0
- package/src/commands/person/create.js +54 -0
- package/src/commands/person/delete.js +39 -0
- package/src/commands/person/get.js +2 -17
- package/src/commands/person/update.js +69 -0
- package/src/commands/pipeline/get.js +26 -0
- package/src/commands/pipeline/list.js +37 -0
- package/src/commands/product/create.js +62 -0
- package/src/commands/product/delete.js +39 -0
- package/src/commands/product/get.js +26 -0
- package/src/commands/product/list.js +45 -0
- package/src/commands/product/update.js +77 -0
- package/src/commands/project/create.js +48 -0
- package/src/commands/project/delete.js +39 -0
- package/src/commands/project/get.js +26 -0
- package/src/commands/project/list.js +39 -0
- package/src/commands/project/update.js +63 -0
- package/src/commands/stage/get.js +26 -0
- package/src/commands/stage/list.js +41 -0
- package/src/commands/webhook/create.js +75 -0
- package/src/commands/webhook/delete.js +39 -0
- package/src/commands/webhook/list.js +33 -0
- package/src/lib/auth.js +227 -3
- package/src/lib/backup.js +122 -0
- package/src/lib/client.js +96 -6
- package/src/lib/confirm.js +10 -0
- package/src/lib/entity-view.js +42 -0
- package/src/lib/input.js +110 -0
- package/src/lib/keychain.js +47 -0
- package/src/lib/output/csv.js +26 -0
- package/src/lib/output/index.js +9 -1
- package/src/lib/output/yaml.js +9 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { getFields, makeResolver } from './fields.js'
|
|
2
|
+
import { flattenRecord } from './output/record.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Output a single entity record: raw JSON for scripting, or a transposed
|
|
6
|
+
* field/value table with custom-field hash keys and option IDs resolved.
|
|
7
|
+
* @param {import('../base-command.js').default} cmd
|
|
8
|
+
* @param {object} record
|
|
9
|
+
* @param {string} [entity] deal | person | org | activity | product — omit
|
|
10
|
+
* for entities without resolvable custom fields (notes, files, webhooks, …)
|
|
11
|
+
*/
|
|
12
|
+
export async function outputRecord(cmd, record, entity) {
|
|
13
|
+
if (cmd.resolveFormat() === 'table') {
|
|
14
|
+
if (
|
|
15
|
+
entity &&
|
|
16
|
+
record.custom_fields &&
|
|
17
|
+
Object.keys(record.custom_fields).length
|
|
18
|
+
) {
|
|
19
|
+
const defs = await getFields(cmd.apiClient, entity)
|
|
20
|
+
record = makeResolver(defs).resolveCustomFields(record)
|
|
21
|
+
}
|
|
22
|
+
await cmd.outputResults(flattenRecord(record), {
|
|
23
|
+
field: { header: 'Field' },
|
|
24
|
+
value: { header: 'Value' },
|
|
25
|
+
})
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
await cmd.outputResults(record, {})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Fetch field definitions only when --field entries are present.
|
|
34
|
+
* @param {import('../base-command.js').default} cmd
|
|
35
|
+
* @param {string} entity
|
|
36
|
+
* @param {string[]} [fieldFlags]
|
|
37
|
+
* @returns {Promise<object[] | undefined>}
|
|
38
|
+
*/
|
|
39
|
+
export async function defsForFields(cmd, entity, fieldFlags) {
|
|
40
|
+
if (!fieldFlags?.length) return undefined
|
|
41
|
+
return getFields(cmd.apiClient, entity)
|
|
42
|
+
}
|
package/src/lib/input.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { CliError } from './errors.js'
|
|
2
|
+
|
|
3
|
+
const NUMERIC_TYPES = new Set(['double', 'monetary', 'int'])
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Build a write-request body from typed flags, repeatable --field entries,
|
|
7
|
+
* and a raw --body JSON string. Precedence: raw body < typed flags/fields.
|
|
8
|
+
*
|
|
9
|
+
* --field entries are "Name=Value" where Name is a field's human name, its
|
|
10
|
+
* field_code, or a 40-char custom-field hash. Enum labels resolve to option
|
|
11
|
+
* IDs; set values are comma-separated labels; numeric field types coerce to
|
|
12
|
+
* Number. Custom-field values nest under custom_fields (v2 shape).
|
|
13
|
+
*
|
|
14
|
+
* @param {object} options
|
|
15
|
+
* @param {Record<string, unknown>} [options.typed] API-named values from typed flags
|
|
16
|
+
* @param {string[]} [options.fields] repeatable --field "Name=Value" entries
|
|
17
|
+
* @param {string} [options.rawBody] raw JSON string from --body
|
|
18
|
+
* @param {object[]} [options.defs] field definitions (required when fields given)
|
|
19
|
+
* @returns {object} request body
|
|
20
|
+
*/
|
|
21
|
+
export function buildWriteBody({
|
|
22
|
+
typed = {},
|
|
23
|
+
fields = [],
|
|
24
|
+
rawBody,
|
|
25
|
+
defs,
|
|
26
|
+
} = {}) {
|
|
27
|
+
let body = {}
|
|
28
|
+
|
|
29
|
+
if (rawBody) {
|
|
30
|
+
try {
|
|
31
|
+
body = JSON.parse(rawBody)
|
|
32
|
+
} catch (err) {
|
|
33
|
+
throw new CliError(`--body is not valid JSON: ${err.message}`, {
|
|
34
|
+
exitCode: 65,
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for (const [key, value] of Object.entries(typed)) {
|
|
40
|
+
if (value !== undefined) body[key] = value
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const entry of fields) {
|
|
44
|
+
const eq = entry.indexOf('=')
|
|
45
|
+
if (eq === -1) {
|
|
46
|
+
throw new CliError(`Invalid --field "${entry}". Expected Name=Value`, {
|
|
47
|
+
exitCode: 64,
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
const name = entry.slice(0, eq).trim()
|
|
51
|
+
const rawValue = entry.slice(eq + 1)
|
|
52
|
+
|
|
53
|
+
const def = findField(defs ?? [], name)
|
|
54
|
+
if (!def) {
|
|
55
|
+
throw new CliError(
|
|
56
|
+
`Unknown field "${name}". Run: pdcli field list <entity>`,
|
|
57
|
+
{ exitCode: 65 },
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const value = coerceValue(def, rawValue)
|
|
62
|
+
|
|
63
|
+
if (def.is_custom_field ?? isHashKey(def.field_code)) {
|
|
64
|
+
body.custom_fields ??= {}
|
|
65
|
+
body.custom_fields[def.field_code] = value
|
|
66
|
+
} else {
|
|
67
|
+
body[def.field_code] = value
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return body
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isHashKey(s) {
|
|
75
|
+
return /^[a-f0-9]{40}$/.test(s)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function findField(defs, name) {
|
|
79
|
+
const lower = name.toLowerCase()
|
|
80
|
+
return defs.find(
|
|
81
|
+
(d) => d.field_name.toLowerCase() === lower || d.field_code === name,
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function coerceValue(def, rawValue) {
|
|
86
|
+
if (def.field_type === 'enum') {
|
|
87
|
+
return resolveOption(def, rawValue)
|
|
88
|
+
}
|
|
89
|
+
if (def.field_type === 'set') {
|
|
90
|
+
return rawValue.split(',').map((label) => resolveOption(def, label.trim()))
|
|
91
|
+
}
|
|
92
|
+
if (NUMERIC_TYPES.has(def.field_type)) {
|
|
93
|
+
return Number(rawValue)
|
|
94
|
+
}
|
|
95
|
+
return rawValue
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function resolveOption(def, label) {
|
|
99
|
+
const option = def.options?.find(
|
|
100
|
+
(o) => o.label.toLowerCase() === label.toLowerCase(),
|
|
101
|
+
)
|
|
102
|
+
if (!option) {
|
|
103
|
+
const valid = def.options?.map((o) => o.label).join(', ') ?? '(none)'
|
|
104
|
+
throw new CliError(
|
|
105
|
+
`Unknown option "${label}" for field "${def.field_name}". Valid: ${valid}`,
|
|
106
|
+
{ exitCode: 65 },
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
return option.id
|
|
110
|
+
}
|
package/src/lib/keychain.js
CHANGED
|
@@ -64,6 +64,53 @@ export async function deleteToken(profile) {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* @typedef {object} OAuthTokens
|
|
69
|
+
* @property {string} accessToken
|
|
70
|
+
* @property {string} refreshToken
|
|
71
|
+
* @property {number} expiresAt epoch ms
|
|
72
|
+
* @property {string} apiDomain e.g. https://acme.pipedrive.com
|
|
73
|
+
* @property {string} clientId
|
|
74
|
+
* @property {string} clientSecret kept in the keychain, never in config
|
|
75
|
+
*/
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {string} profile
|
|
79
|
+
* @returns {Promise<OAuthTokens | null>}
|
|
80
|
+
*/
|
|
81
|
+
export async function getOAuthTokens(profile) {
|
|
82
|
+
if (!Entry) return null
|
|
83
|
+
const account = `${profile}/oauth`
|
|
84
|
+
try {
|
|
85
|
+
const raw = getEntry(account).getPassword()
|
|
86
|
+
return raw ? JSON.parse(raw) : null
|
|
87
|
+
} catch (err) {
|
|
88
|
+
debug('getOAuthTokens error: %s', err.message)
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {string} profile
|
|
95
|
+
* @param {OAuthTokens} tokens
|
|
96
|
+
*/
|
|
97
|
+
export async function setOAuthTokens(profile, tokens) {
|
|
98
|
+
if (!Entry) keychainRequired()
|
|
99
|
+
const account = `${profile}/oauth`
|
|
100
|
+
getEntry(account).setPassword(JSON.stringify(tokens))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** @param {string} profile */
|
|
104
|
+
export async function deleteOAuthTokens(profile) {
|
|
105
|
+
if (!Entry) return
|
|
106
|
+
const account = `${profile}/oauth`
|
|
107
|
+
try {
|
|
108
|
+
getEntry(account).deletePassword()
|
|
109
|
+
} catch (err) {
|
|
110
|
+
debug('deleteOAuthTokens error: %s', err.message)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
67
114
|
export function isKeychainAvailable() {
|
|
68
115
|
return Entry !== null
|
|
69
116
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {object[]} data
|
|
3
|
+
* @param {Record<string, import('./table.js').Column>} columns
|
|
4
|
+
* @returns {string}
|
|
5
|
+
*/
|
|
6
|
+
export function formatCsv(data, columns) {
|
|
7
|
+
if (!data || data.length === 0) return ''
|
|
8
|
+
const entries = Object.entries(columns)
|
|
9
|
+
const header = entries.map(([, col]) => col.header).join(',')
|
|
10
|
+
const rows = data.map((row) =>
|
|
11
|
+
entries
|
|
12
|
+
.map(([key, col]) => {
|
|
13
|
+
const val = col.get ? col.get(row) : row[col.key ?? key]
|
|
14
|
+
return csvEscape(val != null ? String(val) : '')
|
|
15
|
+
})
|
|
16
|
+
.join(','),
|
|
17
|
+
)
|
|
18
|
+
return [header, ...rows].join('\n')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function csvEscape(val) {
|
|
22
|
+
if (val.includes(',') || val.includes('"') || val.includes('\n')) {
|
|
23
|
+
return '"' + val.replace(/"/g, '""') + '"'
|
|
24
|
+
}
|
|
25
|
+
return val
|
|
26
|
+
}
|
package/src/lib/output/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { formatTable } from './table.js'
|
|
2
2
|
import { formatJson } from './json.js'
|
|
3
|
+
import { formatYaml } from './yaml.js'
|
|
4
|
+
import { formatCsv } from './csv.js'
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* @param {object | object[]} data
|
|
6
8
|
* @param {Record<string, import('./table.js').Column>} columns
|
|
7
|
-
* @param {'table' | 'json'} format
|
|
9
|
+
* @param {'table' | 'json' | 'yaml' | 'csv'} format
|
|
8
10
|
* @param {import('@oclif/core').Command} cmd
|
|
9
11
|
*/
|
|
10
12
|
export function formatOutput(data, columns, format, cmd) {
|
|
@@ -14,6 +16,12 @@ export function formatOutput(data, columns, format, cmd) {
|
|
|
14
16
|
case 'json':
|
|
15
17
|
cmd.log(formatJson(data))
|
|
16
18
|
break
|
|
19
|
+
case 'yaml':
|
|
20
|
+
cmd.log(formatYaml(data))
|
|
21
|
+
break
|
|
22
|
+
case 'csv':
|
|
23
|
+
cmd.log(formatCsv(items, columns))
|
|
24
|
+
break
|
|
17
25
|
case 'table':
|
|
18
26
|
default:
|
|
19
27
|
cmd.log(formatTable(items, columns))
|