monday-cli 0.7.1 → 0.8.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 +249 -49
- package/README.md +87 -45
- package/dist/api/assets.d.ts +3 -3
- package/dist/api/column-types.d.ts +14 -7
- package/dist/api/column-types.d.ts.map +1 -1
- package/dist/api/column-types.js +14 -7
- package/dist/api/column-types.js.map +1 -1
- package/dist/api/error-decoration.d.ts +124 -0
- package/dist/api/error-decoration.d.ts.map +1 -0
- package/dist/api/error-decoration.js +161 -0
- package/dist/api/error-decoration.js.map +1 -0
- package/dist/api/fetch-transport-helpers.d.ts +97 -0
- package/dist/api/fetch-transport-helpers.d.ts.map +1 -0
- package/dist/api/fetch-transport-helpers.js +175 -0
- package/dist/api/fetch-transport-helpers.js.map +1 -0
- package/dist/api/file-column-set.d.ts +388 -82
- package/dist/api/file-column-set.d.ts.map +1 -1
- package/dist/api/file-column-set.js +466 -88
- package/dist/api/file-column-set.js.map +1 -1
- package/dist/api/multipart-transport.d.ts +95 -60
- package/dist/api/multipart-transport.d.ts.map +1 -1
- package/dist/api/multipart-transport.js +102 -120
- package/dist/api/multipart-transport.js.map +1 -1
- package/dist/api/transport.d.ts.map +1 -1
- package/dist/api/transport.js +2 -99
- package/dist/api/transport.js.map +1 -1
- package/dist/cli/program.js +1 -1
- package/dist/cli/program.js.map +1 -1
- package/dist/commands/auth/login.js +1 -1
- package/dist/commands/auth/login.js.map +1 -1
- package/dist/commands/auth/logout.js +1 -1
- package/dist/commands/auth/logout.js.map +1 -1
- package/dist/commands/board/column-create.d.ts +20 -2
- package/dist/commands/board/column-create.d.ts.map +1 -1
- package/dist/commands/board/column-create.js +191 -20
- package/dist/commands/board/column-create.js.map +1 -1
- package/dist/commands/completion.js +1 -1
- package/dist/commands/completion.js.map +1 -1
- package/dist/commands/dev/configure.js +1 -1
- package/dist/commands/dev/configure.js.map +1 -1
- package/dist/commands/dev/discover.js +1 -1
- package/dist/commands/dev/discover.js.map +1 -1
- package/dist/commands/dev/doctor.js +1 -1
- package/dist/commands/dev/doctor.js.map +1 -1
- package/dist/commands/dev/epic/items.js +2 -2
- package/dist/commands/dev/epic/items.js.map +1 -1
- package/dist/commands/dev/epic/list.js +2 -2
- package/dist/commands/dev/epic/list.js.map +1 -1
- package/dist/commands/dev/release/list.js +2 -2
- package/dist/commands/dev/release/list.js.map +1 -1
- package/dist/commands/dev/sprint/current.js +2 -2
- package/dist/commands/dev/sprint/current.js.map +1 -1
- package/dist/commands/dev/sprint/items.js +2 -2
- package/dist/commands/dev/sprint/items.js.map +1 -1
- package/dist/commands/dev/sprint/list.js +2 -2
- package/dist/commands/dev/sprint/list.js.map +1 -1
- package/dist/commands/dev/task/block.js +2 -2
- package/dist/commands/dev/task/block.js.map +1 -1
- package/dist/commands/dev/task/done.js +2 -2
- package/dist/commands/dev/task/done.js.map +1 -1
- package/dist/commands/dev/task/list.js +2 -2
- package/dist/commands/dev/task/list.js.map +1 -1
- package/dist/commands/dev/task/start.js +2 -2
- package/dist/commands/dev/task/start.js.map +1 -1
- package/dist/commands/doc/get.js +1 -1
- package/dist/commands/doc/get.js.map +1 -1
- package/dist/commands/doc/list.js +1 -1
- package/dist/commands/doc/list.js.map +1 -1
- package/dist/commands/item/clear.d.ts.map +1 -1
- package/dist/commands/item/clear.js +15 -41
- package/dist/commands/item/clear.js.map +1 -1
- package/dist/commands/item/create.d.ts +93 -1
- package/dist/commands/item/create.d.ts.map +1 -1
- package/dist/commands/item/create.js +474 -53
- package/dist/commands/item/create.js.map +1 -1
- package/dist/commands/item/search.js +7 -7
- package/dist/commands/item/search.js.map +1 -1
- package/dist/commands/item/set.d.ts +1 -0
- package/dist/commands/item/set.d.ts.map +1 -1
- package/dist/commands/item/set.js +94 -1
- package/dist/commands/item/set.js.map +1 -1
- package/dist/commands/item/time-track/start.js +2 -2
- package/dist/commands/item/time-track/start.js.map +1 -1
- package/dist/commands/item/time-track/stop.js +2 -2
- package/dist/commands/item/time-track/stop.js.map +1 -1
- package/dist/commands/item/update.d.ts +128 -11
- package/dist/commands/item/update.d.ts.map +1 -1
- package/dist/commands/item/update.js +784 -82
- package/dist/commands/item/update.js.map +1 -1
- package/dist/commands/item/upload.js +5 -5
- package/dist/commands/item/upload.js.map +1 -1
- package/dist/commands/item/watch.js +2 -2
- package/dist/commands/item/watch.js.map +1 -1
- package/dist/commands/notification/send.js +1 -1
- package/dist/commands/notification/send.js.map +1 -1
- package/dist/commands/update/body-source.d.ts +38 -0
- package/dist/commands/update/body-source.d.ts.map +1 -0
- package/dist/commands/update/body-source.js +80 -0
- package/dist/commands/update/body-source.js.map +1 -0
- package/dist/commands/update/upload.js +3 -3
- package/dist/commands/update/upload.js.map +1 -1
- package/dist/commands/user/team-add-members.js +1 -1
- package/dist/commands/user/team-add-members.js.map +1 -1
- package/dist/commands/user/team-create.js +1 -1
- package/dist/commands/user/team-create.js.map +1 -1
- package/dist/commands/user/team-remove-members.js +1 -1
- package/dist/commands/user/team-remove-members.js.map +1 -1
- package/dist/commands/webhook/create.js +1 -1
- package/dist/commands/webhook/create.js.map +1 -1
- package/dist/commands/webhook/delete.js +1 -1
- package/dist/commands/webhook/delete.js.map +1 -1
- package/dist/commands/webhook/list.js +1 -1
- package/dist/commands/webhook/list.js.map +1 -1
- package/dist/utils/file-source.d.ts +109 -0
- package/dist/utils/file-source.d.ts.map +1 -1
- package/dist/utils/file-source.js +123 -0
- package/dist/utils/file-source.js.map +1 -1
- package/dist/utils/output/table.d.ts +7 -6
- package/dist/utils/output/table.d.ts.map +1 -1
- package/dist/utils/output/table.js +32 -5
- package/dist/utils/output/table.js.map +1 -1
- package/package.json +2 -1
|
@@ -52,9 +52,10 @@ import { parseArgv } from '../parse-argv.js';
|
|
|
52
52
|
import { ApiError, MondayCliError, UsageError } from '../../utils/errors.js';
|
|
53
53
|
import { selectMutation, } from '../../api/column-values.js';
|
|
54
54
|
import { executeItemMutation } from '../../api/item-mutation-execute.js';
|
|
55
|
-
import { executeFileColumnSet, fileColumnSetOutputSchema, preCheckM38FileDispatch, } from '../../api/file-column-set.js';
|
|
56
|
-
import { precheckLocalFile } from '../../utils/file-source.js';
|
|
55
|
+
import { executeFileColumnSet, dispatchFileLegsSequentially, fileColumnSetOutputSchema, fileColumnSetMultiOutputSchema, preCheckM38FileDispatch, } from '../../api/file-column-set.js';
|
|
56
|
+
import { precheckLocalFile, isStdinFileSetSource, readStdinFileSource, resolveStdinFilename, STDIN_FILE_SENTINEL, } from '../../utils/file-source.js';
|
|
57
57
|
import { invalidateBoard } from '../../api/cache.js';
|
|
58
|
+
import { addFileToColumn } from '../../api/assets.js';
|
|
58
59
|
import { dispatchSequential, } from '../../api/partial-success-mutation.js';
|
|
59
60
|
import { dispatchParallel } from '../../api/parallel-dispatch.js';
|
|
60
61
|
import { parseSetRawExpression, } from '../../api/raw-write.js';
|
|
@@ -64,6 +65,7 @@ import { resolveBoardId } from '../../api/item-board-lookup.js';
|
|
|
64
65
|
import { SourceAggregator, mergeCacheAge, mergeSource, } from '../../api/source-aggregator.js';
|
|
65
66
|
import { resolveAndTranslate } from '../../api/resolution-pass.js';
|
|
66
67
|
import { foldAndRemap, mergeResolverWarningsIntoError, } from '../../api/resolver-error-fold.js';
|
|
68
|
+
import { projectCauseForEnvelope, reThrowDecorated, } from '../../api/error-decoration.js';
|
|
67
69
|
import { planChanges } from '../../api/dry-run.js';
|
|
68
70
|
import { buildQueryParams } from '../../api/filters.js';
|
|
69
71
|
import { loadBoardMetadata, refreshBoardMetadata, } from '../../api/board-metadata.js';
|
|
@@ -77,19 +79,25 @@ import { MIN_CONCURRENCY, MAX_CONCURRENCY, } from '../../api/parallel-dispatch.j
|
|
|
77
79
|
/**
|
|
78
80
|
* Output envelope union — projected-item for the JSON translator
|
|
79
81
|
* path (text / status / dropdown / date / people / etc.) +
|
|
80
|
-
* file-dispatch
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* `operation: '
|
|
84
|
-
* `
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* `
|
|
88
|
-
* `
|
|
82
|
+
* single-item file-dispatch envelopes for the friendly `--set
|
|
83
|
+
* <file-col>=<path>` path: v0.6-M38 single-file (`operation:
|
|
84
|
+
* 'add_file_to_column'`) + v0.8-M46 single-item multi-file
|
|
85
|
+
* (`operation: 'add_files_to_columns'`; `assets[]` wraps M31's
|
|
86
|
+
* `Asset` projection per file column).
|
|
87
|
+
*
|
|
88
|
+
* The union admits only the SINGLE-ITEM shapes. The BULK file
|
|
89
|
+
* variants — v0.7-M42 (`item_update_bulk_file_set`) + v0.8-M46
|
|
90
|
+
* (`item_update_bulk_file_set_multi`) — are emitted via their own
|
|
91
|
+
* `bulkFileSet*DataSchema` at the `runItemUpdateBulk*FileDispatch`
|
|
92
|
+
* helpers and stay OUT of this union (the bulk per-item-results
|
|
93
|
+
* shape is structurally distinct from the single-resource `data`
|
|
94
|
+
* payload this schema describes); agents discriminate on `operation`
|
|
95
|
+
* (present + literal value identifies the variant).
|
|
89
96
|
*/
|
|
90
97
|
export const itemUpdateOutputSchema = z.union([
|
|
91
98
|
projectedItemSchema,
|
|
92
99
|
fileColumnSetOutputSchema,
|
|
100
|
+
fileColumnSetMultiOutputSchema,
|
|
93
101
|
]);
|
|
94
102
|
/**
|
|
95
103
|
* Input shape — supports both single-item and bulk shapes.
|
|
@@ -112,6 +120,17 @@ const inputSchema = z
|
|
|
112
120
|
// cli-design §5.3 line 961-972 (resolution-time, not parse-time).
|
|
113
121
|
setRaw: z.array(z.string()).default([]),
|
|
114
122
|
name: z.string().min(1).optional(),
|
|
123
|
+
// v0.8-M47 (D7 fold): companion to a stdin file `--set
|
|
124
|
+
// <file-col>=-` source — sets Monday's wire `Asset.name`. OPTIONAL
|
|
125
|
+
// (probe: `add_file_to_column` accepts any non-empty filename;
|
|
126
|
+
// only an EMPTY one `500`s — `.min(1)` rejects `--filename ""` at
|
|
127
|
+
// the parse boundary as `usage_error`). Default when omitted on a
|
|
128
|
+
// stdin source: `DEFAULT_STDIN_FILENAME` (`"blob"`). Consulted ONLY
|
|
129
|
+
// on a `<file-col>=-` stdin dispatch; ignored otherwise (whether a
|
|
130
|
+
// stdin source exists is only knowable after column resolution, so
|
|
131
|
+
// a no-stdin `--filename` is a harmless no-op rather than a
|
|
132
|
+
// resolution-coupled reject).
|
|
133
|
+
filename: z.string().min(1).optional(),
|
|
115
134
|
board: BoardIdSchema.optional(),
|
|
116
135
|
where: z.array(z.string()).default([]),
|
|
117
136
|
// Empty `--filter-json ''` would slip through `buildQueryParams`
|
|
@@ -259,6 +278,7 @@ export const itemUpdateCommand = {
|
|
|
259
278
|
.option('--set <expr>', 'repeatable <col>=<val> column write', (value, prev) => [...prev, value], [])
|
|
260
279
|
.option('--set-raw <expr>', 'repeatable <col>=<json> raw write (escape hatch — bypasses friendly translator)', (value, prev) => [...prev, value], [])
|
|
261
280
|
.option('--name <n>', 'rename the item')
|
|
281
|
+
.option('--filename <name>', "Asset.name for a stdin file `--set <file-col>=-` source (default \"blob\")")
|
|
262
282
|
.option('--board <bid>', 'board ID (required for bulk; skip lookup for single-item)')
|
|
263
283
|
.option('--where <expr>', 'repeatable bulk filter (cli-design §10.2): <col><op><val>', (value, prev) => [...prev, value], [])
|
|
264
284
|
.option('--filter-json <json>', 'literal Monday query_params for bulk')
|
|
@@ -332,6 +352,29 @@ export const itemUpdateCommand = {
|
|
|
332
352
|
// the pre-check's resolver warnings + source aggregation
|
|
333
353
|
// into the final envelope (P3-1 — IMPL round-1 fix).
|
|
334
354
|
await runItemUpdateSingleFileDispatch({
|
|
355
|
+
client,
|
|
356
|
+
multipart,
|
|
357
|
+
ctx,
|
|
358
|
+
programOpts: program.opts(),
|
|
359
|
+
apiVersion,
|
|
360
|
+
boardId,
|
|
361
|
+
itemId: dispatch.itemId,
|
|
362
|
+
m38,
|
|
363
|
+
isDryRun: globalFlags.dryRun,
|
|
364
|
+
retries: globalFlags.retry,
|
|
365
|
+
filename: parsed.filename,
|
|
366
|
+
toEmit,
|
|
367
|
+
});
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (m38.kind === 'file_multi') {
|
|
371
|
+
// v0.8-M46 D2 carve-out fold — single-item multi-file
|
|
372
|
+
// dispatch path. argv parse + pre-check (which returned
|
|
373
|
+
// `kind: 'file_multi'` here) have already run as live
|
|
374
|
+
// contract; the per-item multi-leg fan-out body runs N
|
|
375
|
+
// sequential `add_file_to_column` legs against the single
|
|
376
|
+
// item (runtime body shipped at v0.8-M46 IMPL).
|
|
377
|
+
await runItemUpdateSingleFileMultiDispatch({
|
|
335
378
|
client,
|
|
336
379
|
multipart,
|
|
337
380
|
ctx,
|
|
@@ -641,10 +684,18 @@ const runBulk = async (inputs) => {
|
|
|
641
684
|
// boundary, BEFORE the items_page walker + confirmation
|
|
642
685
|
// gate. The pre-check resolves setEntries against the now-
|
|
643
686
|
// warm metadata cache and runs
|
|
644
|
-
// `
|
|
687
|
+
// `routeFileColumnDispatch({callShape: 'item_update_bulk'})`:
|
|
645
688
|
//
|
|
646
|
-
// - Multi-file `--set`
|
|
647
|
-
//
|
|
689
|
+
// - Multi-file `--set` with distinct file columns →
|
|
690
|
+
// returns `kind: 'file_bulk_multi'` (v0.8-M46 D2
|
|
691
|
+
// carve-out fold; action body branches into the
|
|
692
|
+
// per-item multi-leg fan-out helper
|
|
693
|
+
// `runItemUpdateBulkFileMultiDispatch` — runtime body
|
|
694
|
+
// shipped at v0.8-M46 IMPL).
|
|
695
|
+
// - Multi-file `--set` with duplicate resolved file
|
|
696
|
+
// columns → throws `'duplicate_resolved_file_columns'`
|
|
697
|
+
// (mirrors JSON path's cross-token duplicate-resolved-
|
|
698
|
+
// ID contract; M46 R1 P2-1 fix).
|
|
648
699
|
// - File `--set` + value `--set` / `--set-raw` / `--name`
|
|
649
700
|
// → throws `'mixed_file_and_value_sets'` (universal rule;
|
|
650
701
|
// mixing forces non-atomic multi-leg dispatch).
|
|
@@ -668,6 +719,7 @@ const runBulk = async (inputs) => {
|
|
|
668
719
|
// unchanged).
|
|
669
720
|
let m38Warnings = [];
|
|
670
721
|
let m38FileBulk;
|
|
722
|
+
let m38FileBulkMulti;
|
|
671
723
|
if (setEntries.length > 0) {
|
|
672
724
|
const m38 = await preCheckM38FileDispatch({
|
|
673
725
|
client,
|
|
@@ -693,6 +745,14 @@ const runBulk = async (inputs) => {
|
|
|
693
745
|
// `executeFileColumnSet` across matched items.
|
|
694
746
|
m38FileBulk = m38;
|
|
695
747
|
}
|
|
748
|
+
if (m38.kind === 'file_bulk_multi') {
|
|
749
|
+
// v0.8-M46 D2 carve-out fold. Hold the file_bulk_multi slot
|
|
750
|
+
// for the multi-leg per-item fan-out below. items_page walker
|
|
751
|
+
// + confirmation gate still apply; the dispatch helper fans N
|
|
752
|
+
// sequential file legs per matched item (runtime body shipped
|
|
753
|
+
// at v0.8-M46 IMPL).
|
|
754
|
+
m38FileBulkMulti = m38;
|
|
755
|
+
}
|
|
696
756
|
}
|
|
697
757
|
const onColumnNotFound = meta.source === 'cache'
|
|
698
758
|
? async () => {
|
|
@@ -848,6 +908,34 @@ const runBulk = async (inputs) => {
|
|
|
848
908
|
});
|
|
849
909
|
return;
|
|
850
910
|
}
|
|
911
|
+
// v0.8-M46 D2 carve-out fold — bulk multi-file `--set` dispatch.
|
|
912
|
+
// The per-item multi-leg fan-out body runs N sequential file legs
|
|
913
|
+
// per matched item (cross-item parallel under `--concurrency` ×
|
|
914
|
+
// within-item sequential; runtime body shipped at v0.8-M46 IMPL).
|
|
915
|
+
// Fires AFTER the items_page walker + confirmation gate (so an
|
|
916
|
+
// agent sees the matched-count via `confirmation_required` before
|
|
917
|
+
// any dispatch fans out) and BEFORE the JSON translator path's
|
|
918
|
+
// `resolveAndTranslate` call.
|
|
919
|
+
if (m38FileBulkMulti !== undefined) {
|
|
920
|
+
await runItemUpdateBulkFileMultiDispatch({
|
|
921
|
+
parsed,
|
|
922
|
+
client,
|
|
923
|
+
multipart,
|
|
924
|
+
ctx,
|
|
925
|
+
programOpts,
|
|
926
|
+
apiVersion,
|
|
927
|
+
boardId,
|
|
928
|
+
matchedItemIds,
|
|
929
|
+
m38: m38FileBulkMulti,
|
|
930
|
+
metaSource: meta.source,
|
|
931
|
+
metaCacheAgeSeconds: meta.cacheAgeSeconds,
|
|
932
|
+
filterWarnings: filterResult.warnings,
|
|
933
|
+
retries: globalFlags.retry,
|
|
934
|
+
isDryRun: globalFlags.dryRun,
|
|
935
|
+
noCache: globalFlags.noCache,
|
|
936
|
+
});
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
851
939
|
const { dateResolution, peopleResolution, tagResolution, relationResolution } = buildResolutionContexts({ client, ctx, globalFlags });
|
|
852
940
|
// 5) Dry-run path: per-item planChanges. Column resolution is
|
|
853
941
|
// cached after the first call; per-item state read fires per
|
|
@@ -1143,34 +1231,16 @@ const runBulk = async (inputs) => {
|
|
|
1143
1231
|
resolutionSource: remapSource,
|
|
1144
1232
|
});
|
|
1145
1233
|
// Decorate with bulk-progress details so agents can see how
|
|
1146
|
-
// many items mutated successfully before the failure
|
|
1234
|
+
// many items mutated successfully before the failure, then
|
|
1235
|
+
// delegate the typed split + wire-metadata spreads to the
|
|
1236
|
+
// shared `reThrowDecorated` helper (R-v0.7-NEW-5).
|
|
1147
1237
|
const existing = remapped.details ?? {};
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
applied_to: appliedItems.map((i) => i.id),
|
|
1155
|
-
failed_at_item: itemId,
|
|
1156
|
-
matched_count: matchedItemIds.length,
|
|
1157
|
-
},
|
|
1158
|
-
});
|
|
1159
|
-
}
|
|
1160
|
-
throw new ApiError(remapped.code, remapped.message, {
|
|
1161
|
-
...(remapped.cause === undefined ? {} : { cause: remapped.cause }),
|
|
1162
|
-
...(remapped.httpStatus === undefined ? {} : { httpStatus: remapped.httpStatus }),
|
|
1163
|
-
...(remapped.mondayCode === undefined ? {} : { mondayCode: remapped.mondayCode }),
|
|
1164
|
-
...(remapped.requestId === undefined ? {} : { requestId: remapped.requestId }),
|
|
1165
|
-
retryable: remapped.retryable,
|
|
1166
|
-
...(remapped.retryAfterSeconds === undefined ? {} : { retryAfterSeconds: remapped.retryAfterSeconds }),
|
|
1167
|
-
details: {
|
|
1168
|
-
...existing,
|
|
1169
|
-
applied_count: appliedItems.length,
|
|
1170
|
-
applied_to: appliedItems.map((i) => i.id),
|
|
1171
|
-
failed_at_item: itemId,
|
|
1172
|
-
matched_count: matchedItemIds.length,
|
|
1173
|
-
},
|
|
1238
|
+
reThrowDecorated(remapped, {
|
|
1239
|
+
...existing,
|
|
1240
|
+
applied_count: appliedItems.length,
|
|
1241
|
+
applied_to: appliedItems.map((i) => i.id),
|
|
1242
|
+
failed_at_item: itemId,
|
|
1243
|
+
matched_count: matchedItemIds.length,
|
|
1174
1244
|
});
|
|
1175
1245
|
}
|
|
1176
1246
|
throw err;
|
|
@@ -1210,6 +1280,79 @@ const runBulk = async (inputs) => {
|
|
|
1210
1280
|
});
|
|
1211
1281
|
};
|
|
1212
1282
|
const runItemUpdateSingleFileDispatch = async (inputs) => {
|
|
1283
|
+
// v0.8-M47 (D7 fold): stdin file `--set <file-col>=-` source. The
|
|
1284
|
+
// pre-check (`routeFileColumnDispatch` stdin scope gate) already
|
|
1285
|
+
// confirmed this is the sole file entry on the single-item callShape;
|
|
1286
|
+
// source the Blob from stdin (via `readStdinFileSource`) instead of a
|
|
1287
|
+
// local path. Dry-run emits the D4 size-less echo WITHOUT consuming
|
|
1288
|
+
// stdin; live buffers stdin once + dispatches the pre-built Blob via
|
|
1289
|
+
// M31's `addFileToColumn` fetcher (the path leg's
|
|
1290
|
+
// `executeFileColumnSet` builds its Blob from a path — stdin's is
|
|
1291
|
+
// already in hand) + emits the same M31-shaped envelope.
|
|
1292
|
+
if (isStdinFileSetSource(inputs.m38.rawValue)) {
|
|
1293
|
+
const filename = resolveStdinFilename(inputs.filename);
|
|
1294
|
+
if (inputs.isDryRun) {
|
|
1295
|
+
emitDryRun({
|
|
1296
|
+
ctx: inputs.ctx,
|
|
1297
|
+
programOpts: inputs.programOpts,
|
|
1298
|
+
plannedChanges: [
|
|
1299
|
+
{
|
|
1300
|
+
operation: 'add_file_to_column',
|
|
1301
|
+
item_id: inputs.itemId,
|
|
1302
|
+
column_id: inputs.m38.columnId,
|
|
1303
|
+
file_path: STDIN_FILE_SENTINEL,
|
|
1304
|
+
filename,
|
|
1305
|
+
},
|
|
1306
|
+
],
|
|
1307
|
+
source: 'none',
|
|
1308
|
+
cacheAgeSeconds: null,
|
|
1309
|
+
warnings: inputs.m38.warnings,
|
|
1310
|
+
apiVersion: inputs.apiVersion,
|
|
1311
|
+
});
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1314
|
+
const stdinSource = await readStdinFileSource(inputs.ctx.stdin, filename);
|
|
1315
|
+
const stdinResult = await addFileToColumn({
|
|
1316
|
+
client: inputs.client,
|
|
1317
|
+
multipart: inputs.multipart,
|
|
1318
|
+
itemId: inputs.itemId,
|
|
1319
|
+
columnId: inputs.m38.columnId,
|
|
1320
|
+
file: stdinSource.blob,
|
|
1321
|
+
filename: stdinSource.filename,
|
|
1322
|
+
signal: inputs.ctx.signal,
|
|
1323
|
+
retries: inputs.retries,
|
|
1324
|
+
});
|
|
1325
|
+
await invalidateBoard(inputs.boardId, inputs.ctx.env);
|
|
1326
|
+
const stdinData = {
|
|
1327
|
+
operation: 'add_file_to_column',
|
|
1328
|
+
item_id: inputs.itemId,
|
|
1329
|
+
column_id: inputs.m38.columnId,
|
|
1330
|
+
filename: stdinSource.filename,
|
|
1331
|
+
file_size_bytes: stdinSource.fileSizeBytes,
|
|
1332
|
+
asset: stdinResult.asset,
|
|
1333
|
+
};
|
|
1334
|
+
emitMutation({
|
|
1335
|
+
ctx: inputs.ctx,
|
|
1336
|
+
data: stdinData,
|
|
1337
|
+
schema: fileColumnSetOutputSchema,
|
|
1338
|
+
programOpts: inputs.programOpts,
|
|
1339
|
+
warnings: inputs.m38.warnings.map((w) => ({
|
|
1340
|
+
code: w.code,
|
|
1341
|
+
message: w.message,
|
|
1342
|
+
details: w.details,
|
|
1343
|
+
})),
|
|
1344
|
+
...inputs.toEmit({
|
|
1345
|
+
data: stdinResult.asset,
|
|
1346
|
+
complexity: stdinResult.complexity,
|
|
1347
|
+
stats: { attempts: 1, totalBackoffMs: 0 },
|
|
1348
|
+
}),
|
|
1349
|
+
source: 'live',
|
|
1350
|
+
cacheAgeSeconds: null,
|
|
1351
|
+
complexity: stdinResult.complexity,
|
|
1352
|
+
resolvedIds: { [inputs.m38.token]: inputs.m38.columnId },
|
|
1353
|
+
});
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1213
1356
|
const precheck = await precheckLocalFile(inputs.m38.rawValue);
|
|
1214
1357
|
if (inputs.isDryRun) {
|
|
1215
1358
|
emitDryRun({
|
|
@@ -1361,13 +1504,15 @@ const runItemUpdateSingleFileDispatch = async (inputs) => {
|
|
|
1361
1504
|
// - R-NEW-72 (post-fix-up cross-doc grep) — apply at every
|
|
1362
1505
|
// Codex IMPL fix-up round that flips a contract surface.
|
|
1363
1506
|
//
|
|
1364
|
-
// **
|
|
1365
|
-
//
|
|
1366
|
-
//
|
|
1367
|
-
// helper
|
|
1368
|
-
//
|
|
1369
|
-
//
|
|
1370
|
-
//
|
|
1507
|
+
// **R-v0.7-NEW-5 — SHIPPED (v0.8 refactor cluster).** The fail-fast
|
|
1508
|
+
// error-decoration block (the `usage_error → UsageError` / else →
|
|
1509
|
+
// `ApiError` typed split + the conditional wire-metadata spreads)
|
|
1510
|
+
// reached 4 consumers (this helper + the JSON-bulk action body + the
|
|
1511
|
+
// M46 file-bulk-multi path + bulk clear) and was lifted to
|
|
1512
|
+
// `reThrowDecorated` in `src/api/error-decoration.ts`. Each site now
|
|
1513
|
+
// assembles its own `details` decoration and delegates the typed
|
|
1514
|
+
// split; the helper carries the focused unit test that recovers the
|
|
1515
|
+
// conditional-spread branch coverage.
|
|
1371
1516
|
// ============================================================
|
|
1372
1517
|
/**
|
|
1373
1518
|
* Per-item dispatch result for v0.7-M42 bulk file `--set` carve-out
|
|
@@ -1422,6 +1567,95 @@ export const bulkFileSetDataSchema = z.object({
|
|
|
1422
1567
|
}),
|
|
1423
1568
|
results: z.array(bulkFileSetResultSchema),
|
|
1424
1569
|
});
|
|
1570
|
+
/**
|
|
1571
|
+
* v0.8-M46 bulk multi-file `--set` per-item result schema (D6
|
|
1572
|
+
* closure — separate envelope shape from M42's single-file
|
|
1573
|
+
* `bulkFileSetResultSchema`). Per-item record carries the
|
|
1574
|
+
* per-leg asset projections + an `applied_file_columns` echo
|
|
1575
|
+
* slot (length 1..N reflecting which file columns landed
|
|
1576
|
+
* successfully on this item). On per-item partial failure mid-
|
|
1577
|
+
* multi-leg under `--continue-on-error`, `applied_file_columns`
|
|
1578
|
+
* length is 0..N-1 (reflecting columns that landed before the
|
|
1579
|
+
* failing leg) + `failed_file_column` carries the failing
|
|
1580
|
+
* column ID + `error` carries the leg's underlying error.
|
|
1581
|
+
*
|
|
1582
|
+
* **Status: schema landed at v0.8-M46 pre-flight contract diff
|
|
1583
|
+
* (Codex R1 P2-2 fix); runtime emit shipped at v0.8-M46 IMPL.**
|
|
1584
|
+
* `runItemUpdateBulkFileMultiDispatch` (below) emits against this
|
|
1585
|
+
* schema. Mirrors M42's per-item partial-success shape extended
|
|
1586
|
+
* with the multi-leg slots.
|
|
1587
|
+
*/
|
|
1588
|
+
export const bulkFileSetMultiResultSchema = z.object({
|
|
1589
|
+
item_id: z.string().min(1),
|
|
1590
|
+
ok: z.boolean(),
|
|
1591
|
+
/**
|
|
1592
|
+
* Per-leg asset projections — one per file column that landed
|
|
1593
|
+
* on this item. Length N on success; length 0..N-1 on per-item
|
|
1594
|
+
* partial failure (`ok: false` with `error` populated). Each
|
|
1595
|
+
* entry carries the column ID + asset shape (mirrors M31's
|
|
1596
|
+
* Asset projection inside a per-leg context).
|
|
1597
|
+
*/
|
|
1598
|
+
assets: z
|
|
1599
|
+
.array(z
|
|
1600
|
+
.object({
|
|
1601
|
+
column_id: z.string().min(1),
|
|
1602
|
+
filename: z.string().min(1),
|
|
1603
|
+
file_size_bytes: z.number().int().nonnegative(),
|
|
1604
|
+
asset: z
|
|
1605
|
+
.object({
|
|
1606
|
+
id: z.string().min(1),
|
|
1607
|
+
name: z.string().min(1),
|
|
1608
|
+
})
|
|
1609
|
+
.loose(),
|
|
1610
|
+
})
|
|
1611
|
+
.strict())
|
|
1612
|
+
.optional(),
|
|
1613
|
+
/**
|
|
1614
|
+
* Echo of file column IDs that landed successfully on this
|
|
1615
|
+
* item, in dispatch order. Length 1..N on success; length
|
|
1616
|
+
* 0..N-1 on per-item partial failure (reflecting columns that
|
|
1617
|
+
* landed before the failing leg).
|
|
1618
|
+
*/
|
|
1619
|
+
applied_file_columns: z.array(z.string().min(1)).optional(),
|
|
1620
|
+
/** Column ID of the failing leg on per-item partial failure. */
|
|
1621
|
+
failed_file_column: z.string().min(1).optional(),
|
|
1622
|
+
error: z
|
|
1623
|
+
.object({
|
|
1624
|
+
code: z.string().min(1),
|
|
1625
|
+
message: z.string().min(1),
|
|
1626
|
+
})
|
|
1627
|
+
.optional(),
|
|
1628
|
+
});
|
|
1629
|
+
/**
|
|
1630
|
+
* v0.8-M46 bulk multi-file `--set` envelope `data` shape.
|
|
1631
|
+
* Discriminate from M42's single-file `bulkFileSetDataSchema`
|
|
1632
|
+
* via `operation: 'item_update_bulk_file_set_multi'` (plural).
|
|
1633
|
+
* Agents reading `data.operation` branch uniformly across
|
|
1634
|
+
* single + multi shapes. Aggregate `summary` extends M42's
|
|
1635
|
+
* shape with `file_count: number` (the N file columns per item
|
|
1636
|
+
* the call attempted).
|
|
1637
|
+
*
|
|
1638
|
+
* Invariant: `matched_count === applied_count + failed_count`
|
|
1639
|
+
* (per-item rollup; mirrors M42 + M25 invariant). Per-item
|
|
1640
|
+
* partial failure mid-multi-leg counts toward `failed_count`
|
|
1641
|
+
* with the per-item record's `applied_file_columns` reflecting
|
|
1642
|
+
* the partial-leg success.
|
|
1643
|
+
*
|
|
1644
|
+
* **Status: schema landed at v0.8-M46 pre-flight contract diff
|
|
1645
|
+
* (Codex R1 P2-2 fix); runtime emit shipped at v0.8-M46 IMPL.**
|
|
1646
|
+
*/
|
|
1647
|
+
export const bulkFileSetMultiDataSchema = z.object({
|
|
1648
|
+
operation: z.literal('item_update_bulk_file_set_multi'),
|
|
1649
|
+
summary: z.object({
|
|
1650
|
+
matched_count: z.number().int().nonnegative(),
|
|
1651
|
+
applied_count: z.number().int().nonnegative(),
|
|
1652
|
+
failed_count: z.number().int().nonnegative(),
|
|
1653
|
+
board_id: z.string().min(1),
|
|
1654
|
+
file_count: z.number().int().min(2),
|
|
1655
|
+
file_column_ids: z.array(z.string().min(1)).min(2),
|
|
1656
|
+
}),
|
|
1657
|
+
results: z.array(bulkFileSetMultiResultSchema),
|
|
1658
|
+
});
|
|
1425
1659
|
/**
|
|
1426
1660
|
* Bulk file `--set` per-item dispatch helper (v0.7-M42 D5 carve-out
|
|
1427
1661
|
* fold).
|
|
@@ -1622,9 +1856,9 @@ export const runItemUpdateBulkFileDispatch = async (inputs) => {
|
|
|
1622
1856
|
// fail-fast error so a follow-up read doesn't serve stale
|
|
1623
1857
|
// metadata. Mirrors the M38 single-item invalidate-on-
|
|
1624
1858
|
// success pattern; the JSON-bulk fail-fast path has the
|
|
1625
|
-
// same gap (
|
|
1626
|
-
//
|
|
1627
|
-
//
|
|
1859
|
+
// same cache-invalidation gap (still-open lift candidate,
|
|
1860
|
+
// unrelated to the now-shipped R-v0.7-NEW-5 error-decoration
|
|
1861
|
+
// lift).
|
|
1628
1862
|
if (appliedAssets.length > 0) {
|
|
1629
1863
|
await invalidateBoard(inputs.boardId, inputs.ctx.env);
|
|
1630
1864
|
}
|
|
@@ -1647,10 +1881,11 @@ export const runItemUpdateBulkFileDispatch = async (inputs) => {
|
|
|
1647
1881
|
noCache: inputs.noCache,
|
|
1648
1882
|
resolutionSource: remapSource,
|
|
1649
1883
|
});
|
|
1650
|
-
// Same decoration shape as the JSON-bulk fail-fast path
|
|
1651
|
-
//
|
|
1652
|
-
//
|
|
1653
|
-
//
|
|
1884
|
+
// Same decoration shape as the JSON-bulk fail-fast path in
|
|
1885
|
+
// `runBulk` above. Grafts `applied_count` / `applied_to` /
|
|
1886
|
+
// `failed_at_item` / `matched_count` onto `details`, then
|
|
1887
|
+
// delegates the typed split to `reThrowDecorated` (which
|
|
1888
|
+
// preserves the existing error class' wire-metadata fields).
|
|
1654
1889
|
const existing = remapped.details ?? {};
|
|
1655
1890
|
const decoration = {
|
|
1656
1891
|
...existing,
|
|
@@ -1659,33 +1894,7 @@ export const runItemUpdateBulkFileDispatch = async (inputs) => {
|
|
|
1659
1894
|
failed_at_item: itemId,
|
|
1660
1895
|
matched_count: inputs.matchedItemIds.length,
|
|
1661
1896
|
};
|
|
1662
|
-
|
|
1663
|
-
throw new UsageError(remapped.message, {
|
|
1664
|
-
...(remapped.cause === undefined
|
|
1665
|
-
? {}
|
|
1666
|
-
: { cause: remapped.cause }),
|
|
1667
|
-
details: decoration,
|
|
1668
|
-
});
|
|
1669
|
-
}
|
|
1670
|
-
throw new ApiError(remapped.code, remapped.message, {
|
|
1671
|
-
...(remapped.cause === undefined
|
|
1672
|
-
? {}
|
|
1673
|
-
: { cause: remapped.cause }),
|
|
1674
|
-
...(remapped.httpStatus === undefined
|
|
1675
|
-
? {}
|
|
1676
|
-
: { httpStatus: remapped.httpStatus }),
|
|
1677
|
-
...(remapped.mondayCode === undefined
|
|
1678
|
-
? {}
|
|
1679
|
-
: { mondayCode: remapped.mondayCode }),
|
|
1680
|
-
...(remapped.requestId === undefined
|
|
1681
|
-
? {}
|
|
1682
|
-
: { requestId: remapped.requestId }),
|
|
1683
|
-
retryable: remapped.retryable,
|
|
1684
|
-
...(remapped.retryAfterSeconds === undefined
|
|
1685
|
-
? {}
|
|
1686
|
-
: { retryAfterSeconds: remapped.retryAfterSeconds }),
|
|
1687
|
-
details: decoration,
|
|
1688
|
-
});
|
|
1897
|
+
reThrowDecorated(remapped, decoration);
|
|
1689
1898
|
}
|
|
1690
1899
|
// Non-CliError programmer bug — re-throw to the runner's
|
|
1691
1900
|
// catch-all (surfaces as `internal_error` whole-call;
|
|
@@ -1879,4 +2088,497 @@ export const runItemUpdateBulkFileDispatch = async (inputs) => {
|
|
|
1879
2088
|
resolvedIds,
|
|
1880
2089
|
});
|
|
1881
2090
|
};
|
|
2091
|
+
/**
|
|
2092
|
+
* Single-item multi-file `--set` dispatch helper (v0.8-M46 D2
|
|
2093
|
+
* carve-out fold). Runtime body shipped at v0.8-M46 IMPL.
|
|
2094
|
+
*
|
|
2095
|
+
* **Execution shape:**
|
|
2096
|
+
*
|
|
2097
|
+
* 1. Single upfront `precheckLocalFile` per file path (N pre-checks
|
|
2098
|
+
* in argv order; D3 closure). Any pre-check failure aborts the
|
|
2099
|
+
* whole call with `usage_error` (`file_not_readable` /
|
|
2100
|
+
* `file_empty`) BEFORE any wire round-trip fires — atomicity-
|
|
2101
|
+
* before-wire per cli-design §5.8.
|
|
2102
|
+
* 2. Dry-run branch — N `add_file_to_column` planned_changes,
|
|
2103
|
+
* `source: 'none'` (pure-local, mirrors M38 single-item
|
|
2104
|
+
* dry-run); no multipart wire fires.
|
|
2105
|
+
* 3. Live branch — sequential N legs via
|
|
2106
|
+
* {@link dispatchFileLegsSequentially} (R-v0.8-NEW-1 shared
|
|
2107
|
+
* helper) against the single `inputs.itemId`. On partial
|
|
2108
|
+
* failure, `invalidateBoard` (any landed legs mutated the
|
|
2109
|
+
* board's asset state wire-side) then `internal_error` with
|
|
2110
|
+
* `details.reason: 'multi_file_update_partial_failure'` +
|
|
2111
|
+
* `details.item_id` + `details.applied_file_columns` (length
|
|
2112
|
+
* 0..N-1) + `details.failed_file_column` + `details.cause`
|
|
2113
|
+
* (M31 wire-failure surface, JSON projection) + `details.hint`.
|
|
2114
|
+
* Unlike the bulk + create paths, the single-item path does NOT
|
|
2115
|
+
* run `foldAndRemap` on the failing leg's cause — it mirrors the
|
|
2116
|
+
* M38 single-item precedent (`runItemUpdateSingleFileDispatch`
|
|
2117
|
+
* doesn't remap either); an archived file column is already
|
|
2118
|
+
* rejected at the pre-check's `includeArchived` archived-column
|
|
2119
|
+
* guard before dispatch.
|
|
2120
|
+
* 4. Full success — single `invalidateBoard` then envelope emit
|
|
2121
|
+
* per `fileColumnSetMultiOutputSchema`
|
|
2122
|
+
* (`operation: 'add_files_to_columns'` + `assets: [...]` +
|
|
2123
|
+
* `applied_file_columns: [...]`, both length N).
|
|
2124
|
+
*/
|
|
2125
|
+
const runItemUpdateSingleFileMultiDispatch = async (inputs) => {
|
|
2126
|
+
// 1) Upfront pre-check per file path, in argv order. A bad path
|
|
2127
|
+
// surfaces `usage_error` (exit 1) whole-call-abort before any
|
|
2128
|
+
// multipart wire leg fires (cli-design §5.8). R-v0.6-NEW-1
|
|
2129
|
+
// `precheckLocalFile` called N times (one per file column).
|
|
2130
|
+
const legEntries = [];
|
|
2131
|
+
for (const entry of inputs.m38.entries) {
|
|
2132
|
+
const precheck = await precheckLocalFile(entry.rawValue);
|
|
2133
|
+
legEntries.push({
|
|
2134
|
+
columnId: entry.columnId,
|
|
2135
|
+
rawValue: entry.rawValue,
|
|
2136
|
+
filePath: precheck.filePath,
|
|
2137
|
+
filename: precheck.filename,
|
|
2138
|
+
fileSizeBytes: precheck.fileSizeBytes,
|
|
2139
|
+
});
|
|
2140
|
+
}
|
|
2141
|
+
const warnings = inputs.m38.warnings.map((w) => ({
|
|
2142
|
+
code: w.code,
|
|
2143
|
+
message: w.message,
|
|
2144
|
+
details: w.details,
|
|
2145
|
+
}));
|
|
2146
|
+
const resolvedIds = Object.fromEntries(inputs.m38.entries.map((e) => [e.token, e.columnId]));
|
|
2147
|
+
// 2) Dry-run branch — N planned_changes; pure-local, `source: 'none'`
|
|
2148
|
+
// (mirrors M38 single-item dry-run).
|
|
2149
|
+
if (inputs.isDryRun) {
|
|
2150
|
+
emitDryRun({
|
|
2151
|
+
ctx: inputs.ctx,
|
|
2152
|
+
programOpts: inputs.programOpts,
|
|
2153
|
+
plannedChanges: legEntries.map((leg) => ({
|
|
2154
|
+
operation: 'add_file_to_column',
|
|
2155
|
+
item_id: inputs.itemId,
|
|
2156
|
+
column_id: leg.columnId,
|
|
2157
|
+
file_path: leg.rawValue,
|
|
2158
|
+
filename: leg.filename,
|
|
2159
|
+
file_size_bytes: leg.fileSizeBytes,
|
|
2160
|
+
})),
|
|
2161
|
+
source: 'none',
|
|
2162
|
+
cacheAgeSeconds: null,
|
|
2163
|
+
warnings,
|
|
2164
|
+
apiVersion: inputs.apiVersion,
|
|
2165
|
+
});
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
2168
|
+
// 3) Live branch — sequential N-leg fan-out against the single item.
|
|
2169
|
+
const dispatch = await dispatchFileLegsSequentially({
|
|
2170
|
+
client: inputs.client,
|
|
2171
|
+
multipart: inputs.multipart,
|
|
2172
|
+
itemId: inputs.itemId,
|
|
2173
|
+
entries: legEntries,
|
|
2174
|
+
signal: inputs.ctx.signal,
|
|
2175
|
+
retries: inputs.retries,
|
|
2176
|
+
});
|
|
2177
|
+
if (dispatch.failure !== undefined) {
|
|
2178
|
+
// Partial failure. If any leg landed, the board's asset state
|
|
2179
|
+
// mutated wire-side — invalidate before re-throwing so a
|
|
2180
|
+
// follow-up read doesn't serve stale metadata (mirrors M42's
|
|
2181
|
+
// fail-fast invalidate-on-partial-apply).
|
|
2182
|
+
if (dispatch.appliedColumns.length > 0) {
|
|
2183
|
+
await invalidateBoard(inputs.boardId, inputs.ctx.env);
|
|
2184
|
+
}
|
|
2185
|
+
const cause = dispatch.failure.cause;
|
|
2186
|
+
const causeProjection = projectCauseForEnvelope(cause);
|
|
2187
|
+
throw new ApiError('internal_error', `Multi-file \`--set\` on item ${inputs.itemId} partially failed: ` +
|
|
2188
|
+
`${String(dispatch.appliedColumns.length)} of ` +
|
|
2189
|
+
`${String(legEntries.length)} file column(s) landed before column ` +
|
|
2190
|
+
`${dispatch.failure.failedColumn} failed ` +
|
|
2191
|
+
`(${cause.code}: ${cause.message}). The applied file columns ` +
|
|
2192
|
+
`persist on Monday; retry only the unfailed columns with ` +
|
|
2193
|
+
`\`monday item set ${inputs.itemId} <file-col>=<path>\`, or reissue ` +
|
|
2194
|
+
`all ${String(legEntries.length)} \`--set\` entries.`, {
|
|
2195
|
+
cause,
|
|
2196
|
+
details: {
|
|
2197
|
+
reason: 'multi_file_update_partial_failure',
|
|
2198
|
+
item_id: inputs.itemId,
|
|
2199
|
+
applied_file_columns: dispatch.appliedColumns,
|
|
2200
|
+
failed_file_column: dispatch.failure.failedColumn,
|
|
2201
|
+
cause: causeProjection,
|
|
2202
|
+
hint: `${String(dispatch.appliedColumns.length)} file column(s) ` +
|
|
2203
|
+
`already landed on item ${inputs.itemId}; retry the unfailed ` +
|
|
2204
|
+
`columns alone with \`monday item set ${inputs.itemId} ` +
|
|
2205
|
+
`<file-col>=<path>\`, or reissue every \`--set\` entry (the ` +
|
|
2206
|
+
`landed columns will be overwritten with the same files).`,
|
|
2207
|
+
},
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
// 4) Full success — single board-cache invalidate before emit
|
|
2211
|
+
// (mirrors M38 single-leg invalidate timing; one board covers
|
|
2212
|
+
// every leg's mutated asset slot).
|
|
2213
|
+
await invalidateBoard(inputs.boardId, inputs.ctx.env);
|
|
2214
|
+
const data = {
|
|
2215
|
+
operation: 'add_files_to_columns',
|
|
2216
|
+
item_id: inputs.itemId,
|
|
2217
|
+
assets: dispatch.assets.map((a) => ({
|
|
2218
|
+
column_id: a.column_id,
|
|
2219
|
+
filename: a.filename,
|
|
2220
|
+
file_size_bytes: a.file_size_bytes,
|
|
2221
|
+
asset: a.asset,
|
|
2222
|
+
})),
|
|
2223
|
+
applied_file_columns: [...dispatch.appliedColumns],
|
|
2224
|
+
};
|
|
2225
|
+
emitMutation({
|
|
2226
|
+
ctx: inputs.ctx,
|
|
2227
|
+
data,
|
|
2228
|
+
schema: fileColumnSetMultiOutputSchema,
|
|
2229
|
+
programOpts: inputs.programOpts,
|
|
2230
|
+
warnings,
|
|
2231
|
+
...inputs.toEmit({
|
|
2232
|
+
data: dispatch.assets,
|
|
2233
|
+
complexity: dispatch.lastComplexity,
|
|
2234
|
+
stats: { attempts: 1, totalBackoffMs: 0 },
|
|
2235
|
+
}),
|
|
2236
|
+
source: 'live',
|
|
2237
|
+
cacheAgeSeconds: null,
|
|
2238
|
+
complexity: dispatch.lastComplexity,
|
|
2239
|
+
resolvedIds,
|
|
2240
|
+
});
|
|
2241
|
+
};
|
|
2242
|
+
/**
|
|
2243
|
+
* Bulk multi-file `--set` per-item dispatch helper (v0.8-M46 D2
|
|
2244
|
+
* carve-out fold). Runtime body shipped at v0.8-M46 IMPL.
|
|
2245
|
+
*
|
|
2246
|
+
* **Execution shape (extends M42's `runItemUpdateBulkFileDispatch`
|
|
2247
|
+
* single-file bulk path to N file legs per item):**
|
|
2248
|
+
*
|
|
2249
|
+
* 1. Single upfront `precheckLocalFile` pass over ALL N file paths
|
|
2250
|
+
* (D3 — N pre-checks total, shared across the M matched items,
|
|
2251
|
+
* NOT N×M). Any failure aborts the whole call with `usage_error`
|
|
2252
|
+
* regardless of `--continue-on-error` per cli-design §5.8.
|
|
2253
|
+
* 2. Dry-run branch — N×M `add_file_to_column` planned_changes
|
|
2254
|
+
* (one per (item, file column) pair); source aggregates the
|
|
2255
|
+
* upstream metadata + items_page legs per M42's pattern.
|
|
2256
|
+
* 3. Live branch — cross-item parallel via M42's `dispatchParallel`
|
|
2257
|
+
* / `dispatchSequential` (under `--concurrency`) × within-item
|
|
2258
|
+
* sequential N legs via {@link dispatchFileLegsSequentially}
|
|
2259
|
+
* (D1). Two shapes per `parsed.continueOnError`:
|
|
2260
|
+
* - **Fail-fast (default)** — sequential over matched items;
|
|
2261
|
+
* first per-item failure aborts whole-call with
|
|
2262
|
+
* `details.applied_to` (items where ALL legs landed) +
|
|
2263
|
+
* `details.applied_file_columns_per_item` (the failed item's
|
|
2264
|
+
* partial-leg map) + `details.failed_at_item` +
|
|
2265
|
+
* `details.failed_file_column`.
|
|
2266
|
+
* - **`--continue-on-error`** — per-item failures land as
|
|
2267
|
+
* `data.results[i].error` extended with
|
|
2268
|
+
* `applied_file_columns` + `failed_file_column`; partially-
|
|
2269
|
+
* landed assets carry on `data.results[i].assets`.
|
|
2270
|
+
* 4. Post-dispatch `invalidateBoard` + emit per
|
|
2271
|
+
* `bulkFileSetMultiDataSchema`
|
|
2272
|
+
* (`operation: 'item_update_bulk_file_set_multi'` +
|
|
2273
|
+
* `summary.{file_count, file_column_ids}`).
|
|
2274
|
+
*
|
|
2275
|
+
* Per-item failures route through `foldAndRemap` BEFORE the per-item
|
|
2276
|
+
* record / fail-fast decoration so the stable-code rule (cli-design
|
|
2277
|
+
* §6.5) surfaces `column_archived` for cache-served file-column
|
|
2278
|
+
* resolution against an archived column — mirrors M42's bulk
|
|
2279
|
+
* single-file remap.
|
|
2280
|
+
*/
|
|
2281
|
+
const runItemUpdateBulkFileMultiDispatch = async (inputs) => {
|
|
2282
|
+
// 1) Single upfront pre-check pass over ALL N file paths (D3).
|
|
2283
|
+
// Shared across the M matched items — N pre-checks, NOT N×M.
|
|
2284
|
+
// Whole-call abort regardless of `--continue-on-error` per
|
|
2285
|
+
// cli-design §5.8 (`precheckLocalFile` throws `usage_error`
|
|
2286
|
+
// with `file_not_readable` / `file_empty`).
|
|
2287
|
+
const legEntries = [];
|
|
2288
|
+
for (const entry of inputs.m38.entries) {
|
|
2289
|
+
const precheck = await precheckLocalFile(entry.rawValue);
|
|
2290
|
+
legEntries.push({
|
|
2291
|
+
columnId: entry.columnId,
|
|
2292
|
+
rawValue: entry.rawValue,
|
|
2293
|
+
filePath: precheck.filePath,
|
|
2294
|
+
filename: precheck.filename,
|
|
2295
|
+
fileSizeBytes: precheck.fileSizeBytes,
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
2298
|
+
const fileColumnIds = legEntries.map((leg) => leg.columnId);
|
|
2299
|
+
const combinedWarnings = dedupeWarnings([
|
|
2300
|
+
...inputs.filterWarnings,
|
|
2301
|
+
...inputs.m38.warnings,
|
|
2302
|
+
]);
|
|
2303
|
+
const resolvedIds = Object.fromEntries(inputs.m38.entries.map((e) => [e.token, e.columnId]));
|
|
2304
|
+
// Source aggregator — mirrors M42's bulk single-file pattern.
|
|
2305
|
+
// Seeds the metadata leg, folds the M38 pre-check leg + a synthetic
|
|
2306
|
+
// 'live' leg for the items_page walker (always fired upstream).
|
|
2307
|
+
const sourceAgg = new SourceAggregator({
|
|
2308
|
+
source: inputs.metaSource,
|
|
2309
|
+
cacheAgeSeconds: inputs.metaCacheAgeSeconds,
|
|
2310
|
+
});
|
|
2311
|
+
if (inputs.m38.source !== undefined) {
|
|
2312
|
+
sourceAgg.record(inputs.m38.source, inputs.m38.cacheAgeSeconds);
|
|
2313
|
+
}
|
|
2314
|
+
sourceAgg.record('live', null);
|
|
2315
|
+
// 2) Dry-run branch — N×M planned_changes (one `add_file_to_column`
|
|
2316
|
+
// per (item, file column) pair). No file bytes loaded, no
|
|
2317
|
+
// multipart wire. Source carries the aggregated upstream legs
|
|
2318
|
+
// (mirrors M42's bulk dry-run, which is NOT pure-local).
|
|
2319
|
+
if (inputs.isDryRun) {
|
|
2320
|
+
const plannedChanges = inputs.matchedItemIds.flatMap((itemId) => legEntries.map((leg) => ({
|
|
2321
|
+
operation: 'add_file_to_column',
|
|
2322
|
+
item_id: itemId,
|
|
2323
|
+
column_id: leg.columnId,
|
|
2324
|
+
file_path: leg.rawValue,
|
|
2325
|
+
filename: leg.filename,
|
|
2326
|
+
file_size_bytes: leg.fileSizeBytes,
|
|
2327
|
+
})));
|
|
2328
|
+
const dryRunAgg = sourceAgg.result();
|
|
2329
|
+
emitDryRun({
|
|
2330
|
+
ctx: inputs.ctx,
|
|
2331
|
+
programOpts: inputs.programOpts,
|
|
2332
|
+
plannedChanges,
|
|
2333
|
+
source: dryRunAgg.source,
|
|
2334
|
+
cacheAgeSeconds: dryRunAgg.cacheAgeSeconds,
|
|
2335
|
+
warnings: combinedWarnings,
|
|
2336
|
+
apiVersion: inputs.apiVersion,
|
|
2337
|
+
});
|
|
2338
|
+
return;
|
|
2339
|
+
}
|
|
2340
|
+
// 3) Live dispatch. Record the dispatch leg as 'live'.
|
|
2341
|
+
sourceAgg.record('live', null);
|
|
2342
|
+
const liveAgg = sourceAgg.result();
|
|
2343
|
+
const remapSource = inputs.m38.source ?? 'live';
|
|
2344
|
+
const fileCount = legEntries.length;
|
|
2345
|
+
const continueOnError = inputs.parsed.continueOnError === true;
|
|
2346
|
+
if (!continueOnError) {
|
|
2347
|
+
// Fail-fast bulk multi-file dispatch. Sequential over matched
|
|
2348
|
+
// items (M30 D2: `--concurrency requires --continue-on-error`).
|
|
2349
|
+
// Each item fans out N sequential file legs; first per-item
|
|
2350
|
+
// failure aborts whole-call.
|
|
2351
|
+
const fullyApplied = [];
|
|
2352
|
+
for (const itemId of inputs.matchedItemIds) {
|
|
2353
|
+
const dispatch = await dispatchFileLegsSequentially({
|
|
2354
|
+
client: inputs.client,
|
|
2355
|
+
multipart: inputs.multipart,
|
|
2356
|
+
itemId,
|
|
2357
|
+
entries: legEntries,
|
|
2358
|
+
signal: inputs.ctx.signal,
|
|
2359
|
+
retries: inputs.retries,
|
|
2360
|
+
});
|
|
2361
|
+
if (dispatch.failure !== undefined) {
|
|
2362
|
+
// Any prior item fully applied OR this item landed ≥1 leg →
|
|
2363
|
+
// the board's asset state mutated wire-side; invalidate
|
|
2364
|
+
// before re-throwing (mirrors M42 fail-fast invalidate).
|
|
2365
|
+
if (fullyApplied.length > 0 || dispatch.appliedColumns.length > 0) {
|
|
2366
|
+
await invalidateBoard(inputs.boardId, inputs.ctx.env);
|
|
2367
|
+
}
|
|
2368
|
+
const remapped = await foldAndRemap({
|
|
2369
|
+
err: dispatch.failure.cause,
|
|
2370
|
+
warnings: inputs.m38.warnings,
|
|
2371
|
+
client: inputs.client,
|
|
2372
|
+
boardId: inputs.boardId,
|
|
2373
|
+
columnIds: fileColumnIds,
|
|
2374
|
+
env: inputs.ctx.env,
|
|
2375
|
+
noCache: inputs.noCache,
|
|
2376
|
+
resolutionSource: remapSource,
|
|
2377
|
+
});
|
|
2378
|
+
const existing = remapped.details ?? {};
|
|
2379
|
+
const decoration = {
|
|
2380
|
+
...existing,
|
|
2381
|
+
applied_count: fullyApplied.length,
|
|
2382
|
+
applied_to: fullyApplied.map((a) => a.itemId),
|
|
2383
|
+
applied_file_columns_per_item: {
|
|
2384
|
+
[itemId]: dispatch.appliedColumns,
|
|
2385
|
+
},
|
|
2386
|
+
failed_at_item: itemId,
|
|
2387
|
+
failed_file_column: dispatch.failure.failedColumn,
|
|
2388
|
+
matched_count: inputs.matchedItemIds.length,
|
|
2389
|
+
file_count: fileCount,
|
|
2390
|
+
file_column_ids: fileColumnIds,
|
|
2391
|
+
};
|
|
2392
|
+
reThrowDecorated(remapped, decoration);
|
|
2393
|
+
}
|
|
2394
|
+
fullyApplied.push({ itemId, assets: dispatch.assets });
|
|
2395
|
+
}
|
|
2396
|
+
// Every item applied all N legs — single board-cache invalidate
|
|
2397
|
+
// before emit.
|
|
2398
|
+
await invalidateBoard(inputs.boardId, inputs.ctx.env);
|
|
2399
|
+
const results = fullyApplied.map(({ itemId, assets }) => ({
|
|
2400
|
+
item_id: itemId,
|
|
2401
|
+
ok: true,
|
|
2402
|
+
assets: assets.map((a) => ({
|
|
2403
|
+
column_id: a.column_id,
|
|
2404
|
+
filename: a.filename,
|
|
2405
|
+
file_size_bytes: a.file_size_bytes,
|
|
2406
|
+
asset: a.asset,
|
|
2407
|
+
})),
|
|
2408
|
+
applied_file_columns: [...fileColumnIds],
|
|
2409
|
+
}));
|
|
2410
|
+
const data = {
|
|
2411
|
+
operation: 'item_update_bulk_file_set_multi',
|
|
2412
|
+
summary: {
|
|
2413
|
+
matched_count: inputs.matchedItemIds.length,
|
|
2414
|
+
applied_count: fullyApplied.length,
|
|
2415
|
+
failed_count: 0,
|
|
2416
|
+
board_id: inputs.boardId,
|
|
2417
|
+
file_count: fileCount,
|
|
2418
|
+
file_column_ids: [...fileColumnIds],
|
|
2419
|
+
},
|
|
2420
|
+
results,
|
|
2421
|
+
};
|
|
2422
|
+
emitMutation({
|
|
2423
|
+
ctx: inputs.ctx,
|
|
2424
|
+
data,
|
|
2425
|
+
schema: bulkFileSetMultiDataSchema,
|
|
2426
|
+
programOpts: inputs.programOpts,
|
|
2427
|
+
warnings: combinedWarnings,
|
|
2428
|
+
source: liveAgg.source,
|
|
2429
|
+
cacheAgeSeconds: liveAgg.cacheAgeSeconds,
|
|
2430
|
+
apiVersion: inputs.apiVersion,
|
|
2431
|
+
resolvedIds,
|
|
2432
|
+
});
|
|
2433
|
+
return;
|
|
2434
|
+
}
|
|
2435
|
+
// `--continue-on-error` path. Per-item failures land as per-record
|
|
2436
|
+
// slots; the envelope is `ok: true` regardless of how many items
|
|
2437
|
+
// failed (universal partial-success rule). Each item's within-item
|
|
2438
|
+
// fan-out is sequential; a partial mid-multi-leg failure records
|
|
2439
|
+
// the landed assets + applied_file_columns + failed_file_column on
|
|
2440
|
+
// the per-item record. `internal_error` (or non-CliError) re-throws
|
|
2441
|
+
// whole-call via the shared dispatcher's escape hatch.
|
|
2442
|
+
const successById = new Map();
|
|
2443
|
+
const partialById = new Map();
|
|
2444
|
+
const perTargetDispatch = async ({ targetId, }) => {
|
|
2445
|
+
const dispatch = await dispatchFileLegsSequentially({
|
|
2446
|
+
client: inputs.client,
|
|
2447
|
+
multipart: inputs.multipart,
|
|
2448
|
+
itemId: targetId,
|
|
2449
|
+
entries: legEntries,
|
|
2450
|
+
signal: inputs.ctx.signal,
|
|
2451
|
+
retries: inputs.retries,
|
|
2452
|
+
});
|
|
2453
|
+
if (dispatch.failure !== undefined) {
|
|
2454
|
+
// Record the partial state (landed assets + applied columns +
|
|
2455
|
+
// failing column) keyed by item_id, then re-throw the remapped
|
|
2456
|
+
// error so the shared dispatcher captures it as `ok: false` +
|
|
2457
|
+
// `error: {code, message}`. `foldAndRemap` NEVER converts a
|
|
2458
|
+
// non-`internal_error` into `internal_error`, so the
|
|
2459
|
+
// dispatcher's `internal_error` escape hatch stays intact.
|
|
2460
|
+
partialById.set(targetId, {
|
|
2461
|
+
assets: dispatch.assets,
|
|
2462
|
+
appliedColumns: dispatch.appliedColumns,
|
|
2463
|
+
failedColumn: dispatch.failure.failedColumn,
|
|
2464
|
+
});
|
|
2465
|
+
const remapped = await foldAndRemap({
|
|
2466
|
+
err: dispatch.failure.cause,
|
|
2467
|
+
warnings: inputs.m38.warnings,
|
|
2468
|
+
client: inputs.client,
|
|
2469
|
+
boardId: inputs.boardId,
|
|
2470
|
+
columnIds: fileColumnIds,
|
|
2471
|
+
env: inputs.ctx.env,
|
|
2472
|
+
noCache: inputs.noCache,
|
|
2473
|
+
resolutionSource: remapSource,
|
|
2474
|
+
});
|
|
2475
|
+
throw remapped;
|
|
2476
|
+
}
|
|
2477
|
+
successById.set(targetId, dispatch.assets);
|
|
2478
|
+
};
|
|
2479
|
+
let dispatchResults;
|
|
2480
|
+
if (inputs.parsed.concurrency !== undefined &&
|
|
2481
|
+
inputs.parsed.concurrency > 1) {
|
|
2482
|
+
dispatchResults = await dispatchParallel(inputs.matchedItemIds, 'item_id', perTargetDispatch, inputs.parsed.concurrency, inputs.ctx.signal);
|
|
2483
|
+
}
|
|
2484
|
+
else {
|
|
2485
|
+
dispatchResults = await dispatchSequential(inputs.matchedItemIds, 'item_id', perTargetDispatch, inputs.ctx.signal);
|
|
2486
|
+
}
|
|
2487
|
+
// Single post-dispatch invalidate (fires even when every item
|
|
2488
|
+
// failed — wire calls still fired against Monday).
|
|
2489
|
+
await invalidateBoard(inputs.boardId, inputs.ctx.env);
|
|
2490
|
+
const results = dispatchResults.map((row) => {
|
|
2491
|
+
const itemIdSlot = row.item_id;
|
|
2492
|
+
/* c8 ignore next 8 — dispatcher contract: every result row carries
|
|
2493
|
+
the id-field slot; this guard catches a contract violation that
|
|
2494
|
+
would surface as a programmer bug, not a Monday-side failure. */
|
|
2495
|
+
if (typeof itemIdSlot !== 'string' || itemIdSlot.length === 0) {
|
|
2496
|
+
throw new ApiError('internal_error', 'bulk multi-file dispatch result row is missing the `item_id` field — dispatcher contract violation.', { details: { record_keys: Object.keys(row) } });
|
|
2497
|
+
}
|
|
2498
|
+
if (row.ok) {
|
|
2499
|
+
const assets = successById.get(itemIdSlot);
|
|
2500
|
+
/* c8 ignore next 8 — side-map invariant: every successful
|
|
2501
|
+
per-target dispatch records its assets; this guard catches a
|
|
2502
|
+
wrapper-layer miss (programmer bug). */
|
|
2503
|
+
if (assets === undefined) {
|
|
2504
|
+
throw new ApiError('internal_error', `bulk multi-file dispatch result row for item_id ${itemIdSlot} reported ok: true but no assets were captured — wrapper-layer side-map miss.`, { details: { item_id: itemIdSlot } });
|
|
2505
|
+
}
|
|
2506
|
+
return {
|
|
2507
|
+
item_id: itemIdSlot,
|
|
2508
|
+
ok: true,
|
|
2509
|
+
assets: assets.map((a) => ({
|
|
2510
|
+
column_id: a.column_id,
|
|
2511
|
+
filename: a.filename,
|
|
2512
|
+
file_size_bytes: a.file_size_bytes,
|
|
2513
|
+
asset: a.asset,
|
|
2514
|
+
})),
|
|
2515
|
+
applied_file_columns: [...fileColumnIds],
|
|
2516
|
+
};
|
|
2517
|
+
}
|
|
2518
|
+
/* c8 ignore next 8 — dispatcher contract: every `ok: false` row
|
|
2519
|
+
carries the `error` slot (populated by the shared dispatcher's
|
|
2520
|
+
per-target error decoration). */
|
|
2521
|
+
if (row.error === undefined) {
|
|
2522
|
+
throw new ApiError('internal_error', `bulk multi-file dispatch result row for item_id ${itemIdSlot} reported ok: false but no error payload was captured — dispatcher contract violation.`, { details: { item_id: itemIdSlot } });
|
|
2523
|
+
}
|
|
2524
|
+
const partial = partialById.get(itemIdSlot);
|
|
2525
|
+
/* c8 ignore next 8 — side-map invariant: every per-target failure
|
|
2526
|
+
routes through `perTargetDispatch`'s `partialById.set` before
|
|
2527
|
+
re-throwing; a miss is a wrapper-layer programmer bug. */
|
|
2528
|
+
if (partial === undefined) {
|
|
2529
|
+
throw new ApiError('internal_error', `bulk multi-file dispatch result row for item_id ${itemIdSlot} reported ok: false but no partial-leg state was captured — wrapper-layer side-map miss.`, { details: { item_id: itemIdSlot } });
|
|
2530
|
+
}
|
|
2531
|
+
return {
|
|
2532
|
+
item_id: itemIdSlot,
|
|
2533
|
+
ok: false,
|
|
2534
|
+
assets: partial.assets.map((a) => ({
|
|
2535
|
+
column_id: a.column_id,
|
|
2536
|
+
filename: a.filename,
|
|
2537
|
+
file_size_bytes: a.file_size_bytes,
|
|
2538
|
+
asset: a.asset,
|
|
2539
|
+
})),
|
|
2540
|
+
applied_file_columns: [...partial.appliedColumns],
|
|
2541
|
+
failed_file_column: partial.failedColumn,
|
|
2542
|
+
error: { code: row.error.code, message: row.error.message },
|
|
2543
|
+
};
|
|
2544
|
+
});
|
|
2545
|
+
const appliedCount = results.filter((r) => r.ok).length;
|
|
2546
|
+
const failedCount = results.filter((r) => !r.ok).length;
|
|
2547
|
+
/* c8 ignore next 11 — invariant: every matched item produces exactly
|
|
2548
|
+
one result row under both dispatchers; mismatch indicates a
|
|
2549
|
+
programmer bug in the dispatcher or the fold. */
|
|
2550
|
+
if (appliedCount + failedCount !== inputs.matchedItemIds.length) {
|
|
2551
|
+
throw new ApiError('internal_error', `bulk multi-file dispatch summary invariant violated — matched_count (${String(inputs.matchedItemIds.length)}) !== applied_count (${String(appliedCount)}) + failed_count (${String(failedCount)}).`, {
|
|
2552
|
+
details: {
|
|
2553
|
+
matched_count: inputs.matchedItemIds.length,
|
|
2554
|
+
applied_count: appliedCount,
|
|
2555
|
+
failed_count: failedCount,
|
|
2556
|
+
board_id: inputs.boardId,
|
|
2557
|
+
},
|
|
2558
|
+
});
|
|
2559
|
+
}
|
|
2560
|
+
const data = {
|
|
2561
|
+
operation: 'item_update_bulk_file_set_multi',
|
|
2562
|
+
summary: {
|
|
2563
|
+
matched_count: inputs.matchedItemIds.length,
|
|
2564
|
+
applied_count: appliedCount,
|
|
2565
|
+
failed_count: failedCount,
|
|
2566
|
+
board_id: inputs.boardId,
|
|
2567
|
+
file_count: fileCount,
|
|
2568
|
+
file_column_ids: [...fileColumnIds],
|
|
2569
|
+
},
|
|
2570
|
+
results,
|
|
2571
|
+
};
|
|
2572
|
+
emitMutation({
|
|
2573
|
+
ctx: inputs.ctx,
|
|
2574
|
+
data,
|
|
2575
|
+
schema: bulkFileSetMultiDataSchema,
|
|
2576
|
+
programOpts: inputs.programOpts,
|
|
2577
|
+
warnings: combinedWarnings,
|
|
2578
|
+
source: liveAgg.source,
|
|
2579
|
+
cacheAgeSeconds: liveAgg.cacheAgeSeconds,
|
|
2580
|
+
apiVersion: inputs.apiVersion,
|
|
2581
|
+
resolvedIds,
|
|
2582
|
+
});
|
|
2583
|
+
};
|
|
1882
2584
|
//# sourceMappingURL=update.js.map
|