@develemail/cli 0.1.5 → 0.1.6
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/index.js +339 -15
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command11 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/send.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -91,7 +91,10 @@ var COL_STATUS = 10;
|
|
|
91
91
|
function col(s, w) {
|
|
92
92
|
return s.slice(0, w).padEnd(w);
|
|
93
93
|
}
|
|
94
|
-
var logsCommand = new Command2("logs").description("List recent emails").option("--limit <n>", "Number of emails to show", "20").option(
|
|
94
|
+
var logsCommand = new Command2("logs").description("List recent emails").option("--limit <n>", "Number of emails to show", "20").option(
|
|
95
|
+
"--status <status>",
|
|
96
|
+
"Filter by status (sent, delivered, queued, deferred, bounced, failed, \u2026)"
|
|
97
|
+
).action(async (opts) => {
|
|
95
98
|
const cfg = loadConfig();
|
|
96
99
|
const params = new URLSearchParams({ limit: opts.limit });
|
|
97
100
|
if (opts.status) params.set("status", opts.status);
|
|
@@ -139,6 +142,12 @@ var tailCommand = new Command3("tail").description("Poll and print new email eve
|
|
|
139
142
|
const cfg = loadConfig();
|
|
140
143
|
const seen = /* @__PURE__ */ new Set();
|
|
141
144
|
const startedAt = opts.since ? new Date(opts.since) : /* @__PURE__ */ new Date();
|
|
145
|
+
if (opts.since && isNaN(startedAt.getTime())) {
|
|
146
|
+
console.error(
|
|
147
|
+
`Error: invalid --since value "${opts.since}" \u2014 use ISO 8601 (e.g. 2026-07-20) or other valid date format`
|
|
148
|
+
);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
142
151
|
console.log("Tailing email events\u2026 (Ctrl-C to stop)");
|
|
143
152
|
const poll = async () => {
|
|
144
153
|
try {
|
|
@@ -173,12 +182,35 @@ import { Command as Command4 } from "commander";
|
|
|
173
182
|
import {
|
|
174
183
|
createServer
|
|
175
184
|
} from "http";
|
|
176
|
-
|
|
185
|
+
|
|
186
|
+
// src/open-browser.ts
|
|
187
|
+
import { execFile } from "child_process";
|
|
177
188
|
import { platform } from "os";
|
|
178
|
-
function
|
|
179
|
-
|
|
180
|
-
|
|
189
|
+
function isHttpUrl(url) {
|
|
190
|
+
try {
|
|
191
|
+
const { protocol } = new URL(url);
|
|
192
|
+
return protocol === "http:" || protocol === "https:";
|
|
193
|
+
} catch {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function commandFor(os, url) {
|
|
198
|
+
if (os === "darwin") return ["open", [url]];
|
|
199
|
+
if (os === "win32") return ["rundll32", ["url.dll,FileProtocolHandler", url]];
|
|
200
|
+
return ["xdg-open", [url]];
|
|
201
|
+
}
|
|
202
|
+
function openBrowser(url, deps = {}) {
|
|
203
|
+
if (!isHttpUrl(url)) return false;
|
|
204
|
+
const spawn = deps.spawn ?? ((file2, args2) => {
|
|
205
|
+
execFile(file2, args2, () => {
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
const [file, args] = commandFor(deps.platform ?? platform(), url);
|
|
209
|
+
spawn(file, args);
|
|
210
|
+
return true;
|
|
181
211
|
}
|
|
212
|
+
|
|
213
|
+
// src/commands/login.ts
|
|
182
214
|
var loginCommand = new Command4("login").description("Authenticate with your DevelEmail instance via browser").option(
|
|
183
215
|
"--url <url>",
|
|
184
216
|
"Base URL of your DevelEmail API",
|
|
@@ -278,7 +310,22 @@ var listCommand = new Command5("list").description("List all projects").action(a
|
|
|
278
310
|
);
|
|
279
311
|
}
|
|
280
312
|
});
|
|
281
|
-
var
|
|
313
|
+
var getCommand = new Command5("get").description("Get project details").argument("<id>", "Project ID").action(async (id) => {
|
|
314
|
+
const { url, token } = loadOwnerSession();
|
|
315
|
+
const project = await apiRequest(`${url}/projects/${id}`, token);
|
|
316
|
+
console.log(`ID: ${project.id}`);
|
|
317
|
+
console.log(`Name: ${project.name}`);
|
|
318
|
+
console.log(`Environment: ${project.environment}`);
|
|
319
|
+
console.log(`From Address: ${project.defaultFromAddress ?? "\u2014"}`);
|
|
320
|
+
console.log(
|
|
321
|
+
`Rate Limit: ${project.rateLimitPerDay ?? "Unlimited"} per day`
|
|
322
|
+
);
|
|
323
|
+
if (project.domainId) {
|
|
324
|
+
console.log(`Domain ID: ${project.domainId}`);
|
|
325
|
+
}
|
|
326
|
+
console.log(`Created At: ${new Date(project.createdAt).toISOString()}`);
|
|
327
|
+
});
|
|
328
|
+
var createCommand = new Command5("create").description("Create a new project").requiredOption("--name <name>", "Project name (slug, e.g. my-app)").option("--domain <domainId>", "Domain ID for this project").option(
|
|
282
329
|
"--env <env>",
|
|
283
330
|
"Environment: development | staging | production",
|
|
284
331
|
"production"
|
|
@@ -289,6 +336,7 @@ var createCommand = new Command5("create").description("Create a new project").r
|
|
|
289
336
|
method: "POST",
|
|
290
337
|
body: JSON.stringify({
|
|
291
338
|
name: opts.name,
|
|
339
|
+
domainId: opts.domain,
|
|
292
340
|
environment: opts.env,
|
|
293
341
|
defaultFromAddress: opts.from,
|
|
294
342
|
rateLimitPerDay: opts.rateLimit ? Number(opts.rateLimit) : void 0,
|
|
@@ -298,10 +346,284 @@ var createCommand = new Command5("create").description("Create a new project").r
|
|
|
298
346
|
console.log(`Created project: ${project.name} (${project.id})`);
|
|
299
347
|
}
|
|
300
348
|
);
|
|
301
|
-
var projectCommand = new Command5("project").description("Manage projects").addCommand(listCommand).addCommand(createCommand);
|
|
349
|
+
var projectCommand = new Command5("project").description("Manage projects").addCommand(listCommand).addCommand(getCommand).addCommand(createCommand);
|
|
302
350
|
|
|
303
|
-
// src/commands/
|
|
351
|
+
// src/commands/domains.ts
|
|
352
|
+
import { Command as Command7 } from "commander";
|
|
353
|
+
|
|
354
|
+
// src/owner-api.ts
|
|
355
|
+
var OwnerApiError = class extends Error {
|
|
356
|
+
constructor(message, status) {
|
|
357
|
+
super(message);
|
|
358
|
+
this.status = status;
|
|
359
|
+
}
|
|
360
|
+
status;
|
|
361
|
+
};
|
|
362
|
+
async function ownerRequest(path, init) {
|
|
363
|
+
const { url, token } = loadOwnerSession();
|
|
364
|
+
const res = await fetch(`${url}${path}`, {
|
|
365
|
+
...init,
|
|
366
|
+
headers: {
|
|
367
|
+
Cookie: `session=${token}`,
|
|
368
|
+
"Content-Type": "application/json",
|
|
369
|
+
...init?.headers ?? {}
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
if (res.status === 401) {
|
|
373
|
+
throw new OwnerApiError(
|
|
374
|
+
"Session expired or missing. Run `devel login` first.",
|
|
375
|
+
401
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
if (!res.ok) {
|
|
379
|
+
const body = await res.json().catch(() => ({}));
|
|
380
|
+
throw new OwnerApiError(body.message ?? res.statusText, res.status);
|
|
381
|
+
}
|
|
382
|
+
return res.json();
|
|
383
|
+
}
|
|
384
|
+
function exitOnError(fn) {
|
|
385
|
+
return async (...args) => {
|
|
386
|
+
try {
|
|
387
|
+
await fn(...args);
|
|
388
|
+
} catch (err) {
|
|
389
|
+
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// src/commands/dns-records.ts
|
|
396
|
+
var BIND_TTL = 3600;
|
|
397
|
+
function formatBind(records) {
|
|
398
|
+
return records.map((r) => {
|
|
399
|
+
const value = r.type === "TXT" ? `"${r.value.replace(/"/g, '\\"')}"` : r.value;
|
|
400
|
+
return `${r.host}. ${BIND_TTL} IN ${r.type} ${value}`;
|
|
401
|
+
}).join("\n");
|
|
402
|
+
}
|
|
403
|
+
function formatTable(records) {
|
|
404
|
+
return records.map(
|
|
405
|
+
(r) => `${r.priority.padEnd(11)} ${r.type.padEnd(5)} ${r.host}
|
|
406
|
+
${r.value}
|
|
407
|
+
(${r.purpose})`
|
|
408
|
+
).join("\n");
|
|
409
|
+
}
|
|
410
|
+
function printRecords(records, format, json) {
|
|
411
|
+
if (json) console.log(JSON.stringify(records, null, 2));
|
|
412
|
+
else if (format === "bind") console.log(formatBind(records));
|
|
413
|
+
else console.log(formatTable(records));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// src/commands/publish-dns.ts
|
|
304
417
|
import { Command as Command6 } from "commander";
|
|
418
|
+
import { createInterface } from "readline/promises";
|
|
419
|
+
function pickZone(domainName, zones) {
|
|
420
|
+
const matches = zones.filter(
|
|
421
|
+
(z) => domainName === z.name || domainName.endsWith(`.${z.name}`)
|
|
422
|
+
);
|
|
423
|
+
if (matches.length === 0) {
|
|
424
|
+
throw new Error(`No Cloudflare zone matches ${domainName}.`);
|
|
425
|
+
}
|
|
426
|
+
const longest = Math.max(...matches.map((z) => z.name.length));
|
|
427
|
+
const best = matches.filter((z) => z.name.length === longest);
|
|
428
|
+
if (best.length > 1) {
|
|
429
|
+
throw new Error(
|
|
430
|
+
`Several zones match ${domainName}: ${best.map((z) => z.id).join(", ")}. Pass --zone.`
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
return best[0];
|
|
434
|
+
}
|
|
435
|
+
function printPlan(plan) {
|
|
436
|
+
for (const w of plan.warnings) console.log(`warning: ${w}`);
|
|
437
|
+
for (const c of plan.changes) {
|
|
438
|
+
const from = c.existingContent ? ` (was: ${c.existingContent})` : "";
|
|
439
|
+
console.log(
|
|
440
|
+
`${c.action.padEnd(6)} ${c.type} ${c.name} -> ${c.content}${from}`
|
|
441
|
+
);
|
|
442
|
+
for (const w of c.warnings) console.log(` warning: ${w}`);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
async function confirm() {
|
|
446
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
447
|
+
try {
|
|
448
|
+
const answer = await rl.question("Apply these changes? [y/N] ");
|
|
449
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
450
|
+
} finally {
|
|
451
|
+
rl.close();
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
var publishDnsCommand = new Command6("publish-dns").description("Publish a domain\u2019s DNS records to Cloudflare").argument("<id>", "Domain ID").option("--zone <zoneId>", "Cloudflare zone ID (default: auto-pick)").option("--yes", "Apply without prompting").option("--json", "Output JSON").action(
|
|
455
|
+
exitOnError(async (id, opts) => {
|
|
456
|
+
let zoneId = opts.zone;
|
|
457
|
+
if (!zoneId) {
|
|
458
|
+
const domain = await ownerRequest(`/domains/${id}`);
|
|
459
|
+
const zones = await ownerRequest("/cloudflare/zones");
|
|
460
|
+
zoneId = pickZone(domain.name, zones).id;
|
|
461
|
+
}
|
|
462
|
+
const plan = await ownerRequest(`/cloudflare/domains/${id}/plan`, {
|
|
463
|
+
method: "POST",
|
|
464
|
+
body: JSON.stringify({ zoneId })
|
|
465
|
+
});
|
|
466
|
+
if (!opts.json) printPlan(plan);
|
|
467
|
+
const apply = opts.yes || process.stdin.isTTY === true && await confirm();
|
|
468
|
+
if (!apply) {
|
|
469
|
+
if (opts.json) console.log(JSON.stringify({ plan, applied: false }));
|
|
470
|
+
else
|
|
471
|
+
console.log("Dry run: nothing applied. Re-run with --yes to apply.");
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
const result = await ownerRequest("/cloudflare/apply", {
|
|
475
|
+
method: "POST",
|
|
476
|
+
body: JSON.stringify(plan)
|
|
477
|
+
});
|
|
478
|
+
if (opts.json)
|
|
479
|
+
console.log(JSON.stringify({ plan, applied: true, result }));
|
|
480
|
+
else console.log("Applied.");
|
|
481
|
+
})
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
// src/commands/domains.ts
|
|
485
|
+
var COL_ID3 = 40;
|
|
486
|
+
var COL_NAME2 = 30;
|
|
487
|
+
var COL_STATUS2 = 15;
|
|
488
|
+
function col3(s, w) {
|
|
489
|
+
return (s ?? "").slice(0, w).padEnd(w);
|
|
490
|
+
}
|
|
491
|
+
function printStatuses(d) {
|
|
492
|
+
console.log(`SPF Status: ${d.spfStatus ?? "\u2014"}`);
|
|
493
|
+
console.log(`DKIM Status: ${d.dkimStatus ?? "\u2014"}`);
|
|
494
|
+
console.log(`DMARC Status: ${d.dmarcStatus ?? "\u2014"}`);
|
|
495
|
+
}
|
|
496
|
+
var listCommand2 = new Command7("list").description("List all domains").action(
|
|
497
|
+
exitOnError(async () => {
|
|
498
|
+
const domains = await ownerRequest("/domains");
|
|
499
|
+
if (domains.length === 0) {
|
|
500
|
+
console.log("No domains found.");
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
const header = `${col3("ID", COL_ID3)} ${col3("NAME", COL_NAME2)} ${col3("STATUS", COL_STATUS2)} CREATED`;
|
|
504
|
+
console.log(header);
|
|
505
|
+
console.log("-".repeat(header.length + 20));
|
|
506
|
+
for (const d of domains) {
|
|
507
|
+
const createdAt = new Date(d.createdAt).toLocaleDateString();
|
|
508
|
+
console.log(
|
|
509
|
+
`${col3(d.id, COL_ID3)} ${col3(d.name, COL_NAME2)} ${col3(d.verificationStatus, COL_STATUS2)} ${createdAt}`
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
})
|
|
513
|
+
);
|
|
514
|
+
var getCommand2 = new Command7("get").description("Get domain details").argument("<id>", "Domain ID").action(
|
|
515
|
+
exitOnError(async (id) => {
|
|
516
|
+
const domain = await ownerRequest(`/domains/${id}`);
|
|
517
|
+
console.log(`ID: ${domain.id}`);
|
|
518
|
+
console.log(`Name: ${domain.name}`);
|
|
519
|
+
console.log(`Verification Status: ${domain.verificationStatus}`);
|
|
520
|
+
printStatuses(domain);
|
|
521
|
+
console.log(`MX Status: ${domain.mxStatus ?? "\u2014"}`);
|
|
522
|
+
console.log(`Return Path Status: ${domain.returnPathStatus ?? "\u2014"}`);
|
|
523
|
+
console.log(`PTR Status: ${domain.ptrStatus ?? "\u2014"}`);
|
|
524
|
+
console.log(`DKIM Selector: ${domain.dkimSelector}`);
|
|
525
|
+
console.log(`DKIM Public Key:
|
|
526
|
+
${domain.dkimPublicKey}`);
|
|
527
|
+
console.log(`Created At: ${new Date(domain.createdAt).toISOString()}`);
|
|
528
|
+
})
|
|
529
|
+
);
|
|
530
|
+
var addCommand = new Command7("add").description("Create a domain and print its required DNS records").argument("<name>", "Domain name, e.g. example.com").option("--dkim-selector <selector>", "DKIM selector").option("--json", "Output JSON").action(
|
|
531
|
+
exitOnError(
|
|
532
|
+
async (name, opts) => {
|
|
533
|
+
const domain = await ownerRequest("/domains", {
|
|
534
|
+
method: "POST",
|
|
535
|
+
body: JSON.stringify({ name, dkimSelector: opts.dkimSelector })
|
|
536
|
+
});
|
|
537
|
+
const records = await ownerRequest(
|
|
538
|
+
`/domains/${domain.id}/dns-records`
|
|
539
|
+
);
|
|
540
|
+
if (opts.json) {
|
|
541
|
+
console.log(JSON.stringify({ domain, records }, null, 2));
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
console.log(`Created domain: ${domain.name} (${domain.id})`);
|
|
545
|
+
console.log("\nRequired DNS records:");
|
|
546
|
+
printRecords(records, "table", false);
|
|
547
|
+
}
|
|
548
|
+
)
|
|
549
|
+
);
|
|
550
|
+
var recordsCommand = new Command7("records").description("Print the DNS records a domain requires").argument("<id>", "Domain ID").option("--json", "Output JSON").option("--format <format>", "table | bind", "table").action(
|
|
551
|
+
exitOnError(
|
|
552
|
+
async (id, opts) => {
|
|
553
|
+
const records = await ownerRequest(
|
|
554
|
+
`/domains/${id}/dns-records`
|
|
555
|
+
);
|
|
556
|
+
printRecords(records, opts.format, opts.json === true);
|
|
557
|
+
}
|
|
558
|
+
)
|
|
559
|
+
);
|
|
560
|
+
var POLL_INTERVAL_MS2 = 1e4;
|
|
561
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
562
|
+
var verifyCommand = new Command7("verify").description("Trigger domain verification").argument("<id>", "Domain ID").option("--wait", "Poll until verified or timed out").option("--timeout <seconds>", "Max seconds to wait with --wait", "600").action(
|
|
563
|
+
exitOnError(
|
|
564
|
+
async (id, opts) => {
|
|
565
|
+
const deadline = Date.now() + Number(opts.timeout) * 1e3;
|
|
566
|
+
for (; ; ) {
|
|
567
|
+
const domain = await ownerRequest(
|
|
568
|
+
`/domains/${id}/verify`,
|
|
569
|
+
{ method: "POST" }
|
|
570
|
+
);
|
|
571
|
+
console.log(`Domain verification: ${domain.name}`);
|
|
572
|
+
console.log(`Current status: ${domain.verificationStatus}`);
|
|
573
|
+
printStatuses(domain);
|
|
574
|
+
if (!opts.wait || domain.verificationStatus === "verified") return;
|
|
575
|
+
if (Date.now() + POLL_INTERVAL_MS2 > deadline) {
|
|
576
|
+
throw new Error(
|
|
577
|
+
`Timed out after ${opts.timeout}s; not yet verified.`
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
await sleep(POLL_INTERVAL_MS2);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
)
|
|
584
|
+
);
|
|
585
|
+
var domainsCommand = new Command7("domains").description("Manage domains").addCommand(listCommand2).addCommand(getCommand2).addCommand(addCommand).addCommand(recordsCommand).addCommand(publishDnsCommand).addCommand(verifyCommand);
|
|
586
|
+
|
|
587
|
+
// src/commands/cloudflare.ts
|
|
588
|
+
import { Command as Command8 } from "commander";
|
|
589
|
+
var connectCommand = new Command8("connect").description("Connect a Cloudflare API token to your organization").requiredOption(
|
|
590
|
+
"--token-env <VAR>",
|
|
591
|
+
"Environment variable holding the Cloudflare API token"
|
|
592
|
+
).action(
|
|
593
|
+
exitOnError(async (opts) => {
|
|
594
|
+
const apiToken = process.env[opts.tokenEnv];
|
|
595
|
+
if (!apiToken) {
|
|
596
|
+
throw new Error(`Environment variable ${opts.tokenEnv} is not set.`);
|
|
597
|
+
}
|
|
598
|
+
await ownerRequest("/cloudflare/connect", {
|
|
599
|
+
method: "POST",
|
|
600
|
+
body: JSON.stringify({ apiToken })
|
|
601
|
+
});
|
|
602
|
+
console.log("Cloudflare connected.");
|
|
603
|
+
})
|
|
604
|
+
);
|
|
605
|
+
var statusCommand = new Command8("status").description("Show whether Cloudflare is connected").action(
|
|
606
|
+
exitOnError(async () => {
|
|
607
|
+
const { connected } = await ownerRequest(
|
|
608
|
+
"/cloudflare/status"
|
|
609
|
+
);
|
|
610
|
+
console.log(connected ? "Connected" : "Not connected");
|
|
611
|
+
})
|
|
612
|
+
);
|
|
613
|
+
var zonesCommand = new Command8("zones").description("List Cloudflare zones").option("--json", "Output JSON").action(
|
|
614
|
+
exitOnError(async (opts) => {
|
|
615
|
+
const zones = await ownerRequest("/cloudflare/zones");
|
|
616
|
+
if (opts.json) {
|
|
617
|
+
console.log(JSON.stringify(zones, null, 2));
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
for (const z of zones) console.log(`${z.id} ${z.name} ${z.status}`);
|
|
621
|
+
})
|
|
622
|
+
);
|
|
623
|
+
var cloudflareCommand = new Command8("cloudflare").description("Manage the Cloudflare integration").addCommand(connectCommand).addCommand(statusCommand).addCommand(zonesCommand);
|
|
624
|
+
|
|
625
|
+
// src/commands/key.ts
|
|
626
|
+
import { Command as Command9 } from "commander";
|
|
305
627
|
function ownerHeaders2(token) {
|
|
306
628
|
return { Cookie: `session=${token}`, "Content-Type": "application/json" };
|
|
307
629
|
}
|
|
@@ -317,7 +639,7 @@ async function apiRequest2(url, token, init) {
|
|
|
317
639
|
}
|
|
318
640
|
return res.json();
|
|
319
641
|
}
|
|
320
|
-
var
|
|
642
|
+
var listCommand3 = new Command9("list").description("List API keys for a project").requiredOption("--project <id>", "Project ID").action(async (opts) => {
|
|
321
643
|
const { url, token } = loadOwnerSession();
|
|
322
644
|
const keys = await apiRequest2(
|
|
323
645
|
`${url}/projects/${opts.project}/api-keys`,
|
|
@@ -332,7 +654,7 @@ var listCommand2 = new Command6("list").description("List API keys for a project
|
|
|
332
654
|
console.log(`${status} ${k.keyPrefix}\u2026 ${k.name} (${k.id})`);
|
|
333
655
|
}
|
|
334
656
|
});
|
|
335
|
-
var createCommand2 = new
|
|
657
|
+
var createCommand2 = new Command9("create").description("Create a new API key for a project").requiredOption("--project <id>", "Project ID").requiredOption("--name <name>", "Key name (e.g. production, ci)").option("--expires <date>", "Expiry date (ISO-8601, e.g. 2027-01-01)").option("--rate-limit <n>", "Max requests per minute").action(
|
|
336
658
|
async (opts) => {
|
|
337
659
|
const { url, token } = loadOwnerSession();
|
|
338
660
|
const result = await apiRequest2(
|
|
@@ -354,10 +676,10 @@ var createCommand2 = new Command6("create").description("Create a new API key fo
|
|
|
354
676
|
console.log("Store the secret now \u2014 it will not be shown again.");
|
|
355
677
|
}
|
|
356
678
|
);
|
|
357
|
-
var keyCommand = new
|
|
679
|
+
var keyCommand = new Command9("key").description("Manage API keys").addCommand(listCommand3).addCommand(createCommand2);
|
|
358
680
|
|
|
359
681
|
// src/commands/init.ts
|
|
360
|
-
import { Command as
|
|
682
|
+
import { Command as Command10 } from "commander";
|
|
361
683
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
362
684
|
import { resolve } from "path";
|
|
363
685
|
function ownerHeaders3(token) {
|
|
@@ -387,7 +709,7 @@ function upsertEnvFile(filePath, vars) {
|
|
|
387
709
|
const content = lines.join("\n").trimEnd() + (lines.some((l) => l.trim()) ? "\n" : "") + appended + "\n";
|
|
388
710
|
writeFileSync2(filePath, content, "utf-8");
|
|
389
711
|
}
|
|
390
|
-
var initCommand = new
|
|
712
|
+
var initCommand = new Command10("init").description(
|
|
391
713
|
"Provision a project + API key and write credentials to an env file"
|
|
392
714
|
).requiredOption(
|
|
393
715
|
"--project <name>",
|
|
@@ -441,10 +763,12 @@ var initCommand = new Command7("init").description(
|
|
|
441
763
|
);
|
|
442
764
|
|
|
443
765
|
// src/index.ts
|
|
444
|
-
var program = new
|
|
766
|
+
var program = new Command11().name("devel").description("DevelEmail CLI \u2014 interact with your DevelEmail instance").version("0.1.0");
|
|
445
767
|
program.addCommand(loginCommand);
|
|
446
768
|
program.addCommand(logoutCommand);
|
|
447
769
|
program.addCommand(projectCommand);
|
|
770
|
+
program.addCommand(domainsCommand);
|
|
771
|
+
program.addCommand(cloudflareCommand);
|
|
448
772
|
program.addCommand(keyCommand);
|
|
449
773
|
program.addCommand(initCommand);
|
|
450
774
|
program.addCommand(sendCommand);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@develemail/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"devel": "dist/index.js"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"commander": "^12.1.0",
|
|
18
18
|
"dotenv": "^16.4.5",
|
|
19
|
-
"@develemail/sdk": "0.
|
|
19
|
+
"@develemail/sdk": "0.4.0"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"tsup": "^8.3.5",
|