nukebase 2.1.2 → 2.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/bin/nukebase.js +3 -0
- package/lib/args.js +1 -1
- package/lib/project-commands.js +122 -0
- package/package.json +1 -1
package/bin/nukebase.js
CHANGED
|
@@ -79,6 +79,9 @@ In a project folder (runs as the account that owns the project)
|
|
|
79
79
|
domain connect a custom domain, step by step
|
|
80
80
|
domain <name> [--email e] connect in one go (DNS already set)
|
|
81
81
|
domain remove disconnect the custom domain
|
|
82
|
+
domain email send the app's emails from the custom domain
|
|
83
|
+
(--root | --sub, --reply-to you@gmail.com, --wait)
|
|
84
|
+
domain email remove go back to sending from nukebase.com
|
|
82
85
|
logs [--follow] recent log output; --follow streams
|
|
83
86
|
download [--out f.zip] the project ZIP, exactly as the server has it
|
|
84
87
|
regiontransfer [region] move this project to another region, step by step
|
package/lib/args.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// `--name=value` works too.
|
|
7
7
|
|
|
8
8
|
const VALUE_FLAGS = new Set([
|
|
9
|
-
'region', 'api', 'as', 'tier', 'streams', 'out', 'email', 'to', 'include', 'name'
|
|
9
|
+
'region', 'api', 'as', 'tier', 'streams', 'out', 'email', 'to', 'include', 'name', 'reply-to'
|
|
10
10
|
]);
|
|
11
11
|
|
|
12
12
|
const SHORT = { y: 'yes', f: 'follow', h: 'help', v: 'version' };
|
package/lib/project-commands.js
CHANGED
|
@@ -854,6 +854,7 @@ async function deleteProject() {
|
|
|
854
854
|
// nukebase domain guided: DNS records, wait, connect
|
|
855
855
|
// nukebase domain <name> [--email you@x] one shot, for scripts (no waiting)
|
|
856
856
|
// nukebase domain remove disconnect it
|
|
857
|
+
// nukebase domain email [remove] send the app's emails from it
|
|
857
858
|
//
|
|
858
859
|
// The guided flow is the dashboard's modal in the terminal: it prints the two
|
|
859
860
|
// A records and the ownership TXT (nukebase.<domain> = nukebase-uid=<uid>,
|
|
@@ -878,6 +879,11 @@ async function domainCommand() {
|
|
|
878
879
|
process.exit(0);
|
|
879
880
|
}
|
|
880
881
|
|
|
882
|
+
if (sub === 'email') {
|
|
883
|
+
await emailDomainCommand(args.positional[1] || null);
|
|
884
|
+
process.exit(0);
|
|
885
|
+
}
|
|
886
|
+
|
|
881
887
|
const email = args.get('email') || (String(accountLabel).includes('@') ? accountLabel : null);
|
|
882
888
|
|
|
883
889
|
// One shot: name given, no hand-holding. Scripts and people who already
|
|
@@ -892,6 +898,7 @@ async function domainCommand() {
|
|
|
892
898
|
await waitForDns(sub, ip, needTxt);
|
|
893
899
|
}
|
|
894
900
|
await connectDomain(sub, email);
|
|
901
|
+
if (args.has('email-domain')) await emailDomainCommand(null);
|
|
895
902
|
process.exit(0);
|
|
896
903
|
}
|
|
897
904
|
|
|
@@ -923,6 +930,13 @@ async function domainCommand() {
|
|
|
923
930
|
|
|
924
931
|
// Step 3 — the server does the real verification and issues the cert.
|
|
925
932
|
await connectDomain(domain, email);
|
|
933
|
+
|
|
934
|
+
// Optional: the app's emails from this domain too.
|
|
935
|
+
if (args.has('email-domain') || await confirm(`\n✉️ Send this app's emails from ${domain} too (instead of nukebase.com)? (y/n) `)) {
|
|
936
|
+
await emailDomainCommand(null);
|
|
937
|
+
} else {
|
|
938
|
+
console.log(' Later: nukebase domain email');
|
|
939
|
+
}
|
|
926
940
|
process.exit(0);
|
|
927
941
|
|
|
928
942
|
async function currentDomain() {
|
|
@@ -931,6 +945,106 @@ async function domainCommand() {
|
|
|
931
945
|
}
|
|
932
946
|
}
|
|
933
947
|
|
|
948
|
+
// nukebase domain email [--root | --sub] [--reply-to you@gmail.com] [--wait]
|
|
949
|
+
// nukebase domain email remove
|
|
950
|
+
//
|
|
951
|
+
// Send the app's emails (sign-in links today) from its custom domain:
|
|
952
|
+
// login@theirapp.com, replies to --reply-to. The sending domain is either the
|
|
953
|
+
// custom domain itself (--root) or mail.<domain> (--sub; safest when the
|
|
954
|
+
// domain already has email). Prints the two DNS records to add, then waits
|
|
955
|
+
// for the mail provider to verify them (Enter to check now, Ctrl+C to leave
|
|
956
|
+
// it: the server keeps checking every 10 minutes for a week). Run again to
|
|
957
|
+
// see the status, change the Reply-To, or switch between --root and --sub.
|
|
958
|
+
async function emailDomainCommand(action) {
|
|
959
|
+
if (action === 'remove') {
|
|
960
|
+
if (!(await confirm(`Stop sending ${project}'s emails from its domain? They go back to nukebase.com. (y/n) `))) {
|
|
961
|
+
console.log('Aborted.'); process.exit(1);
|
|
962
|
+
}
|
|
963
|
+
await projectPost('/remove-email-domain', {}, { label: 'email remove' });
|
|
964
|
+
console.log('✓ Email sending domain removed. Emails are sent from nukebase.com again.');
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
const check = await projectPost('/email-domain-check', {}, { label: 'email' });
|
|
969
|
+
const domain = check.domain;
|
|
970
|
+
const current = check.current;
|
|
971
|
+
const wantRoot = args.has('root') ? true : args.has('sub') ? false : null;
|
|
972
|
+
const replyTo = args.get('reply-to');
|
|
973
|
+
const changing = wantRoot !== null && (!current || current.root !== wantRoot);
|
|
974
|
+
|
|
975
|
+
let rec = current;
|
|
976
|
+
if (!current || changing || replyTo !== undefined) {
|
|
977
|
+
let root = wantRoot;
|
|
978
|
+
if (root === null) root = current ? current.root : null;
|
|
979
|
+
if (root === null) {
|
|
980
|
+
const suggestRoot = check.suggested === 'root';
|
|
981
|
+
console.log(`\n✉️ Send ${project}'s emails from ${domain}\n`);
|
|
982
|
+
console.log(` 1) login@${domain}`);
|
|
983
|
+
console.log(` ${check.hasEmail ? 'This domain already has email set up' + (check.spf ? ' (you will update its SPF record)' : '') + '.' : 'Recommended: this domain has no email set up yet.'}`);
|
|
984
|
+
console.log(` 2) login@${domain}, sent via mail.${domain}`);
|
|
985
|
+
console.log(' Safest if you already use email on this domain: its records are not touched.\n');
|
|
986
|
+
if (process.stdin.isTTY && !AUTO_YES) {
|
|
987
|
+
const a = (await askLine(` Choose 1 or 2 [${suggestRoot ? 1 : 2}]: `)).trim();
|
|
988
|
+
root = a === '1' ? true : a === '2' ? false : suggestRoot;
|
|
989
|
+
} else {
|
|
990
|
+
root = suggestRoot;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
let reply = replyTo;
|
|
994
|
+
if (reply === undefined && !current && process.stdin.isTTY && !AUTO_YES) {
|
|
995
|
+
reply = (await askLine(' Where should replies go? (e.g. you@gmail.com, Enter to skip): ')).trim();
|
|
996
|
+
}
|
|
997
|
+
const body = { root };
|
|
998
|
+
if (typeof reply === 'string') body.replyTo = reply;
|
|
999
|
+
rec = (await projectPost('/email-domain', body, { label: 'email' })).emailDomain;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
printEmailRecords(rec);
|
|
1003
|
+
if (rec.status === 'verified') return;
|
|
1004
|
+
if (!args.has('wait') && (!process.stdin.isTTY || AUTO_YES)) {
|
|
1005
|
+
console.log(' The server checks every 10 minutes. See the status with: nukebase domain email');
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
await waitForEmailDomain();
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
function printEmailRecords(rec) {
|
|
1012
|
+
const ok = rec.status === 'verified';
|
|
1013
|
+
console.log(`\n Sending domain: ${rec.name} ${ok ? `✓ verified — emails go out as login@${rec.fromDomain || rec.name}` : '… waiting for DNS'}`);
|
|
1014
|
+
if (rec.replyTo) console.log(` Replies go to: ${rec.replyTo}`);
|
|
1015
|
+
if (ok) { console.log(''); return; }
|
|
1016
|
+
console.log('\n At your registrar, add these DNS records:\n');
|
|
1017
|
+
for (const r of rec.records || []) {
|
|
1018
|
+
console.log(` ${r.valid ? '✓' : '…'} TXT ${r.host}`);
|
|
1019
|
+
if (r.replace) console.log(' REPLACE your existing SPF record (only one is allowed) with:');
|
|
1020
|
+
console.log(` ${r.value}\n`);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
async function waitForEmailDomain() {
|
|
1025
|
+
console.log(' Waiting for verification. Enter to check now, Ctrl+C to stop (the server keeps checking).\n');
|
|
1026
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1027
|
+
let poke = null;
|
|
1028
|
+
rl.on('line', () => { if (poke) poke(); });
|
|
1029
|
+
const wait = (ms) => new Promise(res => { poke = res; setTimeout(res, ms); });
|
|
1030
|
+
try {
|
|
1031
|
+
for (;;) {
|
|
1032
|
+
const r = await projectPost('/email-domain-status', {}, { label: 'email status' });
|
|
1033
|
+
const rec = r.emailDomain;
|
|
1034
|
+
if (!rec) { console.log('\n The sending domain is gone. Run: nukebase domain email'); return; }
|
|
1035
|
+
const marks = (rec.records || []).map(x => `${x.valid ? '✓' : '…'} ${x.kind.toUpperCase()}`).join(' ');
|
|
1036
|
+
process.stdout.write(`\r ${marks} `);
|
|
1037
|
+
if (rec.status === 'verified') {
|
|
1038
|
+
console.log(`\n\n✓ Verified. ${project}'s emails now go out as login@${rec.fromDomain || rec.name}.`);
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
await wait(20000);
|
|
1042
|
+
}
|
|
1043
|
+
} finally {
|
|
1044
|
+
rl.close();
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
934
1048
|
async function connectDomain(domain, email) {
|
|
935
1049
|
console.log(`🔐 Step 3 — verifying DNS from the server and issuing SSL for ${domain} (up to a minute)...`);
|
|
936
1050
|
const r = await projectPost('/custom-domain', email ? { domain, email } : { domain }, {
|
|
@@ -1289,6 +1403,14 @@ async function regionTransfer() {
|
|
|
1289
1403
|
if (r.status !== 0) {
|
|
1290
1404
|
console.error(`\n⚠️ The domain was not connected on ${target}. It still works on ${source}.`);
|
|
1291
1405
|
console.error(` Finish later with: nukebase domain ${customDomain} --region ${target} --wait${asArgs.length ? ' ' + asArgs.join(' ') : ''}`);
|
|
1406
|
+
} else if (node.emailDomain && node.emailDomain.name) {
|
|
1407
|
+
// Same sending domain on the target: the mail provider already has it
|
|
1408
|
+
// verified (its DNS records do not change), so the target adopts it.
|
|
1409
|
+
const em = node.emailDomain;
|
|
1410
|
+
const emArgs = ['domain', 'email', em.root ? '--root' : '--sub', '--region', target, '--yes', ...asArgs];
|
|
1411
|
+
if (em.replyTo) emArgs.push('--reply-to', em.replyTo);
|
|
1412
|
+
const e = spawnSync(process.execPath, [BIN, ...emArgs], { stdio: 'inherit', cwd: root });
|
|
1413
|
+
if (e.status !== 0) console.error(` Email sending: finish later with: nukebase domain email --region ${target}${asArgs.length ? ' ' + asArgs.join(' ') : ''}`);
|
|
1292
1414
|
}
|
|
1293
1415
|
} else {
|
|
1294
1416
|
console.log(` Skipped. ${customDomain} stays on ${source} for now. When ready:`);
|