@sendmux/cli 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -3
- package/dist/commands/mailbox/create-attachment-upload.d.ts +26 -0
- package/dist/commands/mailbox/create-attachment-upload.d.ts.map +1 -0
- package/dist/commands/mailbox/create-attachment-upload.js +7 -0
- package/dist/commands/mailbox/get-message-attachment.d.ts +6 -0
- package/dist/commands/mailbox/get-message-attachment.d.ts.map +1 -1
- package/dist/commands/mailbox/stream-events.d.ts +18 -0
- package/dist/commands/mailbox/stream-events.d.ts.map +1 -1
- package/dist/commands/mailbox/stream-events.js +8 -0
- package/dist/generated/operations.d.ts +27 -0
- package/dist/generated/operations.d.ts.map +1 -1
- package/dist/generated/operations.js +30 -0
- package/dist/operation-command.d.ts +4 -0
- package/dist/operation-command.d.ts.map +1 -1
- package/dist/operation-flags.d.ts +9 -0
- package/dist/operation-flags.d.ts.map +1 -1
- package/dist/operation-flags.js +29 -4
- package/dist/operation-runner.d.ts.map +1 -1
- package/dist/operation-runner.js +282 -0
- package/oclif.manifest.json +3200 -435
- package/package.json +2 -2
package/dist/operation-runner.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import * as sdk from "@sendmux/sdk";
|
|
2
|
+
import { readFile, stat } from "node:fs/promises";
|
|
3
|
+
import { basename, extname } from "node:path";
|
|
2
4
|
import { parseOperationOptions, } from "./operation-flags.js";
|
|
3
5
|
const surfaceModules = {
|
|
4
6
|
mailbox: sdk.mailbox,
|
|
@@ -11,6 +13,7 @@ const clientFactories = {
|
|
|
11
13
|
sending: sdk.sending.createSendingClient,
|
|
12
14
|
};
|
|
13
15
|
export async function runSdkOperation(command, operation, flags) {
|
|
16
|
+
validateAttachmentConvenienceFlags(command, operation, flags);
|
|
14
17
|
const auth = await command.resolveAuth(flags, operation.requiredKeyKind);
|
|
15
18
|
const clientConfig = {
|
|
16
19
|
apiKey: auth.apiKey,
|
|
@@ -19,6 +22,20 @@ export async function runSdkOperation(command, operation, flags) {
|
|
|
19
22
|
const client = clientFactories[operation.surface](clientConfig);
|
|
20
23
|
const operationOptions = await parseOperationOptions(command, operation, flags);
|
|
21
24
|
const sdkOperation = operationFor(operation);
|
|
25
|
+
if (operation.operationId === "mailboxCreateAttachmentUpload" && flags.file) {
|
|
26
|
+
return createMailboxAttachmentUploadFromFile(command, client, operationOptions, flags);
|
|
27
|
+
}
|
|
28
|
+
if (operation.operationId === "mailboxUploadAttachment" && flags.file) {
|
|
29
|
+
return uploadMailboxAttachmentFromFile(command, client, operationOptions, flags);
|
|
30
|
+
}
|
|
31
|
+
if ((operation.operationId === "mailboxSendMessage" || operation.operationId === "sendingSendEmail") && flags.attach?.length) {
|
|
32
|
+
const nextOptions = await withAttachedFiles(command, operation, client, operationOptions, flags);
|
|
33
|
+
const response = await sdkOperation({
|
|
34
|
+
client,
|
|
35
|
+
...nextOptions,
|
|
36
|
+
});
|
|
37
|
+
return command.renderResult(rawResponseData(response));
|
|
38
|
+
}
|
|
22
39
|
if (operation.operationId === "mailboxStreamEvents") {
|
|
23
40
|
const controller = new AbortController();
|
|
24
41
|
const response = await sdkOperation({
|
|
@@ -26,6 +43,9 @@ export async function runSdkOperation(command, operation, flags) {
|
|
|
26
43
|
...operationOptions,
|
|
27
44
|
signal: controller.signal,
|
|
28
45
|
});
|
|
46
|
+
if (flags.follow) {
|
|
47
|
+
return streamEvents(command, response, controller);
|
|
48
|
+
}
|
|
29
49
|
return command.renderResult(await firstStreamEvent(response, controller));
|
|
30
50
|
}
|
|
31
51
|
if (operation.operationId === "mailboxGetMessageAttachment") {
|
|
@@ -50,6 +70,241 @@ export async function runSdkOperation(command, operation, flags) {
|
|
|
50
70
|
}
|
|
51
71
|
return command.renderResult(data);
|
|
52
72
|
}
|
|
73
|
+
function validateAttachmentConvenienceFlags(command, operation, flags) {
|
|
74
|
+
const supportsFile = operation.operationId === "mailboxUploadAttachment" || operation.operationId === "mailboxCreateAttachmentUpload";
|
|
75
|
+
if (flags.file && !supportsFile) {
|
|
76
|
+
command.error("This command does not support --file. Use --attach on send commands.", { exit: 2 });
|
|
77
|
+
}
|
|
78
|
+
if (flags["via-presigned"] && operation.operationId !== "mailboxUploadAttachment") {
|
|
79
|
+
command.error("--via-presigned is only supported by mailbox:upload-attachment --file.", { exit: 2 });
|
|
80
|
+
}
|
|
81
|
+
const supportsAttach = operation.operationId === "mailboxSendMessage" || operation.operationId === "sendingSendEmail";
|
|
82
|
+
if (flags.attach?.length && !supportsAttach) {
|
|
83
|
+
command.error("--attach is only supported by mailbox:send-message and sending:send.", { exit: 2 });
|
|
84
|
+
}
|
|
85
|
+
if (flags.attach?.length && flags["content-type"] && flags.attach.length > 1) {
|
|
86
|
+
command.error("--content-type with multiple --attach files would apply the same type to every file. Omit it to infer per file.", {
|
|
87
|
+
exit: 2,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function createMailboxAttachmentUploadFromFile(command, client, operationOptions, flags) {
|
|
92
|
+
const file = await readAttachmentFile(command, flags.file, flags["content-type"]);
|
|
93
|
+
const response = await sdk.mailbox.mailboxCreateAttachmentUpload({
|
|
94
|
+
client: client,
|
|
95
|
+
body: {
|
|
96
|
+
content_type: file.contentType,
|
|
97
|
+
filename: file.filename,
|
|
98
|
+
size_bytes: file.sizeBytes,
|
|
99
|
+
},
|
|
100
|
+
query: mailboxUploadQuery(operationOptions),
|
|
101
|
+
});
|
|
102
|
+
return command.renderResult(rawResponseData(response));
|
|
103
|
+
}
|
|
104
|
+
async function uploadMailboxAttachmentFromFile(command, client, operationOptions, flags) {
|
|
105
|
+
const file = await readAttachmentFile(command, flags.file, flags["content-type"]);
|
|
106
|
+
if (flags["via-presigned"]) {
|
|
107
|
+
const intentResponse = await sdk.mailbox.mailboxCreateAttachmentUpload({
|
|
108
|
+
client: client,
|
|
109
|
+
query: mailboxUploadQuery(operationOptions),
|
|
110
|
+
body: {
|
|
111
|
+
content_type: file.contentType,
|
|
112
|
+
filename: file.filename,
|
|
113
|
+
size_bytes: file.sizeBytes,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
const intent = envelopeData(intentResponse, "mailboxCreateAttachmentUpload");
|
|
117
|
+
const uploadUrl = stringField(intent, "upload_url", "mailboxCreateAttachmentUpload");
|
|
118
|
+
const method = stringField(intent, "method", "mailboxCreateAttachmentUpload");
|
|
119
|
+
const headers = recordField(intent, "headers", "mailboxCreateAttachmentUpload");
|
|
120
|
+
const contentType = stringField(headers, "Content-Type", "mailboxCreateAttachmentUpload headers");
|
|
121
|
+
const contentLength = stringField(headers, "Content-Length", "mailboxCreateAttachmentUpload headers");
|
|
122
|
+
if (method !== "PUT") {
|
|
123
|
+
throw new Error(`mailboxCreateAttachmentUpload returned unsupported method ${method}`);
|
|
124
|
+
}
|
|
125
|
+
const putResponse = await fetch(uploadUrl, {
|
|
126
|
+
body: arrayBufferFor(file.bytes),
|
|
127
|
+
headers: {
|
|
128
|
+
"Content-Length": contentLength,
|
|
129
|
+
"Content-Type": contentType,
|
|
130
|
+
},
|
|
131
|
+
method,
|
|
132
|
+
});
|
|
133
|
+
const payload = await putResponse.json().catch(() => undefined);
|
|
134
|
+
if (!putResponse.ok) {
|
|
135
|
+
throw new Error(`Presigned attachment upload failed with HTTP ${putResponse.status}`);
|
|
136
|
+
}
|
|
137
|
+
return command.renderResult(payload);
|
|
138
|
+
}
|
|
139
|
+
const response = await sdk.mailbox.mailboxUploadAttachment({
|
|
140
|
+
client: client,
|
|
141
|
+
body: blobFor(file),
|
|
142
|
+
headers: {
|
|
143
|
+
...(recordOrUndefined(operationOptions.headers)),
|
|
144
|
+
"Content-Type": file.contentType,
|
|
145
|
+
},
|
|
146
|
+
query: {
|
|
147
|
+
...mailboxUploadQuery(operationOptions),
|
|
148
|
+
filename: file.filename,
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
return command.renderResult(rawResponseData(response));
|
|
152
|
+
}
|
|
153
|
+
async function withAttachedFiles(command, operation, client, operationOptions, flags) {
|
|
154
|
+
const body = jsonObjectBody(command, operationOptions.body);
|
|
155
|
+
const existingAttachments = attachmentArray(command, body.attachments);
|
|
156
|
+
const files = [];
|
|
157
|
+
for (const path of flags.attach ?? []) {
|
|
158
|
+
files.push(await readAttachmentFile(command, path, flags["content-type"]));
|
|
159
|
+
}
|
|
160
|
+
if (operation.operationId === "mailboxSendMessage") {
|
|
161
|
+
const uploaded = [];
|
|
162
|
+
for (const file of files) {
|
|
163
|
+
const uploadResponse = await sdk.mailbox.mailboxUploadAttachment({
|
|
164
|
+
client: client,
|
|
165
|
+
body: blobFor(file),
|
|
166
|
+
headers: {
|
|
167
|
+
"Content-Type": file.contentType,
|
|
168
|
+
},
|
|
169
|
+
query: {
|
|
170
|
+
...mailboxUploadQuery(operationOptions),
|
|
171
|
+
filename: file.filename,
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
const result = envelopeData(uploadResponse, "mailboxUploadAttachment");
|
|
175
|
+
uploaded.push({
|
|
176
|
+
blob_id: stringField(result, "blob_id", "mailboxUploadAttachment"),
|
|
177
|
+
content_type: stringField(result, "content_type", "mailboxUploadAttachment"),
|
|
178
|
+
filename: stringField(result, "filename", "mailboxUploadAttachment"),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
...operationOptions,
|
|
183
|
+
body: {
|
|
184
|
+
...body,
|
|
185
|
+
attachments: [...existingAttachments, ...uploaded],
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
...operationOptions,
|
|
191
|
+
body: {
|
|
192
|
+
...body,
|
|
193
|
+
attachments: [
|
|
194
|
+
...existingAttachments,
|
|
195
|
+
...files.map((file) => ({
|
|
196
|
+
content: file.bytes.toString("base64"),
|
|
197
|
+
encoding: "base64",
|
|
198
|
+
filename: file.filename,
|
|
199
|
+
type: file.contentType,
|
|
200
|
+
})),
|
|
201
|
+
],
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
async function readAttachmentFile(command, filePath, contentTypeOverride) {
|
|
206
|
+
const info = await stat(filePath).catch((error) => {
|
|
207
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
208
|
+
command.error(`Could not read attachment file ${filePath}: ${message}`, { exit: 2 });
|
|
209
|
+
});
|
|
210
|
+
if (!info.isFile()) {
|
|
211
|
+
command.error(`Attachment path is not a regular file: ${filePath}`, { exit: 2 });
|
|
212
|
+
}
|
|
213
|
+
if (info.size === 0) {
|
|
214
|
+
command.error(`Attachment file is empty: ${filePath}`, { exit: 2 });
|
|
215
|
+
}
|
|
216
|
+
const bytes = await readFile(filePath);
|
|
217
|
+
return {
|
|
218
|
+
bytes,
|
|
219
|
+
contentType: contentTypeOverride ?? inferContentType(filePath),
|
|
220
|
+
filename: basename(filePath),
|
|
221
|
+
sizeBytes: bytes.byteLength,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function inferContentType(filePath) {
|
|
225
|
+
switch (extname(filePath).toLowerCase()) {
|
|
226
|
+
case ".csv":
|
|
227
|
+
return "text/csv";
|
|
228
|
+
case ".gif":
|
|
229
|
+
return "image/gif";
|
|
230
|
+
case ".htm":
|
|
231
|
+
case ".html":
|
|
232
|
+
return "text/html";
|
|
233
|
+
case ".jpeg":
|
|
234
|
+
case ".jpg":
|
|
235
|
+
return "image/jpeg";
|
|
236
|
+
case ".json":
|
|
237
|
+
return "application/json";
|
|
238
|
+
case ".md":
|
|
239
|
+
case ".txt":
|
|
240
|
+
return "text/plain";
|
|
241
|
+
case ".pdf":
|
|
242
|
+
return "application/pdf";
|
|
243
|
+
case ".png":
|
|
244
|
+
return "image/png";
|
|
245
|
+
case ".webp":
|
|
246
|
+
return "image/webp";
|
|
247
|
+
case ".zip":
|
|
248
|
+
return "application/zip";
|
|
249
|
+
default:
|
|
250
|
+
return "application/octet-stream";
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function jsonObjectBody(command, value) {
|
|
254
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
255
|
+
command.error("--attach requires a JSON object request body.", { exit: 2 });
|
|
256
|
+
}
|
|
257
|
+
return value;
|
|
258
|
+
}
|
|
259
|
+
function attachmentArray(command, value) {
|
|
260
|
+
if (value === undefined) {
|
|
261
|
+
return [];
|
|
262
|
+
}
|
|
263
|
+
if (!Array.isArray(value)) {
|
|
264
|
+
command.error('Request body field "attachments" must be an array when using --attach.', { exit: 2 });
|
|
265
|
+
}
|
|
266
|
+
return value;
|
|
267
|
+
}
|
|
268
|
+
function mailboxUploadQuery(operationOptions) {
|
|
269
|
+
const mailboxId = recordOrUndefined(operationOptions.query)?.mailbox_id;
|
|
270
|
+
return typeof mailboxId === "string" && mailboxId.length > 0 ? { mailbox_id: mailboxId } : {};
|
|
271
|
+
}
|
|
272
|
+
function envelopeData(value, operationId) {
|
|
273
|
+
const raw = rawResponseData(value);
|
|
274
|
+
if (!raw || typeof raw !== "object" || !("data" in raw)) {
|
|
275
|
+
throw new Error(`SDK operation ${operationId} did not return an API envelope`);
|
|
276
|
+
}
|
|
277
|
+
const data = raw.data;
|
|
278
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
279
|
+
throw new Error(`SDK operation ${operationId} did not return object data`);
|
|
280
|
+
}
|
|
281
|
+
return data;
|
|
282
|
+
}
|
|
283
|
+
function stringField(record, field, source) {
|
|
284
|
+
const value = record[field];
|
|
285
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
286
|
+
throw new Error(`${source} did not return string field ${field}`);
|
|
287
|
+
}
|
|
288
|
+
return value;
|
|
289
|
+
}
|
|
290
|
+
function recordField(record, field, source) {
|
|
291
|
+
const value = record[field];
|
|
292
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
293
|
+
throw new Error(`${source} did not return object field ${field}`);
|
|
294
|
+
}
|
|
295
|
+
return value;
|
|
296
|
+
}
|
|
297
|
+
function recordOrUndefined(value) {
|
|
298
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
299
|
+
}
|
|
300
|
+
function blobFor(file) {
|
|
301
|
+
return new Blob([arrayBufferFor(file.bytes)], { type: file.contentType });
|
|
302
|
+
}
|
|
303
|
+
function arrayBufferFor(bytes) {
|
|
304
|
+
const copy = new Uint8Array(bytes.byteLength);
|
|
305
|
+
copy.set(bytes);
|
|
306
|
+
return copy.buffer;
|
|
307
|
+
}
|
|
53
308
|
async function firstStreamEvent(value, controller) {
|
|
54
309
|
const stream = value?.stream;
|
|
55
310
|
if (!stream || typeof stream[Symbol.asyncIterator] !== "function") {
|
|
@@ -75,6 +330,33 @@ async function firstStreamEvent(value, controller) {
|
|
|
75
330
|
await closeAsyncIterator(iterator);
|
|
76
331
|
}
|
|
77
332
|
}
|
|
333
|
+
async function streamEvents(command, value, controller) {
|
|
334
|
+
const stream = value?.stream;
|
|
335
|
+
if (!stream || typeof stream[Symbol.asyncIterator] !== "function") {
|
|
336
|
+
throw new Error("SDK operation mailboxStreamEvents did not return an async stream");
|
|
337
|
+
}
|
|
338
|
+
const iterator = stream[Symbol.asyncIterator]();
|
|
339
|
+
const abort = () => {
|
|
340
|
+
controller.abort();
|
|
341
|
+
};
|
|
342
|
+
process.once("SIGINT", abort);
|
|
343
|
+
process.once("SIGTERM", abort);
|
|
344
|
+
try {
|
|
345
|
+
while (true) {
|
|
346
|
+
const next = await iterator.next();
|
|
347
|
+
if (next.done) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
command.log(JSON.stringify(next.value));
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
finally {
|
|
354
|
+
process.off("SIGINT", abort);
|
|
355
|
+
process.off("SIGTERM", abort);
|
|
356
|
+
controller.abort();
|
|
357
|
+
await closeAsyncIterator(iterator);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
78
360
|
async function closeAsyncIterator(iterator) {
|
|
79
361
|
if (typeof iterator.return !== "function") {
|
|
80
362
|
return;
|