@pitcher/cli-scripts 2.3.0 → 2.5.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 +220 -0
- package/dist/index.js +24 -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.js +5 -8
- package/dist/src/commands/folders.js +5 -8
- 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/themes.d.ts +17 -0
- package/dist/src/commands/themes.js +82 -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/pagination.d.ts +11 -0
- package/dist/src/core/pagination.js +14 -0
- package/dist/src/core/readInput.d.ts +2 -0
- package/dist/src/core/readInput.js +23 -0
- package/dist/src/schema/content.d.ts +3 -0
- package/dist/src/schema/content.js +13 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,3 +86,223 @@ p dev-sync watch --file-id <pitcher_file_id> --org pitcher
|
|
|
86
86
|
- Run from your app's project root (with `dist/` folder) or directly from the `dist/` folder
|
|
87
87
|
- The `watch` command automatically reverts changes on graceful shutdown (Ctrl+C)
|
|
88
88
|
- Apps with dev override show a badge in the installed apps view
|
|
89
|
+
|
|
90
|
+
## To run the `files` command:
|
|
91
|
+
|
|
92
|
+
Manage Pitcher content files — list within an instance, fetch by id, create (upload or link), or update.
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# List files in an instance (paginated)
|
|
96
|
+
p files list --instance-id 01... --api-key myapikey
|
|
97
|
+
|
|
98
|
+
# Filter by tags or specific IDs
|
|
99
|
+
p files list --instance-id 01... --tags brochure,demo --api-key myapikey
|
|
100
|
+
|
|
101
|
+
# Get a single file by id
|
|
102
|
+
p files get 01FILEID... --api-key myapikey
|
|
103
|
+
|
|
104
|
+
# Create a file from a local upload
|
|
105
|
+
p files create --instance-id 01... --name "Q3 Deck" --file ./deck.pdf --api-key myapikey
|
|
106
|
+
|
|
107
|
+
# Create a link/WEB file from a source URL
|
|
108
|
+
p files create --instance-id 01... --name "Product Page" --source-url https://example.com --api-key myapikey
|
|
109
|
+
|
|
110
|
+
# Update a file's tags or metadata (metadata/app-metadata are deep-merged, not replaced)
|
|
111
|
+
p files update 01FILEID... --tags updated,reviewed --api-key myapikey
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## To run the `folders` command:
|
|
115
|
+
|
|
116
|
+
Manage Pitcher content folders — list, fetch (or the instance root), create, and update smart-folder rules.
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# List folders in an instance (paginated)
|
|
120
|
+
p folders list --instance-id 01... --api-key myapikey
|
|
121
|
+
|
|
122
|
+
# Get a folder by id
|
|
123
|
+
p folders get 01FOLDERID... --api-key myapikey
|
|
124
|
+
|
|
125
|
+
# Get the instance root folder
|
|
126
|
+
p folders get --root --instance-id 01... --api-key myapikey
|
|
127
|
+
|
|
128
|
+
# Create a folder under a parent
|
|
129
|
+
p folders create --name "Marketing" --parent-folder-id 01PARENTID... --api-key myapikey
|
|
130
|
+
|
|
131
|
+
# Update a folder's name or access type
|
|
132
|
+
p folders update 01FOLDERID... --name "Sales Enablement" --access-type restricted --api-key myapikey
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## To run the `search` command:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# Ranked cross-type search (files, canvases, users, apps) within an instance
|
|
139
|
+
p search --instance-id 01... --query "quarterly deck" --api-key myapikey
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## To run the `instances` command:
|
|
143
|
+
|
|
144
|
+
Discover Pitcher instances — resolve names to instance IDs before running other commands.
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# List instances (optionally filter by region/storage-region/environment)
|
|
148
|
+
p instances list --api-key myapikey
|
|
149
|
+
|
|
150
|
+
p instances list --environment Demo --api-key myapikey
|
|
151
|
+
|
|
152
|
+
# Get a single instance by id
|
|
153
|
+
p instances info 01INSTANCEID... --api-key myapikey
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## To run the `canvases` command:
|
|
157
|
+
|
|
158
|
+
Read Pitcher canvases — presentations, sections, and blocks.
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
# List canvases in an instance (paginated)
|
|
162
|
+
p canvases list --instance-id 01... --api-key myapikey
|
|
163
|
+
|
|
164
|
+
# Only templates or only sections
|
|
165
|
+
p canvases list --instance-id 01... --is-template --api-key myapikey
|
|
166
|
+
p canvases list --instance-id 01... --is-section --api-key myapikey
|
|
167
|
+
|
|
168
|
+
# Only blocks (blocks are hidden unless --is-block is passed)
|
|
169
|
+
p canvases list --instance-id 01... --is-block --api-key myapikey
|
|
170
|
+
|
|
171
|
+
# Get a single canvas by id
|
|
172
|
+
p canvases get 01CANVASID... --api-key myapikey
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## To run the `shared-links` command:
|
|
176
|
+
|
|
177
|
+
Create and read share links for canvases or files (returns the `short_url`).
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
# Create a share link for a canvas
|
|
181
|
+
p shared-links create --canvas-id 01CANVASID... --api-key myapikey
|
|
182
|
+
|
|
183
|
+
# Create a share link for a file
|
|
184
|
+
p shared-links create --file-id 01FILEID... --api-key myapikey
|
|
185
|
+
|
|
186
|
+
# Fetch the existing share link for a canvas
|
|
187
|
+
p shared-links get --canvas-id 01CANVASID... --api-key myapikey
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## To run the `tags` command:
|
|
191
|
+
|
|
192
|
+
List tags in an instance (the server requires a search term of at least 3 characters).
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
# List tags matching a search term
|
|
196
|
+
p tags list --instance-id 01... --search "demo" --api-key myapikey
|
|
197
|
+
|
|
198
|
+
# Filter by type
|
|
199
|
+
p tags list --instance-id 01... --search "demo" --type canvases --api-key myapikey
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## To run the `email-template` command:
|
|
203
|
+
|
|
204
|
+
Create/list/get HTML email templates for the emails app. `create`, `list`, and `get` require `--instance-id` (`scaffold` does not).
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
# Create a template from an HTML file (inline CSS)
|
|
208
|
+
p email-template create --instance-id 01... --name "Welcome" --body-file ./welcome.html --api-key myapikey
|
|
209
|
+
|
|
210
|
+
# List templates (optionally filter by name/status/category/target)
|
|
211
|
+
p email-template list --instance-id 01... --api-key myapikey
|
|
212
|
+
|
|
213
|
+
# Get a template by id
|
|
214
|
+
p email-template get --instance-id 01... --id 01TEMPLATEID... --api-key myapikey
|
|
215
|
+
|
|
216
|
+
# Generate a starter HTML file with tokens in context
|
|
217
|
+
p email-template scaffold --out ./welcome.html --api-key myapikey
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Run `p email-template create --help` for the full token/marker reference.
|
|
221
|
+
|
|
222
|
+
`scaffold` returns the starter HTML in the envelope `data` (never printed raw to stdout); pass `--out <path>` to write it to a file instead.
|
|
223
|
+
|
|
224
|
+
## To run the `dsr-landing` command:
|
|
225
|
+
|
|
226
|
+
Create/list DSR (Digital Sales Room) landing-page templates, and set one as the instance-scope default. `create`, `list`, and `set-default` require `--instance-id` (`scaffold` does not).
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
# Create a landing template from an HTML file (must contain the email-gate marker)
|
|
230
|
+
p dsr-landing create --instance-id 01... --name "Default" --html-file ./landing.html --api-key myapikey
|
|
231
|
+
|
|
232
|
+
# Create and set as default in one call
|
|
233
|
+
p dsr-landing create --instance-id 01... --name "Default" --html-file ./landing.html --default --api-key myapikey
|
|
234
|
+
|
|
235
|
+
# Set an existing template as the default
|
|
236
|
+
p dsr-landing set-default --instance-id 01... --id 01TEMPLATEID... --api-key myapikey
|
|
237
|
+
|
|
238
|
+
# List templates in an instance
|
|
239
|
+
p dsr-landing list --instance-id 01... --api-key myapikey
|
|
240
|
+
|
|
241
|
+
# Generate a starter HTML file with the required marker and tokens in context
|
|
242
|
+
p dsr-landing scaffold --out ./landing.html --api-key myapikey
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
`--html-file` must contain the `data-pitcher-email-gate` marker verbatim (the platform injects the email-gate form there) — `create` fails client-side before any request if it's missing.
|
|
246
|
+
|
|
247
|
+
Run `p dsr-landing create --help` for the full token/marker reference (editable-region attributes, recipient/customer/org/rep tokens).
|
|
248
|
+
|
|
249
|
+
`scaffold` returns the starter HTML in the envelope `data`; pass `--out <path>` to write it to a file instead.
|
|
250
|
+
|
|
251
|
+
## To run the `announcements` command:
|
|
252
|
+
|
|
253
|
+
Create/list announcement records for the wa-announcements app. `create` and `list` require `--instance-id` (`scaffold` does not).
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
# Create from a JSON data file
|
|
257
|
+
p announcements create --instance-id 01... --from-file ./announcement.json --api-key myapikey
|
|
258
|
+
|
|
259
|
+
# Create from convenience flags only
|
|
260
|
+
p announcements create --instance-id 01... --title "New feature" --body "<p>Check it out</p>" --api-key myapikey
|
|
261
|
+
|
|
262
|
+
# List announcements
|
|
263
|
+
p announcements list --instance-id 01... --api-key myapikey
|
|
264
|
+
|
|
265
|
+
# Generate a fully populated starter data.json
|
|
266
|
+
p announcements scaffold --out ./announcement.json --api-key myapikey
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
`create` requires either `--from-file` or both `--title` and `--body`. `--title`/`--body`/`--priority` override matching keys from `--from-file` when both are given. Run `p announcements create --help` for the full `data` field catalog (media, links, scheduling, target groups, popup overrides) — note `start_at`/`expires_at` are naive local dates (`YYYY-MM-DD[THH:mm]`), not ISO/UTC.
|
|
270
|
+
|
|
271
|
+
`scaffold` returns the starter JSON in the envelope `data`; pass `--out <path>` to write it to a file instead.
|
|
272
|
+
|
|
273
|
+
## To run the `themes` command:
|
|
274
|
+
|
|
275
|
+
Read canvas/branding themes (colors via `settings`, fonts via `typography`, plus `assets`/`logo`) to reference when authoring email/DSR/canvas content. Read-only.
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
# List all themes in the org (returns full theme objects)
|
|
279
|
+
p themes list --api-key myapikey
|
|
280
|
+
|
|
281
|
+
# Narrow to one instance; add per-theme canvas usage and canvas ids
|
|
282
|
+
p themes list --instance-id 01... --with-usage --with-canvas-ids --api-key myapikey
|
|
283
|
+
|
|
284
|
+
# Get one theme by id
|
|
285
|
+
p themes get --id 01THEMEID... --api-key myapikey
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`list` is server-capped (newest-first) with no `--limit`/`--page`; when the cap is hit the envelope carries a truncation warning. `--instance-id` narrows to themes bound to that instance and **excludes** org-level (default) themes, so omit it to see everything usable in the org.
|
|
289
|
+
|
|
290
|
+
## To run the `org` command:
|
|
291
|
+
|
|
292
|
+
Read the organization the API key belongs to.
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
p org get --api-key myapikey
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## To run the `analytics` command:
|
|
299
|
+
|
|
300
|
+
Read rollup analytics totals for an organization or instance. Requires an org-admin API key — a non-admin key returns an auth error.
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
# Organization-wide rollups (org-admin key required)
|
|
304
|
+
p analytics org --api-key myapikey
|
|
305
|
+
|
|
306
|
+
# Instance rollups (instance-admin or org-admin key required)
|
|
307
|
+
p analytics instance --instance-id 01... --api-key myapikey
|
|
308
|
+
```
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Command } from 'commander';
|
|
3
2
|
import { readFileSync } from 'fs';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
3
|
import { dirname, join } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { Command } from 'commander';
|
|
6
6
|
import { createPublishAppCommand } from './src/commands/publishApp.js';
|
|
7
7
|
import { createDevSyncCommand } from './src/commands/devSync.js';
|
|
8
8
|
import { createFilesCommand } from './src/commands/files.js';
|
|
9
9
|
import { createFoldersCommand } from './src/commands/folders.js';
|
|
10
|
+
import { createSearchCommand } from './src/commands/search.js';
|
|
11
|
+
import { createInstancesCommand } from './src/commands/instances.js';
|
|
12
|
+
import { createCanvasesCommand } from './src/commands/canvases.js';
|
|
13
|
+
import { createSharedLinksCommand } from './src/commands/sharedLinks.js';
|
|
14
|
+
import { createTagsCommand } from './src/commands/tags.js';
|
|
15
|
+
import { createOrgCommand } from './src/commands/org.js';
|
|
16
|
+
import { createAnalyticsCommand } from './src/commands/analytics.js';
|
|
17
|
+
import { createAnnouncementsCommand } from './src/commands/announcements.js';
|
|
18
|
+
import { createDsrLandingCommand } from './src/commands/dsrLanding.js';
|
|
19
|
+
import { createEmailTemplateCommand } from './src/commands/emailTemplate.js';
|
|
20
|
+
import { createThemesCommand } from './src/commands/themes.js';
|
|
10
21
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
22
|
const __dirname = dirname(__filename);
|
|
12
23
|
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
@@ -21,4 +32,15 @@ program.addCommand(createPublishAppCommand());
|
|
|
21
32
|
program.addCommand(createDevSyncCommand());
|
|
22
33
|
program.addCommand(createFilesCommand());
|
|
23
34
|
program.addCommand(createFoldersCommand());
|
|
35
|
+
program.addCommand(createSearchCommand());
|
|
36
|
+
program.addCommand(createInstancesCommand());
|
|
37
|
+
program.addCommand(createCanvasesCommand());
|
|
38
|
+
program.addCommand(createSharedLinksCommand());
|
|
39
|
+
program.addCommand(createTagsCommand());
|
|
40
|
+
program.addCommand(createAnnouncementsCommand());
|
|
41
|
+
program.addCommand(createDsrLandingCommand());
|
|
42
|
+
program.addCommand(createEmailTemplateCommand());
|
|
43
|
+
program.addCommand(createThemesCommand());
|
|
44
|
+
program.addCommand(createOrgCommand());
|
|
45
|
+
program.addCommand(createAnalyticsCommand());
|
|
24
46
|
program.parseAsync(process.argv);
|
|
@@ -0,0 +1,12 @@
|
|
|
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 AnalyticsDeps {
|
|
6
|
+
client?: AxiosInstance;
|
|
7
|
+
}
|
|
8
|
+
export declare function runAnalyticsOrg(opts: CommonOpts, deps?: AnalyticsDeps): Promise<Envelope<unknown>>;
|
|
9
|
+
export declare function runAnalyticsInstance(opts: CommonOpts & {
|
|
10
|
+
instanceId?: string;
|
|
11
|
+
}, deps?: AnalyticsDeps): Promise<Envelope<unknown>>;
|
|
12
|
+
export declare function createAnalyticsCommand(): Command;
|
|
@@ -0,0 +1,45 @@
|
|
|
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 { withAuthFlags } from './withAuth.js';
|
|
7
|
+
export async function runAnalyticsOrg(opts, deps = {}) {
|
|
8
|
+
try {
|
|
9
|
+
const client = clientFor(opts, deps);
|
|
10
|
+
const res = await client.get('/api/v1/organization/analytics/');
|
|
11
|
+
return ok(res.data);
|
|
12
|
+
}
|
|
13
|
+
catch (e) {
|
|
14
|
+
return fail(toCliError(e));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function runAnalyticsInstance(opts, deps = {}) {
|
|
18
|
+
try {
|
|
19
|
+
if (!opts.instanceId)
|
|
20
|
+
throw createCliError('VALIDATION', '--instance-id is required for analytics instance');
|
|
21
|
+
const client = clientFor(opts, deps);
|
|
22
|
+
const res = await client.get(`/api/v1/instances/${encodeURIComponent(opts.instanceId)}/analytics/`);
|
|
23
|
+
return ok(res.data);
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
return fail(toCliError(e));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function createAnalyticsCommand() {
|
|
30
|
+
const cmd = new Command('analytics').description('read rollup analytics totals (requires an org-admin API key)');
|
|
31
|
+
withAuthFlags(cmd.command('org'))
|
|
32
|
+
.description('organization rollups (org-admin key required)')
|
|
33
|
+
.action(async (options, command) => {
|
|
34
|
+
const globals = command.optsWithGlobals();
|
|
35
|
+
emitEnvelope(await runAnalyticsOrg({ ...options, quiet: globals.quiet }), globals);
|
|
36
|
+
});
|
|
37
|
+
withAuthFlags(cmd.command('instance'))
|
|
38
|
+
.requiredOption('--instance-id <id>', 'Instance ID')
|
|
39
|
+
.description('instance rollups (instance-admin or org-admin key required)')
|
|
40
|
+
.action(async (options, command) => {
|
|
41
|
+
const globals = command.optsWithGlobals();
|
|
42
|
+
emitEnvelope(await runAnalyticsInstance({ ...options, quiet: globals.quiet }), globals);
|
|
43
|
+
});
|
|
44
|
+
return cmd;
|
|
45
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AxiosInstance } from 'axios';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { type Envelope } from '../core/envelope.js';
|
|
4
|
+
import { type CommonOpts } from '../core/http.js';
|
|
5
|
+
export interface AnnouncementsDeps {
|
|
6
|
+
client?: AxiosInstance;
|
|
7
|
+
}
|
|
8
|
+
export declare function runAnnouncementsCreate(opts: CommonOpts & {
|
|
9
|
+
instanceId?: string;
|
|
10
|
+
fromFile?: string;
|
|
11
|
+
title?: string;
|
|
12
|
+
body?: string;
|
|
13
|
+
priority?: string;
|
|
14
|
+
}, deps?: AnnouncementsDeps): Promise<Envelope<unknown>>;
|
|
15
|
+
export declare function runAnnouncementsList(opts: CommonOpts & {
|
|
16
|
+
instanceId?: string;
|
|
17
|
+
limit?: number;
|
|
18
|
+
offset?: number;
|
|
19
|
+
}, deps?: AnnouncementsDeps): Promise<Envelope<unknown>>;
|
|
20
|
+
export declare function runAnnouncementsScaffold(opts: CommonOpts & {
|
|
21
|
+
out?: string;
|
|
22
|
+
}, _deps?: AnnouncementsDeps): Promise<Envelope<unknown>>;
|
|
23
|
+
export declare function createAnnouncementsCommand(): Command;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { ok, fail } from '../core/envelope.js';
|
|
3
|
+
import { createCliError } from '../core/errors.js';
|
|
4
|
+
import { clientFor, toCliError } from '../core/http.js';
|
|
5
|
+
import { emitEnvelope } from '../core/output.js';
|
|
6
|
+
import { readJsonFile } from '../core/readInput.js';
|
|
7
|
+
import { listLimitOffsetParams } from '../core/pagination.js';
|
|
8
|
+
import { assertIntFlags, isPlainObject } from '../schema/content.js';
|
|
9
|
+
import { ANNOUNCEMENT_SCAFFOLD_JSON, emitScaffold } from './scaffolds.js';
|
|
10
|
+
import { withAuthFlags } from './withAuth.js';
|
|
11
|
+
import { withInstanceFlag } from './withInstance.js';
|
|
12
|
+
const APPSDB_ENDPOINT = '/core/api/protected/appsdb';
|
|
13
|
+
const ANNOUNCEMENT_TYPE = 'wa-announcement';
|
|
14
|
+
const FIELD_HELP = `
|
|
15
|
+
data fields (wa-announcements app; all optional except title/body):
|
|
16
|
+
title string, required announcement headline
|
|
17
|
+
body string (HTML), required announcement body
|
|
18
|
+
media_type 'none' | 'image' | 'video' media attachment kind
|
|
19
|
+
media_url string URL of the attached media
|
|
20
|
+
link_type 'url' | 'file' call-to-action link kind
|
|
21
|
+
link_url string target URL when link_type='url'
|
|
22
|
+
link_label string call-to-action button text
|
|
23
|
+
link_file_id string target file id when link_type='file'
|
|
24
|
+
link_target_name string display name for the linked target
|
|
25
|
+
priority 'normal' | 'important' visual emphasis
|
|
26
|
+
author_name string displayed author
|
|
27
|
+
published_at string display-only publish label
|
|
28
|
+
start_at string when the announcement becomes visible
|
|
29
|
+
expires_at string when the announcement stops being visible
|
|
30
|
+
target_groups string[] content-distribution group scoping
|
|
31
|
+
display_mode ('inline' | 'popup')[] where the announcement renders
|
|
32
|
+
popup_title string | null popup-only title override
|
|
33
|
+
popup_body string | null popup-only body override
|
|
34
|
+
popup_link_label string | null popup-only link label override
|
|
35
|
+
|
|
36
|
+
Naive local dates: start_at/expires_at are 'YYYY-MM-DD' or 'YYYY-MM-DDTHH:mm'
|
|
37
|
+
compared as LOCAL wall-clock time by the app, not ISO/UTC. '' means "none".
|
|
38
|
+
Do not convert these to ISO/UTC — that would shift the displayed time.
|
|
39
|
+
|
|
40
|
+
Run "p announcements scaffold" for a fully populated example.`;
|
|
41
|
+
export async function runAnnouncementsCreate(opts, deps = {}) {
|
|
42
|
+
try {
|
|
43
|
+
if (!opts.instanceId)
|
|
44
|
+
throw createCliError('VALIDATION', '--instance-id is required for announcements create');
|
|
45
|
+
if (!opts.fromFile && !(opts.title && opts.body)) {
|
|
46
|
+
throw createCliError('VALIDATION', 'announcements create requires either --from-file or both --title and --body');
|
|
47
|
+
}
|
|
48
|
+
let data = {};
|
|
49
|
+
if (opts.fromFile) {
|
|
50
|
+
// readJsonFile throws VALIDATION on a missing/unreadable/malformed file, before any request is made.
|
|
51
|
+
const fromFileData = readJsonFile(opts.fromFile);
|
|
52
|
+
if (!isPlainObject(fromFileData)) {
|
|
53
|
+
throw createCliError('VALIDATION', '--from-file must contain a JSON object (not an array or primitive)');
|
|
54
|
+
}
|
|
55
|
+
data = { ...fromFileData };
|
|
56
|
+
}
|
|
57
|
+
// Convenience flags override matching keys from --from-file when set.
|
|
58
|
+
if (opts.title !== undefined)
|
|
59
|
+
data.title = opts.title;
|
|
60
|
+
if (opts.body !== undefined)
|
|
61
|
+
data.body = opts.body;
|
|
62
|
+
if (opts.priority !== undefined)
|
|
63
|
+
data.priority = opts.priority;
|
|
64
|
+
// Re-check after the merge: a --from-file missing (or mistyping) title/body
|
|
65
|
+
// — e.g. `{}`, `{ "body": "x" }`, or `{ "title": 123 }` — must not slip
|
|
66
|
+
// through just because --from-file was present. Require non-empty strings,
|
|
67
|
+
// not just truthy values. No request may be sent when this fails.
|
|
68
|
+
const isNonEmptyString = (v) => typeof v === 'string' && v.trim().length > 0;
|
|
69
|
+
if (!isNonEmptyString(data.title) || !isNonEmptyString(data.body)) {
|
|
70
|
+
throw createCliError('VALIDATION', 'announcement requires non-empty string title and body (via --from-file or --title/--body)');
|
|
71
|
+
}
|
|
72
|
+
const client = clientFor(opts, deps);
|
|
73
|
+
// Deliberately omit `user_id`/`no_user_id`: omitting user_id yields the
|
|
74
|
+
// desired instance-shared row (see design §4.3); there is no --owned flag.
|
|
75
|
+
const res = await client.post(APPSDB_ENDPOINT, { type: ANNOUNCEMENT_TYPE, instance_id: opts.instanceId, data }, { params: { instance_id: opts.instanceId }, headers: { 'x-instance-id': opts.instanceId } });
|
|
76
|
+
return ok(res.data);
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
return fail(toCliError(e));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export async function runAnnouncementsList(opts, deps = {}) {
|
|
83
|
+
try {
|
|
84
|
+
if (!opts.instanceId)
|
|
85
|
+
throw createCliError('VALIDATION', '--instance-id is required for announcements list');
|
|
86
|
+
assertIntFlags(opts);
|
|
87
|
+
const client = clientFor(opts, deps);
|
|
88
|
+
const params = {
|
|
89
|
+
type: ANNOUNCEMENT_TYPE,
|
|
90
|
+
instance_id: opts.instanceId,
|
|
91
|
+
...listLimitOffsetParams(opts),
|
|
92
|
+
};
|
|
93
|
+
const res = await client.get(APPSDB_ENDPOINT, { params, headers: { 'x-instance-id': opts.instanceId } });
|
|
94
|
+
return ok(res.data);
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
return fail(toCliError(e));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export async function runAnnouncementsScaffold(opts, _deps = {}) {
|
|
101
|
+
return emitScaffold(ANNOUNCEMENT_SCAFFOLD_JSON, opts.out);
|
|
102
|
+
}
|
|
103
|
+
export function createAnnouncementsCommand() {
|
|
104
|
+
const cmd = new Command('announcements').description('create/list announcement records for the wa-announcements app');
|
|
105
|
+
withInstanceFlag(withAuthFlags(cmd.command('create')))
|
|
106
|
+
.option('--from-file <path>', 'Path to a JSON file with the announcement data object')
|
|
107
|
+
.option('--title <text>', 'Announcement title (overrides a matching --from-file key)')
|
|
108
|
+
.option('--body <html>', 'Announcement body HTML (overrides a matching --from-file key)')
|
|
109
|
+
.option('--priority <priority>', "Priority, e.g. 'normal' or 'important' (overrides a matching --from-file key)")
|
|
110
|
+
.addHelpText('after', FIELD_HELP)
|
|
111
|
+
.action(async (options, command) => {
|
|
112
|
+
const globals = command.optsWithGlobals();
|
|
113
|
+
emitEnvelope(await runAnnouncementsCreate({ ...options, quiet: globals.quiet }), globals);
|
|
114
|
+
});
|
|
115
|
+
withInstanceFlag(withAuthFlags(cmd.command('list')))
|
|
116
|
+
.option('--limit <n>', 'Limit', (v) => parseInt(v, 10))
|
|
117
|
+
.option('--offset <n>', 'Offset', (v) => parseInt(v, 10))
|
|
118
|
+
.action(async (options, command) => {
|
|
119
|
+
const globals = command.optsWithGlobals();
|
|
120
|
+
emitEnvelope(await runAnnouncementsList({ ...options, quiet: globals.quiet }), globals);
|
|
121
|
+
});
|
|
122
|
+
withAuthFlags(cmd.command('scaffold'))
|
|
123
|
+
.description('generate a fully populated starter announcement data.json')
|
|
124
|
+
.option('--out <path>', 'Write the scaffold JSON to a file instead of returning it inline')
|
|
125
|
+
.addHelpText('after', FIELD_HELP)
|
|
126
|
+
.action(async (options, command) => {
|
|
127
|
+
const globals = command.optsWithGlobals();
|
|
128
|
+
emitEnvelope(await runAnnouncementsScaffold({ ...options, quiet: globals.quiet }), globals);
|
|
129
|
+
});
|
|
130
|
+
return cmd;
|
|
131
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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 CanvasesDeps {
|
|
6
|
+
client?: AxiosInstance;
|
|
7
|
+
}
|
|
8
|
+
export declare function runCanvasesList(opts: CommonOpts & {
|
|
9
|
+
instanceId?: string;
|
|
10
|
+
isTemplate?: boolean;
|
|
11
|
+
isSection?: boolean;
|
|
12
|
+
isBlock?: boolean;
|
|
13
|
+
tags?: string;
|
|
14
|
+
idIn?: string;
|
|
15
|
+
accessType?: string;
|
|
16
|
+
page?: number;
|
|
17
|
+
pageSize?: number;
|
|
18
|
+
}, deps?: CanvasesDeps): Promise<Envelope<{
|
|
19
|
+
results: unknown[];
|
|
20
|
+
pagination: Record<string, unknown>;
|
|
21
|
+
}>>;
|
|
22
|
+
export declare function runCanvasesGet(opts: CommonOpts & {
|
|
23
|
+
id?: string;
|
|
24
|
+
}, deps?: CanvasesDeps): Promise<Envelope<unknown>>;
|
|
25
|
+
export declare function createCanvasesCommand(): Command;
|
|
@@ -0,0 +1,73 @@
|
|
|
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 { listPageParams, listPaginationEcho } from '../core/pagination.js';
|
|
7
|
+
import { validateAccessType, assertIntFlags } from '../schema/content.js';
|
|
8
|
+
import { withAuthFlags } from './withAuth.js';
|
|
9
|
+
export async function runCanvasesList(opts, deps = {}) {
|
|
10
|
+
try {
|
|
11
|
+
if (!opts.instanceId)
|
|
12
|
+
throw createCliError('VALIDATION', '--instance-id is required for canvases list');
|
|
13
|
+
assertIntFlags(opts);
|
|
14
|
+
const client = clientFor(opts, deps);
|
|
15
|
+
const params = { instance_id: opts.instanceId, ...listPageParams(opts) };
|
|
16
|
+
// Send each type flag ONLY when set. The server FilterSet does
|
|
17
|
+
// setdefault("is_block", False), so blocks are hidden unless --is-block.
|
|
18
|
+
if (opts.isTemplate)
|
|
19
|
+
params.is_template = true;
|
|
20
|
+
if (opts.isSection)
|
|
21
|
+
params.is_section = true;
|
|
22
|
+
if (opts.isBlock)
|
|
23
|
+
params.is_block = true;
|
|
24
|
+
if (opts.tags)
|
|
25
|
+
params.tags__in = opts.tags;
|
|
26
|
+
if (opts.idIn)
|
|
27
|
+
params.id__in = opts.idIn;
|
|
28
|
+
const accessType = validateAccessType(opts.accessType);
|
|
29
|
+
if (accessType)
|
|
30
|
+
params.access_type = accessType;
|
|
31
|
+
const res = await client.get('/api/v1/canvases/', { params });
|
|
32
|
+
return ok({ results: res.data.results, pagination: listPaginationEcho(opts, res.data) });
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
return fail(toCliError(e));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export async function runCanvasesGet(opts, deps = {}) {
|
|
39
|
+
try {
|
|
40
|
+
if (!opts.id)
|
|
41
|
+
throw createCliError('VALIDATION', 'canvas id is required');
|
|
42
|
+
const client = clientFor(opts, deps);
|
|
43
|
+
const res = await client.get(`/api/v1/canvases/${encodeURIComponent(opts.id)}`);
|
|
44
|
+
return ok(res.data);
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
return fail(toCliError(e));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export function createCanvasesCommand() {
|
|
51
|
+
const cmd = new Command('canvases').description('read Pitcher canvases (presentations, sections, blocks)');
|
|
52
|
+
withAuthFlags(cmd.command('list'))
|
|
53
|
+
.requiredOption('--instance-id <id>', 'Instance ID')
|
|
54
|
+
.option('--is-template', 'Only templates')
|
|
55
|
+
.option('--is-section', 'Only sections')
|
|
56
|
+
.option('--is-block', 'Only blocks (blocks are hidden unless this flag is set)')
|
|
57
|
+
.option('--tags <tags>', 'Comma-separated tags (filters via tags__in)')
|
|
58
|
+
.option('--id-in <ids>', 'Comma-separated canvas IDs')
|
|
59
|
+
.option('--access-type <type>', 'public | restricted | personal')
|
|
60
|
+
.option('--page <n>', 'Page number', (v) => parseInt(v, 10))
|
|
61
|
+
.option('--page-size <n>', 'Page size (max 500)', (v) => parseInt(v, 10))
|
|
62
|
+
.action(async (options, command) => {
|
|
63
|
+
const globals = command.optsWithGlobals();
|
|
64
|
+
emitEnvelope(await runCanvasesList({ ...options, quiet: globals.quiet }), globals);
|
|
65
|
+
});
|
|
66
|
+
withAuthFlags(cmd.command('get'))
|
|
67
|
+
.argument('<id>', 'Canvas ID')
|
|
68
|
+
.action(async (id, options, command) => {
|
|
69
|
+
const globals = command.optsWithGlobals();
|
|
70
|
+
emitEnvelope(await runCanvasesGet({ ...options, id, quiet: globals.quiet }), globals);
|
|
71
|
+
});
|
|
72
|
+
return cmd;
|
|
73
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
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 DsrLandingDeps {
|
|
6
|
+
client?: AxiosInstance;
|
|
7
|
+
}
|
|
8
|
+
export declare function runDsrLandingCreate(opts: CommonOpts & {
|
|
9
|
+
instanceId?: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
htmlFile?: string;
|
|
12
|
+
cssFile?: string;
|
|
13
|
+
mergeSchema?: string;
|
|
14
|
+
thumbnailUrl?: string;
|
|
15
|
+
language?: string;
|
|
16
|
+
default?: boolean;
|
|
17
|
+
}, deps?: DsrLandingDeps): Promise<Envelope<unknown>>;
|
|
18
|
+
export declare function runDsrLandingSetDefault(opts: CommonOpts & {
|
|
19
|
+
instanceId?: string;
|
|
20
|
+
id?: string;
|
|
21
|
+
}, deps?: DsrLandingDeps): Promise<Envelope<unknown>>;
|
|
22
|
+
export declare function runDsrLandingList(opts: CommonOpts & {
|
|
23
|
+
instanceId?: string;
|
|
24
|
+
}, deps?: DsrLandingDeps): Promise<Envelope<unknown>>;
|
|
25
|
+
export declare function runDsrLandingScaffold(opts: CommonOpts & {
|
|
26
|
+
out?: string;
|
|
27
|
+
}, _deps?: DsrLandingDeps): Promise<Envelope<unknown>>;
|
|
28
|
+
export declare function createDsrLandingCommand(): Command;
|