shipmail-cli 0.5.3 → 0.5.4
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/dist/cli.js +87 -16
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -15,10 +15,28 @@ import {
|
|
|
15
15
|
} from "shipmail";
|
|
16
16
|
|
|
17
17
|
// src/version.ts
|
|
18
|
-
var VERSION = "0.5.
|
|
18
|
+
var VERSION = "0.5.4";
|
|
19
19
|
|
|
20
20
|
// src/cli.ts
|
|
21
21
|
var DEFAULT_BASE_URL = "https://shipmail.to/api/v1";
|
|
22
|
+
var ALLOWED_BASE_URL_HOSTS = ["shipmail.to"];
|
|
23
|
+
var RAW_REQUEST_TIMEOUT_MS = 3e4;
|
|
24
|
+
var NEWSLETTER_BLOCK_TYPES = [
|
|
25
|
+
"heading",
|
|
26
|
+
"paragraph",
|
|
27
|
+
"list",
|
|
28
|
+
"callout",
|
|
29
|
+
"button",
|
|
30
|
+
"image",
|
|
31
|
+
"video",
|
|
32
|
+
"columns",
|
|
33
|
+
"link_list",
|
|
34
|
+
"quote",
|
|
35
|
+
"divider",
|
|
36
|
+
"spacer",
|
|
37
|
+
"code",
|
|
38
|
+
"custom_html"
|
|
39
|
+
];
|
|
22
40
|
var WEBHOOK_EVENT_TYPE_SET = new Set(WEBHOOK_EVENT_TYPES);
|
|
23
41
|
var CliUsageError = class extends Error {
|
|
24
42
|
constructor(message) {
|
|
@@ -39,6 +57,7 @@ function parseCliArgs(argv) {
|
|
|
39
57
|
const options = {};
|
|
40
58
|
for (let i = 0; i < argv.length; i += 1) {
|
|
41
59
|
const token = argv[i];
|
|
60
|
+
if (token === void 0) break;
|
|
42
61
|
if (token === "--") {
|
|
43
62
|
positional.push(...argv.slice(i + 1));
|
|
44
63
|
break;
|
|
@@ -117,14 +136,35 @@ function getStringList(options, key) {
|
|
|
117
136
|
if (isStringArray(value)) return value.flatMap(splitValues);
|
|
118
137
|
return splitValues(value);
|
|
119
138
|
}
|
|
120
|
-
function
|
|
121
|
-
|
|
139
|
+
function validateBaseUrl(rawValue, allowInsecure) {
|
|
140
|
+
let parsed;
|
|
141
|
+
try {
|
|
142
|
+
parsed = new URL(rawValue);
|
|
143
|
+
} catch {
|
|
144
|
+
throw new CliUsageError(`Base URL is not a valid URL: ${rawValue}`);
|
|
145
|
+
}
|
|
146
|
+
const normalized = parsed.toString().replace(/\/+$/, "");
|
|
147
|
+
if (allowInsecure) return normalized;
|
|
148
|
+
if (parsed.protocol !== "https:") {
|
|
149
|
+
throw new CliUsageError(
|
|
150
|
+
"Base URL must use https. Set SHIPMAIL_ALLOW_INSECURE_BASE_URL=1 for development."
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
const host = parsed.hostname.toLowerCase();
|
|
154
|
+
if (!ALLOWED_BASE_URL_HOSTS.some((allowed) => host === allowed || host.endsWith(`.${allowed}`))) {
|
|
155
|
+
throw new CliUsageError(
|
|
156
|
+
`Base URL host "${host}" is not allowed. Set SHIPMAIL_ALLOW_INSECURE_BASE_URL=1 for development.`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
return normalized;
|
|
122
160
|
}
|
|
123
161
|
function apiKey(parsed, env) {
|
|
124
162
|
return getString(parsed.options, "api-key") ?? env["SHIPMAIL_API_KEY"];
|
|
125
163
|
}
|
|
126
164
|
function baseUrl(parsed, env) {
|
|
127
|
-
|
|
165
|
+
const raw = getString(parsed.options, "base-url") ?? env["SHIPMAIL_BASE_URL"];
|
|
166
|
+
if (!raw) return DEFAULT_BASE_URL;
|
|
167
|
+
return validateBaseUrl(raw, env["SHIPMAIL_ALLOW_INSECURE_BASE_URL"] === "1");
|
|
128
168
|
}
|
|
129
169
|
function organizationId(parsed, env) {
|
|
130
170
|
return getString(parsed.options, "organization-id") ?? env["SHIPMAIL_ORGANIZATION_ID"];
|
|
@@ -164,6 +204,23 @@ async function readTextInput(value) {
|
|
|
164
204
|
if (value.startsWith("@")) return readFile(value.slice(1), "utf8");
|
|
165
205
|
return value;
|
|
166
206
|
}
|
|
207
|
+
function isJsonObject(value) {
|
|
208
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
209
|
+
}
|
|
210
|
+
function isNewsletterBlock(value) {
|
|
211
|
+
return isJsonObject(value) && typeof value["type"] === "string" && NEWSLETTER_BLOCK_TYPES.includes(value["type"]);
|
|
212
|
+
}
|
|
213
|
+
function isAutomationDefinition(value) {
|
|
214
|
+
if (!isJsonObject(value) || !isJsonObject(value["trigger"]) || !isJsonObject(value["scope"])) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
return typeof value["trigger"]["type"] === "string" && Array.isArray(value["conditions"]) && Array.isArray(value["actions"]) && (value["mode"] === "draft" || value["mode"] === "send") && Array.isArray(value["scope"]["mailbox_ids"]) && Array.isArray(value["scope"]["calendar_addresses"]);
|
|
218
|
+
}
|
|
219
|
+
function isMessageMetadata(value) {
|
|
220
|
+
return isJsonObject(value) && Object.values(value).every(
|
|
221
|
+
(item) => item === null || typeof item === "string" || typeof item === "number" || typeof item === "boolean"
|
|
222
|
+
);
|
|
223
|
+
}
|
|
167
224
|
async function readNewsletterBlocks(value) {
|
|
168
225
|
const parsed = await readJsonInput(value);
|
|
169
226
|
if (!Array.isArray(parsed)) {
|
|
@@ -172,6 +229,11 @@ async function readNewsletterBlocks(value) {
|
|
|
172
229
|
if (parsed.length === 0) {
|
|
173
230
|
throw new CliUsageError("--blocks must include at least one block.");
|
|
174
231
|
}
|
|
232
|
+
if (!parsed.every(isNewsletterBlock)) {
|
|
233
|
+
throw new CliUsageError(
|
|
234
|
+
`Every --blocks item must be an object whose type is one of: ${NEWSLETTER_BLOCK_TYPES.join(", ")}.`
|
|
235
|
+
);
|
|
236
|
+
}
|
|
175
237
|
return parsed;
|
|
176
238
|
}
|
|
177
239
|
function parseArchiveVisibility(options) {
|
|
@@ -188,8 +250,10 @@ function parseStylingMode(options) {
|
|
|
188
250
|
}
|
|
189
251
|
async function automationDefinition(value) {
|
|
190
252
|
const parsed = await readJsonInput(value);
|
|
191
|
-
if (
|
|
192
|
-
throw new CliUsageError(
|
|
253
|
+
if (!isAutomationDefinition(parsed)) {
|
|
254
|
+
throw new CliUsageError(
|
|
255
|
+
"--definition must be a JSON object with trigger, conditions, actions, mode (draft or send) and scope { mailbox_ids, calendar_addresses }."
|
|
256
|
+
);
|
|
193
257
|
}
|
|
194
258
|
return parsed;
|
|
195
259
|
}
|
|
@@ -306,7 +370,8 @@ async function rawApiRequest(parsed, runOptions, method, path, body, authRequire
|
|
|
306
370
|
const response = await (runOptions.fetch ?? fetch)(`${baseUrl(parsed, env)}${path}`, {
|
|
307
371
|
method,
|
|
308
372
|
headers,
|
|
309
|
-
body: requestBody
|
|
373
|
+
body: requestBody,
|
|
374
|
+
signal: AbortSignal.timeout(RAW_REQUEST_TIMEOUT_MS)
|
|
310
375
|
});
|
|
311
376
|
if (!response.ok) {
|
|
312
377
|
const contentType = response.headers.get("content-type") ?? "";
|
|
@@ -492,7 +557,9 @@ Global options:
|
|
|
492
557
|
|
|
493
558
|
Environment:
|
|
494
559
|
SHIPMAIL_API_KEY API key for authenticated commands.
|
|
495
|
-
SHIPMAIL_BASE_URL Optional API base URL.
|
|
560
|
+
SHIPMAIL_BASE_URL Optional API base URL. Must be https on shipmail.to.
|
|
561
|
+
SHIPMAIL_ALLOW_INSECURE_BASE_URL=1
|
|
562
|
+
Permit a non-https or non-shipmail.to base URL (development only).
|
|
496
563
|
SHIPMAIL_ORGANIZATION_ID Optional delegated child organization ID.
|
|
497
564
|
`;
|
|
498
565
|
}
|
|
@@ -557,8 +624,10 @@ async function messageMetadata(options) {
|
|
|
557
624
|
const raw = getString(options, "metadata");
|
|
558
625
|
if (!raw) return void 0;
|
|
559
626
|
const parsed = await readJsonInput(raw);
|
|
560
|
-
if (!
|
|
561
|
-
throw new CliUsageError(
|
|
627
|
+
if (!isMessageMetadata(parsed)) {
|
|
628
|
+
throw new CliUsageError(
|
|
629
|
+
"--metadata must be a JSON object of string, number, boolean or null values, or @path/to/file.json."
|
|
630
|
+
);
|
|
562
631
|
}
|
|
563
632
|
return parsed;
|
|
564
633
|
}
|
|
@@ -717,7 +786,8 @@ async function runMailboxes(client, parsed, out) {
|
|
|
717
786
|
throw new CliUsageError("Missing --password, --password-file, or --generate-password.");
|
|
718
787
|
}
|
|
719
788
|
const password = passwordFile ? (await readFile(passwordFile, "utf8")).replace(/\r?\n$/, "") : passwordValue;
|
|
720
|
-
|
|
789
|
+
const credentials = generatePassword ? { generate_password: true } : password ? { password } : void 0;
|
|
790
|
+
if (!credentials) {
|
|
721
791
|
throw new CliUsageError("Mailbox password must not be empty.");
|
|
722
792
|
}
|
|
723
793
|
writeJson(
|
|
@@ -725,7 +795,7 @@ async function runMailboxes(client, parsed, out) {
|
|
|
725
795
|
await client.mailboxes.create({
|
|
726
796
|
domain_id: getRequiredString(parsed.options, "domain-id"),
|
|
727
797
|
address: getRequiredString(parsed.options, "address"),
|
|
728
|
-
...
|
|
798
|
+
...credentials,
|
|
729
799
|
...displayName ? { display_name: displayName } : {}
|
|
730
800
|
})
|
|
731
801
|
);
|
|
@@ -887,12 +957,13 @@ async function runMailboxes(client, parsed, out) {
|
|
|
887
957
|
}
|
|
888
958
|
params = { mode: "keep" };
|
|
889
959
|
} else if (mode === "fixed") {
|
|
890
|
-
|
|
960
|
+
const [destinationMailboxId] = destinationMailboxIds;
|
|
961
|
+
if (destinationMailboxIds.length !== 1 || destinationMailboxId === void 0) {
|
|
891
962
|
throw new CliUsageError("--mode fixed requires exactly one --destination-mailbox-id.");
|
|
892
963
|
}
|
|
893
964
|
params = {
|
|
894
965
|
mode: "fixed",
|
|
895
|
-
destination_mailbox_id:
|
|
966
|
+
destination_mailbox_id: destinationMailboxId,
|
|
896
967
|
fallback_mailbox_id: getRequiredString(parsed.options, "fallback-mailbox-id")
|
|
897
968
|
};
|
|
898
969
|
} else if (mode === "round-robin") {
|
|
@@ -1215,10 +1286,10 @@ async function runMessages(client, parsed, out) {
|
|
|
1215
1286
|
if (subcommand === "list") {
|
|
1216
1287
|
const mailboxId = getString(parsed.options, "mailbox-id");
|
|
1217
1288
|
const clientReference = getString(parsed.options, "client-reference");
|
|
1218
|
-
|
|
1289
|
+
const filter = mailboxId ? { mailbox_id: mailboxId, ...clientReference ? { client_reference: clientReference } : {} } : clientReference ? { client_reference: clientReference } : void 0;
|
|
1290
|
+
if (!filter) {
|
|
1219
1291
|
throw new CliUsageError("Provide --mailbox-id or --client-reference.");
|
|
1220
1292
|
}
|
|
1221
|
-
const filter = mailboxId ? { mailbox_id: mailboxId, ...clientReference ? { client_reference: clientReference } : {} } : { client_reference: clientReference };
|
|
1222
1293
|
writeJson(
|
|
1223
1294
|
out,
|
|
1224
1295
|
await client.messages.list({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shipmail-cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "Official CLI for the Shipmail API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"shipmail": "0.5.
|
|
44
|
+
"shipmail": "0.5.10"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"tsup": "^8.4.0",
|