@pitcher/cli-scripts 2.2.2 → 2.4.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/README.md +203 -0
- package/dist/index.js +26 -2
- package/dist/src/commands/analytics.d.ts +12 -0
- package/dist/src/commands/analytics.js +45 -0
- package/dist/src/commands/announcements.d.ts +23 -0
- package/dist/src/commands/announcements.js +131 -0
- package/dist/src/commands/canvases.d.ts +25 -0
- package/dist/src/commands/canvases.js +73 -0
- package/dist/src/commands/dsrLanding.d.ts +28 -0
- package/dist/src/commands/dsrLanding.js +150 -0
- package/dist/src/commands/emailTemplate.d.ts +32 -0
- package/dist/src/commands/emailTemplate.js +124 -0
- package/dist/src/commands/files.d.ts +46 -0
- package/dist/src/commands/files.js +167 -0
- package/dist/src/commands/folders.d.ts +33 -0
- package/dist/src/commands/folders.js +120 -0
- package/dist/src/commands/instances.d.ts +21 -0
- package/dist/src/commands/instances.js +58 -0
- package/dist/src/commands/org.d.ts +9 -0
- package/dist/src/commands/org.js +26 -0
- package/dist/src/commands/scaffolds.d.ts +19 -0
- package/dist/src/commands/scaffolds.js +176 -0
- package/dist/src/commands/search.d.ts +13 -0
- package/dist/src/commands/search.js +35 -0
- package/dist/src/commands/sharedLinks.d.ts +16 -0
- package/dist/src/commands/sharedLinks.js +61 -0
- package/dist/src/commands/tags.d.ts +20 -0
- package/dist/src/commands/tags.js +52 -0
- package/dist/src/commands/withAuth.d.ts +3 -0
- package/dist/src/commands/withAuth.js +7 -0
- package/dist/src/commands/withInstance.d.ts +3 -0
- package/dist/src/commands/withInstance.js +4 -0
- package/dist/src/core/baseUrl.js +2 -2
- package/dist/src/core/http.d.ts +10 -0
- package/dist/src/core/http.js +6 -0
- package/dist/src/core/pagination.d.ts +30 -0
- package/dist/src/core/pagination.js +47 -0
- package/dist/src/core/readInput.d.ts +2 -0
- package/dist/src/core/readInput.js +23 -0
- package/dist/src/core/upload.d.ts +15 -0
- package/dist/src/core/upload.js +30 -0
- package/dist/src/schema/content.d.ts +12 -0
- package/dist/src/schema/content.js +63 -0
- package/package.json +3 -3
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { clientFor, toCliError } from '../core/http.js';
|
|
3
|
+
import { ok, fail } from '../core/envelope.js';
|
|
4
|
+
import { createCliError } from '../core/errors.js';
|
|
5
|
+
import { emitEnvelope } from '../core/output.js';
|
|
6
|
+
import { readTextFile } from '../core/readInput.js';
|
|
7
|
+
import { parseJsonArray } from '../schema/content.js';
|
|
8
|
+
import { withAuthFlags } from './withAuth.js';
|
|
9
|
+
import { withInstanceFlag } from './withInstance.js';
|
|
10
|
+
import { DSR_SCAFFOLD_HTML, emitScaffold } from './scaffolds.js';
|
|
11
|
+
const DSR_ENDPOINT = '/core/api/protected/dsr-landing-templates';
|
|
12
|
+
const REQUIRED_MARKER = 'data-pitcher-email-gate';
|
|
13
|
+
const TOKEN_HELP = `
|
|
14
|
+
Required marker (the platform injects the email-gate form here at render time):
|
|
15
|
+
data-pitcher-email-gate must be present verbatim in --html-file, or create fails client-side
|
|
16
|
+
|
|
17
|
+
Editable regions (optional, edited in the DSR landing editor without touching markup):
|
|
18
|
+
data-field-id="..." unique id for the editable block
|
|
19
|
+
data-field-editable="true" marks the block as editable
|
|
20
|
+
data-field-char-limit="N" max character length enforced by the editor
|
|
21
|
+
|
|
22
|
+
Tokens (resolved by the platform at render time; unknown tokens render blank):
|
|
23
|
+
{{recipient.firstName}}, {{recipient.lastName}}, {{recipient.email}} recipient fields
|
|
24
|
+
{{customer.name}} recipient's company
|
|
25
|
+
{{organization.name}} sharing org's display name
|
|
26
|
+
{{rep.name}} sharing rep's display name
|
|
27
|
+
{{sharedLink.valid_until}} share expiry, if set
|
|
28
|
+
|
|
29
|
+
Run "p dsr-landing scaffold" for a full working example.`;
|
|
30
|
+
export async function runDsrLandingCreate(opts, deps = {}) {
|
|
31
|
+
try {
|
|
32
|
+
if (!opts.name)
|
|
33
|
+
throw createCliError('VALIDATION', '--name is required for dsr-landing create');
|
|
34
|
+
if (!opts.instanceId)
|
|
35
|
+
throw createCliError('VALIDATION', '--instance-id is required for dsr-landing create');
|
|
36
|
+
if (!opts.htmlFile)
|
|
37
|
+
throw createCliError('VALIDATION', '--html-file is required for dsr-landing create');
|
|
38
|
+
// readTextFile throws VALIDATION on a missing/unreadable file, before any request is made.
|
|
39
|
+
const html = readTextFile(opts.htmlFile);
|
|
40
|
+
if (!html.includes(REQUIRED_MARKER)) {
|
|
41
|
+
throw createCliError('VALIDATION', `landing HTML must contain the ${REQUIRED_MARKER} marker`);
|
|
42
|
+
}
|
|
43
|
+
if (opts.language && opts.language.length > 10) {
|
|
44
|
+
throw createCliError('VALIDATION', '--language must be at most 10 characters');
|
|
45
|
+
}
|
|
46
|
+
const body = { instance_id: opts.instanceId, name: opts.name, html };
|
|
47
|
+
if (opts.cssFile)
|
|
48
|
+
body.css = readTextFile(opts.cssFile);
|
|
49
|
+
if (opts.mergeSchema)
|
|
50
|
+
body.merge_schema = parseJsonArray(opts.mergeSchema, 'merge-schema');
|
|
51
|
+
if (opts.thumbnailUrl)
|
|
52
|
+
body.thumbnail_url = opts.thumbnailUrl;
|
|
53
|
+
if (opts.language)
|
|
54
|
+
body.language = opts.language;
|
|
55
|
+
const client = clientFor(opts, deps);
|
|
56
|
+
const res = await client.post(DSR_ENDPOINT, body, { params: { instance_id: opts.instanceId } });
|
|
57
|
+
const created = res.data;
|
|
58
|
+
if (opts.default) {
|
|
59
|
+
// The template was created, but --default asked for a compound operation:
|
|
60
|
+
// if set-default can't complete, the instance default is NOT this template.
|
|
61
|
+
// Report that as a failure (non-ok, non-zero exit) so an agent keying on the
|
|
62
|
+
// envelope doesn't believe the default changed. The created id is surfaced in
|
|
63
|
+
// the message so the caller can retry `set-default` without re-creating.
|
|
64
|
+
const id = created?.id;
|
|
65
|
+
if (!id) {
|
|
66
|
+
return fail(createCliError('UNKNOWN', 'template created, but --default could not be applied: server response had no id'));
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
await client.post(`${DSR_ENDPOINT}/${encodeURIComponent(id)}/set-default`, undefined, {
|
|
70
|
+
params: { instance_id: opts.instanceId },
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
const err = toCliError(e);
|
|
75
|
+
return fail(createCliError(err.code, `template created (id ${id}), but set-default failed: ${err.message}. ` +
|
|
76
|
+
`Retry with: p dsr-landing set-default --instance-id ${opts.instanceId} --id ${id}`));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return ok(created);
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
return fail(toCliError(e));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export async function runDsrLandingSetDefault(opts, deps = {}) {
|
|
86
|
+
try {
|
|
87
|
+
if (!opts.id)
|
|
88
|
+
throw createCliError('VALIDATION', '--id is required for dsr-landing set-default');
|
|
89
|
+
if (!opts.instanceId)
|
|
90
|
+
throw createCliError('VALIDATION', '--instance-id is required for dsr-landing set-default');
|
|
91
|
+
const client = clientFor(opts, deps);
|
|
92
|
+
const res = await client.post(`${DSR_ENDPOINT}/${encodeURIComponent(opts.id)}/set-default`, undefined, {
|
|
93
|
+
params: { instance_id: opts.instanceId },
|
|
94
|
+
});
|
|
95
|
+
return ok(res.data);
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
return fail(toCliError(e));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export async function runDsrLandingList(opts, deps = {}) {
|
|
102
|
+
try {
|
|
103
|
+
if (!opts.instanceId)
|
|
104
|
+
throw createCliError('VALIDATION', '--instance-id is required for dsr-landing list');
|
|
105
|
+
const client = clientFor(opts, deps);
|
|
106
|
+
const res = await client.get(DSR_ENDPOINT, { params: { instance_id: opts.instanceId } });
|
|
107
|
+
return ok(res.data);
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
return fail(toCliError(e));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export async function runDsrLandingScaffold(opts, _deps = {}) {
|
|
114
|
+
return emitScaffold(DSR_SCAFFOLD_HTML, opts.out);
|
|
115
|
+
}
|
|
116
|
+
export function createDsrLandingCommand() {
|
|
117
|
+
const cmd = new Command('dsr-landing').description('create/list DSR (Digital Sales Room) landing-page templates, and set one as default');
|
|
118
|
+
withInstanceFlag(withAuthFlags(cmd.command('create')))
|
|
119
|
+
.requiredOption('--name <name>', 'Template name')
|
|
120
|
+
.requiredOption('--html-file <path>', `Path to the landing HTML (must contain ${REQUIRED_MARKER})`)
|
|
121
|
+
.option('--css-file <path>', 'Path to a CSS file for the template')
|
|
122
|
+
.option('--merge-schema <json>', 'Merge schema array as inline JSON')
|
|
123
|
+
.option('--language <lang>', 'Language code (max 10 chars)')
|
|
124
|
+
.option('--thumbnail-url <url>', 'Thumbnail image URL')
|
|
125
|
+
.option('--default', 'Also set this template as the instance-scope default after creating it')
|
|
126
|
+
.addHelpText('after', TOKEN_HELP)
|
|
127
|
+
.action(async (options, command) => {
|
|
128
|
+
const globals = command.optsWithGlobals();
|
|
129
|
+
emitEnvelope(await runDsrLandingCreate({ ...options, quiet: globals.quiet }), globals);
|
|
130
|
+
});
|
|
131
|
+
withInstanceFlag(withAuthFlags(cmd.command('set-default')))
|
|
132
|
+
.requiredOption('--id <id>', 'Template ID')
|
|
133
|
+
.action(async (options, command) => {
|
|
134
|
+
const globals = command.optsWithGlobals();
|
|
135
|
+
emitEnvelope(await runDsrLandingSetDefault({ ...options, quiet: globals.quiet }), globals);
|
|
136
|
+
});
|
|
137
|
+
withInstanceFlag(withAuthFlags(cmd.command('list'))).action(async (options, command) => {
|
|
138
|
+
const globals = command.optsWithGlobals();
|
|
139
|
+
emitEnvelope(await runDsrLandingList({ ...options, quiet: globals.quiet }), globals);
|
|
140
|
+
});
|
|
141
|
+
withAuthFlags(cmd.command('scaffold'))
|
|
142
|
+
.description('generate a starter DSR landing page HTML with the required marker and tokens in context')
|
|
143
|
+
.option('--out <path>', 'Write the scaffold HTML to a file instead of returning it inline')
|
|
144
|
+
.addHelpText('after', TOKEN_HELP)
|
|
145
|
+
.action(async (options, command) => {
|
|
146
|
+
const globals = command.optsWithGlobals();
|
|
147
|
+
emitEnvelope(await runDsrLandingScaffold({ ...options, quiet: globals.quiet }), globals);
|
|
148
|
+
});
|
|
149
|
+
return cmd;
|
|
150
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import type { AxiosInstance } from 'axios';
|
|
3
|
+
import { type CommonOpts } from '../core/http.js';
|
|
4
|
+
import { type Envelope } from '../core/envelope.js';
|
|
5
|
+
export interface EmailTemplateDeps {
|
|
6
|
+
client?: AxiosInstance;
|
|
7
|
+
}
|
|
8
|
+
export declare function runEmailTemplateCreate(opts: CommonOpts & {
|
|
9
|
+
instanceId?: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
bodyFile?: string;
|
|
12
|
+
subject?: string;
|
|
13
|
+
tags?: string;
|
|
14
|
+
metadata?: string;
|
|
15
|
+
}, deps?: EmailTemplateDeps): Promise<Envelope<unknown>>;
|
|
16
|
+
export declare function runEmailTemplateList(opts: CommonOpts & {
|
|
17
|
+
instanceId?: string;
|
|
18
|
+
name?: string;
|
|
19
|
+
status?: string;
|
|
20
|
+
category?: string;
|
|
21
|
+
target?: string;
|
|
22
|
+
limit?: number;
|
|
23
|
+
offset?: number;
|
|
24
|
+
}, deps?: EmailTemplateDeps): Promise<Envelope<unknown>>;
|
|
25
|
+
export declare function runEmailTemplateGet(opts: CommonOpts & {
|
|
26
|
+
instanceId?: string;
|
|
27
|
+
id?: string;
|
|
28
|
+
}, deps?: EmailTemplateDeps): Promise<Envelope<unknown>>;
|
|
29
|
+
export declare function runEmailTemplateScaffold(opts: CommonOpts & {
|
|
30
|
+
out?: string;
|
|
31
|
+
}, _deps?: EmailTemplateDeps): Promise<Envelope<unknown>>;
|
|
32
|
+
export declare function createEmailTemplateCommand(): Command;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { clientFor, toCliError } from '../core/http.js';
|
|
3
|
+
import { ok, fail } from '../core/envelope.js';
|
|
4
|
+
import { createCliError } from '../core/errors.js';
|
|
5
|
+
import { emitEnvelope } from '../core/output.js';
|
|
6
|
+
import { readTextFile } from '../core/readInput.js';
|
|
7
|
+
import { listLimitOffsetParams } from '../core/pagination.js';
|
|
8
|
+
import { assertIntFlags, parseJsonObject } from '../schema/content.js';
|
|
9
|
+
import { withAuthFlags } from './withAuth.js';
|
|
10
|
+
import { withInstanceFlag } from './withInstance.js';
|
|
11
|
+
import { EMAIL_SCAFFOLD_HTML, emitScaffold } from './scaffolds.js';
|
|
12
|
+
const TEMPLATES_ENDPOINT = '/core/api/protected/emails/templates';
|
|
13
|
+
const TOKEN_HELP = `
|
|
14
|
+
Tokens (resolved by the emails app at send time; unknown tokens render blank):
|
|
15
|
+
{{recipient.firstName}} recipient's first name
|
|
16
|
+
{{recipient.lastName}} recipient's last name
|
|
17
|
+
{{recipient.email}} recipient's email address
|
|
18
|
+
|
|
19
|
+
Run "p email-template scaffold" for a full working example.`;
|
|
20
|
+
export async function runEmailTemplateCreate(opts, deps = {}) {
|
|
21
|
+
try {
|
|
22
|
+
if (!opts.name)
|
|
23
|
+
throw createCliError('VALIDATION', '--name is required for email-template create');
|
|
24
|
+
if (!opts.instanceId)
|
|
25
|
+
throw createCliError('VALIDATION', '--instance-id is required for email-template create');
|
|
26
|
+
const body = { name: opts.name };
|
|
27
|
+
if (opts.subject)
|
|
28
|
+
body.subject = opts.subject;
|
|
29
|
+
// readTextFile throws VALIDATION on a missing/unreadable file, before any request is made.
|
|
30
|
+
if (opts.bodyFile)
|
|
31
|
+
body.body = readTextFile(opts.bodyFile);
|
|
32
|
+
if (opts.tags)
|
|
33
|
+
body.tags = parseJsonObject(opts.tags, 'tags');
|
|
34
|
+
if (opts.metadata)
|
|
35
|
+
body.metadata = parseJsonObject(opts.metadata, 'metadata');
|
|
36
|
+
const client = clientFor(opts, deps);
|
|
37
|
+
const res = await client.post(TEMPLATES_ENDPOINT, body, { headers: { 'x-instance-id': opts.instanceId } });
|
|
38
|
+
return ok(res.data);
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
return fail(toCliError(e));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export async function runEmailTemplateList(opts, deps = {}) {
|
|
45
|
+
try {
|
|
46
|
+
if (!opts.instanceId)
|
|
47
|
+
throw createCliError('VALIDATION', '--instance-id is required for email-template list');
|
|
48
|
+
assertIntFlags(opts);
|
|
49
|
+
const client = clientFor(opts, deps);
|
|
50
|
+
const params = { ...listLimitOffsetParams(opts) };
|
|
51
|
+
if (opts.name)
|
|
52
|
+
params.name = opts.name;
|
|
53
|
+
if (opts.status)
|
|
54
|
+
params.status = opts.status;
|
|
55
|
+
if (opts.category)
|
|
56
|
+
params.category = opts.category;
|
|
57
|
+
if (opts.target)
|
|
58
|
+
params.target = opts.target;
|
|
59
|
+
const res = await client.get(TEMPLATES_ENDPOINT, { params, headers: { 'x-instance-id': opts.instanceId } });
|
|
60
|
+
return ok(res.data);
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
return fail(toCliError(e));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export async function runEmailTemplateGet(opts, deps = {}) {
|
|
67
|
+
try {
|
|
68
|
+
if (!opts.instanceId)
|
|
69
|
+
throw createCliError('VALIDATION', '--instance-id is required for email-template get');
|
|
70
|
+
if (!opts.id)
|
|
71
|
+
throw createCliError('VALIDATION', '--id is required for email-template get');
|
|
72
|
+
const client = clientFor(opts, deps);
|
|
73
|
+
const res = await client.get(`${TEMPLATES_ENDPOINT}/${encodeURIComponent(opts.id)}`, {
|
|
74
|
+
headers: { 'x-instance-id': opts.instanceId },
|
|
75
|
+
});
|
|
76
|
+
return ok(res.data);
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
return fail(toCliError(e));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export async function runEmailTemplateScaffold(opts, _deps = {}) {
|
|
83
|
+
return emitScaffold(EMAIL_SCAFFOLD_HTML, opts.out);
|
|
84
|
+
}
|
|
85
|
+
export function createEmailTemplateCommand() {
|
|
86
|
+
const cmd = new Command('email-template').description('create/list/get HTML email templates for the emails app');
|
|
87
|
+
withInstanceFlag(withAuthFlags(cmd.command('create')))
|
|
88
|
+
.requiredOption('--name <name>', 'Template name')
|
|
89
|
+
.option('--body-file <path>', 'Path to an HTML file (inline CSS) used as the template body')
|
|
90
|
+
.option('--subject <text>', 'Email subject (supports {{tokens}})')
|
|
91
|
+
.option('--tags <json>', 'Tags object as inline JSON')
|
|
92
|
+
.option('--metadata <json>', 'Metadata object as inline JSON')
|
|
93
|
+
.addHelpText('after', TOKEN_HELP)
|
|
94
|
+
.action(async (options, command) => {
|
|
95
|
+
const globals = command.optsWithGlobals();
|
|
96
|
+
emitEnvelope(await runEmailTemplateCreate({ ...options, quiet: globals.quiet }), globals);
|
|
97
|
+
});
|
|
98
|
+
withInstanceFlag(withAuthFlags(cmd.command('list')))
|
|
99
|
+
.option('--name <name>', 'Case-insensitive substring filter, matched against name OR subject (not exact match)')
|
|
100
|
+
.option('--status <status>', 'Status filter')
|
|
101
|
+
.option('--category <category>', 'Category filter')
|
|
102
|
+
.option('--target <target>', 'Target filter')
|
|
103
|
+
.option('--limit <n>', 'Limit', (v) => parseInt(v, 10))
|
|
104
|
+
.option('--offset <n>', 'Offset', (v) => parseInt(v, 10))
|
|
105
|
+
.action(async (options, command) => {
|
|
106
|
+
const globals = command.optsWithGlobals();
|
|
107
|
+
emitEnvelope(await runEmailTemplateList({ ...options, quiet: globals.quiet }), globals);
|
|
108
|
+
});
|
|
109
|
+
withInstanceFlag(withAuthFlags(cmd.command('get')))
|
|
110
|
+
.requiredOption('--id <id>', 'Template ID')
|
|
111
|
+
.action(async (options, command) => {
|
|
112
|
+
const globals = command.optsWithGlobals();
|
|
113
|
+
emitEnvelope(await runEmailTemplateGet({ ...options, quiet: globals.quiet }), globals);
|
|
114
|
+
});
|
|
115
|
+
withAuthFlags(cmd.command('scaffold'))
|
|
116
|
+
.description('generate a starter HTML email template with tokens in context')
|
|
117
|
+
.option('--out <path>', 'Write the scaffold HTML to a file instead of returning it inline')
|
|
118
|
+
.addHelpText('after', TOKEN_HELP)
|
|
119
|
+
.action(async (options, command) => {
|
|
120
|
+
const globals = command.optsWithGlobals();
|
|
121
|
+
emitEnvelope(await runEmailTemplateScaffold({ ...options, quiet: globals.quiet }), globals);
|
|
122
|
+
});
|
|
123
|
+
return cmd;
|
|
124
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import axios, { type AxiosInstance } from 'axios';
|
|
3
|
+
import { type CommonOpts } from '../core/http.js';
|
|
4
|
+
import { type Envelope } from '../core/envelope.js';
|
|
5
|
+
export interface FilesDeps {
|
|
6
|
+
client?: AxiosInstance;
|
|
7
|
+
put?: typeof axios.put;
|
|
8
|
+
readFile?: (p: string) => Buffer;
|
|
9
|
+
}
|
|
10
|
+
export declare function runFilesList(opts: CommonOpts & {
|
|
11
|
+
instanceId?: string;
|
|
12
|
+
tags?: string;
|
|
13
|
+
idIn?: string;
|
|
14
|
+
page?: number;
|
|
15
|
+
pageSize?: number;
|
|
16
|
+
}, deps?: FilesDeps): Promise<Envelope<{
|
|
17
|
+
results: unknown[];
|
|
18
|
+
pagination: Record<string, unknown>;
|
|
19
|
+
}>>;
|
|
20
|
+
export declare function runFilesGet(opts: CommonOpts & {
|
|
21
|
+
id?: string;
|
|
22
|
+
}, deps?: FilesDeps): Promise<Envelope<unknown>>;
|
|
23
|
+
export declare function runFilesCreate(opts: CommonOpts & {
|
|
24
|
+
instanceId?: string;
|
|
25
|
+
name?: string;
|
|
26
|
+
folderId?: string;
|
|
27
|
+
filePath?: string;
|
|
28
|
+
sourceUrl?: string;
|
|
29
|
+
accessType?: string;
|
|
30
|
+
tags?: string;
|
|
31
|
+
metadata?: string;
|
|
32
|
+
appMetadata?: string;
|
|
33
|
+
}, deps?: FilesDeps): Promise<Envelope<unknown>>;
|
|
34
|
+
export declare function runFilesUpdate(opts: CommonOpts & {
|
|
35
|
+
id?: string;
|
|
36
|
+
instanceId?: string;
|
|
37
|
+
name?: string;
|
|
38
|
+
folderId?: string;
|
|
39
|
+
filePath?: string;
|
|
40
|
+
sourceUrl?: string;
|
|
41
|
+
accessType?: string;
|
|
42
|
+
tags?: string;
|
|
43
|
+
metadata?: string;
|
|
44
|
+
appMetadata?: string;
|
|
45
|
+
}, deps?: FilesDeps): Promise<Envelope<unknown>>;
|
|
46
|
+
export declare function createFilesCommand(): Command;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { clientFor, toCliError } from '../core/http.js';
|
|
3
|
+
import { ok, fail } from '../core/envelope.js';
|
|
4
|
+
import { createCliError } from '../core/errors.js';
|
|
5
|
+
import { emitEnvelope } from '../core/output.js';
|
|
6
|
+
import { uploadFile } from '../core/upload.js';
|
|
7
|
+
import { listPageParams, listPaginationEcho } from '../core/pagination.js';
|
|
8
|
+
import { parseTags, parseJsonObject, validateAccessType, deepMerge, assertIntFlags } from '../schema/content.js';
|
|
9
|
+
import { withAuthFlags } from './withAuth.js';
|
|
10
|
+
export async function runFilesList(
|
|
11
|
+
// NOTE: no folderId — the Django files list has no folder filter (listing a
|
|
12
|
+
// folder's files is a folders-retrieve concern). tags filters via `tags__in`.
|
|
13
|
+
opts, deps = {}) {
|
|
14
|
+
try {
|
|
15
|
+
if (!opts.instanceId)
|
|
16
|
+
throw createCliError('VALIDATION', '--instance-id is required for files list');
|
|
17
|
+
assertIntFlags(opts);
|
|
18
|
+
const client = clientFor(opts, deps);
|
|
19
|
+
const params = { instance_id: opts.instanceId, ...listPageParams(opts) };
|
|
20
|
+
if (opts.idIn)
|
|
21
|
+
params.id__in = opts.idIn;
|
|
22
|
+
if (opts.tags)
|
|
23
|
+
params.tags__in = opts.tags;
|
|
24
|
+
const res = await client.get('/api/v1/files/', { params });
|
|
25
|
+
return ok({ results: res.data.results, pagination: listPaginationEcho(opts, res.data) });
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
return fail(toCliError(e));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export async function runFilesGet(opts, deps = {}) {
|
|
32
|
+
try {
|
|
33
|
+
if (!opts.id)
|
|
34
|
+
throw createCliError('VALIDATION', 'file id is required');
|
|
35
|
+
const client = clientFor(opts, deps);
|
|
36
|
+
const res = await client.get(`/api/v1/files/${opts.id}`);
|
|
37
|
+
return ok(res.data);
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
return fail(toCliError(e));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export async function runFilesCreate(opts, deps = {}) {
|
|
44
|
+
try {
|
|
45
|
+
if (!opts.instanceId)
|
|
46
|
+
throw createCliError('VALIDATION', '--instance-id is required for files create');
|
|
47
|
+
const client = clientFor(opts, deps);
|
|
48
|
+
const body = { instance_id: opts.instanceId };
|
|
49
|
+
if (opts.name)
|
|
50
|
+
body.name = opts.name;
|
|
51
|
+
if (opts.folderId)
|
|
52
|
+
body.folder_id = opts.folderId;
|
|
53
|
+
const accessType = validateAccessType(opts.accessType);
|
|
54
|
+
if (accessType)
|
|
55
|
+
body.access_type = accessType;
|
|
56
|
+
if (opts.tags)
|
|
57
|
+
body.tags = parseTags(opts.tags);
|
|
58
|
+
if (opts.metadata)
|
|
59
|
+
body.metadata = parseJsonObject(opts.metadata, 'metadata');
|
|
60
|
+
if (opts.appMetadata)
|
|
61
|
+
body.app_metadata = parseJsonObject(opts.appMetadata, 'app-metadata');
|
|
62
|
+
if (opts.filePath && opts.sourceUrl)
|
|
63
|
+
throw createCliError('VALIDATION', 'pass either --file or --source-url, not both');
|
|
64
|
+
if (opts.filePath) {
|
|
65
|
+
body.content_upload_id = await uploadFile(client, { instanceId: opts.instanceId, filePath: opts.filePath }, deps);
|
|
66
|
+
}
|
|
67
|
+
else if (opts.sourceUrl) {
|
|
68
|
+
body.url = opts.sourceUrl;
|
|
69
|
+
}
|
|
70
|
+
const res = await client.post('/api/v1/files/', body);
|
|
71
|
+
return ok(res.data);
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
return fail(toCliError(e));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export async function runFilesUpdate(opts, deps = {}) {
|
|
78
|
+
try {
|
|
79
|
+
if (!opts.id)
|
|
80
|
+
throw createCliError('VALIDATION', 'file id is required');
|
|
81
|
+
const client = clientFor(opts, deps);
|
|
82
|
+
const body = {};
|
|
83
|
+
if (opts.name)
|
|
84
|
+
body.name = opts.name;
|
|
85
|
+
if (opts.folderId)
|
|
86
|
+
body.folder_id = opts.folderId;
|
|
87
|
+
const accessType = validateAccessType(opts.accessType);
|
|
88
|
+
if (accessType)
|
|
89
|
+
body.access_type = accessType;
|
|
90
|
+
if (opts.tags)
|
|
91
|
+
body.tags = parseTags(opts.tags);
|
|
92
|
+
// Metadata is a JSONField the PATCH replaces wholesale — read-merge-write.
|
|
93
|
+
const metaPatch = opts.metadata ? parseJsonObject(opts.metadata, 'metadata') : undefined;
|
|
94
|
+
const appMetaPatch = opts.appMetadata ? parseJsonObject(opts.appMetadata, 'app-metadata') : undefined;
|
|
95
|
+
if (metaPatch || appMetaPatch) {
|
|
96
|
+
const cur = (await client.get(`/api/v1/files/${opts.id}`)).data;
|
|
97
|
+
if (metaPatch)
|
|
98
|
+
body.metadata = deepMerge(cur.metadata, metaPatch);
|
|
99
|
+
if (appMetaPatch)
|
|
100
|
+
body.app_metadata = deepMerge(cur.app_metadata, appMetaPatch);
|
|
101
|
+
}
|
|
102
|
+
if (opts.filePath && opts.sourceUrl)
|
|
103
|
+
throw createCliError('VALIDATION', 'pass either --file or --source-url, not both');
|
|
104
|
+
if (opts.filePath) {
|
|
105
|
+
if (!opts.instanceId)
|
|
106
|
+
throw createCliError('VALIDATION', '--instance-id is required with --file');
|
|
107
|
+
body.content_upload_id = await uploadFile(client, { instanceId: opts.instanceId, filePath: opts.filePath }, deps);
|
|
108
|
+
}
|
|
109
|
+
else if (opts.sourceUrl) {
|
|
110
|
+
body.url = opts.sourceUrl;
|
|
111
|
+
}
|
|
112
|
+
const res = await client.patch(`/api/v1/files/${opts.id}`, body);
|
|
113
|
+
return ok(res.data);
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
return fail(toCliError(e));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
export function createFilesCommand() {
|
|
120
|
+
const cmd = new Command('files').description('manage Pitcher content files');
|
|
121
|
+
withAuthFlags(cmd.command('list'))
|
|
122
|
+
.requiredOption('--instance-id <id>', 'Instance ID')
|
|
123
|
+
.option('--id-in <ids>', 'Comma-separated file IDs')
|
|
124
|
+
.option('--tags <tags>', 'Comma-separated tags (filters via tags__in)')
|
|
125
|
+
.option('--page <n>', 'Page number', (v) => parseInt(v, 10))
|
|
126
|
+
.option('--page-size <n>', 'Page size (max 500)', (v) => parseInt(v, 10))
|
|
127
|
+
.action(async (options, command) => {
|
|
128
|
+
const globals = command.optsWithGlobals();
|
|
129
|
+
emitEnvelope(await runFilesList({ ...options, quiet: globals.quiet }), globals);
|
|
130
|
+
});
|
|
131
|
+
withAuthFlags(cmd.command('get'))
|
|
132
|
+
.argument('<id>', 'File ID')
|
|
133
|
+
.action(async (id, options, command) => {
|
|
134
|
+
const globals = command.optsWithGlobals();
|
|
135
|
+
emitEnvelope(await runFilesGet({ ...options, id, quiet: globals.quiet }), globals);
|
|
136
|
+
});
|
|
137
|
+
withAuthFlags(cmd.command('create'))
|
|
138
|
+
.requiredOption('--instance-id <id>', 'Instance ID')
|
|
139
|
+
.option('--name <name>', 'File name')
|
|
140
|
+
.option('--folder-id <id>', 'Destination folder')
|
|
141
|
+
.option('--file <path>', 'Local file to upload')
|
|
142
|
+
.option('--source-url <url>', 'Link/WEB file source URL')
|
|
143
|
+
.option('--access-type <type>', 'public | restricted | personal')
|
|
144
|
+
.option('--tags <tags>', 'Comma-separated tags')
|
|
145
|
+
.option('--metadata <json>', 'metadata JSON object')
|
|
146
|
+
.option('--app-metadata <json>', 'app_metadata JSON object')
|
|
147
|
+
.action(async (options, command) => {
|
|
148
|
+
const globals = command.optsWithGlobals();
|
|
149
|
+
emitEnvelope(await runFilesCreate({ ...options, filePath: options.file, quiet: globals.quiet }), globals);
|
|
150
|
+
});
|
|
151
|
+
withAuthFlags(cmd.command('update'))
|
|
152
|
+
.argument('<id>', 'File ID')
|
|
153
|
+
.option('--instance-id <id>', 'Instance ID (required only with --file)')
|
|
154
|
+
.option('--name <name>', 'File name')
|
|
155
|
+
.option('--folder-id <id>', 'Move to folder')
|
|
156
|
+
.option('--file <path>', 'Replace binary with local file')
|
|
157
|
+
.option('--source-url <url>', 'Replace with link/WEB source URL')
|
|
158
|
+
.option('--access-type <type>', 'public | restricted | personal')
|
|
159
|
+
.option('--tags <tags>', 'Comma-separated tags')
|
|
160
|
+
.option('--metadata <json>', 'metadata JSON object (deep-merged)')
|
|
161
|
+
.option('--app-metadata <json>', 'app_metadata JSON object (deep-merged)')
|
|
162
|
+
.action(async (id, options, command) => {
|
|
163
|
+
const globals = command.optsWithGlobals();
|
|
164
|
+
emitEnvelope(await runFilesUpdate({ ...options, id, filePath: options.file, quiet: globals.quiet }), globals);
|
|
165
|
+
});
|
|
166
|
+
return cmd;
|
|
167
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import type { AxiosInstance } from 'axios';
|
|
3
|
+
import { type CommonOpts } from '../core/http.js';
|
|
4
|
+
import { type Envelope } from '../core/envelope.js';
|
|
5
|
+
export interface FoldersDeps {
|
|
6
|
+
client?: AxiosInstance;
|
|
7
|
+
}
|
|
8
|
+
export declare function runFoldersList(opts: CommonOpts & {
|
|
9
|
+
instanceId?: string;
|
|
10
|
+
page?: number;
|
|
11
|
+
pageSize?: number;
|
|
12
|
+
}, deps?: FoldersDeps): Promise<Envelope<{
|
|
13
|
+
results: unknown[];
|
|
14
|
+
pagination: Record<string, unknown>;
|
|
15
|
+
}>>;
|
|
16
|
+
export declare function runFoldersGet(opts: CommonOpts & {
|
|
17
|
+
id?: string;
|
|
18
|
+
root?: boolean;
|
|
19
|
+
instanceId?: string;
|
|
20
|
+
}, deps?: FoldersDeps): Promise<Envelope<unknown>>;
|
|
21
|
+
export declare function runFoldersCreate(opts: CommonOpts & {
|
|
22
|
+
name?: string;
|
|
23
|
+
parentFolderId?: string;
|
|
24
|
+
accessType?: string;
|
|
25
|
+
rules?: string;
|
|
26
|
+
}, deps?: FoldersDeps): Promise<Envelope<unknown>>;
|
|
27
|
+
export declare function runFoldersUpdate(opts: CommonOpts & {
|
|
28
|
+
id?: string;
|
|
29
|
+
name?: string;
|
|
30
|
+
accessType?: string;
|
|
31
|
+
rules?: string;
|
|
32
|
+
}, deps?: FoldersDeps): Promise<Envelope<unknown>>;
|
|
33
|
+
export declare function createFoldersCommand(): Command;
|