monday-cli 0.3.0 → 0.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/CHANGELOG.md +351 -0
- package/README.md +130 -36
- package/dist/api/assets.d.ts +326 -0
- package/dist/api/assets.d.ts.map +1 -0
- package/dist/api/assets.js +519 -0
- package/dist/api/assets.js.map +1 -0
- package/dist/api/column-types.d.ts +11 -7
- package/dist/api/column-types.d.ts.map +1 -1
- package/dist/api/column-types.js +5 -3
- package/dist/api/column-types.js.map +1 -1
- package/dist/api/column-values.d.ts +7 -1
- package/dist/api/column-values.d.ts.map +1 -1
- package/dist/api/column-values.js +15 -6
- package/dist/api/column-values.js.map +1 -1
- package/dist/api/documents.d.ts +519 -0
- package/dist/api/documents.d.ts.map +1 -0
- package/dist/api/documents.js +586 -0
- package/dist/api/documents.js.map +1 -0
- package/dist/api/item-watch.d.ts +263 -0
- package/dist/api/item-watch.d.ts.map +1 -0
- package/dist/api/item-watch.js +709 -0
- package/dist/api/item-watch.js.map +1 -0
- package/dist/api/multipart-transport.d.ts +223 -0
- package/dist/api/multipart-transport.d.ts.map +1 -0
- package/dist/api/multipart-transport.js +274 -0
- package/dist/api/multipart-transport.js.map +1 -0
- package/dist/api/parallel-dispatch.d.ts +155 -0
- package/dist/api/parallel-dispatch.d.ts.map +1 -0
- package/dist/api/parallel-dispatch.js +243 -0
- package/dist/api/parallel-dispatch.js.map +1 -0
- package/dist/api/partial-success-bulk.d.ts +118 -60
- package/dist/api/partial-success-bulk.d.ts.map +1 -1
- package/dist/api/partial-success-bulk.js +137 -79
- package/dist/api/partial-success-bulk.js.map +1 -1
- package/dist/api/partial-success-mutation.d.ts +13 -1
- package/dist/api/partial-success-mutation.d.ts.map +1 -1
- package/dist/api/partial-success-mutation.js +5 -1
- package/dist/api/partial-success-mutation.js.map +1 -1
- package/dist/api/raw-write.d.ts +12 -4
- package/dist/api/raw-write.d.ts.map +1 -1
- package/dist/api/raw-write.js +21 -11
- package/dist/api/raw-write.js.map +1 -1
- package/dist/api/resolve-client.d.ts +11 -0
- package/dist/api/resolve-client.d.ts.map +1 -1
- package/dist/api/resolve-client.js +9 -1
- package/dist/api/resolve-client.js.map +1 -1
- package/dist/cli/run.d.ts +20 -0
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +1 -0
- package/dist/cli/run.js.map +1 -1
- package/dist/commands/board/column-create.d.ts +6 -5
- package/dist/commands/board/column-create.d.ts.map +1 -1
- package/dist/commands/board/column-create.js +9 -6
- package/dist/commands/board/column-create.js.map +1 -1
- package/dist/commands/completion.d.ts +188 -0
- package/dist/commands/completion.d.ts.map +1 -0
- package/dist/commands/completion.js +418 -0
- package/dist/commands/completion.js.map +1 -0
- package/dist/commands/doc/get.d.ts +46 -0
- package/dist/commands/doc/get.d.ts.map +1 -0
- package/dist/commands/doc/get.js +95 -0
- package/dist/commands/doc/get.js.map +1 -0
- package/dist/commands/doc/list.d.ts +83 -0
- package/dist/commands/doc/list.d.ts.map +1 -0
- package/dist/commands/doc/list.js +248 -0
- package/dist/commands/doc/list.js.map +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +46 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/item/create.js +2 -2
- package/dist/commands/item/update.d.ts +1 -0
- package/dist/commands/item/update.d.ts.map +1 -1
- package/dist/commands/item/update.js +61 -0
- package/dist/commands/item/update.js.map +1 -1
- package/dist/commands/item/upload.d.ts +108 -0
- package/dist/commands/item/upload.d.ts.map +1 -0
- package/dist/commands/item/upload.js +370 -0
- package/dist/commands/item/upload.js.map +1 -0
- package/dist/commands/item/watch.d.ts +90 -0
- package/dist/commands/item/watch.d.ts.map +1 -0
- package/dist/commands/item/watch.js +342 -0
- package/dist/commands/item/watch.js.map +1 -0
- package/dist/commands/update/upload.d.ts +69 -0
- package/dist/commands/update/upload.d.ts.map +1 -0
- package/dist/commands/update/upload.js +235 -0
- package/dist/commands/update/upload.js.map +1 -0
- package/dist/types/ids.d.ts +2 -0
- package/dist/types/ids.d.ts.map +1 -1
- package/dist/types/ids.js +9 -2
- package/dist/types/ids.js.map +1 -1
- package/dist/utils/mime.d.ts +24 -0
- package/dist/utils/mime.d.ts.map +1 -0
- package/dist/utils/mime.js +64 -0
- package/dist/utils/mime.js.map +1 -0
- package/dist/utils/output/envelope.d.ts +30 -0
- package/dist/utils/output/envelope.d.ts.map +1 -1
- package/dist/utils/output/envelope.js +26 -0
- package/dist/utils/output/envelope.js.map +1 -1
- package/dist/utils/output/ndjson.d.ts +25 -0
- package/dist/utils/output/ndjson.d.ts.map +1 -1
- package/dist/utils/output/ndjson.js +12 -0
- package/dist/utils/output/ndjson.js.map +1 -1
- package/dist/utils/signal.d.ts +42 -0
- package/dist/utils/signal.d.ts.map +1 -0
- package/dist/utils/signal.js +45 -0
- package/dist/utils/signal.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `monday item watch <iid>` — polling-based event stream over the
|
|
3
|
+
* v0.3-M24 `item-history-projection.ts` projector. Runtime body
|
|
4
|
+
* shipped at v0.4-M29 IMPL (`7b83a3a` + round-1 fix-ups). Pinned
|
|
5
|
+
* per cli-design §13 v0.4 entry + §14.4 closure (`31713fb`) +
|
|
6
|
+
* v0.4-plan §3 M29.
|
|
7
|
+
*
|
|
8
|
+
* **What this verb answers:** "wait for changes on this item + emit
|
|
9
|
+
* them as they arrive". Single CLI invocation polls Monday's
|
|
10
|
+
* `boards(ids:){ activity_logs(item_ids:, from:, limit:) }` surface
|
|
11
|
+
* each tick, projects new rows through the M24 projector, emits one
|
|
12
|
+
* NDJSON record per new event plus a session-summary trailer on exit.
|
|
13
|
+
*
|
|
14
|
+
* **GraphQL operation:** `ItemWatchPoll` (one per poll tick;
|
|
15
|
+
* R-NEW-37 W2 audit-point — operationName pinned in
|
|
16
|
+
* `WATCH_POLL_QUERY` literal at `src/api/item-watch.ts`).
|
|
17
|
+
*
|
|
18
|
+
* **Action shape (M29 IMPL).** Item-board lookup via
|
|
19
|
+
* `lookupItemBoard` → `watchItem` polling loop with NDJSON
|
|
20
|
+
* `onEvent` streaming hook + per-event projection via M24's
|
|
21
|
+
* `projectActivityLogRow` → trailer-meta emit on graceful exit.
|
|
22
|
+
* SIGINT graceful drain via `ctx.signal` (the same AbortSignal seam
|
|
23
|
+
* M22 status uses). The polling loop owns:
|
|
24
|
+
*
|
|
25
|
+
* - Cadence (default 30s; range 1s–1h; `--interval <ms>`).
|
|
26
|
+
* - Watermark advance (last-seen-event-id; `--since <event-id>`
|
|
27
|
+
* bootstraps).
|
|
28
|
+
* - Circuit breaker (reactive on Monday wire errors; trip after 5
|
|
29
|
+
* consecutive failures; per-failure warnings accumulate on
|
|
30
|
+
* `WatchItemResult.warnings` and fold into the trailer's
|
|
31
|
+
* `_meta.warnings` slot at session end per cli-design §6.3 —
|
|
32
|
+
* NOT interleaved with event lines).
|
|
33
|
+
* - Limit enforcement (`--max-events <n>` / `--max-duration
|
|
34
|
+
* <seconds>`).
|
|
35
|
+
* - `--once` short-circuit (drain backlog and exit; do NOT poll).
|
|
36
|
+
*
|
|
37
|
+
* **Output:** NDJSON only at v0.4-M29 — `--json` / `--table` /
|
|
38
|
+
* `--output` global flags ignored (this is a streaming verb).
|
|
39
|
+
* Trailer-meta carries seven M29-specific slots:
|
|
40
|
+
* `events_emitted` + `polls_made` + `failed_polls` +
|
|
41
|
+
* `watch_duration_seconds` + `last_seen_event_id` +
|
|
42
|
+
* `circuit_broken_at` + `exit_reason`. Plus the standard §6.3
|
|
43
|
+
* `_meta.warnings[]` slot collects any `WatchSessionWarning`
|
|
44
|
+
* records the polling loop accumulated (poll_failed,
|
|
45
|
+
* circuit_breaker_armed, unknown_event_kind) — warnings are NOT
|
|
46
|
+
* interleaved with event lines.
|
|
47
|
+
*
|
|
48
|
+
* Idempotent: yes (pure read; re-running with the same `--since` is
|
|
49
|
+
* safe).
|
|
50
|
+
*/
|
|
51
|
+
import { z } from 'zod';
|
|
52
|
+
import { ensureSubcommand } from '../types.js';
|
|
53
|
+
import { parseArgv } from '../parse-argv.js';
|
|
54
|
+
import { resolveClient } from '../../api/resolve-client.js';
|
|
55
|
+
import { lookupItemBoard } from '../../api/item-board-lookup.js';
|
|
56
|
+
import { SourceAggregator } from '../../api/source-aggregator.js';
|
|
57
|
+
import { buildStreamingTrailerMeta, startNdjsonStream, } from '../../utils/output/ndjson.js';
|
|
58
|
+
import { collectSecrets } from '../../cli/envelope-out.js';
|
|
59
|
+
import { ApiError } from '../../utils/errors.js';
|
|
60
|
+
import { ItemIdSchema } from '../../types/ids.js';
|
|
61
|
+
import { CIRCUIT_BREAKER_CONSECUTIVE_FAILS, DEFAULT_WATCH_INTERVAL_MS, MAX_WATCH_INTERVAL_MS, MIN_WATCH_INTERVAL_MS, watchItem, } from '../../api/item-watch.js';
|
|
62
|
+
import { historyEventSchema, } from '../../api/item-history-projection.js';
|
|
63
|
+
/**
|
|
64
|
+
* The `--include` flag accepts a comma-separated list of literal kind
|
|
65
|
+
* values from the M24 closed event-kind taxonomy (9 kinds). Mirrors
|
|
66
|
+
* `item history`'s `--kinds` enum verbatim — forward-compat at the
|
|
67
|
+
* argv boundary even though v0.4-M29 only emits activity-log-sourced
|
|
68
|
+
* kinds (the projector's `update_posted` / `update_replied` variants
|
|
69
|
+
* are from the updates source, which v0.4-M29 doesn't poll; agents
|
|
70
|
+
* passing those kinds get no events at v0.4 but the argv accepts them
|
|
71
|
+
* for v0.5+ comment-polling compatibility).
|
|
72
|
+
*/
|
|
73
|
+
const WATCH_KIND_LITERALS = [
|
|
74
|
+
'update_column_value',
|
|
75
|
+
'create_column',
|
|
76
|
+
'create_group',
|
|
77
|
+
'update_board_name',
|
|
78
|
+
'update_board_nickname',
|
|
79
|
+
'board_workspace_id_changed',
|
|
80
|
+
'update_posted',
|
|
81
|
+
'update_replied',
|
|
82
|
+
'unknown',
|
|
83
|
+
];
|
|
84
|
+
const watchKindSchema = z.enum(WATCH_KIND_LITERALS);
|
|
85
|
+
/**
|
|
86
|
+
* Parses `--include <list>` (comma-separated; commander hands it over
|
|
87
|
+
* as a raw string). Empty entries filtered before validation so
|
|
88
|
+
* `--include update_column_value,` doesn't raise `usage_error`. Empty
|
|
89
|
+
* arrays after the filter raise `usage_error` rather than silently
|
|
90
|
+
* meaning "include everything" — an agent passing `--include ,,` is
|
|
91
|
+
* almost certainly bug, not intent.
|
|
92
|
+
*/
|
|
93
|
+
const includeListSchema = z
|
|
94
|
+
.string()
|
|
95
|
+
.transform((raw) => raw.split(',').map((s) => s.trim()).filter((s) => s.length > 0))
|
|
96
|
+
.pipe(z.array(watchKindSchema).min(1));
|
|
97
|
+
/**
|
|
98
|
+
* Event-ID validator for `--since <event-id>`. Numeric string per
|
|
99
|
+
* Monday's `ActivityLogType.id` shape (NON_NULL String at the wire
|
|
100
|
+
* level but always digits in practice per the M24 probe). Open shape
|
|
101
|
+
* (no length cap) — Monday's event IDs are typically 13+ digits.
|
|
102
|
+
*/
|
|
103
|
+
const eventIdSchema = z
|
|
104
|
+
.string()
|
|
105
|
+
.min(1)
|
|
106
|
+
.regex(/^\d+$/u, { message: 'must be a numeric event ID (digits only)' });
|
|
107
|
+
/**
|
|
108
|
+
* Argv input schema for `monday item watch <iid>`. Validates at the
|
|
109
|
+
* parse boundary; the action body consumes the validated shape
|
|
110
|
+
* directly (no defensive re-checks).
|
|
111
|
+
*
|
|
112
|
+
* Mutual-exclusion rules (superRefine):
|
|
113
|
+
*
|
|
114
|
+
* - `--once` is incompatible with `--max-events` / `--max-duration`.
|
|
115
|
+
* `--once` already pins the exit (drain then return); a max-event
|
|
116
|
+
* ceiling would be redundant and a max-duration ceiling could
|
|
117
|
+
* race the backlog drain.
|
|
118
|
+
* - `--interval <ms>` requires the bare-integer ms form; the closure
|
|
119
|
+
* pins ms semantics (not bare seconds) to disambiguate 30 vs
|
|
120
|
+
* 30000 unambiguously. Range 1000–3600000 (1s–1h).
|
|
121
|
+
*/
|
|
122
|
+
const inputSchema = z
|
|
123
|
+
.object({
|
|
124
|
+
iid: ItemIdSchema,
|
|
125
|
+
/**
|
|
126
|
+
* Polling cadence in milliseconds. Default
|
|
127
|
+
* {@link DEFAULT_WATCH_INTERVAL_MS} (30000ms / 30s) per cli-design
|
|
128
|
+
* §14.4 closure. Range {@link MIN_WATCH_INTERVAL_MS} (1000ms /
|
|
129
|
+
* 1s; faster trips Monday's request-rate concerns + the
|
|
130
|
+
* propagation-lag floor) to {@link MAX_WATCH_INTERVAL_MS}
|
|
131
|
+
* (3600000ms / 1h; slower crosses the "no longer a watch" line —
|
|
132
|
+
* use `cron + monday item history` for hourly cadences).
|
|
133
|
+
*/
|
|
134
|
+
interval: z.coerce
|
|
135
|
+
.number()
|
|
136
|
+
.int()
|
|
137
|
+
.min(MIN_WATCH_INTERVAL_MS)
|
|
138
|
+
.max(MAX_WATCH_INTERVAL_MS)
|
|
139
|
+
.optional(),
|
|
140
|
+
/**
|
|
141
|
+
* Last-seen-event-id watermark for session restart. One-shot
|
|
142
|
+
* bootstrap — the runtime looks up the event's `created_at`
|
|
143
|
+
* once, sets the initial poll-from timestamp, emits any backlog
|
|
144
|
+
* past the watermark, then enters the polling loop. Distinct
|
|
145
|
+
* from a full `--resume <token>` mechanism (still open per
|
|
146
|
+
* cli-design §14.6).
|
|
147
|
+
*/
|
|
148
|
+
since: eventIdSchema.optional(),
|
|
149
|
+
/**
|
|
150
|
+
* Drain backlog from `--since` (or recent N if no `--since`) and
|
|
151
|
+
* exit without polling. Distinct from `--max-events 1` which
|
|
152
|
+
* waits for the NEXT event.
|
|
153
|
+
*/
|
|
154
|
+
once: z.boolean().optional(),
|
|
155
|
+
/**
|
|
156
|
+
* Cap on emitted events. Session exits with
|
|
157
|
+
* `exit_reason: 'max_events'` once the count is reached
|
|
158
|
+
* (success envelope).
|
|
159
|
+
*/
|
|
160
|
+
maxEvents: z.coerce.number().int().positive().optional(),
|
|
161
|
+
/**
|
|
162
|
+
* Wall-clock ceiling in seconds. Session exits with
|
|
163
|
+
* `exit_reason: 'max_duration'` once the duration is reached
|
|
164
|
+
* (current in-flight poll completes first).
|
|
165
|
+
*/
|
|
166
|
+
maxDuration: z.coerce.number().int().positive().optional(),
|
|
167
|
+
/**
|
|
168
|
+
* Comma-separated list of event kinds to retain. Filter applied
|
|
169
|
+
* at projection time (Monday doesn't expose a server-side filter
|
|
170
|
+
* on `activity_logs.event`). Accepts all 9 M24 kinds for
|
|
171
|
+
* forward-compat; v0.4-M29 only emits activity-log-sourced
|
|
172
|
+
* kinds.
|
|
173
|
+
*/
|
|
174
|
+
include: includeListSchema.optional(),
|
|
175
|
+
})
|
|
176
|
+
.strict()
|
|
177
|
+
.superRefine((value, ctx) => {
|
|
178
|
+
if (value.once === true && value.maxEvents !== undefined) {
|
|
179
|
+
ctx.addIssue({
|
|
180
|
+
code: 'custom',
|
|
181
|
+
message: '--once is incompatible with --max-events (pick one)',
|
|
182
|
+
path: ['once'],
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
if (value.once === true && value.maxDuration !== undefined) {
|
|
186
|
+
ctx.addIssue({
|
|
187
|
+
code: 'custom',
|
|
188
|
+
message: '--once is incompatible with --max-duration (pick one)',
|
|
189
|
+
path: ['once'],
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
export const itemWatchCommand = {
|
|
194
|
+
name: 'item.watch',
|
|
195
|
+
summary: 'Poll Monday for activity-log events on an item; stream NDJSON as they arrive',
|
|
196
|
+
examples: [
|
|
197
|
+
'monday item watch 1234567890',
|
|
198
|
+
'monday item watch 1234567890 --interval 60000 # 60s cadence',
|
|
199
|
+
'monday item watch 1234567890 --since 999999 # resume from event-id',
|
|
200
|
+
'monday item watch 1234567890 --once # drain backlog + exit',
|
|
201
|
+
'monday item watch 1234567890 --max-events 10',
|
|
202
|
+
'monday item watch 1234567890 --max-duration 3600 # 1h ceiling',
|
|
203
|
+
'monday item watch 1234567890 --include update_column_value,update_posted',
|
|
204
|
+
],
|
|
205
|
+
idempotent: true,
|
|
206
|
+
inputSchema,
|
|
207
|
+
// Mirrors M24 `item history`: the output schema describes the
|
|
208
|
+
// per-event record shape an agent sees on the NDJSON stream — NOT
|
|
209
|
+
// the session-summary trailer (the trailer-meta shape is documented
|
|
210
|
+
// in output-shapes.md + cli-design §4.3, but a streaming verb has no
|
|
211
|
+
// buffered `data` payload). `monday schema item.watch` reflects
|
|
212
|
+
// the event-record shape so agents pin their per-line parsers
|
|
213
|
+
// against the same discriminated-union taxonomy `item history`
|
|
214
|
+
// uses.
|
|
215
|
+
outputSchema: historyEventSchema,
|
|
216
|
+
attach: (program, ctx) => {
|
|
217
|
+
const noun = ensureSubcommand(program, 'item', 'Item commands');
|
|
218
|
+
noun
|
|
219
|
+
.command('watch <iid>')
|
|
220
|
+
.description(itemWatchCommand.summary)
|
|
221
|
+
.option('--interval <ms>', `polling cadence in milliseconds (${String(MIN_WATCH_INTERVAL_MS)}–${String(MAX_WATCH_INTERVAL_MS)}, default ${String(DEFAULT_WATCH_INTERVAL_MS)})`)
|
|
222
|
+
.option('--since <event-id>', 'resume from a previous session\'s last-seen-event-id (numeric ID from trailer-meta)')
|
|
223
|
+
.option('--once', 'drain backlog from --since (or recent events) and exit without polling')
|
|
224
|
+
.option('--max-events <n>', 'exit cleanly after emitting N events')
|
|
225
|
+
.option('--max-duration <seconds>', 'exit cleanly after N wall-clock seconds')
|
|
226
|
+
.option('--include <list>', 'comma-separated event kinds to retain (e.g. update_column_value,update_posted)')
|
|
227
|
+
.addHelpText('after', [
|
|
228
|
+
'',
|
|
229
|
+
'Examples:',
|
|
230
|
+
...itemWatchCommand.examples.map((e) => ` ${e}`),
|
|
231
|
+
'',
|
|
232
|
+
`NOTE: emits NDJSON only (one event per line + trailing meta).`,
|
|
233
|
+
`Default cadence ${String(DEFAULT_WATCH_INTERVAL_MS)}ms (${String(DEFAULT_WATCH_INTERVAL_MS / 1000)}s); circuit-breaker trips after`,
|
|
234
|
+
`${String(CIRCUIT_BREAKER_CONSECUTIVE_FAILS)} consecutive failed polls. Resume across sessions via`,
|
|
235
|
+
'--since <event-id> from the prior trailer-meta\'s last_seen_event_id.',
|
|
236
|
+
'SIGINT triggers a graceful drain + trailer emit + exit 130.',
|
|
237
|
+
'',
|
|
238
|
+
'Monday\'s activity_logs has an empirically-measured propagation lag',
|
|
239
|
+
'>30s on freshly-edited boards (M24 pre-flight finding 2026-05-11);',
|
|
240
|
+
'cadence faster than 30s would generate polls against unpropagated data.',
|
|
241
|
+
'',
|
|
242
|
+
].join('\n'))
|
|
243
|
+
.action(async (iid, rawOpts) => {
|
|
244
|
+
const merged = { ...rawOpts, iid };
|
|
245
|
+
const parsed = parseArgv(itemWatchCommand.inputSchema, merged);
|
|
246
|
+
const { client, apiVersion } = resolveClient(ctx, program.opts());
|
|
247
|
+
// Item-board lookup short-circuits a missing-item watch with
|
|
248
|
+
// `not_found` before the polling loop spins up. The lookup is
|
|
249
|
+
// a single wire call; SourceAggregator records it as `'live'`
|
|
250
|
+
// so the trailer's `meta.source` stays correct if a future
|
|
251
|
+
// cache layer lifts in here.
|
|
252
|
+
const { boardId } = await lookupItemBoard({
|
|
253
|
+
client,
|
|
254
|
+
itemId: parsed.iid,
|
|
255
|
+
});
|
|
256
|
+
const aggregator = new SourceAggregator();
|
|
257
|
+
aggregator.record('live', null);
|
|
258
|
+
const stream = startNdjsonStream({
|
|
259
|
+
stream: ctx.stdout,
|
|
260
|
+
secrets: collectSecrets(ctx.env, ctx.runtimeSecrets),
|
|
261
|
+
project: (event) => event,
|
|
262
|
+
});
|
|
263
|
+
const result = await watchItem({
|
|
264
|
+
client,
|
|
265
|
+
itemId: parsed.iid,
|
|
266
|
+
boardId,
|
|
267
|
+
intervalMs: parsed.interval ?? DEFAULT_WATCH_INTERVAL_MS,
|
|
268
|
+
...(parsed.since === undefined ? {} : { since: parsed.since }),
|
|
269
|
+
...(parsed.once === undefined ? {} : { once: parsed.once }),
|
|
270
|
+
...(parsed.maxEvents === undefined ? {} : { maxEvents: parsed.maxEvents }),
|
|
271
|
+
...(parsed.maxDuration === undefined ? {} : { maxDurationSeconds: parsed.maxDuration }),
|
|
272
|
+
...(parsed.include === undefined ? {} : { includeKinds: parsed.include }),
|
|
273
|
+
signal: ctx.signal,
|
|
274
|
+
onEvent: stream.onItem,
|
|
275
|
+
});
|
|
276
|
+
aggregator.record(result.source, null);
|
|
277
|
+
const aggregated = aggregator.result('live');
|
|
278
|
+
// `WatchSessionWarning` already structurally satisfies the
|
|
279
|
+
// §6.1 `Warning` shape (code + message + details); the
|
|
280
|
+
// trailer slot accepts the superset directly.
|
|
281
|
+
const trailerWarnings = result.warnings;
|
|
282
|
+
stream.writeTrailer(buildStreamingTrailerMeta({
|
|
283
|
+
ctx: {
|
|
284
|
+
cliVersion: ctx.cliVersion,
|
|
285
|
+
requestId: ctx.requestId,
|
|
286
|
+
clock: ctx.clock,
|
|
287
|
+
},
|
|
288
|
+
apiVersion,
|
|
289
|
+
source: aggregated.source,
|
|
290
|
+
cacheAgeSeconds: aggregated.cacheAgeSeconds,
|
|
291
|
+
result: {
|
|
292
|
+
// `has_more` reflects "the source still has events past
|
|
293
|
+
// this session's exit point" — true for ceiling-driven
|
|
294
|
+
// exits (max_events / max_duration / signal) where the
|
|
295
|
+
// agent might re-invoke with --since; false for
|
|
296
|
+
// once_complete (the backlog was the whole window) and
|
|
297
|
+
// circuit_broken (the session failed, not the source
|
|
298
|
+
// running out).
|
|
299
|
+
hasMore: result.exit_reason === 'max_events' ||
|
|
300
|
+
result.exit_reason === 'max_duration' ||
|
|
301
|
+
result.exit_reason === 'signal',
|
|
302
|
+
totalReturned: result.events_emitted,
|
|
303
|
+
complexity: null,
|
|
304
|
+
},
|
|
305
|
+
session: {
|
|
306
|
+
eventsEmitted: result.events_emitted,
|
|
307
|
+
pollsMade: result.polls_made,
|
|
308
|
+
failedPolls: result.failed_polls,
|
|
309
|
+
watchDurationSeconds: result.watch_duration_seconds,
|
|
310
|
+
lastSeenEventId: result.last_seen_event_id,
|
|
311
|
+
circuitBrokenAt: result.circuit_broken_at,
|
|
312
|
+
exitReason: result.exit_reason,
|
|
313
|
+
},
|
|
314
|
+
warnings: trailerWarnings,
|
|
315
|
+
}));
|
|
316
|
+
// Circuit-broken exit surfaces as a §6.5 failure envelope on
|
|
317
|
+
// stderr AFTER the trailer emitted on stdout. The Monday code
|
|
318
|
+
// that tripped the breaker lives on the last `poll_failed`
|
|
319
|
+
// warning's `details.monday_code` slot (no other source: the
|
|
320
|
+
// ApiError thrown by `client.raw` was caught + converted to a
|
|
321
|
+
// warning inside `watchItem`).
|
|
322
|
+
if (result.exit_reason === 'circuit_broken') {
|
|
323
|
+
const lastPollFailed = [...result.warnings]
|
|
324
|
+
.reverse()
|
|
325
|
+
.find((w) => w.code === 'poll_failed');
|
|
326
|
+
/* c8 ignore next 2 — defensive: circuit_broken always
|
|
327
|
+
trips off a poll_failed accumulation; the find always
|
|
328
|
+
succeeds in production. */
|
|
329
|
+
const mondayCode = lastPollFailed?.details.monday_code ??
|
|
330
|
+
'complexity_exceeded';
|
|
331
|
+
throw new ApiError(mondayCode, `watch session tripped the circuit breaker after ${String(result.failed_polls)} failed polls (${String(CIRCUIT_BREAKER_CONSECUTIVE_FAILS)} consecutive)`, {
|
|
332
|
+
details: {
|
|
333
|
+
failed_polls: result.failed_polls,
|
|
334
|
+
circuit_broken_at: result.circuit_broken_at,
|
|
335
|
+
events_emitted: result.events_emitted,
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
//# sourceMappingURL=watch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch.js","sourceRoot":"","sources":["../../../src/commands/item/watch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `monday update upload <uid> <file>` — attach a file to an Update
|
|
3
|
+
* (comment) via Monday's `add_file_to_update` mutation
|
|
4
|
+
* (cli-design.md §4.3 + §6.4 asset-upload sub-section + §13 v0.4
|
|
5
|
+
* entry; v0.4-plan.md §3 M31).
|
|
6
|
+
*
|
|
7
|
+
* **Wire shape.** Single multipart/form-data round-trip via
|
|
8
|
+
* {@link addFileToUpdate} against `mutation AddFileToUpdate` with
|
|
9
|
+
* `operationName: 'AddFileToUpdate'` (R-NEW-37 W2 audit-point).
|
|
10
|
+
* Same `MultipartTransport` seam as `item upload`; no column-id
|
|
11
|
+
* involved (Updates carry attachments via `Update.assets` directly,
|
|
12
|
+
* not via column values).
|
|
13
|
+
*
|
|
14
|
+
* **Argv shape.** Two positional args:
|
|
15
|
+
*
|
|
16
|
+
* - `<uid>` — numeric update ID; brand-validated via
|
|
17
|
+
* {@link UpdateIdSchema}.
|
|
18
|
+
* - `<file>` — local file path (same constraints as
|
|
19
|
+
* `monday item upload`: regular readable file; stdin not
|
|
20
|
+
* supported at v0.4-M31).
|
|
21
|
+
*
|
|
22
|
+
* **No column-type validation needed.** Updates accept any file
|
|
23
|
+
* type Monday supports — no column-shape gating like
|
|
24
|
+
* `item upload`'s `file`-only check. Server-side validation handles
|
|
25
|
+
* the rest (size cap, filename sanity, virus scan).
|
|
26
|
+
*
|
|
27
|
+
* **Local file failures + size handling — same `details.reason`
|
|
28
|
+
* discrimination as `item upload`.** Three values:
|
|
29
|
+
* - `'file_not_readable'` — ENOENT / EACCES / path is a
|
|
30
|
+
* directory; fires at IMPL via `fs.stat()` pre-check.
|
|
31
|
+
* - `'file_empty'` — zero-byte file; fires via `fs.stat()`.
|
|
32
|
+
* - `'file_too_large'` — Monday's server-side size-cap
|
|
33
|
+
* rejection rewrap; carries `details.file_size_bytes`
|
|
34
|
+
* from the local `fs.stat()` measurement at upload
|
|
35
|
+
* time (NOT a Monday error-payload field — Monday's
|
|
36
|
+
* wire rejection may not surface a size; the CLI
|
|
37
|
+
* threads the locally-measured size into the details
|
|
38
|
+
* slot for a stable agent-keyed envelope).
|
|
39
|
+
* No CLI-side hardcoded size pre-check; Monday's per-file cap
|
|
40
|
+
* is plan-tier-dependent and not exposed via the schema.
|
|
41
|
+
*
|
|
42
|
+
* **`--dry-run` shape** per §6.4 asset-upload variant — emits
|
|
43
|
+
* `{operation: 'add_file_to_update', update_id, file_path,
|
|
44
|
+
* filename, file_size_bytes}` with `meta.source: 'none'`. No wire
|
|
45
|
+
* call fires.
|
|
46
|
+
*
|
|
47
|
+
* **Idempotency: NO** — re-running uploads a second copy. Agents
|
|
48
|
+
* needing register-once dedupe on `Update.assets` reads.
|
|
49
|
+
*
|
|
50
|
+
* **Cache invalidation.** N/A — Updates aren't part of the §8
|
|
51
|
+
* cache scope (board-metadata-only); the upload changes the
|
|
52
|
+
* Update's asset collection but nothing the cache tracks.
|
|
53
|
+
*
|
|
54
|
+
* **Status: runtime body shipped at v0.4-M31 IMPL** — mirrors
|
|
55
|
+
* `item upload` minus the column-resolution + cache-invalidation
|
|
56
|
+
* legs (Updates aren't part of the §8 cache scope; no per-column
|
|
57
|
+
* type check needed because Updates accept any file type Monday
|
|
58
|
+
* supports).
|
|
59
|
+
*/
|
|
60
|
+
import { z } from 'zod';
|
|
61
|
+
import { type CommandModule } from '../types.js';
|
|
62
|
+
import { type UpdateUploadOutput } from '../../api/assets.js';
|
|
63
|
+
declare const inputSchema: z.ZodObject<{
|
|
64
|
+
updateId: z.core.$ZodBranded<z.ZodString, "UpdateId", "out">;
|
|
65
|
+
file: z.ZodString;
|
|
66
|
+
}, z.core.$strict>;
|
|
67
|
+
export declare const updateUploadCommand: CommandModule<z.infer<typeof inputSchema>, UpdateUploadOutput>;
|
|
68
|
+
export {};
|
|
69
|
+
//# sourceMappingURL=upload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../../src/commands/update/upload.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAGnE,OAAO,EAEL,KAAK,kBAAkB,EAExB,MAAM,qBAAqB,CAAC;AAM7B,QAAA,MAAM,WAAW;;;kBAcN,CAAC;AAEZ,eAAO,MAAM,mBAAmB,EAAE,aAAa,CAC7C,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,EAC3B,kBAAkB,CAuLnB,CAAC"}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `monday update upload <uid> <file>` — attach a file to an Update
|
|
3
|
+
* (comment) via Monday's `add_file_to_update` mutation
|
|
4
|
+
* (cli-design.md §4.3 + §6.4 asset-upload sub-section + §13 v0.4
|
|
5
|
+
* entry; v0.4-plan.md §3 M31).
|
|
6
|
+
*
|
|
7
|
+
* **Wire shape.** Single multipart/form-data round-trip via
|
|
8
|
+
* {@link addFileToUpdate} against `mutation AddFileToUpdate` with
|
|
9
|
+
* `operationName: 'AddFileToUpdate'` (R-NEW-37 W2 audit-point).
|
|
10
|
+
* Same `MultipartTransport` seam as `item upload`; no column-id
|
|
11
|
+
* involved (Updates carry attachments via `Update.assets` directly,
|
|
12
|
+
* not via column values).
|
|
13
|
+
*
|
|
14
|
+
* **Argv shape.** Two positional args:
|
|
15
|
+
*
|
|
16
|
+
* - `<uid>` — numeric update ID; brand-validated via
|
|
17
|
+
* {@link UpdateIdSchema}.
|
|
18
|
+
* - `<file>` — local file path (same constraints as
|
|
19
|
+
* `monday item upload`: regular readable file; stdin not
|
|
20
|
+
* supported at v0.4-M31).
|
|
21
|
+
*
|
|
22
|
+
* **No column-type validation needed.** Updates accept any file
|
|
23
|
+
* type Monday supports — no column-shape gating like
|
|
24
|
+
* `item upload`'s `file`-only check. Server-side validation handles
|
|
25
|
+
* the rest (size cap, filename sanity, virus scan).
|
|
26
|
+
*
|
|
27
|
+
* **Local file failures + size handling — same `details.reason`
|
|
28
|
+
* discrimination as `item upload`.** Three values:
|
|
29
|
+
* - `'file_not_readable'` — ENOENT / EACCES / path is a
|
|
30
|
+
* directory; fires at IMPL via `fs.stat()` pre-check.
|
|
31
|
+
* - `'file_empty'` — zero-byte file; fires via `fs.stat()`.
|
|
32
|
+
* - `'file_too_large'` — Monday's server-side size-cap
|
|
33
|
+
* rejection rewrap; carries `details.file_size_bytes`
|
|
34
|
+
* from the local `fs.stat()` measurement at upload
|
|
35
|
+
* time (NOT a Monday error-payload field — Monday's
|
|
36
|
+
* wire rejection may not surface a size; the CLI
|
|
37
|
+
* threads the locally-measured size into the details
|
|
38
|
+
* slot for a stable agent-keyed envelope).
|
|
39
|
+
* No CLI-side hardcoded size pre-check; Monday's per-file cap
|
|
40
|
+
* is plan-tier-dependent and not exposed via the schema.
|
|
41
|
+
*
|
|
42
|
+
* **`--dry-run` shape** per §6.4 asset-upload variant — emits
|
|
43
|
+
* `{operation: 'add_file_to_update', update_id, file_path,
|
|
44
|
+
* filename, file_size_bytes}` with `meta.source: 'none'`. No wire
|
|
45
|
+
* call fires.
|
|
46
|
+
*
|
|
47
|
+
* **Idempotency: NO** — re-running uploads a second copy. Agents
|
|
48
|
+
* needing register-once dedupe on `Update.assets` reads.
|
|
49
|
+
*
|
|
50
|
+
* **Cache invalidation.** N/A — Updates aren't part of the §8
|
|
51
|
+
* cache scope (board-metadata-only); the upload changes the
|
|
52
|
+
* Update's asset collection but nothing the cache tracks.
|
|
53
|
+
*
|
|
54
|
+
* **Status: runtime body shipped at v0.4-M31 IMPL** — mirrors
|
|
55
|
+
* `item upload` minus the column-resolution + cache-invalidation
|
|
56
|
+
* legs (Updates aren't part of the §8 cache scope; no per-column
|
|
57
|
+
* type check needed because Updates accept any file type Monday
|
|
58
|
+
* supports).
|
|
59
|
+
*/
|
|
60
|
+
import { z } from 'zod';
|
|
61
|
+
import { stat as fsStat, access as fsAccess, readFile } from 'node:fs/promises';
|
|
62
|
+
import { constants as fsConstants } from 'node:fs';
|
|
63
|
+
import { resolve as resolvePath, basename } from 'node:path';
|
|
64
|
+
import { ensureSubcommand } from '../types.js';
|
|
65
|
+
import { parseArgv } from '../parse-argv.js';
|
|
66
|
+
import { UpdateIdSchema } from '../../types/ids.js';
|
|
67
|
+
import { updateUploadOutputSchema, addFileToUpdate, } from '../../api/assets.js';
|
|
68
|
+
import { resolveClient } from '../../api/resolve-client.js';
|
|
69
|
+
import { UsageError, asError, errorCode } from '../../utils/errors.js';
|
|
70
|
+
import { sniffContentType } from '../../utils/mime.js';
|
|
71
|
+
import { emitMutation, emitDryRun } from '../emit.js';
|
|
72
|
+
const inputSchema = z
|
|
73
|
+
.object({
|
|
74
|
+
updateId: UpdateIdSchema,
|
|
75
|
+
file: z
|
|
76
|
+
.string()
|
|
77
|
+
.min(1, {
|
|
78
|
+
message: '<file> must be a non-empty local file path; stdin (`-`) is not supported in v0.4-M31 (a future contract extension may add stdin support once a `--filename <name>` companion flag is pinned).',
|
|
79
|
+
})
|
|
80
|
+
.refine((p) => p !== '-', {
|
|
81
|
+
message: '<file> cannot be `-` — stdin upload is not supported in v0.4-M31. Pass a local file path resolved relative to cwd. A future contract extension may add stdin support once a `--filename <name>` companion flag is pinned.',
|
|
82
|
+
}),
|
|
83
|
+
})
|
|
84
|
+
.strict();
|
|
85
|
+
export const updateUploadCommand = {
|
|
86
|
+
name: 'update.upload',
|
|
87
|
+
summary: 'Attach a file to an Update (comment) via add_file_to_update',
|
|
88
|
+
examples: [
|
|
89
|
+
'monday update upload 98765 ./screenshot.png',
|
|
90
|
+
'monday update upload 98765 ./report.pdf --dry-run',
|
|
91
|
+
],
|
|
92
|
+
idempotent: false,
|
|
93
|
+
inputSchema,
|
|
94
|
+
outputSchema: updateUploadOutputSchema,
|
|
95
|
+
attach: (program, ctx) => {
|
|
96
|
+
const noun = ensureSubcommand(program, 'update', 'Update (comment) commands (cli-design §4.3 UPDATE)');
|
|
97
|
+
noun
|
|
98
|
+
.command('upload <updateId> <file>')
|
|
99
|
+
.description(updateUploadCommand.summary)
|
|
100
|
+
.addHelpText('after', [
|
|
101
|
+
'',
|
|
102
|
+
'Examples:',
|
|
103
|
+
...updateUploadCommand.examples.map((e) => ` ${e}`),
|
|
104
|
+
'',
|
|
105
|
+
'Notes:',
|
|
106
|
+
' - Uploads cross the wire as multipart/form-data (different transport from JSON-only verbs).',
|
|
107
|
+
' - File path is resolved relative to the cwd; stdin (`-`) is not supported in this release.',
|
|
108
|
+
' - Re-running with the same args creates a second Asset; `add_file_to_update` is not idempotent.',
|
|
109
|
+
'',
|
|
110
|
+
].join('\n'))
|
|
111
|
+
.action(async (updateIdArg, fileArg) => {
|
|
112
|
+
const parsed = parseArgv(updateUploadCommand.inputSchema, {
|
|
113
|
+
updateId: updateIdArg,
|
|
114
|
+
file: fileArg,
|
|
115
|
+
});
|
|
116
|
+
// Same fs.stat() + fs.access(R_OK) pre-check shape as
|
|
117
|
+
// `item upload` (round-1 P2-2 fix). Pre-resolveClient so a
|
|
118
|
+
// missing/unreadable-file error surfaces as usage_error
|
|
119
|
+
// (exit 1) before any token check.
|
|
120
|
+
const filePath = resolvePath(process.cwd(), parsed.file);
|
|
121
|
+
const filename = basename(filePath);
|
|
122
|
+
let fileSizeBytes;
|
|
123
|
+
try {
|
|
124
|
+
const stats = await fsStat(filePath);
|
|
125
|
+
if (!stats.isFile()) {
|
|
126
|
+
throw new UsageError(`<file> ${JSON.stringify(parsed.file)} is not a regular file ` +
|
|
127
|
+
`(resolved to ${JSON.stringify(filePath)}).`, {
|
|
128
|
+
details: {
|
|
129
|
+
reason: 'file_not_readable',
|
|
130
|
+
file_path: filePath,
|
|
131
|
+
hint: 'pass a path to a regular readable file; directories ' +
|
|
132
|
+
'and special files (sockets, devices) are rejected.',
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
await fsAccess(filePath, fsConstants.R_OK);
|
|
137
|
+
fileSizeBytes = stats.size;
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
if (err instanceof UsageError) {
|
|
141
|
+
throw err;
|
|
142
|
+
}
|
|
143
|
+
const code = errorCode(err);
|
|
144
|
+
throw new UsageError(`<file> ${JSON.stringify(parsed.file)} cannot be read ` +
|
|
145
|
+
`(resolved to ${JSON.stringify(filePath)}): ` +
|
|
146
|
+
`${asError(err).message}.`, {
|
|
147
|
+
cause: err,
|
|
148
|
+
details: {
|
|
149
|
+
reason: 'file_not_readable',
|
|
150
|
+
file_path: filePath,
|
|
151
|
+
...(code === undefined ? {} : { errno_code: code }),
|
|
152
|
+
hint: 'check that the path exists, is readable by the current ' +
|
|
153
|
+
'user, and isn\'t a directory.',
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
if (fileSizeBytes === 0) {
|
|
158
|
+
throw new UsageError(`<file> ${JSON.stringify(parsed.file)} is empty (0 bytes); ` +
|
|
159
|
+
`Monday rejects empty uploads server-side.`, {
|
|
160
|
+
details: {
|
|
161
|
+
reason: 'file_empty',
|
|
162
|
+
file_path: filePath,
|
|
163
|
+
filename,
|
|
164
|
+
file_size_bytes: 0,
|
|
165
|
+
hint: 'Monday returns FILE_SIZE_LIMIT_EXCEEDED on empty ' +
|
|
166
|
+
'uploads. Provide a non-empty file or remove the upload ' +
|
|
167
|
+
'call.',
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
const { client, globalFlags, apiVersion, multipart, toEmit } = resolveClient(ctx, program.opts());
|
|
172
|
+
if (globalFlags.dryRun) {
|
|
173
|
+
// D5 closure mirror — dry-run is fs.stat()-backed; no
|
|
174
|
+
// wire mutation; no file bytes loaded. `update upload`
|
|
175
|
+
// dry-run carries `update_id` instead of `item_id` +
|
|
176
|
+
// `column_id`; otherwise structurally identical to the
|
|
177
|
+
// `item upload` dry-run shape. `file_path` is the
|
|
178
|
+
// argv-derived path per cli-design §6.4 sample (round-2
|
|
179
|
+
// P3-2 fix; mirrors `item upload`).
|
|
180
|
+
emitDryRun({
|
|
181
|
+
ctx,
|
|
182
|
+
programOpts: program.opts(),
|
|
183
|
+
plannedChanges: [
|
|
184
|
+
{
|
|
185
|
+
operation: 'add_file_to_update',
|
|
186
|
+
update_id: parsed.updateId,
|
|
187
|
+
file_path: parsed.file,
|
|
188
|
+
filename,
|
|
189
|
+
file_size_bytes: fileSizeBytes,
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
source: 'none',
|
|
193
|
+
cacheAgeSeconds: null,
|
|
194
|
+
apiVersion,
|
|
195
|
+
});
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const bytes = await readFile(filePath);
|
|
199
|
+
const file = new Blob([bytes], { type: sniffContentType(filename) });
|
|
200
|
+
const result = await addFileToUpdate({
|
|
201
|
+
client,
|
|
202
|
+
multipart,
|
|
203
|
+
updateId: parsed.updateId,
|
|
204
|
+
file,
|
|
205
|
+
filename,
|
|
206
|
+
signal: ctx.signal,
|
|
207
|
+
retries: globalFlags.retry,
|
|
208
|
+
});
|
|
209
|
+
// No cache invalidation per D6 — Updates aren't part of the
|
|
210
|
+
// §8 cache scope (which covers board metadata only).
|
|
211
|
+
const data = {
|
|
212
|
+
operation: 'add_file_to_update',
|
|
213
|
+
update_id: parsed.updateId,
|
|
214
|
+
filename,
|
|
215
|
+
file_size_bytes: fileSizeBytes,
|
|
216
|
+
asset: result.asset,
|
|
217
|
+
};
|
|
218
|
+
emitMutation({
|
|
219
|
+
ctx,
|
|
220
|
+
data,
|
|
221
|
+
schema: updateUploadCommand.outputSchema,
|
|
222
|
+
programOpts: program.opts(),
|
|
223
|
+
...toEmit({
|
|
224
|
+
data: result.asset,
|
|
225
|
+
complexity: result.complexity,
|
|
226
|
+
stats: { attempts: 1, totalBackoffMs: 0 },
|
|
227
|
+
}),
|
|
228
|
+
source: 'live',
|
|
229
|
+
cacheAgeSeconds: null,
|
|
230
|
+
complexity: result.complexity,
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
//# sourceMappingURL=upload.js.map
|