nukebase 2.0.4 → 2.0.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/bin/nukebase.js +13 -0
- package/lib/project-commands.js +37 -14
- package/lib/update-check.js +73 -0
- package/package.json +9 -2
package/bin/nukebase.js
CHANGED
|
@@ -8,6 +8,19 @@
|
|
|
8
8
|
const args = require('../lib/args').parse(process.argv.slice(2));
|
|
9
9
|
const { version } = require('../package.json');
|
|
10
10
|
|
|
11
|
+
// "nukebase X is available": checked once a day while the command runs, printed
|
|
12
|
+
// when the command finishes. 'exit' covers both kinds of command: project
|
|
13
|
+
// commands end with process.exit, account commands end by themselves.
|
|
14
|
+
// Not after --version, whose output scripts read.
|
|
15
|
+
if (!(args.has('version') && !args.command)) {
|
|
16
|
+
const updates = require('../lib/update-check');
|
|
17
|
+
updates.scheduleCheck();
|
|
18
|
+
process.on('exit', () => {
|
|
19
|
+
const line = updates.notice(version);
|
|
20
|
+
if (line) console.error(line);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
11
24
|
const ACCOUNT_COMMANDS = ['login', 'logout', 'accounts', 'use', 'whoami', 'create', 'link'];
|
|
12
25
|
const PROJECT_COMMANDS = [
|
|
13
26
|
'push', 'pull', 'update', 'rollback', 'restorepoints', 'restore-points', 'backup', 'env',
|
package/lib/project-commands.js
CHANGED
|
@@ -857,7 +857,9 @@ async function deleteProject() {
|
|
|
857
857
|
// nukebase domain remove disconnect it
|
|
858
858
|
//
|
|
859
859
|
// The guided flow is the dashboard's modal in the terminal: it prints the two
|
|
860
|
-
// A records
|
|
860
|
+
// A records and the ownership TXT (nukebase.<domain> = nukebase-uid=<uid>,
|
|
861
|
+
// which the server requires on a domain's first connection so no other
|
|
862
|
+
// account can claim it) to create, polls DNS until they are in place (Enter to check now,
|
|
861
863
|
// Ctrl+C to give up and come back later), then asks the server to verify and
|
|
862
864
|
// issue the certificate — the slow part, up to a minute at Let's Encrypt.
|
|
863
865
|
// --email is the cert contact; it defaults to the account email in
|
|
@@ -886,8 +888,9 @@ async function domainCommand() {
|
|
|
886
888
|
if (sub) {
|
|
887
889
|
if (args.has('wait')) {
|
|
888
890
|
const ip = await regionIp();
|
|
889
|
-
|
|
890
|
-
|
|
891
|
+
const needTxt = (await currentDomain()) !== sub; // already ours: proven when connected
|
|
892
|
+
printDnsRecords(ip, needTxt);
|
|
893
|
+
await waitForDns(sub, ip, needTxt);
|
|
891
894
|
}
|
|
892
895
|
await connectDomain(sub, email);
|
|
893
896
|
process.exit(0);
|
|
@@ -916,8 +919,8 @@ async function domainCommand() {
|
|
|
916
919
|
if (!/^[a-z0-9.-]+\.[a-z]{2,}$/.test(domain)) { console.log(` "${domain}" does not look like a domain.`); domain = ''; }
|
|
917
920
|
}
|
|
918
921
|
|
|
919
|
-
printDnsRecords(ip);
|
|
920
|
-
await waitForDns(domain, ip);
|
|
922
|
+
printDnsRecords(ip, true);
|
|
923
|
+
await waitForDns(domain, ip, true);
|
|
921
924
|
|
|
922
925
|
// Step 3 — the server does the real verification and issues the cert.
|
|
923
926
|
await connectDomain(domain, email);
|
|
@@ -962,12 +965,20 @@ async function regionIp() {
|
|
|
962
965
|
return null;
|
|
963
966
|
}
|
|
964
967
|
|
|
965
|
-
|
|
968
|
+
// The ownership record the server checks on a domain's first connection.
|
|
969
|
+
const domainTxtValue = () => `nukebase-uid=${uid}`;
|
|
970
|
+
|
|
971
|
+
function printDnsRecords(ip, withTxt) {
|
|
966
972
|
console.log('\n Step 1 — point it here. At your registrar (GoDaddy, Namecheap,');
|
|
967
973
|
console.log(' Cloudflare, ...) add these DNS records:\n');
|
|
968
|
-
console.log(' Type Name
|
|
969
|
-
console.log(` A @
|
|
970
|
-
console.log(` A www
|
|
974
|
+
console.log(' Type Name Value');
|
|
975
|
+
console.log(` A @ ${ip || "(this region's IP — see the dashboard)"}`);
|
|
976
|
+
console.log(` A www ${ip || '(same)'}`);
|
|
977
|
+
if (withTxt) {
|
|
978
|
+
console.log(` TXT nukebase ${domainTxtValue()}`);
|
|
979
|
+
console.log('\n The TXT record proves the domain is yours, so no other account can');
|
|
980
|
+
console.log(' connect it. One on your main domain covers its subdomains too.');
|
|
981
|
+
}
|
|
971
982
|
console.log('\n If you use Cloudflare, set the records to "DNS only" (grey cloud)');
|
|
972
983
|
console.log(' so the certificate can be issued.\n');
|
|
973
984
|
}
|
|
@@ -981,7 +992,7 @@ function printDnsRecords(ip) {
|
|
|
981
992
|
// long after the server sees it — and on some Windows setups Node's own
|
|
982
993
|
// resolver cannot reach the system DNS server at all (ECONNREFUSED on every
|
|
983
994
|
// name). getaddrinfo is the fallback for a machine that blocks 1.1.1.1.
|
|
984
|
-
async function waitForDns(domain, ip) {
|
|
995
|
+
async function waitForDns(domain, ip, needTxt) {
|
|
985
996
|
console.log(' Step 2 — waiting for DNS. This can take a few minutes.');
|
|
986
997
|
console.log(' Press Enter to check now, Ctrl+C to stop and come back later.\n');
|
|
987
998
|
const dns = require('dns').promises;
|
|
@@ -990,19 +1001,31 @@ async function waitForDns(domain, ip) {
|
|
|
990
1001
|
try { return await pub.resolve4(name); } catch (e) {}
|
|
991
1002
|
try { return (await dns.lookup(name, { all: true, family: 4 })).map(a => a.address); } catch (e) { return []; }
|
|
992
1003
|
};
|
|
993
|
-
|
|
1004
|
+
// Same rule as the server: nukebase.<domain>, then each parent.
|
|
1005
|
+
const want = domainTxtValue().toLowerCase();
|
|
1006
|
+
const txtFound = async () => {
|
|
1007
|
+
const labels = domain.split('.');
|
|
1008
|
+
for (let i = 0; i <= labels.length - 2; i++) {
|
|
1009
|
+
let recs;
|
|
1010
|
+
try { recs = await pub.resolveTxt('nukebase.' + labels.slice(i).join('.')); } catch (e) { continue; }
|
|
1011
|
+
if (recs.some(chunks => chunks.join('').trim().toLowerCase() === want)) return true;
|
|
1012
|
+
}
|
|
1013
|
+
return false;
|
|
1014
|
+
};
|
|
1015
|
+
let apexOk = false, wwwOk = false, txtOk = !needTxt;
|
|
994
1016
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
995
1017
|
let poke = null;
|
|
996
1018
|
rl.on('line', () => { if (poke) poke(); });
|
|
997
1019
|
const wait = (ms) => new Promise(res => { poke = res; setTimeout(res, ms); });
|
|
998
1020
|
try {
|
|
999
1021
|
for (;;) {
|
|
1000
|
-
const [apex, www] = await Promise.all([lookup(domain), lookup(`www.${domain}`)]);
|
|
1022
|
+
const [apex, www, txt] = await Promise.all([lookup(domain), lookup(`www.${domain}`), needTxt ? txtFound() : true]);
|
|
1001
1023
|
apexOk = ip ? apex.includes(ip) : apex.length > 0;
|
|
1002
1024
|
wwwOk = ip ? www.includes(ip) : www.length > 0;
|
|
1025
|
+
txtOk = txt;
|
|
1003
1026
|
const mark = ok => ok ? '✓' : '…';
|
|
1004
|
-
process.stdout.write(`\r ${mark(apexOk)} ${domain} ${apex.length ? '→ ' + apex.join(', ') : '(no record yet)'} ${mark(wwwOk)} www ${www.length ? '→ ' + www.join(', ') : '(no record yet)'} `);
|
|
1005
|
-
if (apexOk) break;
|
|
1027
|
+
process.stdout.write(`\r ${mark(apexOk)} ${domain} ${apex.length ? '→ ' + apex.join(', ') : '(no record yet)'} ${mark(wwwOk)} www ${www.length ? '→ ' + www.join(', ') : '(no record yet)'}${needTxt ? ` ${mark(txtOk)} TXT ${txtOk ? 'found' : '(not yet)'}` : ''} `);
|
|
1028
|
+
if (apexOk && txtOk) break;
|
|
1006
1029
|
await wait(15000);
|
|
1007
1030
|
}
|
|
1008
1031
|
} finally {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// "A newer nukebase is available" notice.
|
|
2
|
+
//
|
|
3
|
+
// A global install never updates itself, and while one is installed even
|
|
4
|
+
// `npx nukebase` keeps running it. So once a day, while a command runs, the
|
|
5
|
+
// CLI asks the npm registry for the latest version (one small request, 1.5 s
|
|
6
|
+
// at most) and saves it next to the credentials. When the command finishes it
|
|
7
|
+
// prints one line if that version is newer than the one running.
|
|
8
|
+
//
|
|
9
|
+
// Off when NUKEBASE_NO_UPDATE_CHECK or CI is set.
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { FILE: CREDENTIALS_FILE } = require('./credentials');
|
|
14
|
+
|
|
15
|
+
const CACHE = path.join(path.dirname(CREDENTIALS_FILE), 'update-check.json');
|
|
16
|
+
const CHECK_EVERY_MS = 24 * 60 * 60 * 1000;
|
|
17
|
+
const REGISTRY_URL = 'https://registry.npmjs.org/nukebase/latest';
|
|
18
|
+
|
|
19
|
+
const disabled = () => !!(process.env.NUKEBASE_NO_UPDATE_CHECK || process.env.CI);
|
|
20
|
+
|
|
21
|
+
function readCache() {
|
|
22
|
+
try { return JSON.parse(fs.readFileSync(CACHE, 'utf8')); } catch { return {}; }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// "2.0.10" > "2.0.9"; pre-release tags are ignored.
|
|
26
|
+
function isNewer(a, b) {
|
|
27
|
+
const pa = String(a).split('-')[0].split('.').map(Number);
|
|
28
|
+
const pb = String(b).split('-')[0].split('.').map(Number);
|
|
29
|
+
for (let i = 0; i < 3; i++) {
|
|
30
|
+
if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) > (pb[i] || 0);
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Ask the registry, if a day has passed since the last time. Runs alongside
|
|
36
|
+
// the command; a command that exits first simply checks again next time.
|
|
37
|
+
// Never throws.
|
|
38
|
+
function scheduleCheck() {
|
|
39
|
+
if (disabled()) return;
|
|
40
|
+
if (Date.now() - (readCache().checkedAt || 0) < CHECK_EVERY_MS) return;
|
|
41
|
+
runCheck().catch(() => {});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// The notice for the version running now, or null.
|
|
45
|
+
function notice(current) {
|
|
46
|
+
if (disabled()) return null;
|
|
47
|
+
const { latest } = readCache();
|
|
48
|
+
if (!latest || !isNewer(latest, current)) return null;
|
|
49
|
+
// Run through npx (from npm's cache), or installed globally?
|
|
50
|
+
const viaNpx = __dirname.split(path.sep).includes('_npx');
|
|
51
|
+
const how = viaNpx
|
|
52
|
+
? 'Run it with: npx nukebase@latest <command>'
|
|
53
|
+
: 'Update with: npm i -g nukebase@latest';
|
|
54
|
+
return `\nnukebase ${latest} is available (you have ${current}). ${how}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// One request to the registry; the answer is saved for a day.
|
|
58
|
+
async function runCheck() {
|
|
59
|
+
let latest = null;
|
|
60
|
+
try {
|
|
61
|
+
const r = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(1500) });
|
|
62
|
+
if (r.ok) latest = (await r.json()).version || null;
|
|
63
|
+
} catch {}
|
|
64
|
+
try {
|
|
65
|
+
fs.mkdirSync(path.dirname(CACHE), { recursive: true });
|
|
66
|
+
// Record the attempt even when it failed, so an offline machine doesn't
|
|
67
|
+
// re-check on every command; keep the last known version.
|
|
68
|
+
const prev = readCache();
|
|
69
|
+
fs.writeFileSync(CACHE, JSON.stringify({ checkedAt: Date.now(), latest: latest || prev.latest || null }) + '\n');
|
|
70
|
+
} catch {}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = { scheduleCheck, notice, isNewer };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nukebase",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
4
4
|
"description": "NukeBase CLI: create, deploy and manage NukeBase projects",
|
|
5
5
|
"bin": {
|
|
6
6
|
"nukebase": "bin/nukebase.js"
|
|
@@ -17,6 +17,13 @@
|
|
|
17
17
|
"ws": "^8.20.0"
|
|
18
18
|
},
|
|
19
19
|
"homepage": "https://nukebase.com",
|
|
20
|
-
"keywords": [
|
|
20
|
+
"keywords": [
|
|
21
|
+
"nukebase",
|
|
22
|
+
"database",
|
|
23
|
+
"realtime",
|
|
24
|
+
"hosting",
|
|
25
|
+
"cli",
|
|
26
|
+
"deploy"
|
|
27
|
+
],
|
|
21
28
|
"license": "MIT"
|
|
22
29
|
}
|