databull 0.0.0-stage → 1.1.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 +46 -2
- package/dist/databull.mjs +3677 -0
- package/package.json +21 -4
|
@@ -0,0 +1,3677 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
9
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
|
|
28
|
+
// node_modules/sisteransi/src/index.js
|
|
29
|
+
var require_src = __commonJS({
|
|
30
|
+
"node_modules/sisteransi/src/index.js"(exports, module) {
|
|
31
|
+
"use strict";
|
|
32
|
+
var ESC = "\x1B";
|
|
33
|
+
var CSI = `${ESC}[`;
|
|
34
|
+
var beep = "\x07";
|
|
35
|
+
var cursor = {
|
|
36
|
+
to(x2, y3) {
|
|
37
|
+
if (!y3) return `${CSI}${x2 + 1}G`;
|
|
38
|
+
return `${CSI}${y3 + 1};${x2 + 1}H`;
|
|
39
|
+
},
|
|
40
|
+
move(x2, y3) {
|
|
41
|
+
let ret = "";
|
|
42
|
+
if (x2 < 0) ret += `${CSI}${-x2}D`;
|
|
43
|
+
else if (x2 > 0) ret += `${CSI}${x2}C`;
|
|
44
|
+
if (y3 < 0) ret += `${CSI}${-y3}A`;
|
|
45
|
+
else if (y3 > 0) ret += `${CSI}${y3}B`;
|
|
46
|
+
return ret;
|
|
47
|
+
},
|
|
48
|
+
up: (count = 1) => `${CSI}${count}A`,
|
|
49
|
+
down: (count = 1) => `${CSI}${count}B`,
|
|
50
|
+
forward: (count = 1) => `${CSI}${count}C`,
|
|
51
|
+
backward: (count = 1) => `${CSI}${count}D`,
|
|
52
|
+
nextLine: (count = 1) => `${CSI}E`.repeat(count),
|
|
53
|
+
prevLine: (count = 1) => `${CSI}F`.repeat(count),
|
|
54
|
+
left: `${CSI}G`,
|
|
55
|
+
hide: `${CSI}?25l`,
|
|
56
|
+
show: `${CSI}?25h`,
|
|
57
|
+
save: `${ESC}7`,
|
|
58
|
+
restore: `${ESC}8`
|
|
59
|
+
};
|
|
60
|
+
var scroll = {
|
|
61
|
+
up: (count = 1) => `${CSI}S`.repeat(count),
|
|
62
|
+
down: (count = 1) => `${CSI}T`.repeat(count)
|
|
63
|
+
};
|
|
64
|
+
var erase = {
|
|
65
|
+
screen: `${CSI}2J`,
|
|
66
|
+
up: (count = 1) => `${CSI}1J`.repeat(count),
|
|
67
|
+
down: (count = 1) => `${CSI}J`.repeat(count),
|
|
68
|
+
line: `${CSI}2K`,
|
|
69
|
+
lineEnd: `${CSI}K`,
|
|
70
|
+
lineStart: `${CSI}1K`,
|
|
71
|
+
lines(count) {
|
|
72
|
+
let clear = "";
|
|
73
|
+
for (let i = 0; i < count; i++)
|
|
74
|
+
clear += this.line + (i < count - 1 ? cursor.up() : "");
|
|
75
|
+
if (count)
|
|
76
|
+
clear += cursor.left;
|
|
77
|
+
return clear;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
module.exports = { cursor, scroll, erase, beep };
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// node_modules/picocolors/picocolors.js
|
|
85
|
+
var require_picocolors = __commonJS({
|
|
86
|
+
"node_modules/picocolors/picocolors.js"(exports, module) {
|
|
87
|
+
var p2 = process || {};
|
|
88
|
+
var argv = p2.argv || [];
|
|
89
|
+
var env = p2.env || {};
|
|
90
|
+
var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p2.platform === "win32" || (p2.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
|
|
91
|
+
var formatter = (open, close, replace = open) => (input) => {
|
|
92
|
+
let string = "" + input, index = string.indexOf(close, open.length);
|
|
93
|
+
return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
|
|
94
|
+
};
|
|
95
|
+
var replaceClose = (string, close, replace, index) => {
|
|
96
|
+
let result = "", cursor = 0;
|
|
97
|
+
do {
|
|
98
|
+
result += string.substring(cursor, index) + replace;
|
|
99
|
+
cursor = index + close.length;
|
|
100
|
+
index = string.indexOf(close, cursor);
|
|
101
|
+
} while (~index);
|
|
102
|
+
return result + string.substring(cursor);
|
|
103
|
+
};
|
|
104
|
+
var createColors = (enabled = isColorSupported) => {
|
|
105
|
+
let f = enabled ? formatter : () => String;
|
|
106
|
+
return {
|
|
107
|
+
isColorSupported: enabled,
|
|
108
|
+
reset: f("\x1B[0m", "\x1B[0m"),
|
|
109
|
+
bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
|
|
110
|
+
dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
|
|
111
|
+
italic: f("\x1B[3m", "\x1B[23m"),
|
|
112
|
+
underline: f("\x1B[4m", "\x1B[24m"),
|
|
113
|
+
inverse: f("\x1B[7m", "\x1B[27m"),
|
|
114
|
+
hidden: f("\x1B[8m", "\x1B[28m"),
|
|
115
|
+
strikethrough: f("\x1B[9m", "\x1B[29m"),
|
|
116
|
+
black: f("\x1B[30m", "\x1B[39m"),
|
|
117
|
+
red: f("\x1B[31m", "\x1B[39m"),
|
|
118
|
+
green: f("\x1B[32m", "\x1B[39m"),
|
|
119
|
+
yellow: f("\x1B[33m", "\x1B[39m"),
|
|
120
|
+
blue: f("\x1B[34m", "\x1B[39m"),
|
|
121
|
+
magenta: f("\x1B[35m", "\x1B[39m"),
|
|
122
|
+
cyan: f("\x1B[36m", "\x1B[39m"),
|
|
123
|
+
white: f("\x1B[37m", "\x1B[39m"),
|
|
124
|
+
gray: f("\x1B[90m", "\x1B[39m"),
|
|
125
|
+
bgBlack: f("\x1B[40m", "\x1B[49m"),
|
|
126
|
+
bgRed: f("\x1B[41m", "\x1B[49m"),
|
|
127
|
+
bgGreen: f("\x1B[42m", "\x1B[49m"),
|
|
128
|
+
bgYellow: f("\x1B[43m", "\x1B[49m"),
|
|
129
|
+
bgBlue: f("\x1B[44m", "\x1B[49m"),
|
|
130
|
+
bgMagenta: f("\x1B[45m", "\x1B[49m"),
|
|
131
|
+
bgCyan: f("\x1B[46m", "\x1B[49m"),
|
|
132
|
+
bgWhite: f("\x1B[47m", "\x1B[49m"),
|
|
133
|
+
blackBright: f("\x1B[90m", "\x1B[39m"),
|
|
134
|
+
redBright: f("\x1B[91m", "\x1B[39m"),
|
|
135
|
+
greenBright: f("\x1B[92m", "\x1B[39m"),
|
|
136
|
+
yellowBright: f("\x1B[93m", "\x1B[39m"),
|
|
137
|
+
blueBright: f("\x1B[94m", "\x1B[39m"),
|
|
138
|
+
magentaBright: f("\x1B[95m", "\x1B[39m"),
|
|
139
|
+
cyanBright: f("\x1B[96m", "\x1B[39m"),
|
|
140
|
+
whiteBright: f("\x1B[97m", "\x1B[39m"),
|
|
141
|
+
bgBlackBright: f("\x1B[100m", "\x1B[49m"),
|
|
142
|
+
bgRedBright: f("\x1B[101m", "\x1B[49m"),
|
|
143
|
+
bgGreenBright: f("\x1B[102m", "\x1B[49m"),
|
|
144
|
+
bgYellowBright: f("\x1B[103m", "\x1B[49m"),
|
|
145
|
+
bgBlueBright: f("\x1B[104m", "\x1B[49m"),
|
|
146
|
+
bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
|
|
147
|
+
bgCyanBright: f("\x1B[106m", "\x1B[49m"),
|
|
148
|
+
bgWhiteBright: f("\x1B[107m", "\x1B[49m")
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
module.exports = createColors();
|
|
152
|
+
module.exports.createColors = createColors;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// src/errors.mjs
|
|
157
|
+
var Fail = class extends Error {
|
|
158
|
+
constructor(message, { fix = "", code = "", cause } = {}) {
|
|
159
|
+
super(message);
|
|
160
|
+
this.name = "Fail";
|
|
161
|
+
this.fix = fix;
|
|
162
|
+
this.code = code;
|
|
163
|
+
if (cause) this.cause = cause;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
var Cancelled = class extends Error {
|
|
167
|
+
constructor() {
|
|
168
|
+
super("cancelled");
|
|
169
|
+
this.name = "Cancelled";
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
var SUPPORT = "adgan.business@gmail.com";
|
|
173
|
+
|
|
174
|
+
// src/args.mjs
|
|
175
|
+
var DATA_COMMANDS = ["sites", "stats", "sources", "breakdown", "compare", "sales", "visitors", "visitor", "live", "goals", "funnels", "ltv", "query"];
|
|
176
|
+
var COMMANDS = ["install", "update", "reset-password", "domain", "status", "license", "uninstall", "mcp", "api-key", ...DATA_COMMANDS, "help"];
|
|
177
|
+
var TAKES_ARGS = /* @__PURE__ */ new Set(["api-key", ...DATA_COMMANDS]);
|
|
178
|
+
var COMMAND_ALIASES = { license: "license", "reset": "reset-password", upgrade: "update", remove: "uninstall", "api-keys": "api-key", keys: "api-key", key: "api-key", overview: "stats", referrers: "sources", realtime: "live", payments: "sales" };
|
|
179
|
+
var VALUE_FLAGS = ["key", "account", "name", "domain", "api", "bundle", "home", "period", "from", "to", "site", "limit", "by", "dim", "sort", "url", "api-key", "client", "funnel", "page", "label"];
|
|
180
|
+
var MULTI_FLAGS = ["filter"];
|
|
181
|
+
var BOOL_FLAGS = ["yes", "open", "help", "version", "deactivate", "delete-database", "debug", "json", "pii", "customers", "series"];
|
|
182
|
+
var SHORT = { y: "yes", h: "help", v: "version", p: "period", s: "site", n: "limit", f: "filter" };
|
|
183
|
+
var SHORT_VALUE = /* @__PURE__ */ new Set(["p", "s", "n", "f"]);
|
|
184
|
+
function parseArgs(argv) {
|
|
185
|
+
const out3 = { command: null, flags: { open: true }, args: [] };
|
|
186
|
+
for (let i = 0; i < argv.length; i++) {
|
|
187
|
+
const arg = argv[i];
|
|
188
|
+
if (arg === "--") continue;
|
|
189
|
+
if (arg.startsWith("--")) {
|
|
190
|
+
let [name, value] = splitOnce(arg.slice(2), "=");
|
|
191
|
+
if (name.startsWith("no-") && BOOL_FLAGS.includes(name.slice(3))) {
|
|
192
|
+
if (value !== void 0) throw new Fail(`--${name} doesn't take a value.`);
|
|
193
|
+
out3.flags[name.slice(3)] = false;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (BOOL_FLAGS.includes(name)) {
|
|
197
|
+
if (value !== void 0) throw new Fail(`--${name} doesn't take a value.`);
|
|
198
|
+
out3.flags[camel(name)] = true;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (VALUE_FLAGS.includes(name) || MULTI_FLAGS.includes(name)) {
|
|
202
|
+
if (value === void 0) {
|
|
203
|
+
value = argv[i + 1];
|
|
204
|
+
if (value === void 0 || value.startsWith("--")) throw new Fail(`--${name} needs a value.`, { fix: `Example: --${name} ${example(name)}` });
|
|
205
|
+
i++;
|
|
206
|
+
}
|
|
207
|
+
if (MULTI_FLAGS.includes(name)) (out3.flags[name] ||= []).push(value);
|
|
208
|
+
else out3.flags[name === "api-key" ? "apiKey" : name] = value;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
throw new Fail(`Unknown option --${name}.`, { fix: "Run `npx databull@latest help` to see every option." });
|
|
212
|
+
}
|
|
213
|
+
if (arg.startsWith("-") && arg.length > 1 && !/^-\d/.test(arg)) {
|
|
214
|
+
const letters = arg.slice(1);
|
|
215
|
+
if (letters.length === 1 && SHORT_VALUE.has(letters)) {
|
|
216
|
+
const v2 = argv[i + 1];
|
|
217
|
+
if (v2 === void 0 || v2.startsWith("-")) throw new Fail(`-${letters} needs a value.`, { fix: `Example: -${letters} ${example(SHORT[letters])}` });
|
|
218
|
+
i++;
|
|
219
|
+
if (MULTI_FLAGS.includes(SHORT[letters])) (out3.flags[SHORT[letters]] ||= []).push(v2);
|
|
220
|
+
else out3.flags[SHORT[letters]] = v2;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
for (const ch of letters) {
|
|
224
|
+
if (!SHORT[ch] || SHORT_VALUE.has(ch)) throw new Fail(`Unknown option -${ch}.`, { fix: "Run `npx databull@latest help` to see every option." });
|
|
225
|
+
out3.flags[SHORT[ch]] = true;
|
|
226
|
+
}
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (out3.command && TAKES_ARGS.has(out3.command)) {
|
|
230
|
+
out3.args.push(arg);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (out3.command) throw new Fail(`Unexpected "${arg}".`, { fix: "Run `npx databull@latest help` to see every command." });
|
|
234
|
+
const cmd = COMMAND_ALIASES[arg] || arg;
|
|
235
|
+
if (!COMMANDS.includes(cmd)) throw new Fail(`Unknown command "${arg}".`, { fix: `Commands: ${COMMANDS.join(", ")}.` });
|
|
236
|
+
out3.command = cmd;
|
|
237
|
+
}
|
|
238
|
+
if (out3.flags.help) out3.command = "help";
|
|
239
|
+
else if (out3.flags.version) out3.command = "version";
|
|
240
|
+
else if (!out3.command) out3.command = "install";
|
|
241
|
+
return out3;
|
|
242
|
+
}
|
|
243
|
+
function splitOnce(s, sep) {
|
|
244
|
+
const i = s.indexOf(sep);
|
|
245
|
+
return i < 0 ? [s, void 0] : [s.slice(0, i), s.slice(i + 1)];
|
|
246
|
+
}
|
|
247
|
+
function camel(s) {
|
|
248
|
+
return s.replace(/-([a-z])/g, (_3, c) => c.toUpperCase());
|
|
249
|
+
}
|
|
250
|
+
function example(name) {
|
|
251
|
+
return {
|
|
252
|
+
key: "XXXX-XXXX",
|
|
253
|
+
account: "<account id>",
|
|
254
|
+
name: "databull",
|
|
255
|
+
domain: "analytics.example.com",
|
|
256
|
+
api: "https://databull.app",
|
|
257
|
+
bundle: "./databull-1.0.0.tgz",
|
|
258
|
+
home: "~/.databull",
|
|
259
|
+
period: "7d",
|
|
260
|
+
from: "2026-10-01",
|
|
261
|
+
to: "2026-10-31",
|
|
262
|
+
site: "example.com",
|
|
263
|
+
limit: "20",
|
|
264
|
+
by: "revenue",
|
|
265
|
+
dim: "referrer",
|
|
266
|
+
sort: "revenue",
|
|
267
|
+
url: "https://analytics.example.com",
|
|
268
|
+
"api-key": "dbk_\u2026",
|
|
269
|
+
client: "claude",
|
|
270
|
+
funnel: "Checkout",
|
|
271
|
+
page: "1",
|
|
272
|
+
label: '"CI"',
|
|
273
|
+
filter: "country=US"
|
|
274
|
+
}[name];
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/config.mjs
|
|
278
|
+
import fs from "node:fs";
|
|
279
|
+
import os from "node:os";
|
|
280
|
+
import path from "node:path";
|
|
281
|
+
function resolvePaths(home) {
|
|
282
|
+
const root = path.resolve(expandHome(home || process.env.DATABULL_HOME || path.join(os.homedir(), ".databull")));
|
|
283
|
+
return {
|
|
284
|
+
home: root,
|
|
285
|
+
config: path.join(root, "config.json"),
|
|
286
|
+
tools: path.join(root, "tools"),
|
|
287
|
+
logs: path.join(root, "logs"),
|
|
288
|
+
installs: path.join(root, "installs"),
|
|
289
|
+
installDir: (name) => path.join(root, "installs", name)
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
function expandHome(p2) {
|
|
293
|
+
return p2 === "~" || p2.startsWith("~/") ? path.join(os.homedir(), p2.slice(1)) : p2;
|
|
294
|
+
}
|
|
295
|
+
function ensureDir(dir) {
|
|
296
|
+
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
297
|
+
}
|
|
298
|
+
function readConfig(paths) {
|
|
299
|
+
let text;
|
|
300
|
+
try {
|
|
301
|
+
text = fs.readFileSync(paths.config, "utf8");
|
|
302
|
+
} catch (err) {
|
|
303
|
+
if (err.code === "ENOENT") return { installs: [] };
|
|
304
|
+
throw new Fail(`Couldn't read ${paths.config} (${err.code}).`, { fix: "Check the file permissions, then try again." });
|
|
305
|
+
}
|
|
306
|
+
let cfg;
|
|
307
|
+
try {
|
|
308
|
+
cfg = JSON.parse(text);
|
|
309
|
+
} catch {
|
|
310
|
+
throw new Fail(`${paths.config} isn't valid JSON.`, { fix: "Fix or move that file away, then try again." });
|
|
311
|
+
}
|
|
312
|
+
if (!cfg || typeof cfg !== "object") cfg = {};
|
|
313
|
+
if (!Array.isArray(cfg.installs)) cfg.installs = [];
|
|
314
|
+
return cfg;
|
|
315
|
+
}
|
|
316
|
+
function writeConfig(paths, cfg) {
|
|
317
|
+
ensureDir(paths.home);
|
|
318
|
+
const tmp = `${paths.config}.${process.pid}.tmp`;
|
|
319
|
+
fs.writeFileSync(tmp, JSON.stringify(cfg, null, 2) + "\n", { mode: 384 });
|
|
320
|
+
fs.chmodSync(tmp, 384);
|
|
321
|
+
fs.renameSync(tmp, paths.config);
|
|
322
|
+
fs.chmodSync(paths.config, 384);
|
|
323
|
+
}
|
|
324
|
+
function findInstall(cfg, name, accountId) {
|
|
325
|
+
return cfg.installs.find((i) => i.name === name && (!accountId || i.account_id === accountId)) || null;
|
|
326
|
+
}
|
|
327
|
+
function upsertInstall(cfg, install) {
|
|
328
|
+
const idx = cfg.installs.findIndex((i) => i.name === install.name && i.account_id === install.account_id);
|
|
329
|
+
if (idx >= 0) cfg.installs[idx] = { ...cfg.installs[idx], ...install };
|
|
330
|
+
else cfg.installs.push(install);
|
|
331
|
+
return cfg;
|
|
332
|
+
}
|
|
333
|
+
function forgetInstallFields(cfg, install, fields) {
|
|
334
|
+
const saved = cfg.installs.find((i) => i.name === install.name && i.account_id === install.account_id);
|
|
335
|
+
if (saved) for (const f of fields) delete saved[f];
|
|
336
|
+
return cfg;
|
|
337
|
+
}
|
|
338
|
+
function removeInstall(cfg, install) {
|
|
339
|
+
cfg.installs = cfg.installs.filter((i) => !(i.name === install.name && i.account_id === install.account_id));
|
|
340
|
+
return cfg;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// src/log.mjs
|
|
344
|
+
import fs2 from "node:fs";
|
|
345
|
+
import path2 from "node:path";
|
|
346
|
+
function createLog(dir) {
|
|
347
|
+
let file = null;
|
|
348
|
+
const open = () => {
|
|
349
|
+
if (file) return file;
|
|
350
|
+
try {
|
|
351
|
+
ensureDir(dir);
|
|
352
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
353
|
+
file = path2.join(dir, `${stamp}.log`);
|
|
354
|
+
fs2.writeFileSync(file, `databull ${process.argv.slice(2).map(redactArg).join(" ")}
|
|
355
|
+
`, { mode: 384 });
|
|
356
|
+
} catch {
|
|
357
|
+
file = null;
|
|
358
|
+
}
|
|
359
|
+
return file;
|
|
360
|
+
};
|
|
361
|
+
return {
|
|
362
|
+
get path() {
|
|
363
|
+
return open();
|
|
364
|
+
},
|
|
365
|
+
write(text) {
|
|
366
|
+
const f = open();
|
|
367
|
+
if (!f) return;
|
|
368
|
+
try {
|
|
369
|
+
fs2.appendFileSync(f, String(text).endsWith("\n") ? String(text) : `${text}
|
|
370
|
+
`);
|
|
371
|
+
} catch {
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
section(title) {
|
|
375
|
+
this.write(`
|
|
376
|
+
\u2500\u2500 ${(/* @__PURE__ */ new Date()).toISOString()} ${title}`);
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
var redactNext = false;
|
|
381
|
+
function redactArg(a) {
|
|
382
|
+
if (redactNext) {
|
|
383
|
+
redactNext = false;
|
|
384
|
+
return "***";
|
|
385
|
+
}
|
|
386
|
+
if (a === "--key") redactNext = true;
|
|
387
|
+
if (a.startsWith("--key=")) return "--key=***";
|
|
388
|
+
return a;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// src/tools.mjs
|
|
392
|
+
import fs3 from "node:fs";
|
|
393
|
+
import path3 from "node:path";
|
|
394
|
+
import { spawn } from "node:child_process";
|
|
395
|
+
var WRANGLER_VERSION = "4.152.0";
|
|
396
|
+
var children = /* @__PURE__ */ new Set();
|
|
397
|
+
function killChildren() {
|
|
398
|
+
for (const c of children) {
|
|
399
|
+
try {
|
|
400
|
+
c.kill("SIGTERM");
|
|
401
|
+
} catch {
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function wranglerPath(paths) {
|
|
406
|
+
return path3.join(paths.tools, "node_modules", "wrangler", "bin", "wrangler.js");
|
|
407
|
+
}
|
|
408
|
+
function installedVersion(paths) {
|
|
409
|
+
try {
|
|
410
|
+
return JSON.parse(fs3.readFileSync(path3.join(paths.tools, "node_modules", "wrangler", "package.json"), "utf8")).version;
|
|
411
|
+
} catch {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
function npmCommand() {
|
|
416
|
+
const ep = process.env.npm_execpath;
|
|
417
|
+
if (ep && /npm-cli\.js$/.test(ep) && fs3.existsSync(ep)) return [process.execPath, [ep]];
|
|
418
|
+
if (ep && /npx-cli\.js$/.test(ep)) {
|
|
419
|
+
const npmCli = path3.join(path3.dirname(ep), "npm-cli.js");
|
|
420
|
+
if (fs3.existsSync(npmCli)) return [process.execPath, [npmCli]];
|
|
421
|
+
}
|
|
422
|
+
return [process.platform === "win32" ? "npm.cmd" : "npm", []];
|
|
423
|
+
}
|
|
424
|
+
function startTools(paths, log) {
|
|
425
|
+
const override = process.env.DATABULL_WRANGLER;
|
|
426
|
+
const ready = (value) => {
|
|
427
|
+
const p2 = Promise.resolve(value);
|
|
428
|
+
const get2 = () => p2;
|
|
429
|
+
get2.ready = true;
|
|
430
|
+
return get2;
|
|
431
|
+
};
|
|
432
|
+
if (override) return ready(path3.resolve(override));
|
|
433
|
+
const bin = wranglerPath(paths);
|
|
434
|
+
if (installedVersion(paths) === WRANGLER_VERSION && fs3.existsSync(bin)) return ready(bin);
|
|
435
|
+
const promise = new Promise((resolve, reject) => {
|
|
436
|
+
try {
|
|
437
|
+
ensureDir(paths.tools);
|
|
438
|
+
const pkg = path3.join(paths.tools, "package.json");
|
|
439
|
+
if (!fs3.existsSync(pkg)) fs3.writeFileSync(pkg, JSON.stringify({ name: "databull-tools", private: true }, null, 2));
|
|
440
|
+
} catch (err) {
|
|
441
|
+
reject(new Fail(`Couldn't create ${paths.tools} (${err.code}).`, { fix: "Check that your home folder is writable, or pass --home <dir>." }));
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
const [cmd, pre] = npmCommand();
|
|
445
|
+
const args = [...pre, "install", "--prefix", paths.tools, "--no-audit", "--no-fund", "--loglevel=error", `wrangler@${WRANGLER_VERSION}`];
|
|
446
|
+
let output = "";
|
|
447
|
+
const child = spawn(cmd, args, { cwd: paths.tools, stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, npm_config_update_notifier: "false" } });
|
|
448
|
+
children.add(child);
|
|
449
|
+
child.stdout.on("data", (d3) => output += d3);
|
|
450
|
+
child.stderr.on("data", (d3) => output += d3);
|
|
451
|
+
child.on("error", (err) => {
|
|
452
|
+
children.delete(child);
|
|
453
|
+
log.section("npm install wrangler (failed to start)");
|
|
454
|
+
log.write(String(err));
|
|
455
|
+
reject(new Fail(`Couldn't run npm to fetch Cloudflare's wrangler tool.`, { fix: "Make sure Node.js and npm are installed (node -v, npm -v), then try again." }));
|
|
456
|
+
});
|
|
457
|
+
child.on("close", (code) => {
|
|
458
|
+
children.delete(child);
|
|
459
|
+
log.section(`npm install wrangler@${WRANGLER_VERSION} \u2192 exit ${code}`);
|
|
460
|
+
log.write(output);
|
|
461
|
+
if (code === 0 && fs3.existsSync(bin)) resolve(bin);
|
|
462
|
+
else
|
|
463
|
+
reject(
|
|
464
|
+
new Fail(`Couldn't download Cloudflare's wrangler tool.`, {
|
|
465
|
+
fix: /ENOTFOUND|ETIMEDOUT|ECONNRESET|EAI_AGAIN|network/i.test(output) ? "Check your internet connection, then run the same command again." : "Run the same command again. If it keeps failing, the log below has npm\u2019s full message."
|
|
466
|
+
})
|
|
467
|
+
);
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
promise.catch(() => {
|
|
471
|
+
});
|
|
472
|
+
const get = () => promise;
|
|
473
|
+
get.ready = false;
|
|
474
|
+
promise.then(() => get.ready = true, () => {
|
|
475
|
+
});
|
|
476
|
+
return get;
|
|
477
|
+
}
|
|
478
|
+
async function runWrangler(ctx, args, { accountId, title, interactive = false, onOutput, secretOutput = false } = {}) {
|
|
479
|
+
const bin = await ctx.wrangler();
|
|
480
|
+
const env = {
|
|
481
|
+
...process.env,
|
|
482
|
+
WRANGLER_SEND_METRICS: "false",
|
|
483
|
+
NO_COLOR: "1",
|
|
484
|
+
FORCE_COLOR: "0",
|
|
485
|
+
WRANGLER_HIDE_BANNER: "true"
|
|
486
|
+
};
|
|
487
|
+
if (accountId) env.CLOUDFLARE_ACCOUNT_ID = accountId;
|
|
488
|
+
else delete env.CLOUDFLARE_ACCOUNT_ID;
|
|
489
|
+
return new Promise((resolve, reject) => {
|
|
490
|
+
const child = spawn(process.execPath, [bin, ...args], { cwd: ctx.cwd || ctx.paths.home, stdio: [interactive ? "inherit" : "ignore", "pipe", "pipe"], env });
|
|
491
|
+
children.add(child);
|
|
492
|
+
let stdout = "";
|
|
493
|
+
let stderr = "";
|
|
494
|
+
child.stdout.on("data", (d3) => {
|
|
495
|
+
stdout += d3;
|
|
496
|
+
onOutput?.(String(d3));
|
|
497
|
+
});
|
|
498
|
+
child.stderr.on("data", (d3) => {
|
|
499
|
+
stderr += d3;
|
|
500
|
+
onOutput?.(String(d3));
|
|
501
|
+
});
|
|
502
|
+
child.on("error", (err) => {
|
|
503
|
+
children.delete(child);
|
|
504
|
+
reject(new Fail(`Couldn't start wrangler (${err.code || err.message}).`, { fix: `Delete ${ctx.paths.tools} and run the same command again.` }));
|
|
505
|
+
});
|
|
506
|
+
child.on("close", (code) => {
|
|
507
|
+
children.delete(child);
|
|
508
|
+
ctx.log.section(`wrangler ${title || args.join(" ")} \u2192 exit ${code}`);
|
|
509
|
+
ctx.log.write(secretOutput ? "(output hidden: contains a token)" : stdout);
|
|
510
|
+
if (stderr) ctx.log.write(stderr);
|
|
511
|
+
resolve({ code, stdout, stderr });
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
function parseJsonOutput(text) {
|
|
516
|
+
const s = String(text);
|
|
517
|
+
const lines = s.split("\n");
|
|
518
|
+
let offset = 0;
|
|
519
|
+
for (const l2 of lines) {
|
|
520
|
+
const t = l2.trimStart();
|
|
521
|
+
if (t.startsWith("{") || t.startsWith("[")) {
|
|
522
|
+
const start = offset + (l2.length - t.length);
|
|
523
|
+
const close = t[0] === "{" ? "}" : "]";
|
|
524
|
+
let end2 = s.lastIndexOf(close);
|
|
525
|
+
while (end2 > start) {
|
|
526
|
+
try {
|
|
527
|
+
return JSON.parse(s.slice(start, end2 + 1));
|
|
528
|
+
} catch {
|
|
529
|
+
}
|
|
530
|
+
end2 = s.lastIndexOf(close, end2 - 1);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
offset += l2.length + 1;
|
|
534
|
+
}
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// src/util.mjs
|
|
539
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
540
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
541
|
+
var sleep = (ms) => new Promise((r2) => setTimeout(r2, ms));
|
|
542
|
+
var b64url = (bytes) => randomBytes(bytes).toString("base64url");
|
|
543
|
+
var sha256hex = (data) => createHash("sha256").update(data).digest("hex");
|
|
544
|
+
function maskKey(key) {
|
|
545
|
+
if (!key) return "\u2014";
|
|
546
|
+
const s = String(key);
|
|
547
|
+
return s.length <= 8 ? "\u2022\u2022\u2022\u2022" : `${s.slice(0, 4)}\u2022\u2022\u2022\u2022${s.slice(-4)}`;
|
|
548
|
+
}
|
|
549
|
+
function compareVersions(a, b3) {
|
|
550
|
+
const pa = String(a || "0").split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
|
|
551
|
+
const pb = String(b3 || "0").split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
|
|
552
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
553
|
+
const d3 = (pa[i] || 0) - (pb[i] || 0);
|
|
554
|
+
if (d3) return d3 > 0 ? 1 : -1;
|
|
555
|
+
}
|
|
556
|
+
return 0;
|
|
557
|
+
}
|
|
558
|
+
async function fetchWithTimeout(url, opts = {}, timeoutMs = 2e4) {
|
|
559
|
+
const ctrl = new AbortController();
|
|
560
|
+
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
561
|
+
try {
|
|
562
|
+
return await fetch(url, { ...opts, signal: ctrl.signal });
|
|
563
|
+
} finally {
|
|
564
|
+
clearTimeout(t);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function isNetworkError(err) {
|
|
568
|
+
return err && (err.name === "AbortError" || err.name === "TypeError" || /fetch failed|ECONN|ENOTFOUND|ETIMEDOUT|EAI_AGAIN|socket/i.test(`${err.message} ${err.cause?.code || ""}`));
|
|
569
|
+
}
|
|
570
|
+
async function retry(fn, { attempts = 3, base = Number(process.env.DATABULL_RETRY_MS || 1e3), retryable = isNetworkError } = {}) {
|
|
571
|
+
let last;
|
|
572
|
+
for (let i = 0; i < attempts; i++) {
|
|
573
|
+
try {
|
|
574
|
+
return await fn(i);
|
|
575
|
+
} catch (err) {
|
|
576
|
+
last = err;
|
|
577
|
+
if (i === attempts - 1 || !retryable(err)) throw err;
|
|
578
|
+
await sleep(base * 2 ** i);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
throw last;
|
|
582
|
+
}
|
|
583
|
+
function openUrl(url) {
|
|
584
|
+
if (process.env.DATABULL_NO_BROWSER) return false;
|
|
585
|
+
const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", '""', url.replace(/&/g, "^&")]] : ["xdg-open", [url]];
|
|
586
|
+
try {
|
|
587
|
+
const child = spawn2(cmd, args, { stdio: "ignore", detached: true });
|
|
588
|
+
child.on("error", () => {
|
|
589
|
+
});
|
|
590
|
+
child.unref();
|
|
591
|
+
return true;
|
|
592
|
+
} catch {
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
597
|
+
|
|
598
|
+
// node_modules/@clack/core/dist/index.mjs
|
|
599
|
+
var import_sisteransi = __toESM(require_src(), 1);
|
|
600
|
+
import { stdin as j, stdout as M } from "node:process";
|
|
601
|
+
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
602
|
+
import O from "node:readline";
|
|
603
|
+
import { Writable as X } from "node:stream";
|
|
604
|
+
function DD({ onlyFirst: e2 = false } = {}) {
|
|
605
|
+
const t = ["[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?(?:\\u0007|\\u001B\\u005C|\\u009C))", "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))"].join("|");
|
|
606
|
+
return new RegExp(t, e2 ? void 0 : "g");
|
|
607
|
+
}
|
|
608
|
+
var uD = DD();
|
|
609
|
+
function P(e2) {
|
|
610
|
+
if (typeof e2 != "string") throw new TypeError(`Expected a \`string\`, got \`${typeof e2}\``);
|
|
611
|
+
return e2.replace(uD, "");
|
|
612
|
+
}
|
|
613
|
+
function L(e2) {
|
|
614
|
+
return e2 && e2.__esModule && Object.prototype.hasOwnProperty.call(e2, "default") ? e2.default : e2;
|
|
615
|
+
}
|
|
616
|
+
var W = { exports: {} };
|
|
617
|
+
(function(e2) {
|
|
618
|
+
var u2 = {};
|
|
619
|
+
e2.exports = u2, u2.eastAsianWidth = function(F2) {
|
|
620
|
+
var s = F2.charCodeAt(0), i = F2.length == 2 ? F2.charCodeAt(1) : 0, D2 = s;
|
|
621
|
+
return 55296 <= s && s <= 56319 && 56320 <= i && i <= 57343 && (s &= 1023, i &= 1023, D2 = s << 10 | i, D2 += 65536), D2 == 12288 || 65281 <= D2 && D2 <= 65376 || 65504 <= D2 && D2 <= 65510 ? "F" : D2 == 8361 || 65377 <= D2 && D2 <= 65470 || 65474 <= D2 && D2 <= 65479 || 65482 <= D2 && D2 <= 65487 || 65490 <= D2 && D2 <= 65495 || 65498 <= D2 && D2 <= 65500 || 65512 <= D2 && D2 <= 65518 ? "H" : 4352 <= D2 && D2 <= 4447 || 4515 <= D2 && D2 <= 4519 || 4602 <= D2 && D2 <= 4607 || 9001 <= D2 && D2 <= 9002 || 11904 <= D2 && D2 <= 11929 || 11931 <= D2 && D2 <= 12019 || 12032 <= D2 && D2 <= 12245 || 12272 <= D2 && D2 <= 12283 || 12289 <= D2 && D2 <= 12350 || 12353 <= D2 && D2 <= 12438 || 12441 <= D2 && D2 <= 12543 || 12549 <= D2 && D2 <= 12589 || 12593 <= D2 && D2 <= 12686 || 12688 <= D2 && D2 <= 12730 || 12736 <= D2 && D2 <= 12771 || 12784 <= D2 && D2 <= 12830 || 12832 <= D2 && D2 <= 12871 || 12880 <= D2 && D2 <= 13054 || 13056 <= D2 && D2 <= 19903 || 19968 <= D2 && D2 <= 42124 || 42128 <= D2 && D2 <= 42182 || 43360 <= D2 && D2 <= 43388 || 44032 <= D2 && D2 <= 55203 || 55216 <= D2 && D2 <= 55238 || 55243 <= D2 && D2 <= 55291 || 63744 <= D2 && D2 <= 64255 || 65040 <= D2 && D2 <= 65049 || 65072 <= D2 && D2 <= 65106 || 65108 <= D2 && D2 <= 65126 || 65128 <= D2 && D2 <= 65131 || 110592 <= D2 && D2 <= 110593 || 127488 <= D2 && D2 <= 127490 || 127504 <= D2 && D2 <= 127546 || 127552 <= D2 && D2 <= 127560 || 127568 <= D2 && D2 <= 127569 || 131072 <= D2 && D2 <= 194367 || 177984 <= D2 && D2 <= 196605 || 196608 <= D2 && D2 <= 262141 ? "W" : 32 <= D2 && D2 <= 126 || 162 <= D2 && D2 <= 163 || 165 <= D2 && D2 <= 166 || D2 == 172 || D2 == 175 || 10214 <= D2 && D2 <= 10221 || 10629 <= D2 && D2 <= 10630 ? "Na" : D2 == 161 || D2 == 164 || 167 <= D2 && D2 <= 168 || D2 == 170 || 173 <= D2 && D2 <= 174 || 176 <= D2 && D2 <= 180 || 182 <= D2 && D2 <= 186 || 188 <= D2 && D2 <= 191 || D2 == 198 || D2 == 208 || 215 <= D2 && D2 <= 216 || 222 <= D2 && D2 <= 225 || D2 == 230 || 232 <= D2 && D2 <= 234 || 236 <= D2 && D2 <= 237 || D2 == 240 || 242 <= D2 && D2 <= 243 || 247 <= D2 && D2 <= 250 || D2 == 252 || D2 == 254 || D2 == 257 || D2 == 273 || D2 == 275 || D2 == 283 || 294 <= D2 && D2 <= 295 || D2 == 299 || 305 <= D2 && D2 <= 307 || D2 == 312 || 319 <= D2 && D2 <= 322 || D2 == 324 || 328 <= D2 && D2 <= 331 || D2 == 333 || 338 <= D2 && D2 <= 339 || 358 <= D2 && D2 <= 359 || D2 == 363 || D2 == 462 || D2 == 464 || D2 == 466 || D2 == 468 || D2 == 470 || D2 == 472 || D2 == 474 || D2 == 476 || D2 == 593 || D2 == 609 || D2 == 708 || D2 == 711 || 713 <= D2 && D2 <= 715 || D2 == 717 || D2 == 720 || 728 <= D2 && D2 <= 731 || D2 == 733 || D2 == 735 || 768 <= D2 && D2 <= 879 || 913 <= D2 && D2 <= 929 || 931 <= D2 && D2 <= 937 || 945 <= D2 && D2 <= 961 || 963 <= D2 && D2 <= 969 || D2 == 1025 || 1040 <= D2 && D2 <= 1103 || D2 == 1105 || D2 == 8208 || 8211 <= D2 && D2 <= 8214 || 8216 <= D2 && D2 <= 8217 || 8220 <= D2 && D2 <= 8221 || 8224 <= D2 && D2 <= 8226 || 8228 <= D2 && D2 <= 8231 || D2 == 8240 || 8242 <= D2 && D2 <= 8243 || D2 == 8245 || D2 == 8251 || D2 == 8254 || D2 == 8308 || D2 == 8319 || 8321 <= D2 && D2 <= 8324 || D2 == 8364 || D2 == 8451 || D2 == 8453 || D2 == 8457 || D2 == 8467 || D2 == 8470 || 8481 <= D2 && D2 <= 8482 || D2 == 8486 || D2 == 8491 || 8531 <= D2 && D2 <= 8532 || 8539 <= D2 && D2 <= 8542 || 8544 <= D2 && D2 <= 8555 || 8560 <= D2 && D2 <= 8569 || D2 == 8585 || 8592 <= D2 && D2 <= 8601 || 8632 <= D2 && D2 <= 8633 || D2 == 8658 || D2 == 8660 || D2 == 8679 || D2 == 8704 || 8706 <= D2 && D2 <= 8707 || 8711 <= D2 && D2 <= 8712 || D2 == 8715 || D2 == 8719 || D2 == 8721 || D2 == 8725 || D2 == 8730 || 8733 <= D2 && D2 <= 8736 || D2 == 8739 || D2 == 8741 || 8743 <= D2 && D2 <= 8748 || D2 == 8750 || 8756 <= D2 && D2 <= 8759 || 8764 <= D2 && D2 <= 8765 || D2 == 8776 || D2 == 8780 || D2 == 8786 || 8800 <= D2 && D2 <= 8801 || 8804 <= D2 && D2 <= 8807 || 8810 <= D2 && D2 <= 8811 || 8814 <= D2 && D2 <= 8815 || 8834 <= D2 && D2 <= 8835 || 8838 <= D2 && D2 <= 8839 || D2 == 8853 || D2 == 8857 || D2 == 8869 || D2 == 8895 || D2 == 8978 || 9312 <= D2 && D2 <= 9449 || 9451 <= D2 && D2 <= 9547 || 9552 <= D2 && D2 <= 9587 || 9600 <= D2 && D2 <= 9615 || 9618 <= D2 && D2 <= 9621 || 9632 <= D2 && D2 <= 9633 || 9635 <= D2 && D2 <= 9641 || 9650 <= D2 && D2 <= 9651 || 9654 <= D2 && D2 <= 9655 || 9660 <= D2 && D2 <= 9661 || 9664 <= D2 && D2 <= 9665 || 9670 <= D2 && D2 <= 9672 || D2 == 9675 || 9678 <= D2 && D2 <= 9681 || 9698 <= D2 && D2 <= 9701 || D2 == 9711 || 9733 <= D2 && D2 <= 9734 || D2 == 9737 || 9742 <= D2 && D2 <= 9743 || 9748 <= D2 && D2 <= 9749 || D2 == 9756 || D2 == 9758 || D2 == 9792 || D2 == 9794 || 9824 <= D2 && D2 <= 9825 || 9827 <= D2 && D2 <= 9829 || 9831 <= D2 && D2 <= 9834 || 9836 <= D2 && D2 <= 9837 || D2 == 9839 || 9886 <= D2 && D2 <= 9887 || 9918 <= D2 && D2 <= 9919 || 9924 <= D2 && D2 <= 9933 || 9935 <= D2 && D2 <= 9953 || D2 == 9955 || 9960 <= D2 && D2 <= 9983 || D2 == 10045 || D2 == 10071 || 10102 <= D2 && D2 <= 10111 || 11093 <= D2 && D2 <= 11097 || 12872 <= D2 && D2 <= 12879 || 57344 <= D2 && D2 <= 63743 || 65024 <= D2 && D2 <= 65039 || D2 == 65533 || 127232 <= D2 && D2 <= 127242 || 127248 <= D2 && D2 <= 127277 || 127280 <= D2 && D2 <= 127337 || 127344 <= D2 && D2 <= 127386 || 917760 <= D2 && D2 <= 917999 || 983040 <= D2 && D2 <= 1048573 || 1048576 <= D2 && D2 <= 1114109 ? "A" : "N";
|
|
622
|
+
}, u2.characterLength = function(F2) {
|
|
623
|
+
var s = this.eastAsianWidth(F2);
|
|
624
|
+
return s == "F" || s == "W" || s == "A" ? 2 : 1;
|
|
625
|
+
};
|
|
626
|
+
function t(F2) {
|
|
627
|
+
return F2.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[^\uD800-\uDFFF]/g) || [];
|
|
628
|
+
}
|
|
629
|
+
u2.length = function(F2) {
|
|
630
|
+
for (var s = t(F2), i = 0, D2 = 0; D2 < s.length; D2++) i = i + this.characterLength(s[D2]);
|
|
631
|
+
return i;
|
|
632
|
+
}, u2.slice = function(F2, s, i) {
|
|
633
|
+
textLen = u2.length(F2), s = s || 0, i = i || 1, s < 0 && (s = textLen + s), i < 0 && (i = textLen + i);
|
|
634
|
+
for (var D2 = "", C2 = 0, n = t(F2), E = 0; E < n.length; E++) {
|
|
635
|
+
var a = n[E], o2 = u2.length(a);
|
|
636
|
+
if (C2 >= s - (o2 == 2 ? 1 : 0)) if (C2 + o2 <= i) D2 += a;
|
|
637
|
+
else break;
|
|
638
|
+
C2 += o2;
|
|
639
|
+
}
|
|
640
|
+
return D2;
|
|
641
|
+
};
|
|
642
|
+
})(W);
|
|
643
|
+
var tD = W.exports;
|
|
644
|
+
var eD = L(tD);
|
|
645
|
+
var FD = function() {
|
|
646
|
+
return /\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62(?:\uDB40\uDC77\uDB40\uDC6C\uDB40\uDC73|\uDB40\uDC73\uDB40\uDC63\uDB40\uDC74|\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67)\uDB40\uDC7F|(?:\uD83E\uDDD1\uD83C\uDFFF\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB-\uDFFE])|(?:\uD83E\uDDD1\uD83C\uDFFE\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB-\uDFFD\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFD\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFC\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB\uDFFD-\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFB\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFC-\uDFFF])|\uD83D\uDC68(?:\uD83C\uDFFB(?:\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFF]))|\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFC-\uDFFF])|[\u2695\u2696\u2708]\uFE0F|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD]))?|(?:\uD83C[\uDFFC-\uDFFF])\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFF]))|\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83D\uDC68|(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFE])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|(?:\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708])\uFE0F|\u200D(?:(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D[\uDC66\uDC67])|\uD83D[\uDC66\uDC67])|\uD83C\uDFFF|\uD83C\uDFFE|\uD83C\uDFFD|\uD83C\uDFFC)?|(?:\uD83D\uDC69(?:\uD83C\uDFFB\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69])|(?:\uD83C[\uDFFC-\uDFFF])\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69]))|\uD83E\uDDD1(?:\uD83C[\uDFFB-\uDFFF])\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1)(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|\uD83D\uDC69(?:\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFB\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD]))|\uD83E\uDDD1(?:\u200D(?:\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFB\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD]))|\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D[\uDC66\uDC67])|\uD83D\uDC69\u200D\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|(?:\uD83D\uDC41\uFE0F\u200D\uD83D\uDDE8|\uD83E\uDDD1(?:\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708]|\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708])|\uD83D\uDC69(?:\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708]|\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708])|\uD83D\uDE36\u200D\uD83C\uDF2B|\uD83C\uDFF3\uFE0F\u200D\u26A7|\uD83D\uDC3B\u200D\u2744|(?:(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC6F|\uD83E[\uDD3C\uDDDE\uDDDF])\u200D[\u2640\u2642]|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642]|\uD83C\uDFF4\u200D\u2620|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD])\u200D[\u2640\u2642]|[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u2328\u23CF\u23ED-\u23EF\u23F1\u23F2\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB\u25FC\u2600-\u2604\u260E\u2611\u2618\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u2692\u2694-\u2697\u2699\u269B\u269C\u26A0\u26A7\u26B0\u26B1\u26C8\u26CF\u26D1\u26D3\u26E9\u26F0\u26F1\u26F4\u26F7\u26F8\u2702\u2708\u2709\u270F\u2712\u2714\u2716\u271D\u2721\u2733\u2734\u2744\u2747\u2763\u27A1\u2934\u2935\u2B05-\u2B07\u3030\u303D\u3297\u3299]|\uD83C[\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37\uDF21\uDF24-\uDF2C\uDF36\uDF7D\uDF96\uDF97\uDF99-\uDF9B\uDF9E\uDF9F\uDFCD\uDFCE\uDFD4-\uDFDF\uDFF5\uDFF7]|\uD83D[\uDC3F\uDCFD\uDD49\uDD4A\uDD6F\uDD70\uDD73\uDD76-\uDD79\uDD87\uDD8A-\uDD8D\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA\uDECB\uDECD-\uDECF\uDEE0-\uDEE5\uDEE9\uDEF0\uDEF3])\uFE0F|\uD83C\uDFF3\uFE0F\u200D\uD83C\uDF08|\uD83D\uDC69\u200D\uD83D\uDC67|\uD83D\uDC69\u200D\uD83D\uDC66|\uD83D\uDE35\u200D\uD83D\uDCAB|\uD83D\uDE2E\u200D\uD83D\uDCA8|\uD83D\uDC15\u200D\uD83E\uDDBA|\uD83E\uDDD1(?:\uD83C\uDFFF|\uD83C\uDFFE|\uD83C\uDFFD|\uD83C\uDFFC|\uD83C\uDFFB)?|\uD83D\uDC69(?:\uD83C\uDFFF|\uD83C\uDFFE|\uD83C\uDFFD|\uD83C\uDFFC|\uD83C\uDFFB)?|\uD83C\uDDFD\uD83C\uDDF0|\uD83C\uDDF6\uD83C\uDDE6|\uD83C\uDDF4\uD83C\uDDF2|\uD83D\uDC08\u200D\u2B1B|\u2764\uFE0F\u200D(?:\uD83D\uDD25|\uD83E\uDE79)|\uD83D\uDC41\uFE0F|\uD83C\uDFF3\uFE0F|\uD83C\uDDFF(?:\uD83C[\uDDE6\uDDF2\uDDFC])|\uD83C\uDDFE(?:\uD83C[\uDDEA\uDDF9])|\uD83C\uDDFC(?:\uD83C[\uDDEB\uDDF8])|\uD83C\uDDFB(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA])|\uD83C\uDDFA(?:\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF])|\uD83C\uDDF9(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF])|\uD83C\uDDF8(?:\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF])|\uD83C\uDDF7(?:\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC])|\uD83C\uDDF5(?:\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE])|\uD83C\uDDF3(?:\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF])|\uD83C\uDDF2(?:\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF])|\uD83C\uDDF1(?:\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE])|\uD83C\uDDF0(?:\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF])|\uD83C\uDDEF(?:\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5])|\uD83C\uDDEE(?:\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9])|\uD83C\uDDED(?:\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA])|\uD83C\uDDEC(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE])|\uD83C\uDDEB(?:\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7])|\uD83C\uDDEA(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA])|\uD83C\uDDE9(?:\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF])|\uD83C\uDDE8(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF])|\uD83C\uDDE7(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF])|\uD83C\uDDE6(?:\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF])|[#\*0-9]\uFE0F\u20E3|\u2764\uFE0F|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])|\uD83C\uDFF4|(?:[\u270A\u270B]|\uD83C[\uDF85\uDFC2\uDFC7]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC]|\uD83E[\uDD0C\uDD0F\uDD18-\uDD1C\uDD1E\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5])(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u261D\u270C\u270D]|\uD83D[\uDD74\uDD90])(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])|[\u270A\u270B]|\uD83C[\uDF85\uDFC2\uDFC7]|\uD83D[\uDC08\uDC15\uDC3B\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE2E\uDE35\uDE36\uDE4C\uDE4F\uDEC0\uDECC]|\uD83E[\uDD0C\uDD0F\uDD18-\uDD1C\uDD1E\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5]|\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD]|\uD83D\uDC6F|\uD83E[\uDD3C\uDDDE\uDDDF]|[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF84\uDF86-\uDF93\uDFA0-\uDFC1\uDFC5\uDFC6\uDFC8\uDFC9\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC07\uDC09-\uDC14\uDC16-\uDC3A\uDC3C-\uDC3E\uDC40\uDC44\uDC45\uDC51-\uDC65\uDC6A\uDC79-\uDC7B\uDC7D-\uDC80\uDC84\uDC88-\uDC8E\uDC90\uDC92-\uDCA9\uDCAB-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDDA4\uDDFB-\uDE2D\uDE2F-\uDE34\uDE37-\uDE44\uDE48-\uDE4A\uDE80-\uDEA2\uDEA4-\uDEB3\uDEB7-\uDEBF\uDEC1-\uDEC5\uDED0-\uDED2\uDED5-\uDED7\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD0D\uDD0E\uDD10-\uDD17\uDD1D\uDD20-\uDD25\uDD27-\uDD2F\uDD3A\uDD3F-\uDD45\uDD47-\uDD76\uDD78\uDD7A-\uDDB4\uDDB7\uDDBA\uDDBC-\uDDCB\uDDD0\uDDE0-\uDDFF\uDE70-\uDE74\uDE78-\uDE7A\uDE80-\uDE86\uDE90-\uDEA8\uDEB0-\uDEB6\uDEC0-\uDEC2\uDED0-\uDED6]|(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDED5-\uDED7\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDD78\uDD7A-\uDDCB\uDDCD-\uDDFF\uDE70-\uDE74\uDE78-\uDE7A\uDE80-\uDE86\uDE90-\uDEA8\uDEB0-\uDEB6\uDEC0-\uDEC2\uDED0-\uDED6])|(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26A7\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDED5-\uDED7\uDEE0-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDD78\uDD7A-\uDDCB\uDDCD-\uDDFF\uDE70-\uDE74\uDE78-\uDE7A\uDE80-\uDE86\uDE90-\uDEA8\uDEB0-\uDEB6\uDEC0-\uDEC2\uDED0-\uDED6])\uFE0F|(?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85\uDFC2-\uDFC4\uDFC7\uDFCA-\uDFCC]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66-\uDC78\uDC7C\uDC81-\uDC83\uDC85-\uDC87\uDC8F\uDC91\uDCAA\uDD74\uDD75\uDD7A\uDD90\uDD95\uDD96\uDE45-\uDE47\uDE4B-\uDE4F\uDEA3\uDEB4-\uDEB6\uDEC0\uDECC]|\uD83E[\uDD0C\uDD0F\uDD18-\uDD1F\uDD26\uDD30-\uDD39\uDD3C-\uDD3E\uDD77\uDDB5\uDDB6\uDDB8\uDDB9\uDDBB\uDDCD-\uDDCF\uDDD1-\uDDDD])/g;
|
|
647
|
+
};
|
|
648
|
+
var sD = L(FD);
|
|
649
|
+
function p(e2, u2 = {}) {
|
|
650
|
+
if (typeof e2 != "string" || e2.length === 0 || (u2 = { ambiguousIsNarrow: true, ...u2 }, e2 = P(e2), e2.length === 0)) return 0;
|
|
651
|
+
e2 = e2.replace(sD(), " ");
|
|
652
|
+
const t = u2.ambiguousIsNarrow ? 1 : 2;
|
|
653
|
+
let F2 = 0;
|
|
654
|
+
for (const s of e2) {
|
|
655
|
+
const i = s.codePointAt(0);
|
|
656
|
+
if (i <= 31 || i >= 127 && i <= 159 || i >= 768 && i <= 879) continue;
|
|
657
|
+
switch (eD.eastAsianWidth(s)) {
|
|
658
|
+
case "F":
|
|
659
|
+
case "W":
|
|
660
|
+
F2 += 2;
|
|
661
|
+
break;
|
|
662
|
+
case "A":
|
|
663
|
+
F2 += t;
|
|
664
|
+
break;
|
|
665
|
+
default:
|
|
666
|
+
F2 += 1;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return F2;
|
|
670
|
+
}
|
|
671
|
+
var w = 10;
|
|
672
|
+
var N = (e2 = 0) => (u2) => `\x1B[${u2 + e2}m`;
|
|
673
|
+
var I = (e2 = 0) => (u2) => `\x1B[${38 + e2};5;${u2}m`;
|
|
674
|
+
var R = (e2 = 0) => (u2, t, F2) => `\x1B[${38 + e2};2;${u2};${t};${F2}m`;
|
|
675
|
+
var r = { modifier: { reset: [0, 0], bold: [1, 22], dim: [2, 22], italic: [3, 23], underline: [4, 24], overline: [53, 55], inverse: [7, 27], hidden: [8, 28], strikethrough: [9, 29] }, color: { black: [30, 39], red: [31, 39], green: [32, 39], yellow: [33, 39], blue: [34, 39], magenta: [35, 39], cyan: [36, 39], white: [37, 39], blackBright: [90, 39], gray: [90, 39], grey: [90, 39], redBright: [91, 39], greenBright: [92, 39], yellowBright: [93, 39], blueBright: [94, 39], magentaBright: [95, 39], cyanBright: [96, 39], whiteBright: [97, 39] }, bgColor: { bgBlack: [40, 49], bgRed: [41, 49], bgGreen: [42, 49], bgYellow: [43, 49], bgBlue: [44, 49], bgMagenta: [45, 49], bgCyan: [46, 49], bgWhite: [47, 49], bgBlackBright: [100, 49], bgGray: [100, 49], bgGrey: [100, 49], bgRedBright: [101, 49], bgGreenBright: [102, 49], bgYellowBright: [103, 49], bgBlueBright: [104, 49], bgMagentaBright: [105, 49], bgCyanBright: [106, 49], bgWhiteBright: [107, 49] } };
|
|
676
|
+
Object.keys(r.modifier);
|
|
677
|
+
var iD = Object.keys(r.color);
|
|
678
|
+
var CD = Object.keys(r.bgColor);
|
|
679
|
+
[...iD, ...CD];
|
|
680
|
+
function rD() {
|
|
681
|
+
const e2 = /* @__PURE__ */ new Map();
|
|
682
|
+
for (const [u2, t] of Object.entries(r)) {
|
|
683
|
+
for (const [F2, s] of Object.entries(t)) r[F2] = { open: `\x1B[${s[0]}m`, close: `\x1B[${s[1]}m` }, t[F2] = r[F2], e2.set(s[0], s[1]);
|
|
684
|
+
Object.defineProperty(r, u2, { value: t, enumerable: false });
|
|
685
|
+
}
|
|
686
|
+
return Object.defineProperty(r, "codes", { value: e2, enumerable: false }), r.color.close = "\x1B[39m", r.bgColor.close = "\x1B[49m", r.color.ansi = N(), r.color.ansi256 = I(), r.color.ansi16m = R(), r.bgColor.ansi = N(w), r.bgColor.ansi256 = I(w), r.bgColor.ansi16m = R(w), Object.defineProperties(r, { rgbToAnsi256: { value: (u2, t, F2) => u2 === t && t === F2 ? u2 < 8 ? 16 : u2 > 248 ? 231 : Math.round((u2 - 8) / 247 * 24) + 232 : 16 + 36 * Math.round(u2 / 255 * 5) + 6 * Math.round(t / 255 * 5) + Math.round(F2 / 255 * 5), enumerable: false }, hexToRgb: { value: (u2) => {
|
|
687
|
+
const t = /[a-f\d]{6}|[a-f\d]{3}/i.exec(u2.toString(16));
|
|
688
|
+
if (!t) return [0, 0, 0];
|
|
689
|
+
let [F2] = t;
|
|
690
|
+
F2.length === 3 && (F2 = [...F2].map((i) => i + i).join(""));
|
|
691
|
+
const s = Number.parseInt(F2, 16);
|
|
692
|
+
return [s >> 16 & 255, s >> 8 & 255, s & 255];
|
|
693
|
+
}, enumerable: false }, hexToAnsi256: { value: (u2) => r.rgbToAnsi256(...r.hexToRgb(u2)), enumerable: false }, ansi256ToAnsi: { value: (u2) => {
|
|
694
|
+
if (u2 < 8) return 30 + u2;
|
|
695
|
+
if (u2 < 16) return 90 + (u2 - 8);
|
|
696
|
+
let t, F2, s;
|
|
697
|
+
if (u2 >= 232) t = ((u2 - 232) * 10 + 8) / 255, F2 = t, s = t;
|
|
698
|
+
else {
|
|
699
|
+
u2 -= 16;
|
|
700
|
+
const C2 = u2 % 36;
|
|
701
|
+
t = Math.floor(u2 / 36) / 5, F2 = Math.floor(C2 / 6) / 5, s = C2 % 6 / 5;
|
|
702
|
+
}
|
|
703
|
+
const i = Math.max(t, F2, s) * 2;
|
|
704
|
+
if (i === 0) return 30;
|
|
705
|
+
let D2 = 30 + (Math.round(s) << 2 | Math.round(F2) << 1 | Math.round(t));
|
|
706
|
+
return i === 2 && (D2 += 60), D2;
|
|
707
|
+
}, enumerable: false }, rgbToAnsi: { value: (u2, t, F2) => r.ansi256ToAnsi(r.rgbToAnsi256(u2, t, F2)), enumerable: false }, hexToAnsi: { value: (u2) => r.ansi256ToAnsi(r.hexToAnsi256(u2)), enumerable: false } }), r;
|
|
708
|
+
}
|
|
709
|
+
var ED = rD();
|
|
710
|
+
var d = /* @__PURE__ */ new Set(["\x1B", "\x9B"]);
|
|
711
|
+
var oD = 39;
|
|
712
|
+
var y = "\x07";
|
|
713
|
+
var V = "[";
|
|
714
|
+
var nD = "]";
|
|
715
|
+
var G = "m";
|
|
716
|
+
var _ = `${nD}8;;`;
|
|
717
|
+
var z = (e2) => `${d.values().next().value}${V}${e2}${G}`;
|
|
718
|
+
var K = (e2) => `${d.values().next().value}${_}${e2}${y}`;
|
|
719
|
+
var aD = (e2) => e2.split(" ").map((u2) => p(u2));
|
|
720
|
+
var k = (e2, u2, t) => {
|
|
721
|
+
const F2 = [...u2];
|
|
722
|
+
let s = false, i = false, D2 = p(P(e2[e2.length - 1]));
|
|
723
|
+
for (const [C2, n] of F2.entries()) {
|
|
724
|
+
const E = p(n);
|
|
725
|
+
if (D2 + E <= t ? e2[e2.length - 1] += n : (e2.push(n), D2 = 0), d.has(n) && (s = true, i = F2.slice(C2 + 1).join("").startsWith(_)), s) {
|
|
726
|
+
i ? n === y && (s = false, i = false) : n === G && (s = false);
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
D2 += E, D2 === t && C2 < F2.length - 1 && (e2.push(""), D2 = 0);
|
|
730
|
+
}
|
|
731
|
+
!D2 && e2[e2.length - 1].length > 0 && e2.length > 1 && (e2[e2.length - 2] += e2.pop());
|
|
732
|
+
};
|
|
733
|
+
var hD = (e2) => {
|
|
734
|
+
const u2 = e2.split(" ");
|
|
735
|
+
let t = u2.length;
|
|
736
|
+
for (; t > 0 && !(p(u2[t - 1]) > 0); ) t--;
|
|
737
|
+
return t === u2.length ? e2 : u2.slice(0, t).join(" ") + u2.slice(t).join("");
|
|
738
|
+
};
|
|
739
|
+
var lD = (e2, u2, t = {}) => {
|
|
740
|
+
if (t.trim !== false && e2.trim() === "") return "";
|
|
741
|
+
let F2 = "", s, i;
|
|
742
|
+
const D2 = aD(e2);
|
|
743
|
+
let C2 = [""];
|
|
744
|
+
for (const [E, a] of e2.split(" ").entries()) {
|
|
745
|
+
t.trim !== false && (C2[C2.length - 1] = C2[C2.length - 1].trimStart());
|
|
746
|
+
let o2 = p(C2[C2.length - 1]);
|
|
747
|
+
if (E !== 0 && (o2 >= u2 && (t.wordWrap === false || t.trim === false) && (C2.push(""), o2 = 0), (o2 > 0 || t.trim === false) && (C2[C2.length - 1] += " ", o2++)), t.hard && D2[E] > u2) {
|
|
748
|
+
const c = u2 - o2, f = 1 + Math.floor((D2[E] - c - 1) / u2);
|
|
749
|
+
Math.floor((D2[E] - 1) / u2) < f && C2.push(""), k(C2, a, u2);
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
if (o2 + D2[E] > u2 && o2 > 0 && D2[E] > 0) {
|
|
753
|
+
if (t.wordWrap === false && o2 < u2) {
|
|
754
|
+
k(C2, a, u2);
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
C2.push("");
|
|
758
|
+
}
|
|
759
|
+
if (o2 + D2[E] > u2 && t.wordWrap === false) {
|
|
760
|
+
k(C2, a, u2);
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
C2[C2.length - 1] += a;
|
|
764
|
+
}
|
|
765
|
+
t.trim !== false && (C2 = C2.map((E) => hD(E)));
|
|
766
|
+
const n = [...C2.join(`
|
|
767
|
+
`)];
|
|
768
|
+
for (const [E, a] of n.entries()) {
|
|
769
|
+
if (F2 += a, d.has(a)) {
|
|
770
|
+
const { groups: c } = new RegExp(`(?:\\${V}(?<code>\\d+)m|\\${_}(?<uri>.*)${y})`).exec(n.slice(E).join("")) || { groups: {} };
|
|
771
|
+
if (c.code !== void 0) {
|
|
772
|
+
const f = Number.parseFloat(c.code);
|
|
773
|
+
s = f === oD ? void 0 : f;
|
|
774
|
+
} else c.uri !== void 0 && (i = c.uri.length === 0 ? void 0 : c.uri);
|
|
775
|
+
}
|
|
776
|
+
const o2 = ED.codes.get(Number(s));
|
|
777
|
+
n[E + 1] === `
|
|
778
|
+
` ? (i && (F2 += K("")), s && o2 && (F2 += z(o2))) : a === `
|
|
779
|
+
` && (s && o2 && (F2 += z(s)), i && (F2 += K(i)));
|
|
780
|
+
}
|
|
781
|
+
return F2;
|
|
782
|
+
};
|
|
783
|
+
function Y(e2, u2, t) {
|
|
784
|
+
return String(e2).normalize().replace(/\r\n/g, `
|
|
785
|
+
`).split(`
|
|
786
|
+
`).map((F2) => lD(F2, u2, t)).join(`
|
|
787
|
+
`);
|
|
788
|
+
}
|
|
789
|
+
var xD = ["up", "down", "left", "right", "space", "enter", "cancel"];
|
|
790
|
+
var B = { actions: new Set(xD), aliases: /* @__PURE__ */ new Map([["k", "up"], ["j", "down"], ["h", "left"], ["l", "right"], ["", "cancel"], ["escape", "cancel"]]) };
|
|
791
|
+
function $(e2, u2) {
|
|
792
|
+
if (typeof e2 == "string") return B.aliases.get(e2) === u2;
|
|
793
|
+
for (const t of e2) if (t !== void 0 && $(t, u2)) return true;
|
|
794
|
+
return false;
|
|
795
|
+
}
|
|
796
|
+
function BD(e2, u2) {
|
|
797
|
+
if (e2 === u2) return;
|
|
798
|
+
const t = e2.split(`
|
|
799
|
+
`), F2 = u2.split(`
|
|
800
|
+
`), s = [];
|
|
801
|
+
for (let i = 0; i < Math.max(t.length, F2.length); i++) t[i] !== F2[i] && s.push(i);
|
|
802
|
+
return s;
|
|
803
|
+
}
|
|
804
|
+
var AD = globalThis.process.platform.startsWith("win");
|
|
805
|
+
var S = Symbol("clack:cancel");
|
|
806
|
+
function pD(e2) {
|
|
807
|
+
return e2 === S;
|
|
808
|
+
}
|
|
809
|
+
function m(e2, u2) {
|
|
810
|
+
const t = e2;
|
|
811
|
+
t.isTTY && t.setRawMode(u2);
|
|
812
|
+
}
|
|
813
|
+
var gD = Object.defineProperty;
|
|
814
|
+
var vD = (e2, u2, t) => u2 in e2 ? gD(e2, u2, { enumerable: true, configurable: true, writable: true, value: t }) : e2[u2] = t;
|
|
815
|
+
var h = (e2, u2, t) => (vD(e2, typeof u2 != "symbol" ? u2 + "" : u2, t), t);
|
|
816
|
+
var x = class {
|
|
817
|
+
constructor(u2, t = true) {
|
|
818
|
+
h(this, "input"), h(this, "output"), h(this, "_abortSignal"), h(this, "rl"), h(this, "opts"), h(this, "_render"), h(this, "_track", false), h(this, "_prevFrame", ""), h(this, "_subscribers", /* @__PURE__ */ new Map()), h(this, "_cursor", 0), h(this, "state", "initial"), h(this, "error", ""), h(this, "value");
|
|
819
|
+
const { input: F2 = j, output: s = M, render: i, signal: D2, ...C2 } = u2;
|
|
820
|
+
this.opts = C2, this.onKeypress = this.onKeypress.bind(this), this.close = this.close.bind(this), this.render = this.render.bind(this), this._render = i.bind(this), this._track = t, this._abortSignal = D2, this.input = F2, this.output = s;
|
|
821
|
+
}
|
|
822
|
+
unsubscribe() {
|
|
823
|
+
this._subscribers.clear();
|
|
824
|
+
}
|
|
825
|
+
setSubscriber(u2, t) {
|
|
826
|
+
const F2 = this._subscribers.get(u2) ?? [];
|
|
827
|
+
F2.push(t), this._subscribers.set(u2, F2);
|
|
828
|
+
}
|
|
829
|
+
on(u2, t) {
|
|
830
|
+
this.setSubscriber(u2, { cb: t });
|
|
831
|
+
}
|
|
832
|
+
once(u2, t) {
|
|
833
|
+
this.setSubscriber(u2, { cb: t, once: true });
|
|
834
|
+
}
|
|
835
|
+
emit(u2, ...t) {
|
|
836
|
+
const F2 = this._subscribers.get(u2) ?? [], s = [];
|
|
837
|
+
for (const i of F2) i.cb(...t), i.once && s.push(() => F2.splice(F2.indexOf(i), 1));
|
|
838
|
+
for (const i of s) i();
|
|
839
|
+
}
|
|
840
|
+
prompt() {
|
|
841
|
+
return new Promise((u2, t) => {
|
|
842
|
+
if (this._abortSignal) {
|
|
843
|
+
if (this._abortSignal.aborted) return this.state = "cancel", this.close(), u2(S);
|
|
844
|
+
this._abortSignal.addEventListener("abort", () => {
|
|
845
|
+
this.state = "cancel", this.close();
|
|
846
|
+
}, { once: true });
|
|
847
|
+
}
|
|
848
|
+
const F2 = new X();
|
|
849
|
+
F2._write = (s, i, D2) => {
|
|
850
|
+
this._track && (this.value = this.rl?.line.replace(/\t/g, ""), this._cursor = this.rl?.cursor ?? 0, this.emit("value", this.value)), D2();
|
|
851
|
+
}, this.input.pipe(F2), this.rl = O.createInterface({ input: this.input, output: F2, tabSize: 2, prompt: "", escapeCodeTimeout: 50, terminal: true }), O.emitKeypressEvents(this.input, this.rl), this.rl.prompt(), this.opts.initialValue !== void 0 && this._track && this.rl.write(this.opts.initialValue), this.input.on("keypress", this.onKeypress), m(this.input, true), this.output.on("resize", this.render), this.render(), this.once("submit", () => {
|
|
852
|
+
this.output.write(import_sisteransi.cursor.show), this.output.off("resize", this.render), m(this.input, false), u2(this.value);
|
|
853
|
+
}), this.once("cancel", () => {
|
|
854
|
+
this.output.write(import_sisteransi.cursor.show), this.output.off("resize", this.render), m(this.input, false), u2(S);
|
|
855
|
+
});
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
onKeypress(u2, t) {
|
|
859
|
+
if (this.state === "error" && (this.state = "active"), t?.name && (!this._track && B.aliases.has(t.name) && this.emit("cursor", B.aliases.get(t.name)), B.actions.has(t.name) && this.emit("cursor", t.name)), u2 && (u2.toLowerCase() === "y" || u2.toLowerCase() === "n") && this.emit("confirm", u2.toLowerCase() === "y"), u2 === " " && this.opts.placeholder && (this.value || (this.rl?.write(this.opts.placeholder), this.emit("value", this.opts.placeholder))), u2 && this.emit("key", u2.toLowerCase()), t?.name === "return") {
|
|
860
|
+
if (this.opts.validate) {
|
|
861
|
+
const F2 = this.opts.validate(this.value);
|
|
862
|
+
F2 && (this.error = F2 instanceof Error ? F2.message : F2, this.state = "error", this.rl?.write(this.value));
|
|
863
|
+
}
|
|
864
|
+
this.state !== "error" && (this.state = "submit");
|
|
865
|
+
}
|
|
866
|
+
$([u2, t?.name, t?.sequence], "cancel") && (this.state = "cancel"), (this.state === "submit" || this.state === "cancel") && this.emit("finalize"), this.render(), (this.state === "submit" || this.state === "cancel") && this.close();
|
|
867
|
+
}
|
|
868
|
+
close() {
|
|
869
|
+
this.input.unpipe(), this.input.removeListener("keypress", this.onKeypress), this.output.write(`
|
|
870
|
+
`), m(this.input, false), this.rl?.close(), this.rl = void 0, this.emit(`${this.state}`, this.value), this.unsubscribe();
|
|
871
|
+
}
|
|
872
|
+
restoreCursor() {
|
|
873
|
+
const u2 = Y(this._prevFrame, process.stdout.columns, { hard: true }).split(`
|
|
874
|
+
`).length - 1;
|
|
875
|
+
this.output.write(import_sisteransi.cursor.move(-999, u2 * -1));
|
|
876
|
+
}
|
|
877
|
+
render() {
|
|
878
|
+
const u2 = Y(this._render(this) ?? "", process.stdout.columns, { hard: true });
|
|
879
|
+
if (u2 !== this._prevFrame) {
|
|
880
|
+
if (this.state === "initial") this.output.write(import_sisteransi.cursor.hide);
|
|
881
|
+
else {
|
|
882
|
+
const t = BD(this._prevFrame, u2);
|
|
883
|
+
if (this.restoreCursor(), t && t?.length === 1) {
|
|
884
|
+
const F2 = t[0];
|
|
885
|
+
this.output.write(import_sisteransi.cursor.move(0, F2)), this.output.write(import_sisteransi.erase.lines(1));
|
|
886
|
+
const s = u2.split(`
|
|
887
|
+
`);
|
|
888
|
+
this.output.write(s[F2]), this._prevFrame = u2, this.output.write(import_sisteransi.cursor.move(0, s.length - F2 - 1));
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
if (t && t?.length > 1) {
|
|
892
|
+
const F2 = t[0];
|
|
893
|
+
this.output.write(import_sisteransi.cursor.move(0, F2)), this.output.write(import_sisteransi.erase.down());
|
|
894
|
+
const s = u2.split(`
|
|
895
|
+
`).slice(F2);
|
|
896
|
+
this.output.write(s.join(`
|
|
897
|
+
`)), this._prevFrame = u2;
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
this.output.write(import_sisteransi.erase.down());
|
|
901
|
+
}
|
|
902
|
+
this.output.write(u2), this.state === "initial" && (this.state = "active"), this._prevFrame = u2;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
var dD = class extends x {
|
|
907
|
+
get cursor() {
|
|
908
|
+
return this.value ? 0 : 1;
|
|
909
|
+
}
|
|
910
|
+
get _value() {
|
|
911
|
+
return this.cursor === 0;
|
|
912
|
+
}
|
|
913
|
+
constructor(u2) {
|
|
914
|
+
super(u2, false), this.value = !!u2.initialValue, this.on("value", () => {
|
|
915
|
+
this.value = this._value;
|
|
916
|
+
}), this.on("confirm", (t) => {
|
|
917
|
+
this.output.write(import_sisteransi.cursor.move(0, -1)), this.value = t, this.state = "submit", this.close();
|
|
918
|
+
}), this.on("cursor", () => {
|
|
919
|
+
this.value = !this.value;
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
var A;
|
|
924
|
+
A = /* @__PURE__ */ new WeakMap();
|
|
925
|
+
var OD = Object.defineProperty;
|
|
926
|
+
var PD = (e2, u2, t) => u2 in e2 ? OD(e2, u2, { enumerable: true, configurable: true, writable: true, value: t }) : e2[u2] = t;
|
|
927
|
+
var J = (e2, u2, t) => (PD(e2, typeof u2 != "symbol" ? u2 + "" : u2, t), t);
|
|
928
|
+
var LD = class extends x {
|
|
929
|
+
constructor(u2) {
|
|
930
|
+
super(u2, false), J(this, "options"), J(this, "cursor", 0), this.options = u2.options, this.cursor = this.options.findIndex(({ value: t }) => t === u2.initialValue), this.cursor === -1 && (this.cursor = 0), this.changeValue(), this.on("cursor", (t) => {
|
|
931
|
+
switch (t) {
|
|
932
|
+
case "left":
|
|
933
|
+
case "up":
|
|
934
|
+
this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1;
|
|
935
|
+
break;
|
|
936
|
+
case "down":
|
|
937
|
+
case "right":
|
|
938
|
+
this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1;
|
|
939
|
+
break;
|
|
940
|
+
}
|
|
941
|
+
this.changeValue();
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
get _value() {
|
|
945
|
+
return this.options[this.cursor];
|
|
946
|
+
}
|
|
947
|
+
changeValue() {
|
|
948
|
+
this.value = this._value.value;
|
|
949
|
+
}
|
|
950
|
+
};
|
|
951
|
+
var RD = class extends x {
|
|
952
|
+
get valueWithCursor() {
|
|
953
|
+
if (this.state === "submit") return this.value;
|
|
954
|
+
if (this.cursor >= this.value.length) return `${this.value}\u2588`;
|
|
955
|
+
const u2 = this.value.slice(0, this.cursor), [t, ...F2] = this.value.slice(this.cursor);
|
|
956
|
+
return `${u2}${import_picocolors.default.inverse(t)}${F2.join("")}`;
|
|
957
|
+
}
|
|
958
|
+
get cursor() {
|
|
959
|
+
return this._cursor;
|
|
960
|
+
}
|
|
961
|
+
constructor(u2) {
|
|
962
|
+
super(u2), this.on("finalize", () => {
|
|
963
|
+
this.value || (this.value = u2.defaultValue);
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
|
|
968
|
+
// node_modules/@clack/prompts/dist/index.mjs
|
|
969
|
+
var import_picocolors2 = __toESM(require_picocolors(), 1);
|
|
970
|
+
var import_sisteransi2 = __toESM(require_src(), 1);
|
|
971
|
+
import y2 from "node:process";
|
|
972
|
+
function ce() {
|
|
973
|
+
return y2.platform !== "win32" ? y2.env.TERM !== "linux" : !!y2.env.CI || !!y2.env.WT_SESSION || !!y2.env.TERMINUS_SUBLIME || y2.env.ConEmuTask === "{cmd::Cmder}" || y2.env.TERM_PROGRAM === "Terminus-Sublime" || y2.env.TERM_PROGRAM === "vscode" || y2.env.TERM === "xterm-256color" || y2.env.TERM === "alacritty" || y2.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
|
|
974
|
+
}
|
|
975
|
+
var V2 = ce();
|
|
976
|
+
var u = (t, n) => V2 ? t : n;
|
|
977
|
+
var le = u("\u25C6", "*");
|
|
978
|
+
var L2 = u("\u25A0", "x");
|
|
979
|
+
var W2 = u("\u25B2", "x");
|
|
980
|
+
var C = u("\u25C7", "o");
|
|
981
|
+
var ue = u("\u250C", "T");
|
|
982
|
+
var o = u("\u2502", "|");
|
|
983
|
+
var d2 = u("\u2514", "\u2014");
|
|
984
|
+
var k2 = u("\u25CF", ">");
|
|
985
|
+
var P2 = u("\u25CB", " ");
|
|
986
|
+
var A2 = u("\u25FB", "[\u2022]");
|
|
987
|
+
var T = u("\u25FC", "[+]");
|
|
988
|
+
var F = u("\u25FB", "[ ]");
|
|
989
|
+
var $e = u("\u25AA", "\u2022");
|
|
990
|
+
var _2 = u("\u2500", "-");
|
|
991
|
+
var me = u("\u256E", "+");
|
|
992
|
+
var de = u("\u251C", "+");
|
|
993
|
+
var pe = u("\u256F", "+");
|
|
994
|
+
var q = u("\u25CF", "\u2022");
|
|
995
|
+
var D = u("\u25C6", "*");
|
|
996
|
+
var U = u("\u25B2", "!");
|
|
997
|
+
var K2 = u("\u25A0", "x");
|
|
998
|
+
var b2 = (t) => {
|
|
999
|
+
switch (t) {
|
|
1000
|
+
case "initial":
|
|
1001
|
+
case "active":
|
|
1002
|
+
return import_picocolors2.default.cyan(le);
|
|
1003
|
+
case "cancel":
|
|
1004
|
+
return import_picocolors2.default.red(L2);
|
|
1005
|
+
case "error":
|
|
1006
|
+
return import_picocolors2.default.yellow(W2);
|
|
1007
|
+
case "submit":
|
|
1008
|
+
return import_picocolors2.default.green(C);
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
var G2 = (t) => {
|
|
1012
|
+
const { cursor: n, options: r2, style: i } = t, s = t.maxItems ?? Number.POSITIVE_INFINITY, c = Math.max(process.stdout.rows - 4, 0), a = Math.min(c, Math.max(s, 5));
|
|
1013
|
+
let l2 = 0;
|
|
1014
|
+
n >= l2 + a - 3 ? l2 = Math.max(Math.min(n - a + 3, r2.length - a), 0) : n < l2 + 2 && (l2 = Math.max(n - 2, 0));
|
|
1015
|
+
const $2 = a < r2.length && l2 > 0, g = a < r2.length && l2 + a < r2.length;
|
|
1016
|
+
return r2.slice(l2, l2 + a).map((p2, v2, f) => {
|
|
1017
|
+
const j2 = v2 === 0 && $2, E = v2 === f.length - 1 && g;
|
|
1018
|
+
return j2 || E ? import_picocolors2.default.dim("...") : i(p2, v2 + l2 === n);
|
|
1019
|
+
});
|
|
1020
|
+
};
|
|
1021
|
+
var he = (t) => new RD({ validate: t.validate, placeholder: t.placeholder, defaultValue: t.defaultValue, initialValue: t.initialValue, render() {
|
|
1022
|
+
const n = `${import_picocolors2.default.gray(o)}
|
|
1023
|
+
${b2(this.state)} ${t.message}
|
|
1024
|
+
`, r2 = t.placeholder ? import_picocolors2.default.inverse(t.placeholder[0]) + import_picocolors2.default.dim(t.placeholder.slice(1)) : import_picocolors2.default.inverse(import_picocolors2.default.hidden("_")), i = this.value ? this.valueWithCursor : r2;
|
|
1025
|
+
switch (this.state) {
|
|
1026
|
+
case "error":
|
|
1027
|
+
return `${n.trim()}
|
|
1028
|
+
${import_picocolors2.default.yellow(o)} ${i}
|
|
1029
|
+
${import_picocolors2.default.yellow(d2)} ${import_picocolors2.default.yellow(this.error)}
|
|
1030
|
+
`;
|
|
1031
|
+
case "submit":
|
|
1032
|
+
return `${n}${import_picocolors2.default.gray(o)} ${import_picocolors2.default.dim(this.value || t.placeholder)}`;
|
|
1033
|
+
case "cancel":
|
|
1034
|
+
return `${n}${import_picocolors2.default.gray(o)} ${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(this.value ?? ""))}${this.value?.trim() ? `
|
|
1035
|
+
${import_picocolors2.default.gray(o)}` : ""}`;
|
|
1036
|
+
default:
|
|
1037
|
+
return `${n}${import_picocolors2.default.cyan(o)} ${i}
|
|
1038
|
+
${import_picocolors2.default.cyan(d2)}
|
|
1039
|
+
`;
|
|
1040
|
+
}
|
|
1041
|
+
} }).prompt();
|
|
1042
|
+
var ye = (t) => {
|
|
1043
|
+
const n = t.active ?? "Yes", r2 = t.inactive ?? "No";
|
|
1044
|
+
return new dD({ active: n, inactive: r2, initialValue: t.initialValue ?? true, render() {
|
|
1045
|
+
const i = `${import_picocolors2.default.gray(o)}
|
|
1046
|
+
${b2(this.state)} ${t.message}
|
|
1047
|
+
`, s = this.value ? n : r2;
|
|
1048
|
+
switch (this.state) {
|
|
1049
|
+
case "submit":
|
|
1050
|
+
return `${i}${import_picocolors2.default.gray(o)} ${import_picocolors2.default.dim(s)}`;
|
|
1051
|
+
case "cancel":
|
|
1052
|
+
return `${i}${import_picocolors2.default.gray(o)} ${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(s))}
|
|
1053
|
+
${import_picocolors2.default.gray(o)}`;
|
|
1054
|
+
default:
|
|
1055
|
+
return `${i}${import_picocolors2.default.cyan(o)} ${this.value ? `${import_picocolors2.default.green(k2)} ${n}` : `${import_picocolors2.default.dim(P2)} ${import_picocolors2.default.dim(n)}`} ${import_picocolors2.default.dim("/")} ${this.value ? `${import_picocolors2.default.dim(P2)} ${import_picocolors2.default.dim(r2)}` : `${import_picocolors2.default.green(k2)} ${r2}`}
|
|
1056
|
+
${import_picocolors2.default.cyan(d2)}
|
|
1057
|
+
`;
|
|
1058
|
+
}
|
|
1059
|
+
} }).prompt();
|
|
1060
|
+
};
|
|
1061
|
+
var ve = (t) => {
|
|
1062
|
+
const n = (r2, i) => {
|
|
1063
|
+
const s = r2.label ?? String(r2.value);
|
|
1064
|
+
switch (i) {
|
|
1065
|
+
case "selected":
|
|
1066
|
+
return `${import_picocolors2.default.dim(s)}`;
|
|
1067
|
+
case "active":
|
|
1068
|
+
return `${import_picocolors2.default.green(k2)} ${s} ${r2.hint ? import_picocolors2.default.dim(`(${r2.hint})`) : ""}`;
|
|
1069
|
+
case "cancelled":
|
|
1070
|
+
return `${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(s))}`;
|
|
1071
|
+
default:
|
|
1072
|
+
return `${import_picocolors2.default.dim(P2)} ${import_picocolors2.default.dim(s)}`;
|
|
1073
|
+
}
|
|
1074
|
+
};
|
|
1075
|
+
return new LD({ options: t.options, initialValue: t.initialValue, render() {
|
|
1076
|
+
const r2 = `${import_picocolors2.default.gray(o)}
|
|
1077
|
+
${b2(this.state)} ${t.message}
|
|
1078
|
+
`;
|
|
1079
|
+
switch (this.state) {
|
|
1080
|
+
case "submit":
|
|
1081
|
+
return `${r2}${import_picocolors2.default.gray(o)} ${n(this.options[this.cursor], "selected")}`;
|
|
1082
|
+
case "cancel":
|
|
1083
|
+
return `${r2}${import_picocolors2.default.gray(o)} ${n(this.options[this.cursor], "cancelled")}
|
|
1084
|
+
${import_picocolors2.default.gray(o)}`;
|
|
1085
|
+
default:
|
|
1086
|
+
return `${r2}${import_picocolors2.default.cyan(o)} ${G2({ cursor: this.cursor, options: this.options, maxItems: t.maxItems, style: (i, s) => n(i, s ? "active" : "inactive") }).join(`
|
|
1087
|
+
${import_picocolors2.default.cyan(o)} `)}
|
|
1088
|
+
${import_picocolors2.default.cyan(d2)}
|
|
1089
|
+
`;
|
|
1090
|
+
}
|
|
1091
|
+
} }).prompt();
|
|
1092
|
+
};
|
|
1093
|
+
var J2 = `${import_picocolors2.default.gray(o)} `;
|
|
1094
|
+
|
|
1095
|
+
// src/ui.mjs
|
|
1096
|
+
var import_picocolors3 = __toESM(require_picocolors(), 1);
|
|
1097
|
+
var out = process.stdout;
|
|
1098
|
+
var isTTY = () => !!out.isTTY;
|
|
1099
|
+
var BAR = import_picocolors3.default.gray("\u2502");
|
|
1100
|
+
var FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
|
|
1101
|
+
var activeSpinner = null;
|
|
1102
|
+
function banner(version, subtitle) {
|
|
1103
|
+
const mark = import_picocolors3.default.bgBlue(import_picocolors3.default.white(import_picocolors3.default.bold(" DataBull ")));
|
|
1104
|
+
out.write(`
|
|
1105
|
+
${import_picocolors3.default.gray("\u250C")} ${mark} ${import_picocolors3.default.dim(`v${version}`)}${subtitle ? import_picocolors3.default.dim(` \xB7 ${subtitle}`) : ""}
|
|
1106
|
+
${BAR}
|
|
1107
|
+
`);
|
|
1108
|
+
}
|
|
1109
|
+
function line(text = "") {
|
|
1110
|
+
out.write(text ? `${BAR} ${text}
|
|
1111
|
+
` : `${BAR}
|
|
1112
|
+
`);
|
|
1113
|
+
}
|
|
1114
|
+
function info(text) {
|
|
1115
|
+
out.write(`${import_picocolors3.default.blue("\u25CF")} ${text}
|
|
1116
|
+
`);
|
|
1117
|
+
}
|
|
1118
|
+
function success(text) {
|
|
1119
|
+
out.write(`${import_picocolors3.default.green("\u25C6")} ${text}
|
|
1120
|
+
`);
|
|
1121
|
+
}
|
|
1122
|
+
function warn(text, fix) {
|
|
1123
|
+
out.write(`${import_picocolors3.default.yellow("\u25B2")} ${text}
|
|
1124
|
+
`);
|
|
1125
|
+
if (fix) out.write(`${BAR} ${import_picocolors3.default.dim(fix)}
|
|
1126
|
+
`);
|
|
1127
|
+
}
|
|
1128
|
+
function end(text) {
|
|
1129
|
+
out.write(`${import_picocolors3.default.gray("\u2514")} ${text}
|
|
1130
|
+
|
|
1131
|
+
`);
|
|
1132
|
+
}
|
|
1133
|
+
function step(n, total, label) {
|
|
1134
|
+
const prefix = total ? import_picocolors3.default.dim(`${n}/${total} `) : "";
|
|
1135
|
+
let text = label;
|
|
1136
|
+
let frame = 0;
|
|
1137
|
+
let timer = null;
|
|
1138
|
+
const started = Date.now();
|
|
1139
|
+
const render = () => {
|
|
1140
|
+
const secs = Math.floor((Date.now() - started) / 1e3);
|
|
1141
|
+
const clock = secs >= 5 ? ` ${secs}s` : "";
|
|
1142
|
+
const room = (out.columns || 80) - 4 - (total ? `${n}/${total} `.length : 0) - clock.length - 1;
|
|
1143
|
+
const shown = text.length > room ? `${text.slice(0, Math.max(10, room - 1))}\u2026` : text;
|
|
1144
|
+
out.write(`\r\x1B[2K${import_picocolors3.default.magenta(FRAMES[frame++ % FRAMES.length])} ${prefix}${shown}${import_picocolors3.default.dim(clock)}`);
|
|
1145
|
+
};
|
|
1146
|
+
if (isTTY()) {
|
|
1147
|
+
out.write("\x1B[?25l");
|
|
1148
|
+
render();
|
|
1149
|
+
timer = setInterval(render, 100);
|
|
1150
|
+
} else {
|
|
1151
|
+
out.write(`\u2026 ${prefix}${text}
|
|
1152
|
+
`);
|
|
1153
|
+
}
|
|
1154
|
+
const stop = (symbol, msg, detail) => {
|
|
1155
|
+
if (timer) clearInterval(timer);
|
|
1156
|
+
timer = null;
|
|
1157
|
+
if (isTTY()) out.write("\r\x1B[2K\x1B[?25h");
|
|
1158
|
+
out.write(`${symbol} ${prefix}${msg}${detail ? import_picocolors3.default.dim(` ${detail}`) : ""}
|
|
1159
|
+
`);
|
|
1160
|
+
if (activeSpinner === api) activeSpinner = null;
|
|
1161
|
+
};
|
|
1162
|
+
const api = {
|
|
1163
|
+
update(msg) {
|
|
1164
|
+
if (msg === text) return;
|
|
1165
|
+
text = msg;
|
|
1166
|
+
if (!isTTY()) out.write(` ${msg}
|
|
1167
|
+
`);
|
|
1168
|
+
},
|
|
1169
|
+
done(msg = label, detail = "") {
|
|
1170
|
+
stop(import_picocolors3.default.green("\u25C7"), msg, detail);
|
|
1171
|
+
},
|
|
1172
|
+
warn(msg, detail = "") {
|
|
1173
|
+
stop(import_picocolors3.default.yellow("\u25B2"), msg, detail);
|
|
1174
|
+
},
|
|
1175
|
+
fail(msg = label) {
|
|
1176
|
+
stop(import_picocolors3.default.red("\u25A0"), msg);
|
|
1177
|
+
},
|
|
1178
|
+
// Pause the spinner while something else writes to the terminal (e.g. a login link).
|
|
1179
|
+
pause() {
|
|
1180
|
+
if (timer) clearInterval(timer);
|
|
1181
|
+
timer = null;
|
|
1182
|
+
if (isTTY()) out.write("\r\x1B[2K\x1B[?25h");
|
|
1183
|
+
},
|
|
1184
|
+
resume() {
|
|
1185
|
+
if (isTTY() && !timer) {
|
|
1186
|
+
render();
|
|
1187
|
+
timer = setInterval(render, 100);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
activeSpinner = api;
|
|
1192
|
+
return api;
|
|
1193
|
+
}
|
|
1194
|
+
async function withStep(n, total, label, fn) {
|
|
1195
|
+
const s = step(n, total, label);
|
|
1196
|
+
try {
|
|
1197
|
+
const result = await fn(s);
|
|
1198
|
+
if (activeSpinner === s) s.done(typeof result?.label === "string" ? result.label : label, result?.detail || "");
|
|
1199
|
+
return result;
|
|
1200
|
+
} catch (err) {
|
|
1201
|
+
if (activeSpinner === s) s.fail(label);
|
|
1202
|
+
throw err;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
function stopSpinner() {
|
|
1206
|
+
if (activeSpinner) activeSpinner.fail(import_picocolors3.default.dim("Stopped"));
|
|
1207
|
+
}
|
|
1208
|
+
function box(title, lines, { color = import_picocolors3.default.blue } = {}) {
|
|
1209
|
+
const strip2 = (s) => s.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, "");
|
|
1210
|
+
const width = Math.max(strip2(title).length + 2, ...lines.map((l2) => strip2(l2).length)) + 2;
|
|
1211
|
+
if (isTTY() && out.columns && width + 2 > out.columns) {
|
|
1212
|
+
out.write(`${BAR}
|
|
1213
|
+
${color("\u256D\u2500")} ${import_picocolors3.default.bold(title)}
|
|
1214
|
+
`);
|
|
1215
|
+
for (const l2 of lines) out.write(`${color("\u2502")} ${l2}
|
|
1216
|
+
`);
|
|
1217
|
+
out.write(`${color("\u2570\u2500")}
|
|
1218
|
+
`);
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
out.write(`${BAR}
|
|
1222
|
+
${color("\u256D")}${color("\u2500")} ${import_picocolors3.default.bold(title)} ${color("\u2500".repeat(Math.max(0, width - strip2(title).length - 3)))}${color("\u256E")}
|
|
1223
|
+
`);
|
|
1224
|
+
for (const l2 of lines) out.write(`${color("\u2502")} ${l2}${" ".repeat(Math.max(0, width - strip2(l2).length - 1))}${color("\u2502")}
|
|
1225
|
+
`);
|
|
1226
|
+
out.write(`${color("\u2570")}${color("\u2500".repeat(width))}${color("\u256F")}
|
|
1227
|
+
`);
|
|
1228
|
+
}
|
|
1229
|
+
function printFailure(err, logPath) {
|
|
1230
|
+
stopSpinner();
|
|
1231
|
+
if (err instanceof Fail) {
|
|
1232
|
+
out.write(`${BAR}
|
|
1233
|
+
${import_picocolors3.default.red("\u25A0")} ${import_picocolors3.default.bold(err.message)}
|
|
1234
|
+
`);
|
|
1235
|
+
if (err.fix) out.write(`${BAR} ${err.fix}
|
|
1236
|
+
`);
|
|
1237
|
+
} else {
|
|
1238
|
+
out.write(`${BAR}
|
|
1239
|
+
${import_picocolors3.default.red("\u25A0")} ${import_picocolors3.default.bold("Something unexpected went wrong.")}
|
|
1240
|
+
`);
|
|
1241
|
+
out.write(`${BAR} ${import_picocolors3.default.dim(String(err?.message || err))}
|
|
1242
|
+
`);
|
|
1243
|
+
out.write(`${BAR} Run the same command again; it resumes safely. If it keeps happening, email adgan.business@gmail.com with the log.
|
|
1244
|
+
`);
|
|
1245
|
+
}
|
|
1246
|
+
if (logPath) out.write(`${BAR} ${import_picocolors3.default.dim(`Log: ${logPath}`)}
|
|
1247
|
+
`);
|
|
1248
|
+
out.write(`${import_picocolors3.default.gray("\u2514")}
|
|
1249
|
+
|
|
1250
|
+
`);
|
|
1251
|
+
}
|
|
1252
|
+
function printCancelled() {
|
|
1253
|
+
stopSpinner();
|
|
1254
|
+
out.write(`
|
|
1255
|
+
${import_picocolors3.default.gray("\u2514")} Stopped. Run the same command again any time; it picks up where it left off.
|
|
1256
|
+
|
|
1257
|
+
`);
|
|
1258
|
+
}
|
|
1259
|
+
var canPrompt = () => !!process.stdin.isTTY && isTTY() && !process.env.DATABULL_NO_PROMPT;
|
|
1260
|
+
function need(flag) {
|
|
1261
|
+
if (!canPrompt()) throw new Fail(`This step needs an answer, and there's no terminal to ask in.`, { fix: `Pass ${flag}, or run the command in a terminal.` });
|
|
1262
|
+
}
|
|
1263
|
+
function check(value) {
|
|
1264
|
+
if (pD(value)) throw new Cancelled();
|
|
1265
|
+
return value;
|
|
1266
|
+
}
|
|
1267
|
+
async function askText({ message, placeholder, initialValue, defaultValue, validate: validate2, flag }) {
|
|
1268
|
+
need(flag);
|
|
1269
|
+
return check(await he({ message, placeholder, initialValue, defaultValue, validate: validate2 }));
|
|
1270
|
+
}
|
|
1271
|
+
async function askSelect({ message, options, initialValue, maxItems, flag }) {
|
|
1272
|
+
need(flag);
|
|
1273
|
+
return check(await ve({ message, options, initialValue, maxItems }));
|
|
1274
|
+
}
|
|
1275
|
+
async function askConfirm({ message, initialValue = true, flag }) {
|
|
1276
|
+
need(flag);
|
|
1277
|
+
return check(await ye({ message, initialValue }));
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// src/cloudflare.mjs
|
|
1281
|
+
var cfBase = () => (process.env.DATABULL_CF_API || "https://api.cloudflare.com/client/v4").replace(/\/$/, "");
|
|
1282
|
+
var CfError = class extends Error {
|
|
1283
|
+
constructor(status, errors, path7) {
|
|
1284
|
+
const first = errors?.[0];
|
|
1285
|
+
super(first?.message || `Cloudflare API ${status}`);
|
|
1286
|
+
this.status = status;
|
|
1287
|
+
this.errors = errors || [];
|
|
1288
|
+
this.codes = this.errors.map((e2) => e2.code);
|
|
1289
|
+
this.path = path7;
|
|
1290
|
+
}
|
|
1291
|
+
};
|
|
1292
|
+
async function cf(ctx, method, path7, body) {
|
|
1293
|
+
const token = await ctx.cfToken();
|
|
1294
|
+
return retry(
|
|
1295
|
+
async () => {
|
|
1296
|
+
let res;
|
|
1297
|
+
try {
|
|
1298
|
+
res = await fetchWithTimeout(
|
|
1299
|
+
cfBase() + path7,
|
|
1300
|
+
{
|
|
1301
|
+
method,
|
|
1302
|
+
headers: { authorization: `Bearer ${token}`, "content-type": "application/json", "user-agent": `databull-cli/${ctx.version}` },
|
|
1303
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
1304
|
+
},
|
|
1305
|
+
3e4
|
|
1306
|
+
);
|
|
1307
|
+
} catch (err) {
|
|
1308
|
+
ctx.log.write(`cf ${method} ${path7} \u2192 network error ${err.message}`);
|
|
1309
|
+
throw err;
|
|
1310
|
+
}
|
|
1311
|
+
let json2 = null;
|
|
1312
|
+
try {
|
|
1313
|
+
json2 = await res.json();
|
|
1314
|
+
} catch {
|
|
1315
|
+
}
|
|
1316
|
+
ctx.log.write(`cf ${method} ${path7} \u2192 ${res.status}${json2 && !json2.success ? ` ${JSON.stringify(json2.errors)}` : ""}`);
|
|
1317
|
+
if (res.status === 429 || res.status >= 500) {
|
|
1318
|
+
const e2 = new CfError(res.status, json2?.errors, path7);
|
|
1319
|
+
e2.retry = true;
|
|
1320
|
+
throw e2;
|
|
1321
|
+
}
|
|
1322
|
+
if (!res.ok || !json2?.success) throw new CfError(res.status, json2?.errors, path7);
|
|
1323
|
+
return json2;
|
|
1324
|
+
},
|
|
1325
|
+
{ retryable: (err) => err.retry || isNetworkError(err) }
|
|
1326
|
+
).catch((err) => {
|
|
1327
|
+
if (err instanceof CfError) {
|
|
1328
|
+
if (err.status === 401 || err.codes.includes(1e4) || err.codes.includes(9109))
|
|
1329
|
+
throw new Fail(`Cloudflare didn't accept your login.`, { fix: "Run `npx wrangler logout`, then run this command again to log in fresh." });
|
|
1330
|
+
if (err.retry) throw new Fail(`Cloudflare's API is having trouble right now (HTTP ${err.status}).`, { fix: "Wait a minute, then run the same command again." });
|
|
1331
|
+
throw err;
|
|
1332
|
+
}
|
|
1333
|
+
if (isNetworkError(err)) throw new Fail(`Couldn't reach Cloudflare.`, { fix: "Check your internet connection, then run the same command again." });
|
|
1334
|
+
throw err;
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
async function cfList(ctx, path7, perPage = 50) {
|
|
1338
|
+
const out3 = [];
|
|
1339
|
+
for (let page = 1; page < 50; page++) {
|
|
1340
|
+
const sep = path7.includes("?") ? "&" : "?";
|
|
1341
|
+
const json2 = await cf(ctx, "GET", `${path7}${sep}page=${page}&per_page=${perPage}`);
|
|
1342
|
+
out3.push(...json2.result || []);
|
|
1343
|
+
const info2 = json2.result_info;
|
|
1344
|
+
if (!info2 || !Array.isArray(json2.result) || json2.result.length < perPage) break;
|
|
1345
|
+
if (info2.total_pages && page >= info2.total_pages) break;
|
|
1346
|
+
}
|
|
1347
|
+
return out3;
|
|
1348
|
+
}
|
|
1349
|
+
async function whoami(ctx) {
|
|
1350
|
+
const r2 = await runWrangler(ctx, ["whoami", "--json"], { title: "whoami --json" });
|
|
1351
|
+
const json2 = parseJsonOutput(r2.stdout);
|
|
1352
|
+
if (r2.code === 0 && json2?.loggedIn) return json2;
|
|
1353
|
+
return null;
|
|
1354
|
+
}
|
|
1355
|
+
async function login(ctx, s) {
|
|
1356
|
+
s.pause();
|
|
1357
|
+
line();
|
|
1358
|
+
info(`Next, log in to Cloudflare. A browser tab will open: log in and click ${import_picocolors3.default.bold("Allow")}, then come back here.`);
|
|
1359
|
+
if (!ctx.flags.open) line(import_picocolors3.default.dim("(--no-open: open the link below yourself)"));
|
|
1360
|
+
let shownUrl = false;
|
|
1361
|
+
const args = ["login"];
|
|
1362
|
+
if (!ctx.flags.open) args.push("--browser=false");
|
|
1363
|
+
const r2 = await runWrangler(ctx, args, {
|
|
1364
|
+
title: "login",
|
|
1365
|
+
onOutput(chunk) {
|
|
1366
|
+
const m2 = !shownUrl && chunk.match(/https:\/\/dash\.cloudflare\.com\/oauth2\/auth\S+/);
|
|
1367
|
+
if (m2) {
|
|
1368
|
+
shownUrl = true;
|
|
1369
|
+
line(ctx.flags.open ? `If no tab opened, use this link:` : "Open this link to log in:");
|
|
1370
|
+
line(import_picocolors3.default.cyan(m2[0]));
|
|
1371
|
+
line();
|
|
1372
|
+
s.resume();
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
});
|
|
1376
|
+
if (!shownUrl) s.resume();
|
|
1377
|
+
if (r2.code !== 0) {
|
|
1378
|
+
const denied = /denied|consent/i.test(r2.stdout + r2.stderr);
|
|
1379
|
+
throw new Fail(denied ? "The Cloudflare login was cancelled." : `Couldn't log in to Cloudflare.`, {
|
|
1380
|
+
fix: denied ? "Run the same command again and click Allow in the browser." : "Run the same command again. If your browser can\u2019t reach localhost:8976, run `npx wrangler login --device` first."
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
async function ensureLogin(ctx, s) {
|
|
1385
|
+
let me2 = await whoami(ctx);
|
|
1386
|
+
if (!me2) {
|
|
1387
|
+
await login(ctx, s);
|
|
1388
|
+
me2 = await whoami(ctx);
|
|
1389
|
+
if (!me2) throw new Fail(`Cloudflare login didn't stick.`, { fix: "Run the same command again. If it repeats, run `npx wrangler logout`, then try again." });
|
|
1390
|
+
}
|
|
1391
|
+
ctx.cfAccounts = me2.accounts || [];
|
|
1392
|
+
ctx.cfEmail = me2.email;
|
|
1393
|
+
return me2;
|
|
1394
|
+
}
|
|
1395
|
+
function tokenGetter(ctx) {
|
|
1396
|
+
let cached = null;
|
|
1397
|
+
return async () => {
|
|
1398
|
+
if (cached) return cached;
|
|
1399
|
+
const r2 = await runWrangler(ctx, ["auth", "token", "--json"], { title: "auth token --json", secretOutput: true });
|
|
1400
|
+
const json2 = parseJsonOutput(r2.stdout);
|
|
1401
|
+
if (r2.code !== 0 || !json2?.token) throw new Fail(`Couldn't get a Cloudflare token from wrangler.`, { fix: "Run the same command again; you may be asked to log in." });
|
|
1402
|
+
ctx.log.write(`token type: ${json2.type}`);
|
|
1403
|
+
cached = json2.token;
|
|
1404
|
+
return cached;
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
async function accounts(ctx) {
|
|
1408
|
+
if (ctx.cfAccounts?.length) return ctx.cfAccounts.map((a) => ({ id: a.id, name: a.name }));
|
|
1409
|
+
const list = await cfList(ctx, "/accounts");
|
|
1410
|
+
return list.map((a) => ({ id: a.id, name: a.name }));
|
|
1411
|
+
}
|
|
1412
|
+
async function getSubdomain(ctx, accountId) {
|
|
1413
|
+
try {
|
|
1414
|
+
const json2 = await cf(ctx, "GET", `/accounts/${accountId}/workers/subdomain`);
|
|
1415
|
+
return json2.result?.subdomain || null;
|
|
1416
|
+
} catch (err) {
|
|
1417
|
+
if (err instanceof CfError && (err.status === 404 || err.codes.includes(10007))) return null;
|
|
1418
|
+
throw err;
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
function slugFromAccountName(name) {
|
|
1422
|
+
let s = String(name || "").toLowerCase();
|
|
1423
|
+
s = s.replace(/['’]s account$/, "").replace(/ account$/, "");
|
|
1424
|
+
if (s.includes("@")) s = s.split("@")[0];
|
|
1425
|
+
s = s.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40).replace(/-+$/, "");
|
|
1426
|
+
return s.length >= 3 ? s : `databull-${s || "site"}`.replace(/-+$/, "");
|
|
1427
|
+
}
|
|
1428
|
+
async function createSubdomain(ctx, accountId, accountName) {
|
|
1429
|
+
const base = slugFromAccountName(accountName);
|
|
1430
|
+
const candidates = [base, ...Array.from({ length: 5 }, () => `${base}-${Math.random().toString(36).slice(2, 6)}`)];
|
|
1431
|
+
let last;
|
|
1432
|
+
for (const sub of candidates) {
|
|
1433
|
+
try {
|
|
1434
|
+
const json2 = await cf(ctx, "PUT", `/accounts/${accountId}/workers/subdomain`, { subdomain: sub });
|
|
1435
|
+
return json2.result?.subdomain || sub;
|
|
1436
|
+
} catch (err) {
|
|
1437
|
+
if (!(err instanceof CfError)) throw err;
|
|
1438
|
+
last = err;
|
|
1439
|
+
if (err.status >= 400 && err.status < 500 && err.status !== 403) continue;
|
|
1440
|
+
break;
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
throw new Fail(`Couldn't create a workers.dev address for this Cloudflare account.`, {
|
|
1444
|
+
fix: `Open dash.cloudflare.com \u2192 Workers & Pages, pick a workers.dev subdomain there, then run this again. (${last?.message || "unknown error"})`
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
async function workerExists(ctx, accountId, name) {
|
|
1448
|
+
try {
|
|
1449
|
+
await cf(ctx, "GET", `/accounts/${accountId}/workers/scripts/${encodeURIComponent(name)}/settings`);
|
|
1450
|
+
return true;
|
|
1451
|
+
} catch (err) {
|
|
1452
|
+
if (err instanceof CfError && (err.status === 404 || err.codes.includes(10007))) return false;
|
|
1453
|
+
throw err;
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
async function workerSettings(ctx, accountId, name) {
|
|
1457
|
+
const json2 = await cf(ctx, "GET", `/accounts/${accountId}/workers/scripts/${encodeURIComponent(name)}/settings`);
|
|
1458
|
+
return json2.result || {};
|
|
1459
|
+
}
|
|
1460
|
+
async function listWorkers(ctx, accountId) {
|
|
1461
|
+
const json2 = await cf(ctx, "GET", `/accounts/${accountId}/workers/scripts`);
|
|
1462
|
+
return (json2.result || []).map((s) => s.id);
|
|
1463
|
+
}
|
|
1464
|
+
async function deleteWorker(ctx, accountId, name) {
|
|
1465
|
+
try {
|
|
1466
|
+
await cf(ctx, "DELETE", `/accounts/${accountId}/workers/scripts/${encodeURIComponent(name)}?force=true`);
|
|
1467
|
+
return true;
|
|
1468
|
+
} catch (err) {
|
|
1469
|
+
if (err instanceof CfError && (err.status === 404 || err.codes.includes(10007))) return false;
|
|
1470
|
+
throw err;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
async function activeZones(ctx, accountId) {
|
|
1474
|
+
const zones = await cfList(ctx, `/zones?account.id=${accountId}&status=active`);
|
|
1475
|
+
return zones.map((z2) => ({ id: z2.id, name: z2.name })).sort((a, b3) => a.name.localeCompare(b3.name));
|
|
1476
|
+
}
|
|
1477
|
+
async function customDomainOwner(ctx, accountId, hostname) {
|
|
1478
|
+
try {
|
|
1479
|
+
const json2 = await cf(ctx, "GET", `/accounts/${accountId}/workers/domains?hostname=${encodeURIComponent(hostname)}`);
|
|
1480
|
+
const hit = (json2.result || []).find((d3) => d3.hostname === hostname);
|
|
1481
|
+
return hit ? hit.service : null;
|
|
1482
|
+
} catch (err) {
|
|
1483
|
+
if (err instanceof CfError) throw new Fail(`Couldn't check whether ${hostname} is free.`, { fix: `Cloudflare said: ${err.message}. Run the same command again in a minute.` });
|
|
1484
|
+
throw err;
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
async function customDomainRecords(ctx, accountId, service) {
|
|
1488
|
+
try {
|
|
1489
|
+
const json2 = await cf(ctx, "GET", `/accounts/${accountId}/workers/domains?service=${encodeURIComponent(service)}`);
|
|
1490
|
+
return (json2.result || []).filter((d3) => !d3.service || d3.service === service).map((d3) => ({ id: d3.id, hostname: d3.hostname }));
|
|
1491
|
+
} catch (err) {
|
|
1492
|
+
if (err instanceof CfError) return [];
|
|
1493
|
+
throw err;
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
async function customDomainsOf(ctx, accountId, service) {
|
|
1497
|
+
return (await customDomainRecords(ctx, accountId, service)).map((d3) => d3.hostname);
|
|
1498
|
+
}
|
|
1499
|
+
async function attachCustomDomain(ctx, accountId, service, hostname) {
|
|
1500
|
+
const owner = await customDomainOwner(ctx, accountId, hostname);
|
|
1501
|
+
if (owner === service) return false;
|
|
1502
|
+
if (owner) throw domainTaken(hostname, owner);
|
|
1503
|
+
try {
|
|
1504
|
+
await cf(ctx, "PUT", `/accounts/${accountId}/workers/scripts/${encodeURIComponent(service)}/domains/records`, {
|
|
1505
|
+
override_scope: true,
|
|
1506
|
+
override_existing_origin: false,
|
|
1507
|
+
override_existing_dns_record: false,
|
|
1508
|
+
origins: [{ hostname }]
|
|
1509
|
+
});
|
|
1510
|
+
return true;
|
|
1511
|
+
} catch (err) {
|
|
1512
|
+
if (!(err instanceof CfError)) throw err;
|
|
1513
|
+
if (err.codes.includes(100117) || /dns/i.test(err.message))
|
|
1514
|
+
throw new Fail(`${hostname} already has a DNS record, so it wasn't touched.`, {
|
|
1515
|
+
code: "dns_conflict",
|
|
1516
|
+
fix: `Delete that record in dash.cloudflare.com \u2192 DNS and run the same command again, or pick another address with --domain (or --domain workers.dev).`
|
|
1517
|
+
});
|
|
1518
|
+
if (err.codes.includes(100116) || /already|in use|another|origin/i.test(err.message)) throw domainTaken(hostname, null);
|
|
1519
|
+
throw new Fail(`Cloudflare couldn't connect ${hostname}.`, { code: "domain_failed", fix: `Cloudflare said: ${err.message}. Run the same command again, or pick another address with --domain.` });
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
function domainTaken(hostname, owner) {
|
|
1523
|
+
return new Fail(owner ? `${hostname} is already used by the Worker "${owner}".` : `${hostname} is already used by another Worker.`, {
|
|
1524
|
+
code: "domain_taken",
|
|
1525
|
+
fix: "Pick a different subdomain, or detach it from that Worker in the Cloudflare dashboard first."
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
async function detachCustomDomain(ctx, accountId, service, hostname) {
|
|
1529
|
+
const hits = (await customDomainRecords(ctx, accountId, service)).filter((d3) => d3.hostname === hostname && d3.id);
|
|
1530
|
+
for (const d3 of hits) {
|
|
1531
|
+
try {
|
|
1532
|
+
await cf(ctx, "DELETE", `/accounts/${accountId}/workers/domains/${encodeURIComponent(d3.id)}`);
|
|
1533
|
+
} catch (err) {
|
|
1534
|
+
if (!(err instanceof CfError && err.status === 404)) throw err;
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
return hits.length > 0;
|
|
1538
|
+
}
|
|
1539
|
+
async function findD1(ctx, accountId, name) {
|
|
1540
|
+
const list = await cfList(ctx, `/accounts/${accountId}/d1/database?name=${encodeURIComponent(name)}`, 100);
|
|
1541
|
+
const db = list.find((d3) => d3.name === name);
|
|
1542
|
+
return db ? { id: db.uuid, name: db.name } : null;
|
|
1543
|
+
}
|
|
1544
|
+
async function createD1(ctx, accountId, name) {
|
|
1545
|
+
try {
|
|
1546
|
+
const json2 = await cf(ctx, "POST", `/accounts/${accountId}/d1/database`, { name });
|
|
1547
|
+
return { id: json2.result.uuid, name: json2.result.name || name };
|
|
1548
|
+
} catch (err) {
|
|
1549
|
+
if (err instanceof CfError) {
|
|
1550
|
+
if (/limit|quota|maximum|exceeded/i.test(err.message) || err.codes.includes(7406))
|
|
1551
|
+
throw new Fail(`Your Cloudflare account has reached its D1 database limit.`, {
|
|
1552
|
+
fix: "Delete a database you no longer use (dash.cloudflare.com \u2192 Storage & databases \u2192 D1), or upgrade to Workers Paid, then run this again."
|
|
1553
|
+
});
|
|
1554
|
+
if (/already exists/i.test(err.message)) {
|
|
1555
|
+
const db = await findD1(ctx, accountId, name);
|
|
1556
|
+
if (db) return db;
|
|
1557
|
+
}
|
|
1558
|
+
throw new Fail(`Cloudflare couldn't create the database "${name}".`, { fix: `Cloudflare said: ${err.message}. Run the same command again, or pick another name with --name.` });
|
|
1559
|
+
}
|
|
1560
|
+
throw err;
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
async function deleteD1(ctx, accountId, id) {
|
|
1564
|
+
try {
|
|
1565
|
+
await cf(ctx, "DELETE", `/accounts/${accountId}/d1/database/${id}`);
|
|
1566
|
+
return true;
|
|
1567
|
+
} catch (err) {
|
|
1568
|
+
if (err instanceof CfError && err.status === 404) return false;
|
|
1569
|
+
throw err;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
async function d1Query(ctx, accountId, id, sql) {
|
|
1573
|
+
try {
|
|
1574
|
+
const json2 = await cf(ctx, "POST", `/accounts/${accountId}/d1/database/${id}/query`, { sql });
|
|
1575
|
+
const results = json2.result || [];
|
|
1576
|
+
return results[results.length - 1]?.results || [];
|
|
1577
|
+
} catch (err) {
|
|
1578
|
+
if (err instanceof CfError && /no such table/i.test(err.message)) return null;
|
|
1579
|
+
throw err;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
// src/deploy.mjs
|
|
1584
|
+
import fs5 from "node:fs";
|
|
1585
|
+
import os2 from "node:os";
|
|
1586
|
+
import path5 from "node:path";
|
|
1587
|
+
|
|
1588
|
+
// src/untar.mjs
|
|
1589
|
+
import fs4 from "node:fs";
|
|
1590
|
+
import path4 from "node:path";
|
|
1591
|
+
import { gunzipSync } from "node:zlib";
|
|
1592
|
+
var TarError = class extends Error {
|
|
1593
|
+
};
|
|
1594
|
+
var BLOCK = 512;
|
|
1595
|
+
function str(buf, start, len) {
|
|
1596
|
+
const slice = buf.subarray(start, start + len);
|
|
1597
|
+
const nul = slice.indexOf(0);
|
|
1598
|
+
return slice.subarray(0, nul < 0 ? len : nul).toString("utf8");
|
|
1599
|
+
}
|
|
1600
|
+
function octal(buf, start, len) {
|
|
1601
|
+
if (buf[start] & 128) {
|
|
1602
|
+
let n = 0;
|
|
1603
|
+
for (let i = start + 1; i < start + len; i++) n = n * 256 + buf[i];
|
|
1604
|
+
return n;
|
|
1605
|
+
}
|
|
1606
|
+
const s = str(buf, start, len).trim();
|
|
1607
|
+
if (!s) return 0;
|
|
1608
|
+
if (!/^[0-7]+$/.test(s)) throw new TarError("corrupt tar header");
|
|
1609
|
+
return parseInt(s, 8);
|
|
1610
|
+
}
|
|
1611
|
+
function checksumOk(header2) {
|
|
1612
|
+
const stored = octal(header2, 148, 8);
|
|
1613
|
+
let sum = 0;
|
|
1614
|
+
for (let i = 0; i < BLOCK; i++) sum += i >= 148 && i < 156 ? 32 : header2[i];
|
|
1615
|
+
return sum === stored;
|
|
1616
|
+
}
|
|
1617
|
+
function safePath(name) {
|
|
1618
|
+
let n = String(name).replace(/\\/g, "/");
|
|
1619
|
+
if (n.startsWith("/") || /^[a-zA-Z]:/.test(n)) throw new TarError(`unsafe path in bundle: ${name}`);
|
|
1620
|
+
const parts = [];
|
|
1621
|
+
for (const part of n.split("/")) {
|
|
1622
|
+
if (part === "" || part === ".") continue;
|
|
1623
|
+
if (part === "..") throw new TarError(`unsafe path in bundle: ${name}`);
|
|
1624
|
+
parts.push(part);
|
|
1625
|
+
}
|
|
1626
|
+
return parts.join("/");
|
|
1627
|
+
}
|
|
1628
|
+
function parsePax(data) {
|
|
1629
|
+
const out3 = {};
|
|
1630
|
+
let i = 0;
|
|
1631
|
+
const text = data.toString("utf8");
|
|
1632
|
+
while (i < text.length) {
|
|
1633
|
+
const sp = text.indexOf(" ", i);
|
|
1634
|
+
if (sp < 0) break;
|
|
1635
|
+
const len = parseInt(text.slice(i, sp), 10);
|
|
1636
|
+
if (!len) break;
|
|
1637
|
+
const record = text.slice(sp + 1, i + len - 1);
|
|
1638
|
+
const eq = record.indexOf("=");
|
|
1639
|
+
if (eq > 0) out3[record.slice(0, eq)] = record.slice(eq + 1);
|
|
1640
|
+
i += len;
|
|
1641
|
+
}
|
|
1642
|
+
return out3;
|
|
1643
|
+
}
|
|
1644
|
+
function readTar(input) {
|
|
1645
|
+
const buf = input[0] === 31 && input[1] === 139 ? gunzipSync(input) : input;
|
|
1646
|
+
const entries = [];
|
|
1647
|
+
let off = 0;
|
|
1648
|
+
let longName = null;
|
|
1649
|
+
let pax = null;
|
|
1650
|
+
while (off + BLOCK <= buf.length) {
|
|
1651
|
+
const header2 = buf.subarray(off, off + BLOCK);
|
|
1652
|
+
if (header2.every((b3) => b3 === 0)) break;
|
|
1653
|
+
if (!checksumOk(header2)) throw new TarError("corrupt tar header (bad checksum)");
|
|
1654
|
+
const size = octal(header2, 124, 12);
|
|
1655
|
+
const type = String.fromCharCode(header2[156] || 48);
|
|
1656
|
+
const dataStart = off + BLOCK;
|
|
1657
|
+
if (dataStart + size > buf.length) throw new TarError("truncated tar archive");
|
|
1658
|
+
const data = buf.subarray(dataStart, dataStart + size);
|
|
1659
|
+
off = dataStart + Math.ceil(size / BLOCK) * BLOCK;
|
|
1660
|
+
if (type === "L") {
|
|
1661
|
+
longName = str(data, 0, data.length);
|
|
1662
|
+
continue;
|
|
1663
|
+
}
|
|
1664
|
+
if (type === "x") {
|
|
1665
|
+
pax = parsePax(data);
|
|
1666
|
+
continue;
|
|
1667
|
+
}
|
|
1668
|
+
if (type === "g") continue;
|
|
1669
|
+
let name = str(header2, 0, 100);
|
|
1670
|
+
if (str(header2, 257, 5) === "ustar") {
|
|
1671
|
+
const prefix = str(header2, 345, 155);
|
|
1672
|
+
if (prefix) name = `${prefix}/${name}`;
|
|
1673
|
+
}
|
|
1674
|
+
if (pax?.path) name = pax.path;
|
|
1675
|
+
if (longName) name = longName;
|
|
1676
|
+
longName = null;
|
|
1677
|
+
pax = null;
|
|
1678
|
+
if (type === "1" || type === "2") throw new TarError(`links aren't allowed in the bundle: ${name}`);
|
|
1679
|
+
if (type !== "0" && type !== "5" && type !== "7") throw new TarError(`unsupported entry in bundle: ${name}`);
|
|
1680
|
+
const rel = safePath(name);
|
|
1681
|
+
if (!rel) continue;
|
|
1682
|
+
entries.push(type === "5" ? { path: rel, type: "dir" } : { path: rel, type: "file", data: Buffer.from(data) });
|
|
1683
|
+
}
|
|
1684
|
+
return entries;
|
|
1685
|
+
}
|
|
1686
|
+
function extractEntries(entries, dir) {
|
|
1687
|
+
const root = path4.resolve(dir);
|
|
1688
|
+
for (const e2 of entries) {
|
|
1689
|
+
const target = path4.resolve(root, safePath(e2.path));
|
|
1690
|
+
if (target !== root && !target.startsWith(root + path4.sep)) throw new TarError(`unsafe path in bundle: ${e2.path}`);
|
|
1691
|
+
if (e2.type === "dir") fs4.mkdirSync(target, { recursive: true });
|
|
1692
|
+
else {
|
|
1693
|
+
fs4.mkdirSync(path4.dirname(target), { recursive: true });
|
|
1694
|
+
fs4.writeFileSync(target, e2.data);
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
// src/deploy.mjs
|
|
1700
|
+
var BUNDLE_FILES = ["manifest.json", "worker.js", "assets", "migrations", "CHANGELOG.md"];
|
|
1701
|
+
function readBundle(buf, cliVersion) {
|
|
1702
|
+
let entries;
|
|
1703
|
+
try {
|
|
1704
|
+
entries = readTar(buf);
|
|
1705
|
+
} catch (err) {
|
|
1706
|
+
if (err instanceof TarError || /incorrect header|unexpected end|invalid/i.test(err.message))
|
|
1707
|
+
throw new Fail(`The DataBull bundle is damaged (${err.message}).`, { fix: "Run the same command again to download it fresh." });
|
|
1708
|
+
throw err;
|
|
1709
|
+
}
|
|
1710
|
+
const manifestEntry = entries.find((e2) => e2.path === "manifest.json" && e2.type === "file");
|
|
1711
|
+
let manifest;
|
|
1712
|
+
try {
|
|
1713
|
+
manifest = JSON.parse(manifestEntry.data.toString("utf8"));
|
|
1714
|
+
} catch {
|
|
1715
|
+
throw new Fail(`The DataBull bundle has no readable manifest.json.`, { fix: "Run the same command again to download it fresh." });
|
|
1716
|
+
}
|
|
1717
|
+
const main2 = manifest.main || "worker.js";
|
|
1718
|
+
if (!manifest.version || !entries.some((e2) => e2.path === main2)) throw new Fail(`The DataBull bundle is incomplete.`, { fix: "Run the same command again to download it fresh." });
|
|
1719
|
+
if (manifest.min_cli && compareVersions(cliVersion, manifest.min_cli) < 0)
|
|
1720
|
+
throw new Fail(`DataBull ${manifest.version} needs a newer installer (${manifest.min_cli}+).`, { fix: "Run it with `npx databull@latest \u2026` so npm fetches the newest installer." });
|
|
1721
|
+
return { manifest, entries };
|
|
1722
|
+
}
|
|
1723
|
+
function writeBundle(dir, entries) {
|
|
1724
|
+
fs5.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
1725
|
+
for (const f of BUNDLE_FILES) fs5.rmSync(path5.join(dir, f), { recursive: true, force: true });
|
|
1726
|
+
extractEntries(entries, dir);
|
|
1727
|
+
}
|
|
1728
|
+
function readLocalManifest(dir) {
|
|
1729
|
+
try {
|
|
1730
|
+
const m2 = JSON.parse(fs5.readFileSync(path5.join(dir, "manifest.json"), "utf8"));
|
|
1731
|
+
return fs5.existsSync(path5.join(dir, m2.main || "worker.js")) ? m2 : null;
|
|
1732
|
+
} catch {
|
|
1733
|
+
return null;
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
function workersDevUrl(name, subdomain) {
|
|
1737
|
+
return `https://${name}.${subdomain}.workers.dev`;
|
|
1738
|
+
}
|
|
1739
|
+
function buildWranglerConfig({ name, manifest, d1Name, d1Id, url, api }) {
|
|
1740
|
+
const cfg = {
|
|
1741
|
+
name,
|
|
1742
|
+
main: manifest.main || "worker.js",
|
|
1743
|
+
compatibility_date: manifest.compatibility_date,
|
|
1744
|
+
compatibility_flags: manifest.compatibility_flags || ["nodejs_compat"],
|
|
1745
|
+
workers_dev: true
|
|
1746
|
+
};
|
|
1747
|
+
Object.assign(cfg, {
|
|
1748
|
+
assets: { directory: "assets", binding: "ASSETS", not_found_handling: "single-page-application", run_worker_first: ["/*", "!/assets/*", "!/js/*"] },
|
|
1749
|
+
d1_databases: [{ binding: "DB", database_name: d1Name, database_id: d1Id, migrations_dir: "migrations" }],
|
|
1750
|
+
ratelimits: [{ name: "COLLECT_LIMITER", namespace_id: "4271", simple: { limit: 240, period: 60 } }],
|
|
1751
|
+
triggers: { crons: ["* * * * *"] },
|
|
1752
|
+
observability: { enabled: true },
|
|
1753
|
+
vars: { APP_NAME: "DataBull", APP_URL: url, DATABULL_EDITION: "selfhost", DATABULL_API: api }
|
|
1754
|
+
});
|
|
1755
|
+
return cfg;
|
|
1756
|
+
}
|
|
1757
|
+
function writeWranglerConfig(dir, cfg) {
|
|
1758
|
+
const file = path5.join(dir, "wrangler.json");
|
|
1759
|
+
fs5.writeFileSync(file, JSON.stringify(cfg, null, 2) + "\n", { mode: 384 });
|
|
1760
|
+
return file;
|
|
1761
|
+
}
|
|
1762
|
+
function ensureD1Config(dir, install) {
|
|
1763
|
+
const file = path5.join(dir, "wrangler.json");
|
|
1764
|
+
if (fs5.existsSync(file)) return file;
|
|
1765
|
+
fs5.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
1766
|
+
const cfg = { name: install.name, compatibility_date: "2026-10-01", d1_databases: [{ binding: "DB", database_name: install.d1_name, database_id: install.d1_id }] };
|
|
1767
|
+
fs5.writeFileSync(file, JSON.stringify(cfg, null, 2) + "\n", { mode: 384 });
|
|
1768
|
+
return file;
|
|
1769
|
+
}
|
|
1770
|
+
function generateSecrets() {
|
|
1771
|
+
return { JWT_SECRET: b64url(48), ENCRYPTION_KEY: b64url(48), PROXY_SECRET: b64url(48), SETUP_TOKEN: b64url(24) };
|
|
1772
|
+
}
|
|
1773
|
+
var liveTemp = /* @__PURE__ */ new Set();
|
|
1774
|
+
function cleanupTemp() {
|
|
1775
|
+
for (const dir of liveTemp) {
|
|
1776
|
+
try {
|
|
1777
|
+
fs5.rmSync(dir, { recursive: true, force: true });
|
|
1778
|
+
} catch {
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
liveTemp.clear();
|
|
1782
|
+
}
|
|
1783
|
+
async function withSecretsFile(secrets, fn) {
|
|
1784
|
+
const dir = fs5.mkdtempSync(path5.join(os2.tmpdir(), "databull-"));
|
|
1785
|
+
liveTemp.add(dir);
|
|
1786
|
+
const file = path5.join(dir, "secrets.json");
|
|
1787
|
+
try {
|
|
1788
|
+
fs5.writeFileSync(file, JSON.stringify(secrets), { mode: 384 });
|
|
1789
|
+
fs5.chmodSync(file, 384);
|
|
1790
|
+
return await fn(file);
|
|
1791
|
+
} finally {
|
|
1792
|
+
fs5.rmSync(dir, { recursive: true, force: true });
|
|
1793
|
+
liveTemp.delete(dir);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
function sqlString(v2) {
|
|
1797
|
+
if (v2 === null || v2 === void 0) return "NULL";
|
|
1798
|
+
return `'${String(v2).replace(/\u0000/g, "").replace(/'/g, "''")}'`;
|
|
1799
|
+
}
|
|
1800
|
+
function instanceSettingsSql({ licenseEmail, installedAt, installName }, nowMs = Date.now()) {
|
|
1801
|
+
const rows = [
|
|
1802
|
+
["license_email", licenseEmail],
|
|
1803
|
+
["installed_at", installedAt],
|
|
1804
|
+
["install_name", installName]
|
|
1805
|
+
].map(([k3, v2]) => `(${sqlString(k3)},${sqlString(v2 ?? "")},${Math.floor(nowMs)})`).join(",");
|
|
1806
|
+
return `INSERT INTO instance_settings (key,value,updated_at) VALUES ${rows} ON CONFLICT(key) DO UPDATE SET value=excluded.value, updated_at=excluded.updated_at;`;
|
|
1807
|
+
}
|
|
1808
|
+
function resetToken() {
|
|
1809
|
+
const token = b64url(32);
|
|
1810
|
+
return { token, hash: sha256hex(token) };
|
|
1811
|
+
}
|
|
1812
|
+
function resetSql(hash, nowMs = Date.now()) {
|
|
1813
|
+
if (!/^[0-9a-f]{64}$/.test(hash)) throw new Error("bad reset hash");
|
|
1814
|
+
const now = Math.floor(nowMs);
|
|
1815
|
+
return `INSERT INTO password_resets (token_hash,user_id,created_at,expires_at) SELECT '${hash}', id, ${now}, ${now + 18e5} FROM users ORDER BY id LIMIT 1;`;
|
|
1816
|
+
}
|
|
1817
|
+
var tail = (r2) => `${r2.stdout}
|
|
1818
|
+
${r2.stderr}`;
|
|
1819
|
+
async function applyMigrations(ctx, install, configFile) {
|
|
1820
|
+
const r2 = await runWrangler({ ...ctx, cwd: path5.dirname(configFile) }, ["d1", "migrations", "apply", "DB", "--remote", "--config", configFile], {
|
|
1821
|
+
accountId: install.account_id,
|
|
1822
|
+
title: "d1 migrations apply DB --remote"
|
|
1823
|
+
});
|
|
1824
|
+
if (r2.code !== 0) throw explainWranglerFailure(tail(r2), "migrations", install);
|
|
1825
|
+
return r2;
|
|
1826
|
+
}
|
|
1827
|
+
async function deployWorker(ctx, install, configFile, secrets) {
|
|
1828
|
+
const run = (secretsFile) => {
|
|
1829
|
+
const args = ["deploy", "--config", configFile];
|
|
1830
|
+
if (secretsFile) args.push("--secrets-file", secretsFile);
|
|
1831
|
+
return runWrangler({ ...ctx, cwd: path5.dirname(configFile) }, args, { accountId: install.account_id, title: "deploy" });
|
|
1832
|
+
};
|
|
1833
|
+
const r2 = secrets && Object.keys(secrets).length ? await withSecretsFile(secrets, run) : await run(null);
|
|
1834
|
+
if (r2.code !== 0) throw explainWranglerFailure(tail(r2), "deploy", install);
|
|
1835
|
+
return r2;
|
|
1836
|
+
}
|
|
1837
|
+
async function putSecrets(ctx, install, configFile, secrets) {
|
|
1838
|
+
const run = (file) => runWrangler({ ...ctx, cwd: path5.dirname(configFile) }, ["secret", "bulk", file, "--name", install.name, "--config", configFile], { accountId: install.account_id, title: "secret bulk" });
|
|
1839
|
+
const r2 = await withSecretsFile(secrets, run);
|
|
1840
|
+
if (r2.code !== 0) throw explainWranglerFailure(tail(r2), "secrets", install);
|
|
1841
|
+
return r2;
|
|
1842
|
+
}
|
|
1843
|
+
async function d1Execute(ctx, install, configFile, sql, title) {
|
|
1844
|
+
const r2 = await runWrangler({ ...ctx, cwd: path5.dirname(configFile) }, ["d1", "execute", install.d1_name, "--remote", "--config", configFile, "--yes", "--json", "--command", sql], {
|
|
1845
|
+
accountId: install.account_id,
|
|
1846
|
+
title: `d1 execute ${title}`
|
|
1847
|
+
});
|
|
1848
|
+
if (r2.code !== 0) throw explainWranglerFailure(tail(r2), "sql", install);
|
|
1849
|
+
const json2 = parseJsonOutput(r2.stdout);
|
|
1850
|
+
const last = Array.isArray(json2) ? json2[json2.length - 1] : json2;
|
|
1851
|
+
return { changes: last?.meta?.changes ?? null, results: last?.results || [] };
|
|
1852
|
+
}
|
|
1853
|
+
function explainWranglerFailure(output, phase, install = {}) {
|
|
1854
|
+
const o2 = String(output);
|
|
1855
|
+
const host = install.custom_domain || "that address";
|
|
1856
|
+
if (/not logged in|You are not authenticated|CLOUDFLARE_API_TOKEN|Authentication error \[code: 10000\]|Invalid access token/i.test(o2))
|
|
1857
|
+
return new Fail(`You're not logged in to Cloudflare (or the login expired).`, { fix: "Run the same command again; it will ask you to log in." });
|
|
1858
|
+
if (/externally managed DNS record|already has (a|an existing) DNS record|DNS record.*already exists|code: 100117/i.test(o2))
|
|
1859
|
+
return new Fail(`${host} already has a DNS record, so Cloudflare won't attach it to DataBull.`, {
|
|
1860
|
+
fix: `Delete the existing record for ${host} in dash.cloudflare.com \u2192 DNS, or choose a different subdomain with \`npx databull@latest domain\`.`
|
|
1861
|
+
});
|
|
1862
|
+
if (/already (in use|attached|assigned)|is already a custom domain|code: 100116/i.test(o2))
|
|
1863
|
+
return new Fail(`${host} is already used by another Worker.`, { fix: "Pick a different subdomain, or detach it from the other Worker in the Cloudflare dashboard first." });
|
|
1864
|
+
if (/Could not find zone|zone .*not found|not a zone on this account|code: 100114|code: 100115/i.test(o2))
|
|
1865
|
+
return new Fail(`${host} isn't on this Cloudflare account.`, { fix: "Add the domain to this account in dash.cloudflare.com first, or use the free workers.dev address." });
|
|
1866
|
+
if (/You need a workers\.dev subdomain|register a workers\.dev subdomain|no workers\.dev subdomain/i.test(o2))
|
|
1867
|
+
return new Fail(`This Cloudflare account has no workers.dev address yet.`, { fix: "Run the same command again; it creates one for you." });
|
|
1868
|
+
if (/maximum number of (databases|D1)|database limit|exceeded.*databases/i.test(o2))
|
|
1869
|
+
return new Fail(`Your Cloudflare account has reached its D1 database limit.`, { fix: "Delete a database you no longer use (dash.cloudflare.com \u2192 D1), then run this again." });
|
|
1870
|
+
if (/Couldn't find a D1 DB|database .* not found|code: 7404/i.test(o2))
|
|
1871
|
+
return new Fail(`The database for this install is missing on Cloudflare.`, { fix: "Run `npx databull@latest` to install again; it creates a fresh database." });
|
|
1872
|
+
if (/exceeded (the )?(script|worker) (size|limit)|maximum number of workers|code: 10037/i.test(o2))
|
|
1873
|
+
return new Fail(`Cloudflare refused the upload: this account hit a Workers limit.`, { fix: "Delete an unused Worker in the dashboard (or upgrade to Workers Paid), then run this again." });
|
|
1874
|
+
if (/ENOTFOUND|ETIMEDOUT|ECONNRESET|EAI_AGAIN|fetch failed|network/i.test(o2))
|
|
1875
|
+
return new Fail(`Lost the connection to Cloudflare.`, { fix: "Check your internet connection, then run the same command again; it resumes safely." });
|
|
1876
|
+
const what = { migrations: "Updating the database failed.", deploy: "Cloudflare rejected the deploy.", sql: "Running a database command failed.", secrets: "Saving the new setup token failed." }[phase] || "A Cloudflare step failed.";
|
|
1877
|
+
const said = o2.match(/✘ \[ERROR\] ([^\n]+)/)?.[1] || o2.match(/\[ERROR\] ([^\n]+)/)?.[1];
|
|
1878
|
+
return new Fail(said ? `${what} Cloudflare said: ${said.trim()}` : what, { fix: `Run the same command again; it resumes safely. If it fails twice, email ${SUPPORT} with the log.` });
|
|
1879
|
+
}
|
|
1880
|
+
async function fetchHealth(url, timeoutMs = 8e3) {
|
|
1881
|
+
const mock = process.env.DATABULL_TEST_HEALTH_URL;
|
|
1882
|
+
const target = mock ? `${mock}/api/health?host=${encodeURIComponent(new URL(url).host)}` : `${url.replace(/\/$/, "")}/api/health`;
|
|
1883
|
+
try {
|
|
1884
|
+
const res = await fetchWithTimeout(target, { headers: { "cache-control": "no-cache" } }, timeoutMs);
|
|
1885
|
+
if (!res.ok) return null;
|
|
1886
|
+
const j2 = await res.json();
|
|
1887
|
+
return j2 && j2.ok ? j2 : null;
|
|
1888
|
+
} catch {
|
|
1889
|
+
return null;
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
async function fetchNeedsSetup(url, timeoutMs = 8e3) {
|
|
1893
|
+
const mock = process.env.DATABULL_TEST_HEALTH_URL;
|
|
1894
|
+
const target = mock ? `${mock}/api/auth/status?host=${encodeURIComponent(new URL(url).host)}` : `${url.replace(/\/$/, "")}/api/auth/status`;
|
|
1895
|
+
try {
|
|
1896
|
+
const res = await fetchWithTimeout(target, { headers: { "cache-control": "no-cache" } }, timeoutMs);
|
|
1897
|
+
if (!res.ok) return null;
|
|
1898
|
+
const j2 = await res.json();
|
|
1899
|
+
return typeof j2?.needsSetup === "boolean" ? j2.needsSetup : null;
|
|
1900
|
+
} catch {
|
|
1901
|
+
return null;
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
async function waitForHealth(url, version, { timeoutMs = Number(process.env.DATABULL_HEALTH_TIMEOUT_MS || 3e5), intervalMs = Number(process.env.DATABULL_HEALTH_INTERVAL_MS || 3e3), onWait } = {}) {
|
|
1905
|
+
const started = Date.now();
|
|
1906
|
+
while (Date.now() - started < timeoutMs) {
|
|
1907
|
+
const h2 = await fetchHealth(url);
|
|
1908
|
+
if (h2 && (!version || h2.version === version)) return true;
|
|
1909
|
+
onWait?.(Date.now() - started, h2);
|
|
1910
|
+
await sleep(intervalMs);
|
|
1911
|
+
}
|
|
1912
|
+
return false;
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
// src/site.mjs
|
|
1916
|
+
function apiBase(flags) {
|
|
1917
|
+
return String(flags.api || process.env.DATABULL_API || "https://databull.app").replace(/\/+$/, "");
|
|
1918
|
+
}
|
|
1919
|
+
var unreachable = (api) => new Fail(`Couldn't reach ${api.replace(/^https?:\/\//, "")}.`, { fix: "Check your internet connection, then run the same command again." });
|
|
1920
|
+
function licenseFail(code, message) {
|
|
1921
|
+
switch (code) {
|
|
1922
|
+
case "not_found":
|
|
1923
|
+
return new Fail(`That license key wasn't found.`, { code, fix: "Check for typos: copy it from your receipt email or the page you saw after paying." });
|
|
1924
|
+
case "inactive":
|
|
1925
|
+
return new Fail("This license key is disabled.", { code, fix: `If that's a mistake, email ${SUPPORT} and we'll sort it out.` });
|
|
1926
|
+
case "limit_reached":
|
|
1927
|
+
return new Fail("This license key is already active on 3 installs.", {
|
|
1928
|
+
code,
|
|
1929
|
+
fix: `Free one by running \`npx databull@latest license --deactivate\` on an old install, or email ${SUPPORT}.`
|
|
1930
|
+
});
|
|
1931
|
+
case "not_databull":
|
|
1932
|
+
return new Fail(`That key is for a different product, not DataBull.`, { code, fix: "Use the DataBull license key from your receipt email." });
|
|
1933
|
+
case "invalid_instance":
|
|
1934
|
+
return new Fail(`This install's license activation is no longer valid.`, { code, fix: "Run `npx databull@latest update` and enter your license key again." });
|
|
1935
|
+
case "bad_request":
|
|
1936
|
+
return new Fail(message || `The license server couldn't read that request.`, { code, fix: `Check your license key and run the same command again. If it keeps happening, email ${SUPPORT}.` });
|
|
1937
|
+
default:
|
|
1938
|
+
return new Fail(message || `The license server said no (${code || "unknown error"}).`, { code, fix: `Try again in a minute. If it keeps happening, email ${SUPPORT}.` });
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
async function call(ctx, method, path7, body, headers = {}) {
|
|
1942
|
+
const url = ctx.api + path7;
|
|
1943
|
+
try {
|
|
1944
|
+
return await retry(async () => {
|
|
1945
|
+
const res = await fetchWithTimeout(
|
|
1946
|
+
url,
|
|
1947
|
+
{ method, headers: { "content-type": "application/json", "user-agent": `databull-cli/${ctx.version}`, ...headers }, body: body ? JSON.stringify(body) : void 0 },
|
|
1948
|
+
3e4
|
|
1949
|
+
);
|
|
1950
|
+
if (res.status >= 500 && res.status !== 501) {
|
|
1951
|
+
const e2 = new Error(`HTTP ${res.status}`);
|
|
1952
|
+
e2.retry = true;
|
|
1953
|
+
e2.status = res.status;
|
|
1954
|
+
throw e2;
|
|
1955
|
+
}
|
|
1956
|
+
return res;
|
|
1957
|
+
}, { retryable: (e2) => e2.retry || isNetworkError(e2) });
|
|
1958
|
+
} catch (err) {
|
|
1959
|
+
ctx.log.write(`site ${method} ${path7} \u2192 ${err.message}`);
|
|
1960
|
+
if (err.retry) throw new Fail(`databull.app is having trouble right now (HTTP ${err.status}).`, { fix: "Wait a minute, then run the same command again." });
|
|
1961
|
+
throw unreachable(ctx.api);
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
async function json(ctx, method, path7, body) {
|
|
1965
|
+
const res = await call(ctx, method, path7, body);
|
|
1966
|
+
let data = null;
|
|
1967
|
+
try {
|
|
1968
|
+
data = await res.json();
|
|
1969
|
+
} catch {
|
|
1970
|
+
}
|
|
1971
|
+
ctx.log.write(`site ${method} ${path7} \u2192 ${res.status}${data?.error ? ` ${data.error}` : ""}`);
|
|
1972
|
+
if (!res.ok || data?.ok === false) {
|
|
1973
|
+
if (!data) throw new Fail(`databull.app sent an unexpected answer (HTTP ${res.status}).`, { fix: "Try again in a minute, or check that --api points at the right address." });
|
|
1974
|
+
throw licenseFail(data.error, data.message);
|
|
1975
|
+
}
|
|
1976
|
+
return data;
|
|
1977
|
+
}
|
|
1978
|
+
function checkKey(key) {
|
|
1979
|
+
const k3 = String(key || "").trim();
|
|
1980
|
+
if (k3.length < 8 || k3.length > 200)
|
|
1981
|
+
throw new Fail(`That doesn't look like a full license key.`, { code: "bad_key", fix: "Copy the whole key from your receipt email or the page you saw after paying." });
|
|
1982
|
+
return k3;
|
|
1983
|
+
}
|
|
1984
|
+
async function activate(ctx, licenseKey, name) {
|
|
1985
|
+
const r2 = await json(ctx, "POST", "/api/license/activate", { license_key: licenseKey, name });
|
|
1986
|
+
if (!r2.instance_id) throw new Fail("The license server sent an incomplete answer.", { fix: "Run the same command again." });
|
|
1987
|
+
return r2;
|
|
1988
|
+
}
|
|
1989
|
+
async function validate(ctx, licenseKey, instanceId) {
|
|
1990
|
+
const r2 = await json(ctx, "POST", "/api/license/validate", { license_key: licenseKey, instance_id: instanceId });
|
|
1991
|
+
return !!r2.valid;
|
|
1992
|
+
}
|
|
1993
|
+
async function deactivate(ctx, licenseKey, instanceId) {
|
|
1994
|
+
await json(ctx, "POST", "/api/license/deactivate", { license_key: licenseKey, instance_id: instanceId });
|
|
1995
|
+
}
|
|
1996
|
+
async function latestRelease(ctx) {
|
|
1997
|
+
const r2 = await json(ctx, "GET", "/api/releases/latest");
|
|
1998
|
+
if (!r2.version || !r2.sha256) throw new Fail("The release info from databull.app looks incomplete.", { fix: "Try again in a few minutes." });
|
|
1999
|
+
return r2;
|
|
2000
|
+
}
|
|
2001
|
+
async function downloadBundle(ctx, release, licenseKey, instanceId) {
|
|
2002
|
+
const res = await call(ctx, "GET", `/api/releases/download?version=${encodeURIComponent(release.version)}`, null, {
|
|
2003
|
+
"x-license-key": licenseKey,
|
|
2004
|
+
"x-license-instance": instanceId
|
|
2005
|
+
});
|
|
2006
|
+
if (!res.ok) {
|
|
2007
|
+
let data = null;
|
|
2008
|
+
try {
|
|
2009
|
+
data = await res.json();
|
|
2010
|
+
} catch {
|
|
2011
|
+
}
|
|
2012
|
+
ctx.log.write(`site GET download \u2192 ${res.status} ${data?.error || ""}`);
|
|
2013
|
+
if (res.status === 404 || data?.error === "no_release")
|
|
2014
|
+
throw new Fail(`DataBull ${release.version} isn't available for download right now.`, { code: "release_missing", fix: `Run the same command again in a few minutes. If it keeps happening, email ${SUPPORT}.` });
|
|
2015
|
+
if (data?.error) throw licenseFail(data.error, data.message);
|
|
2016
|
+
throw new Fail(`Couldn't download DataBull ${release.version} (HTTP ${res.status}).`, { fix: "Run the same command again in a minute." });
|
|
2017
|
+
}
|
|
2018
|
+
let buf;
|
|
2019
|
+
try {
|
|
2020
|
+
buf = Buffer.from(await res.arrayBuffer());
|
|
2021
|
+
} catch {
|
|
2022
|
+
throw new Fail("The download was interrupted.", { fix: "Run the same command again." });
|
|
2023
|
+
}
|
|
2024
|
+
const sum = sha256hex(buf);
|
|
2025
|
+
ctx.log.write(`download ${release.version}: ${buf.length} bytes sha256 ${sum}`);
|
|
2026
|
+
const expected = String(release.sha256 || res.headers.get("x-databull-sha256") || "").toLowerCase();
|
|
2027
|
+
if (!expected) throw new Fail(`Couldn't verify the download of DataBull ${release.version}, so it wasn't used.`, { fix: `Run the same command again. If it keeps happening, email ${SUPPORT}.` });
|
|
2028
|
+
if (release.size && buf.length !== Number(release.size) || sum !== expected) {
|
|
2029
|
+
throw new Fail(`The download didn't match its checksum, so it wasn't used.`, { fix: "Run the same command again. If it keeps happening, email " + SUPPORT + "." });
|
|
2030
|
+
}
|
|
2031
|
+
return buf;
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
// src/commands/shared.mjs
|
|
2035
|
+
import fs6 from "node:fs";
|
|
2036
|
+
import os3 from "node:os";
|
|
2037
|
+
import path6 from "node:path";
|
|
2038
|
+
var CMD = "npx databull@latest";
|
|
2039
|
+
var DOCS = "https://databull.app/docs";
|
|
2040
|
+
function save(ctx) {
|
|
2041
|
+
writeConfig(ctx.paths, ctx.cfg);
|
|
2042
|
+
}
|
|
2043
|
+
function instanceName() {
|
|
2044
|
+
const host = os3.hostname().replace(/\.local$/, "").slice(0, 40) || "computer";
|
|
2045
|
+
return `databull on ${host}`;
|
|
2046
|
+
}
|
|
2047
|
+
async function pickInstall(ctx, { verb = "use" } = {}) {
|
|
2048
|
+
let list = ctx.cfg.installs;
|
|
2049
|
+
if (ctx.flags.account) list = list.filter((i) => i.account_id === ctx.flags.account || i.account_name?.toLowerCase() === ctx.flags.account.toLowerCase());
|
|
2050
|
+
if (ctx.flags.name) {
|
|
2051
|
+
const hit = list.filter((i) => i.name === ctx.flags.name);
|
|
2052
|
+
if (!hit.length) return null;
|
|
2053
|
+
list = hit;
|
|
2054
|
+
}
|
|
2055
|
+
if (!list.length) return null;
|
|
2056
|
+
if (list.length === 1) return list[0];
|
|
2057
|
+
const choice = await askSelect({
|
|
2058
|
+
message: `Which install do you want to ${verb}?`,
|
|
2059
|
+
options: list.map((i, idx) => ({ value: idx, label: i.name, hint: `${i.url.replace(/^https:\/\//, "")} \xB7 ${i.account_name || i.account_id}` })),
|
|
2060
|
+
flag: "--name <worker>"
|
|
2061
|
+
});
|
|
2062
|
+
return list[choice];
|
|
2063
|
+
}
|
|
2064
|
+
async function connect(ctx, n, total, { accountId } = {}) {
|
|
2065
|
+
return withStep(n, total, "Connecting to Cloudflare", async (s) => {
|
|
2066
|
+
if (!ctx.wrangler.ready) {
|
|
2067
|
+
s.update("Getting Cloudflare tools ready (first run only, about 30 seconds)");
|
|
2068
|
+
await ctx.wrangler();
|
|
2069
|
+
s.update("Connecting to Cloudflare");
|
|
2070
|
+
}
|
|
2071
|
+
const me2 = await ensureLogin(ctx, s);
|
|
2072
|
+
const accounts2 = await accounts(ctx);
|
|
2073
|
+
if (!accounts2.length) throw new Fail("This Cloudflare login has no accounts.", { fix: "Open dash.cloudflare.com once to finish creating your account, then run this again." });
|
|
2074
|
+
ctx.cfAccountsList = accounts2;
|
|
2075
|
+
let account = null;
|
|
2076
|
+
const want = accountId || ctx.flags.account;
|
|
2077
|
+
if (want) {
|
|
2078
|
+
account = accounts2.find((a) => a.id === want || a.name.toLowerCase() === String(want).toLowerCase());
|
|
2079
|
+
if (!account)
|
|
2080
|
+
throw new Fail(`This Cloudflare login can't see account ${want}.`, {
|
|
2081
|
+
fix: accountId ? "Log in as the user that owns it: run `npx wrangler logout`, then run this again." : `Accounts you can use: ${accounts2.map((a) => a.name).join(", ")}.`
|
|
2082
|
+
});
|
|
2083
|
+
} else if (accounts2.length === 1) account = accounts2[0];
|
|
2084
|
+
ctx.cfMe = me2;
|
|
2085
|
+
if (account) return { account, label: "Connected to Cloudflare", detail: me2.email ? `${me2.email} \xB7 ${account.name}` : account.name };
|
|
2086
|
+
return { account: null, label: "Connected to Cloudflare", detail: me2.email || "" };
|
|
2087
|
+
}).then(async (r2) => {
|
|
2088
|
+
if (r2.account) return r2.account;
|
|
2089
|
+
const id = await askSelect({
|
|
2090
|
+
message: "Which Cloudflare account should DataBull live in?",
|
|
2091
|
+
options: ctx.cfAccountsList.map((a) => ({ value: a.id, label: a.name, hint: a.id })),
|
|
2092
|
+
flag: "--account <id>"
|
|
2093
|
+
});
|
|
2094
|
+
return ctx.cfAccountsList.find((a) => a.id === id);
|
|
2095
|
+
});
|
|
2096
|
+
}
|
|
2097
|
+
async function getBundle(ctx, s, { licenseKey, instanceId, release }) {
|
|
2098
|
+
if (ctx.flags.bundle) {
|
|
2099
|
+
const file = path6.resolve(ctx.flags.bundle);
|
|
2100
|
+
let buf2;
|
|
2101
|
+
try {
|
|
2102
|
+
buf2 = fs6.readFileSync(file);
|
|
2103
|
+
} catch {
|
|
2104
|
+
throw new Fail(`Can't read the bundle at ${file}.`, { fix: "Check the path you passed to --bundle." });
|
|
2105
|
+
}
|
|
2106
|
+
const b4 = readBundle(buf2, ctx.version);
|
|
2107
|
+
return { ...b4, notes: changelogTop(b4.entries, b4.manifest.version) };
|
|
2108
|
+
}
|
|
2109
|
+
release = release || await latestRelease(ctx);
|
|
2110
|
+
s?.update(`Downloading DataBull ${release.version}`);
|
|
2111
|
+
const buf = await downloadBundle(ctx, release, licenseKey, instanceId);
|
|
2112
|
+
const b3 = readBundle(buf, ctx.version);
|
|
2113
|
+
if (b3.manifest.version !== release.version) throw new Fail(`The download is version ${b3.manifest.version}, expected ${release.version}.`, { fix: "Run the same command again in a minute." });
|
|
2114
|
+
return { ...b3, notes: release.notes || changelogTop(b3.entries, b3.manifest.version) };
|
|
2115
|
+
}
|
|
2116
|
+
function changelogTop(entries, version) {
|
|
2117
|
+
const e2 = entries.find((x2) => x2.path === "CHANGELOG.md");
|
|
2118
|
+
if (!e2) return "";
|
|
2119
|
+
const text = e2.data.toString("utf8");
|
|
2120
|
+
const parts = text.split(/\n(?=## )/);
|
|
2121
|
+
const hit = parts.find((p2) => p2.startsWith("## ") && p2.includes(version)) || parts.find((p2) => p2.startsWith("## ")) || "";
|
|
2122
|
+
return hit.trim();
|
|
2123
|
+
}
|
|
2124
|
+
function showNotes(version, notes) {
|
|
2125
|
+
if (!notes) return;
|
|
2126
|
+
const lines = String(notes).split("\n").map((l2) => l2.replace(/^#+\s*/, "").replace(/\*\*(.+?)\*\*/g, "$1").trimEnd()).filter((l2, i, arr) => l2 || i > 0 && arr[i - 1]);
|
|
2127
|
+
while (lines.length && (lines[0] === "" || lines[0].replace(/^v/, "").startsWith(version))) lines.shift();
|
|
2128
|
+
lines.splice(14);
|
|
2129
|
+
if (lines.length) box(`What's new in ${version}`, lines.map((l2) => l2.slice(0, 96)), { color: import_picocolors3.default.gray });
|
|
2130
|
+
}
|
|
2131
|
+
async function ensureLicense(ctx, install, s) {
|
|
2132
|
+
if (install.license_key && install.instance_id) {
|
|
2133
|
+
const valid = await validate(ctx, install.license_key, install.instance_id);
|
|
2134
|
+
if (valid) return { changed: false };
|
|
2135
|
+
ctx.log.write("license instance no longer valid; re-activating");
|
|
2136
|
+
}
|
|
2137
|
+
let key = ctx.flags.key ? checkKey(ctx.flags.key) : install.license_key;
|
|
2138
|
+
if (!key) {
|
|
2139
|
+
s?.pause();
|
|
2140
|
+
key = await askKey();
|
|
2141
|
+
s?.resume();
|
|
2142
|
+
}
|
|
2143
|
+
if (install.url) {
|
|
2144
|
+
const live = await fetchHealth(install.url).catch(() => null);
|
|
2145
|
+
if (live?.license_instance && await validate(ctx, key.trim(), live.license_instance).catch(() => false)) {
|
|
2146
|
+
install.license_key = key.trim();
|
|
2147
|
+
install.instance_id = live.license_instance;
|
|
2148
|
+
return { changed: true };
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
const r2 = await activate(ctx, key.trim(), instanceName());
|
|
2152
|
+
install.license_key = key.trim();
|
|
2153
|
+
install.instance_id = r2.instance_id;
|
|
2154
|
+
install.license_email = r2.email || install.license_email || null;
|
|
2155
|
+
return { changed: true };
|
|
2156
|
+
}
|
|
2157
|
+
async function askKey() {
|
|
2158
|
+
return (await askText({
|
|
2159
|
+
message: "Your DataBull license key",
|
|
2160
|
+
placeholder: "from your receipt email",
|
|
2161
|
+
validate: (v2) => !v2 || v2.trim().length < 8 || v2.trim().length > 200 ? "Paste the full key from your receipt email." : void 0,
|
|
2162
|
+
flag: "--key <license key>"
|
|
2163
|
+
})).trim();
|
|
2164
|
+
}
|
|
2165
|
+
async function discoverInstalls(ctx, account, onlyName) {
|
|
2166
|
+
const subdomain = await getSubdomain(ctx, account.id);
|
|
2167
|
+
if (!subdomain) return [];
|
|
2168
|
+
const names = onlyName ? [onlyName] : await listWorkers(ctx, account.id);
|
|
2169
|
+
const found = [];
|
|
2170
|
+
const queue = [...names];
|
|
2171
|
+
const worker = async () => {
|
|
2172
|
+
while (queue.length) {
|
|
2173
|
+
const name = queue.shift();
|
|
2174
|
+
const url = `https://${name}.${subdomain}.workers.dev`;
|
|
2175
|
+
const h2 = await fetchHealth(url, 6e3);
|
|
2176
|
+
if (h2?.version) found.push({ name, url, version: h2.version });
|
|
2177
|
+
}
|
|
2178
|
+
};
|
|
2179
|
+
await Promise.all(Array.from({ length: 8 }, worker));
|
|
2180
|
+
const out3 = [];
|
|
2181
|
+
for (const f of found.sort((a, b3) => a.name.localeCompare(b3.name))) {
|
|
2182
|
+
const settings = await workerSettings(ctx, account.id, f.name).catch(() => ({}));
|
|
2183
|
+
const bindings = settings.bindings || [];
|
|
2184
|
+
const marked = bindings.some((b3) => b3.type === "plain_text" && b3.name === "DATABULL_EDITION" && b3.text === "selfhost");
|
|
2185
|
+
const db = bindings.find((b3) => b3.type === "d1" && b3.name === "DB");
|
|
2186
|
+
if (!marked || !db) {
|
|
2187
|
+
ctx.log.write(`discover: ${f.name} answers /api/health but isn't DataBull (marker ${marked}, DB ${!!db})`);
|
|
2188
|
+
continue;
|
|
2189
|
+
}
|
|
2190
|
+
const d1Id = db.id || db.database_id;
|
|
2191
|
+
let d1Name = f.name;
|
|
2192
|
+
try {
|
|
2193
|
+
const j2 = await cf(ctx, "GET", `/accounts/${account.id}/d1/database/${d1Id}`);
|
|
2194
|
+
d1Name = j2.result?.name || d1Name;
|
|
2195
|
+
} catch {
|
|
2196
|
+
}
|
|
2197
|
+
const domains = await customDomainsOf(ctx, account.id, f.name);
|
|
2198
|
+
const custom = domains[0] || null;
|
|
2199
|
+
out3.push({
|
|
2200
|
+
name: f.name,
|
|
2201
|
+
account_id: account.id,
|
|
2202
|
+
account_name: account.name,
|
|
2203
|
+
d1_id: d1Id,
|
|
2204
|
+
d1_name: d1Name,
|
|
2205
|
+
url: custom ? `https://${custom}` : f.url,
|
|
2206
|
+
custom_domain: custom,
|
|
2207
|
+
license_key: null,
|
|
2208
|
+
instance_id: null,
|
|
2209
|
+
license_email: null,
|
|
2210
|
+
version: f.version,
|
|
2211
|
+
installed_at: null,
|
|
2212
|
+
updated_at: null
|
|
2213
|
+
});
|
|
2214
|
+
}
|
|
2215
|
+
return out3;
|
|
2216
|
+
}
|
|
2217
|
+
async function pickDiscovered(ctx, found, verb) {
|
|
2218
|
+
if (ctx.flags.name) return found.find((f2) => f2.name === ctx.flags.name) || null;
|
|
2219
|
+
if (found.length > 1)
|
|
2220
|
+
return found[await askSelect({ message: `Which install do you want to ${verb}?`, options: found.map((f2, i) => ({ value: i, label: f2.name, hint: f2.url.replace("https://", "") })), flag: "--name <worker>" })];
|
|
2221
|
+
const f = found[0];
|
|
2222
|
+
if (!canPrompt()) throw new Fail(`Found the DataBull install "${f.name}" (${f.url.replace("https://", "")}), but this computer has no record of it.`, { fix: `Run again with --name ${f.name} to confirm it's the one.` });
|
|
2223
|
+
const ok = await askConfirm({ message: `${verb[0].toUpperCase()}${verb.slice(1)} "${f.name}" (${f.url.replace("https://", "")})?`, flag: `--name ${f.name}` });
|
|
2224
|
+
if (!ok) throw new Fail("Nothing changed.", { fix: "Pass --name <worker> to pick a different install." });
|
|
2225
|
+
return f;
|
|
2226
|
+
}
|
|
2227
|
+
var setupLink = (install) => `${install.url}/setup?token=${install.setup_token}`;
|
|
2228
|
+
async function setupPending(ctx, install) {
|
|
2229
|
+
if (!install?.setup_token) return false;
|
|
2230
|
+
const needs = await fetchNeedsSetup(install.url);
|
|
2231
|
+
if (needs === false) {
|
|
2232
|
+
delete install.setup_token;
|
|
2233
|
+
const rec = ctx.cfg.installs.find((i) => i.name === install.name && i.account_id === install.account_id);
|
|
2234
|
+
if (rec) delete rec.setup_token;
|
|
2235
|
+
save(ctx);
|
|
2236
|
+
return false;
|
|
2237
|
+
}
|
|
2238
|
+
return true;
|
|
2239
|
+
}
|
|
2240
|
+
function showSetupBox(install, { title = "Finish setting up", note } = {}) {
|
|
2241
|
+
box(title, [
|
|
2242
|
+
`${install.name} is deployed but has no login yet. Open this link to create it:`,
|
|
2243
|
+
import_picocolors3.default.cyan(setupLink(install)),
|
|
2244
|
+
"",
|
|
2245
|
+
import_picocolors3.default.dim(note || "If the page doesn't load yet, give it a few minutes; new addresses can take a while.")
|
|
2246
|
+
], { color: import_picocolors3.default.yellow });
|
|
2247
|
+
}
|
|
2248
|
+
async function remindSetup(ctx, install, opts) {
|
|
2249
|
+
if (!await setupPending(ctx, install)) return false;
|
|
2250
|
+
showSetupBox(install, opts);
|
|
2251
|
+
return true;
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
// src/commands/update.mjs
|
|
2255
|
+
var TOTAL = 6;
|
|
2256
|
+
async function runUpdate(ctx, { install = null } = {}) {
|
|
2257
|
+
let n = 0;
|
|
2258
|
+
let account = null;
|
|
2259
|
+
install = install || await pickInstall(ctx, { verb: "update" });
|
|
2260
|
+
if (!install) {
|
|
2261
|
+
line(ctx.cfg.installs.length ? `No install named "${ctx.flags.name}" on this computer; looking on Cloudflare.` : `No installs recorded on this computer; looking on Cloudflare.`);
|
|
2262
|
+
account = await connect(ctx, ++n, TOTAL);
|
|
2263
|
+
const found = await withStep(null, null, "Looking for DataBull installs", async () => {
|
|
2264
|
+
const list = await discoverInstalls(ctx, account, ctx.flags.name);
|
|
2265
|
+
return { value: list, label: list.length ? `Found ${list.length} DataBull install${list.length > 1 ? "s" : ""}` : "No DataBull installs found" };
|
|
2266
|
+
}).then((r2) => r2.value);
|
|
2267
|
+
if (!found.length) throw new Fail(`There's no DataBull install on ${account.name}.`, { fix: `Install it with \`${CMD}\`.` });
|
|
2268
|
+
install = await pickDiscovered(ctx, found, "update");
|
|
2269
|
+
}
|
|
2270
|
+
let licenseChanged = false;
|
|
2271
|
+
const check2 = await withStep(++n, TOTAL, "Checking for updates", async (s) => {
|
|
2272
|
+
const lic = await ensureLicense(ctx, install, s);
|
|
2273
|
+
licenseChanged = lic.changed;
|
|
2274
|
+
if (licenseChanged) {
|
|
2275
|
+
upsertInstall(ctx.cfg, { ...install, installed_at: install.installed_at || nowIso() });
|
|
2276
|
+
save(ctx);
|
|
2277
|
+
}
|
|
2278
|
+
const [release, health] = await Promise.all([ctx.flags.bundle ? null : latestRelease(ctx), fetchHealth(install.url)]);
|
|
2279
|
+
const live2 = health?.version || null;
|
|
2280
|
+
return { value: { release, live: live2 }, label: release ? `Latest is ${release.version}` : "Using the bundle from --bundle", detail: live2 ? `${install.name} runs ${live2}` : `${install.name} isn't answering right now` };
|
|
2281
|
+
}).then((r2) => r2.value);
|
|
2282
|
+
if (check2.release && check2.live && compareVersions(check2.live, check2.release.version) >= 0) {
|
|
2283
|
+
if (install.version !== check2.live) {
|
|
2284
|
+
install.version = check2.live;
|
|
2285
|
+
upsertInstall(ctx.cfg, install);
|
|
2286
|
+
save(ctx);
|
|
2287
|
+
}
|
|
2288
|
+
await remindSetup(ctx, install);
|
|
2289
|
+
end(`${install.name} is up to date (${check2.live}). Nothing to do.`);
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
if (check2.release) {
|
|
2293
|
+
showNotes(check2.release.version, check2.release.notes);
|
|
2294
|
+
if (!ctx.flags.yes && canPrompt()) {
|
|
2295
|
+
const go = await askConfirm({ message: `Update ${install.name} to ${check2.release.version}?`, flag: "--yes" });
|
|
2296
|
+
if (!go) {
|
|
2297
|
+
end("No changes made.");
|
|
2298
|
+
return;
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
if (!account) account = await connect(ctx, ++n, TOTAL, { accountId: install.account_id });
|
|
2303
|
+
const installDir = ctx.paths.installDir(install.name);
|
|
2304
|
+
const bundle = await withStep(++n, TOTAL, "Downloading DataBull", async (s) => {
|
|
2305
|
+
const b3 = await getBundle(ctx, s, { licenseKey: install.license_key, instanceId: install.instance_id, release: check2.release });
|
|
2306
|
+
writeBundle(installDir, b3.entries);
|
|
2307
|
+
return { value: b3, label: `DataBull ${b3.manifest.version} ready`, detail: ctx.flags.bundle ? "from --bundle" : "checksum verified" };
|
|
2308
|
+
}).then((r2) => r2.value);
|
|
2309
|
+
const version = bundle.manifest.version;
|
|
2310
|
+
const configFile = writeWranglerConfig(
|
|
2311
|
+
installDir,
|
|
2312
|
+
buildWranglerConfig({ name: install.name, manifest: bundle.manifest, d1Name: install.d1_name, d1Id: install.d1_id, url: install.url, api: ctx.api })
|
|
2313
|
+
);
|
|
2314
|
+
await withStep(++n, TOTAL, "Updating the database", async () => {
|
|
2315
|
+
await applyMigrations(ctx, install, configFile);
|
|
2316
|
+
return { label: "Database up to date" };
|
|
2317
|
+
});
|
|
2318
|
+
await withStep(++n, TOTAL, "Deploying to Cloudflare", async () => {
|
|
2319
|
+
await deployWorker(ctx, install, configFile, licenseChanged ? { LICENSE_KEY: install.license_key, LICENSE_INSTANCE: install.instance_id } : null);
|
|
2320
|
+
return { label: `Deployed ${version}` };
|
|
2321
|
+
});
|
|
2322
|
+
Object.assign(install, { version, updated_at: nowIso(), installed_at: install.installed_at || nowIso() });
|
|
2323
|
+
upsertInstall(ctx.cfg, install);
|
|
2324
|
+
save(ctx);
|
|
2325
|
+
const live = await waitLive(ctx, ++n, TOTAL, install.url, version, !!install.custom_domain);
|
|
2326
|
+
box(live ? `Updated to ${version}` : `${version} is deployed`, [import_picocolors3.default.cyan(install.url), check2.live ? import_picocolors3.default.dim(`was ${check2.live}`) : ""].filter(Boolean), { color: live ? import_picocolors3.default.green : import_picocolors3.default.yellow });
|
|
2327
|
+
await remindSetup(ctx, install);
|
|
2328
|
+
end(live ? "All done." : `It can take a minute to switch over. Open ${install.url} shortly.`);
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2331
|
+
// src/commands/install.mjs
|
|
2332
|
+
var TOTAL2 = 8;
|
|
2333
|
+
var NAME_RE = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
2334
|
+
var HOST_RE = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z0-9-]{0,62}$/;
|
|
2335
|
+
async function runInstall(ctx) {
|
|
2336
|
+
const cfg = ctx.cfg;
|
|
2337
|
+
let pending = cfg.pending || null;
|
|
2338
|
+
if (!pending) {
|
|
2339
|
+
const unfinished = cfg.installs.filter((i) => i.setup_token && (!ctx.flags.name || i.name === ctx.flags.name));
|
|
2340
|
+
for (const i of unfinished) {
|
|
2341
|
+
if (await remindSetup(ctx, i, { title: `${i.name} is waiting for you` })) {
|
|
2342
|
+
end(`Already installed at ${i.url}. To set up a separate install, run again with --name <new name>.`);
|
|
2343
|
+
return;
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
if (pending) info(`Picking up where you left off${pending.name ? ` with ${import_picocolors3.default.bold(pending.name)}` : ""}.`);
|
|
2348
|
+
else {
|
|
2349
|
+
line(`This sets up DataBull on your own Cloudflare account. It takes about two minutes.`);
|
|
2350
|
+
line(import_picocolors3.default.dim(`You'll need your license key and a (free) Cloudflare account.`));
|
|
2351
|
+
line();
|
|
2352
|
+
}
|
|
2353
|
+
let key = ctx.flags.key ? checkKey(ctx.flags.key) : pending?.license_key;
|
|
2354
|
+
if (!key) key = await askKey();
|
|
2355
|
+
pending = await withStep(1, TOTAL2, "Checking your license", async () => {
|
|
2356
|
+
if (pending?.license_key === key && pending.instance_id) {
|
|
2357
|
+
if (await validate(ctx, key, pending.instance_id)) return { value: pending, label: "License active", detail: pending.license_email || "" };
|
|
2358
|
+
}
|
|
2359
|
+
const r2 = await activate(ctx, key, instanceName());
|
|
2360
|
+
const next = { ...pending?.license_key === key ? pending : {}, license_key: key, instance_id: r2.instance_id, license_email: r2.email || null, started_at: pending?.started_at || nowIso() };
|
|
2361
|
+
cfg.pending = next;
|
|
2362
|
+
save(ctx);
|
|
2363
|
+
return { value: next, label: "License activated", detail: [r2.customer_name, r2.email].filter(Boolean).join(" \xB7 ") };
|
|
2364
|
+
}).then((r2) => r2.value);
|
|
2365
|
+
const account = await connect(ctx, 2, TOTAL2, { accountId: pending.account_id && !ctx.flags.account ? pending.account_id : void 0 });
|
|
2366
|
+
const subdomain = await withStep(3, TOTAL2, "Checking your Cloudflare account", async (s) => {
|
|
2367
|
+
let sub = await getSubdomain(ctx, account.id);
|
|
2368
|
+
if (sub) return { value: sub, label: "Cloudflare account ready", detail: `${account.name} \xB7 ${sub}.workers.dev` };
|
|
2369
|
+
s.update("Creating your free workers.dev address");
|
|
2370
|
+
sub = await createSubdomain(ctx, account.id, account.name);
|
|
2371
|
+
return { value: sub, label: "Created your workers.dev address", detail: `${sub}.workers.dev` };
|
|
2372
|
+
}).then((r2) => r2.value);
|
|
2373
|
+
const resuming = pending.account_id === account.id && pending.name;
|
|
2374
|
+
const choice = await chooseName(ctx, account, subdomain, resuming ? pending : null);
|
|
2375
|
+
if (choice.update) return handOffToUpdate(ctx, pending, choice.update.install);
|
|
2376
|
+
const { name, d1: existingD1, reuseData } = choice;
|
|
2377
|
+
const customDomain = await chooseAddress(ctx, account, name, subdomain, resuming && pending.name === name ? pending : null);
|
|
2378
|
+
const url = customDomain ? `https://${customDomain}` : workersDevUrl(name, subdomain);
|
|
2379
|
+
Object.assign(pending, { name, account_id: account.id, account_name: account.name, url, custom_domain: customDomain, d1_name: name, domain_failed: false });
|
|
2380
|
+
if (!pending.secrets) pending.secrets = generateSecrets();
|
|
2381
|
+
cfg.pending = pending;
|
|
2382
|
+
save(ctx);
|
|
2383
|
+
await withStep(4, TOTAL2, "Creating your database", async () => {
|
|
2384
|
+
if (existingD1) {
|
|
2385
|
+
Object.assign(pending, { d1_id: existingD1.id, d1_name: existingD1.name, reused_data: !!reuseData });
|
|
2386
|
+
save(ctx);
|
|
2387
|
+
return { label: "Database ready", detail: `${existingD1.name} \xB7 ${reuseData ? "kept your existing data" : "reused"}` };
|
|
2388
|
+
}
|
|
2389
|
+
const db = await createD1(ctx, account.id, name);
|
|
2390
|
+
Object.assign(pending, { d1_id: db.id, d1_name: db.name, reused_data: false });
|
|
2391
|
+
save(ctx);
|
|
2392
|
+
return { label: "Database created", detail: db.name };
|
|
2393
|
+
});
|
|
2394
|
+
const installDir = ctx.paths.installDir(name);
|
|
2395
|
+
const bundle = await withStep(5, TOTAL2, ctx.flags.bundle ? "Reading the DataBull bundle" : "Downloading DataBull", async (s) => {
|
|
2396
|
+
const b3 = await getBundle(ctx, s, { licenseKey: pending.license_key, instanceId: pending.instance_id });
|
|
2397
|
+
writeBundle(installDir, b3.entries);
|
|
2398
|
+
return { value: b3, label: `DataBull ${b3.manifest.version} ready`, detail: ctx.flags.bundle ? "from --bundle" : "checksum verified" };
|
|
2399
|
+
}).then((r2) => r2.value);
|
|
2400
|
+
const version = bundle.manifest.version;
|
|
2401
|
+
const install = {
|
|
2402
|
+
name,
|
|
2403
|
+
account_id: account.id,
|
|
2404
|
+
account_name: account.name,
|
|
2405
|
+
d1_id: pending.d1_id,
|
|
2406
|
+
d1_name: pending.d1_name,
|
|
2407
|
+
url,
|
|
2408
|
+
custom_domain: customDomain,
|
|
2409
|
+
license_key: pending.license_key,
|
|
2410
|
+
instance_id: pending.instance_id,
|
|
2411
|
+
license_email: pending.license_email,
|
|
2412
|
+
version,
|
|
2413
|
+
installed_at: pending.started_at || nowIso(),
|
|
2414
|
+
updated_at: nowIso()
|
|
2415
|
+
};
|
|
2416
|
+
const configFile = writeWranglerConfig(installDir, buildWranglerConfig({ name, manifest: bundle.manifest, d1Name: install.d1_name, d1Id: install.d1_id, url, api: ctx.api }));
|
|
2417
|
+
await withStep(6, TOTAL2, "Setting up the database tables", async () => {
|
|
2418
|
+
await applyMigrations(ctx, install, configFile);
|
|
2419
|
+
return { label: "Database tables ready" };
|
|
2420
|
+
});
|
|
2421
|
+
await withStep(7, TOTAL2, "Deploying to Cloudflare", async (s) => {
|
|
2422
|
+
await deployWorker(ctx, install, configFile, { ...pending.secrets, LICENSE_KEY: install.license_key, LICENSE_INSTANCE: install.instance_id });
|
|
2423
|
+
pending.deployed = true;
|
|
2424
|
+
save(ctx);
|
|
2425
|
+
if (customDomain) {
|
|
2426
|
+
s.update(`Connecting ${customDomain}`);
|
|
2427
|
+
try {
|
|
2428
|
+
await attachCustomDomain(ctx, account.id, name, customDomain);
|
|
2429
|
+
} catch (err) {
|
|
2430
|
+
pending.domain_failed = true;
|
|
2431
|
+
save(ctx);
|
|
2432
|
+
throw err;
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
s.update("Saving instance settings");
|
|
2436
|
+
await d1Execute(ctx, install, configFile, instanceSettingsSql({ licenseEmail: install.license_email, installedAt: install.installed_at, installName: name }), "instance settings");
|
|
2437
|
+
return { label: "Deployed", detail: customDomain ? `${customDomain} + ${name}.${subdomain}.workers.dev` : url.replace("https://", "") };
|
|
2438
|
+
});
|
|
2439
|
+
const reusedData = !!pending.reused_data;
|
|
2440
|
+
if (!reusedData) install.setup_token = pending.secrets.SETUP_TOKEN;
|
|
2441
|
+
upsertInstall(cfg, install);
|
|
2442
|
+
delete cfg.pending;
|
|
2443
|
+
save(ctx);
|
|
2444
|
+
if (!reusedData) {
|
|
2445
|
+
line(import_picocolors3.default.dim("Your setup link (shown again at the end, and any time with `status`):"));
|
|
2446
|
+
line(import_picocolors3.default.dim(setupLink(install)));
|
|
2447
|
+
}
|
|
2448
|
+
const live = await waitLive(ctx, 8, TOTAL2, url, version, !!customDomain);
|
|
2449
|
+
const target = reusedData ? url : setupLink(install);
|
|
2450
|
+
const opened = live && ctx.flags.open && openUrl(target);
|
|
2451
|
+
const lines = [import_picocolors3.default.bold(import_picocolors3.default.cyan(url)), ""];
|
|
2452
|
+
if (reusedData) lines.push("Your existing data is kept. Log in with your usual email and password.", import_picocolors3.default.dim("Payment connections and Telegram alerts need their keys entered again."));
|
|
2453
|
+
else lines.push(opened ? "Finish in the browser tab that just opened: create your login." : "Open this link to create your login:", import_picocolors3.default.cyan(target));
|
|
2454
|
+
lines.push("", `${import_picocolors3.default.dim("Update later")} ${CMD} update`, `${import_picocolors3.default.dim("Forgot password")} ${CMD} reset-password`, `${import_picocolors3.default.dim("Own domain")} ${CMD} domain`, `${import_picocolors3.default.dim("Docs")} ${DOCS}`);
|
|
2455
|
+
box(live ? "DataBull is live" : "DataBull is deployed", lines, { color: live ? import_picocolors3.default.green : import_picocolors3.default.yellow });
|
|
2456
|
+
end(live ? "All done. Enjoy your numbers." : `Give it a few minutes, then open ${url}.`);
|
|
2457
|
+
}
|
|
2458
|
+
async function chooseName(ctx, account, subdomain, pending) {
|
|
2459
|
+
let name = ctx.flags.name || pending?.name || null;
|
|
2460
|
+
const fromFlag = !!ctx.flags.name;
|
|
2461
|
+
if (!name && (ctx.flags.yes || !canPrompt())) name = "databull";
|
|
2462
|
+
for (; ; ) {
|
|
2463
|
+
if (!name) {
|
|
2464
|
+
name = await askText({
|
|
2465
|
+
message: "Name for your DataBull Worker",
|
|
2466
|
+
placeholder: "databull",
|
|
2467
|
+
defaultValue: "databull",
|
|
2468
|
+
validate: (v2) => v2 && !NAME_RE.test(v2.trim()) ? "Use lowercase letters, numbers and dashes (max 63)." : void 0,
|
|
2469
|
+
flag: "--name <worker>"
|
|
2470
|
+
});
|
|
2471
|
+
name = (name || "databull").trim();
|
|
2472
|
+
}
|
|
2473
|
+
if (!NAME_RE.test(name)) throw new Fail(`"${name}" can't be a Worker name.`, { fix: "Use lowercase letters, numbers and dashes, up to 63 characters." });
|
|
2474
|
+
const verdict = await withStep(null, null, `Checking "${name}"`, async () => {
|
|
2475
|
+
const exists = await workerExists(ctx, account.id, name);
|
|
2476
|
+
const d1 = await findD1(ctx, account.id, name);
|
|
2477
|
+
let users = 0;
|
|
2478
|
+
if (d1) {
|
|
2479
|
+
const rows = await d1Query(ctx, account.id, d1.id, "SELECT COUNT(*) AS n FROM users");
|
|
2480
|
+
users = rows?.[0]?.n || 0;
|
|
2481
|
+
}
|
|
2482
|
+
const label = pending?.name === name ? `Continuing with "${name}"` : exists ? `A Worker named "${name}" already exists` : `"${name}" is free`;
|
|
2483
|
+
return { value: { exists, d1, users }, label, detail: d1 && !exists ? `database "${name}" exists${users ? " with data" : ""}` : "" };
|
|
2484
|
+
}).then((r2) => r2.value);
|
|
2485
|
+
if (pending?.name === name && (!verdict.d1 || pending.d1_id === verdict.d1.id)) {
|
|
2486
|
+
return { name, d1: verdict.d1, reuseData: !!pending.reused_data };
|
|
2487
|
+
}
|
|
2488
|
+
if (verdict.exists) {
|
|
2489
|
+
const known = findInstall(ctx.cfg, name, account.id);
|
|
2490
|
+
const ours = known || (await discoverInstalls(ctx, account, name))[0];
|
|
2491
|
+
if (ours) {
|
|
2492
|
+
line(`${import_picocolors3.default.bold(name)} is already a DataBull install${ours.url ? ` (${ours.url.replace("https://", "")})` : ""}.`);
|
|
2493
|
+
const go = ctx.flags.yes || await askConfirm({ message: "Update it to the latest version instead?", flag: "--yes" });
|
|
2494
|
+
if (go) return { update: { install: ours, discovered: !known } };
|
|
2495
|
+
} else warn(`"${name}" is a Worker on this account that isn't DataBull, so it's left alone.`);
|
|
2496
|
+
if (fromFlag || !canPrompt()) throw new Fail(`A Worker named "${name}" already exists on this account.`, { fix: "Choose another name with --name." });
|
|
2497
|
+
name = null;
|
|
2498
|
+
continue;
|
|
2499
|
+
}
|
|
2500
|
+
if (verdict.d1 && verdict.users > 0) {
|
|
2501
|
+
warn(`A database named "${name}" already holds DataBull data (an earlier install?).`);
|
|
2502
|
+
let reuse = !!ctx.flags.yes;
|
|
2503
|
+
if (!reuse) {
|
|
2504
|
+
if (!canPrompt()) throw new Fail(`The database "${name}" already holds data.`, { fix: "Pass --yes to reuse it (keeps your data), or choose another name with --name." });
|
|
2505
|
+
reuse = await askSelect({
|
|
2506
|
+
message: "What should happen to it?",
|
|
2507
|
+
options: [
|
|
2508
|
+
{ value: true, label: "Reuse it", hint: "keeps your sites, visitors and revenue" },
|
|
2509
|
+
{ value: false, label: "Pick a different name", hint: "starts fresh, leaves that database alone" }
|
|
2510
|
+
]
|
|
2511
|
+
});
|
|
2512
|
+
}
|
|
2513
|
+
if (!reuse) {
|
|
2514
|
+
if (fromFlag) throw new Fail(`Not reusing "${name}".`, { fix: "Run again with a different --name." });
|
|
2515
|
+
name = null;
|
|
2516
|
+
continue;
|
|
2517
|
+
}
|
|
2518
|
+
return { name, d1: verdict.d1, reuseData: true };
|
|
2519
|
+
}
|
|
2520
|
+
return { name, d1: verdict.d1, reuseData: false };
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
async function chooseAddress(ctx, account, name, subdomain, pending) {
|
|
2524
|
+
const devUrl = workersDevUrl(name, subdomain);
|
|
2525
|
+
let host;
|
|
2526
|
+
if (ctx.flags.domain) host = ctx.flags.domain.trim().toLowerCase();
|
|
2527
|
+
else if (pending && "custom_domain" in pending && !(pending.domain_failed && canPrompt())) return pending.custom_domain;
|
|
2528
|
+
let zones = null;
|
|
2529
|
+
const loadZones = async () => zones ??= await activeZones(ctx, account.id);
|
|
2530
|
+
if (!host) {
|
|
2531
|
+
await loadZones();
|
|
2532
|
+
if (!zones.length || !canPrompt()) return null;
|
|
2533
|
+
const pick = await askSelect({
|
|
2534
|
+
message: "Where should DataBull live?",
|
|
2535
|
+
options: [
|
|
2536
|
+
{ value: "dev", label: devUrl.replace("https://", ""), hint: "free, works right away" },
|
|
2537
|
+
{ value: "custom", label: "A subdomain on one of my domains", hint: `e.g. analytics.${zones[0].name}` }
|
|
2538
|
+
],
|
|
2539
|
+
flag: "--domain <host|workers.dev>"
|
|
2540
|
+
});
|
|
2541
|
+
if (pick === "dev") return null;
|
|
2542
|
+
const zone2 = zones.length === 1 ? zones[0].name : await askSelect({ message: "Which domain?", options: zones.map((z2) => ({ value: z2.name, label: z2.name })), maxItems: 10, flag: "--domain <host>" });
|
|
2543
|
+
host = (await askText({
|
|
2544
|
+
message: "Full address",
|
|
2545
|
+
placeholder: `analytics.${zone2}`,
|
|
2546
|
+
defaultValue: `analytics.${zone2}`,
|
|
2547
|
+
validate: (v2) => {
|
|
2548
|
+
const h2 = (v2 || `analytics.${zone2}`).trim().toLowerCase();
|
|
2549
|
+
if (!HOST_RE.test(h2)) return "That doesn\u2019t look like a hostname.";
|
|
2550
|
+
if (h2 !== zone2 && !h2.endsWith(`.${zone2}`)) return `It has to end in .${zone2}`;
|
|
2551
|
+
if (isSiteAddress(h2, zone2)) return `That's usually your website. Use a subdomain like analytics.${zone2}.`;
|
|
2552
|
+
}
|
|
2553
|
+
})).trim().toLowerCase();
|
|
2554
|
+
}
|
|
2555
|
+
if (host === "workers.dev" || host === "workers-dev" || host === devUrl.replace("https://", "")) return null;
|
|
2556
|
+
host = host.replace(/^https?:\/\//, "").replace(/\/.*$/, "");
|
|
2557
|
+
if (!HOST_RE.test(host)) throw new Fail(`"${host}" doesn't look like a hostname.`, { fix: "Example: --domain analytics.example.com (or --domain workers.dev)." });
|
|
2558
|
+
await loadZones();
|
|
2559
|
+
const zone = zones.filter((z2) => host === z2.name || host.endsWith(`.${z2.name}`)).sort((a, b3) => b3.name.length - a.name.length)[0];
|
|
2560
|
+
if (!zone)
|
|
2561
|
+
throw new Fail(`${host} isn't on this Cloudflare account.`, {
|
|
2562
|
+
fix: zones.length ? `Use a subdomain of: ${zones.map((z2) => z2.name).join(", ")}. Or pass --domain workers.dev.` : "Add your domain to Cloudflare first, or pass --domain workers.dev."
|
|
2563
|
+
});
|
|
2564
|
+
if (isSiteAddress(host, zone.name))
|
|
2565
|
+
throw new Fail(`DataBull needs its own subdomain, not ${host}.`, { fix: `${host} is usually your website. Use something like --domain analytics.${zone.name}.` });
|
|
2566
|
+
const owner = await customDomainOwner(ctx, account.id, host);
|
|
2567
|
+
if (owner && owner !== name) throw new Fail(`${host} is already used by the Worker "${owner}".`, { fix: "Pick a different subdomain, or detach it from that Worker in the Cloudflare dashboard." });
|
|
2568
|
+
return host;
|
|
2569
|
+
}
|
|
2570
|
+
var isSiteAddress = (host, zone) => host === zone || host === `www.${zone}`;
|
|
2571
|
+
async function waitLive(ctx, n, total, url, version, custom) {
|
|
2572
|
+
const s = step(n, total, `Waiting for ${url.replace("https://", "")} to come online`);
|
|
2573
|
+
const ok = await waitForHealth(url, version, {
|
|
2574
|
+
onWait(ms) {
|
|
2575
|
+
if (ms > 3e4) s.update(`Still waiting for ${url.replace("https://", "")}. ${custom ? "New custom domains need a certificate; this can take a few minutes." : "Fresh workers.dev addresses can take a few minutes."}`);
|
|
2576
|
+
}
|
|
2577
|
+
});
|
|
2578
|
+
if (ok) s.done("Live", url.replace("https://", ""));
|
|
2579
|
+
else s.warn(`${url.replace("https://", "")} isn't answering yet`, "it usually appears within a few minutes");
|
|
2580
|
+
return ok;
|
|
2581
|
+
}
|
|
2582
|
+
async function handOffToUpdate(ctx, pending, install) {
|
|
2583
|
+
if (!install.license_key || !install.instance_id) {
|
|
2584
|
+
Object.assign(install, { license_key: pending.license_key, instance_id: pending.instance_id, license_email: pending.license_email });
|
|
2585
|
+
} else if (pending.instance_id && pending.instance_id !== install.instance_id) {
|
|
2586
|
+
await deactivate(ctx, pending.license_key, pending.instance_id).catch(() => ctx.log.write("could not release the extra activation"));
|
|
2587
|
+
}
|
|
2588
|
+
delete ctx.cfg.pending;
|
|
2589
|
+
save(ctx);
|
|
2590
|
+
line();
|
|
2591
|
+
return runUpdate(ctx, { install });
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
// src/commands/reset.mjs
|
|
2595
|
+
async function runReset(ctx) {
|
|
2596
|
+
let install = await pickInstall(ctx, { verb: "reset the password for" });
|
|
2597
|
+
const total = 2;
|
|
2598
|
+
const account = await connect(ctx, 1, total, { accountId: install?.account_id });
|
|
2599
|
+
if (!install) {
|
|
2600
|
+
const found = await discoverInstalls(ctx, account, ctx.flags.name);
|
|
2601
|
+
if (!found.length) throw new Fail(`There's no DataBull install on ${account.name}.`, { fix: `Install it with \`${CMD}\`.` });
|
|
2602
|
+
install = await pickDiscovered(ctx, found, "reset the password for");
|
|
2603
|
+
}
|
|
2604
|
+
const { token, hash } = resetToken();
|
|
2605
|
+
const configFile = ensureD1Config(ctx.paths.installDir(install.name), install);
|
|
2606
|
+
const changes = await withStep(2, total, "Creating a reset link", async () => {
|
|
2607
|
+
const r2 = await d1Execute(ctx, install, configFile, resetSql(hash), "password reset");
|
|
2608
|
+
return { value: r2.changes, label: r2.changes === 0 ? "No account to reset yet" : "Reset link ready", detail: r2.changes === 0 ? "" : "valid for 30 minutes" };
|
|
2609
|
+
}).then((r2) => r2.value);
|
|
2610
|
+
if (changes === 0) {
|
|
2611
|
+
if (!install.setup_token) {
|
|
2612
|
+
await withStep(null, null, "Making a new setup link", async () => {
|
|
2613
|
+
const token2 = b64url(24);
|
|
2614
|
+
await putSecrets(ctx, install, configFile, { SETUP_TOKEN: token2 });
|
|
2615
|
+
install.setup_token = token2;
|
|
2616
|
+
upsertInstall(ctx.cfg, { ...install, installed_at: install.installed_at || (/* @__PURE__ */ new Date()).toISOString() });
|
|
2617
|
+
save(ctx);
|
|
2618
|
+
return { label: "New setup link ready" };
|
|
2619
|
+
});
|
|
2620
|
+
}
|
|
2621
|
+
const opened2 = ctx.flags.open && openUrl(setupLink(install));
|
|
2622
|
+
showSetupBox(install, { title: "No login yet", note: opened2 ? "Opened in your browser. Nothing to reset: create your login there." : "Nothing to reset yet: create your login with this link." });
|
|
2623
|
+
end("Done.");
|
|
2624
|
+
return;
|
|
2625
|
+
}
|
|
2626
|
+
const link = `${install.url}/reset-password?token=${token}`;
|
|
2627
|
+
const opened = ctx.flags.open && openUrl(link);
|
|
2628
|
+
box("Reset your password", [opened ? "Opened in your browser. If it didn\u2019t open, use this link:" : "Open this link to choose a new password:", import_picocolors3.default.cyan(link), "", import_picocolors3.default.dim("Works once, for 30 minutes. Your data is untouched.")], { color: import_picocolors3.default.green });
|
|
2629
|
+
end("Done.");
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
// src/commands/domain.mjs
|
|
2633
|
+
var TOTAL3 = 4;
|
|
2634
|
+
async function runDomain(ctx) {
|
|
2635
|
+
const install = await pickInstall(ctx, { verb: "change the address of" });
|
|
2636
|
+
if (!install) throw new Fail("No DataBull installs on this computer.", { fix: `Install with \`${CMD}\`, or run \`${CMD} update\` once to find an existing install.` });
|
|
2637
|
+
const account = await connect(ctx, 1, TOTAL3, { accountId: install.account_id });
|
|
2638
|
+
line(`${install.name} lives at ${import_picocolors3.default.cyan(install.url)}`);
|
|
2639
|
+
const subdomain = await getSubdomain(ctx, account.id);
|
|
2640
|
+
if (!subdomain) throw new Fail("This Cloudflare account has no workers.dev address.", { fix: `Run \`${CMD}\` once; it creates one.` });
|
|
2641
|
+
const host = await chooseAddress(ctx, account, install.name, subdomain, null);
|
|
2642
|
+
if ((host || null) === (install.custom_domain || null)) {
|
|
2643
|
+
end(`${install.name} already uses ${host || "its workers.dev address"}. Nothing to change.`);
|
|
2644
|
+
return;
|
|
2645
|
+
}
|
|
2646
|
+
const url = host ? `https://${host}` : workersDevUrl(install.name, subdomain);
|
|
2647
|
+
const installDir = ctx.paths.installDir(install.name);
|
|
2648
|
+
const health = await fetchHealth(install.url);
|
|
2649
|
+
const liveVersion = health?.version || null;
|
|
2650
|
+
const want = liveVersion || install.version || null;
|
|
2651
|
+
let manifest = readLocalManifest(installDir);
|
|
2652
|
+
let licenseChanged = false;
|
|
2653
|
+
await withStep(2, TOTAL3, "Preparing DataBull", async (s) => {
|
|
2654
|
+
if (manifest && want && manifest.version === want) return { label: `Using DataBull ${manifest.version}`, detail: "already on this computer" };
|
|
2655
|
+
const lic = await ensureLicense(ctx, install, s);
|
|
2656
|
+
licenseChanged = lic.changed;
|
|
2657
|
+
let b3;
|
|
2658
|
+
try {
|
|
2659
|
+
b3 = await getBundle(ctx, s, { licenseKey: install.license_key, instanceId: install.instance_id, release: want && !ctx.flags.bundle ? { version: want } : void 0 });
|
|
2660
|
+
} catch (err) {
|
|
2661
|
+
if (err?.code !== "release_missing" || !want) throw err;
|
|
2662
|
+
ctx.log.write(`release ${want} missing; falling back to the latest (migrations will run)`);
|
|
2663
|
+
b3 = await getBundle(ctx, s, { licenseKey: install.license_key, instanceId: install.instance_id });
|
|
2664
|
+
}
|
|
2665
|
+
writeBundle(installDir, b3.entries);
|
|
2666
|
+
manifest = b3.manifest;
|
|
2667
|
+
return { label: `DataBull ${manifest.version} ready`, detail: manifest.version === liveVersion ? "same version as live" : "" };
|
|
2668
|
+
});
|
|
2669
|
+
const next = { ...install, custom_domain: host, url };
|
|
2670
|
+
const configFile = writeWranglerConfig(installDir, buildWranglerConfig({ name: next.name, manifest, d1Name: next.d1_name, d1Id: next.d1_id, url, api: ctx.api }));
|
|
2671
|
+
if (manifest.version !== liveVersion) {
|
|
2672
|
+
await withStep(null, null, "Updating the database", async () => {
|
|
2673
|
+
await applyMigrations(ctx, next, configFile);
|
|
2674
|
+
return { label: "Database up to date" };
|
|
2675
|
+
});
|
|
2676
|
+
}
|
|
2677
|
+
await withStep(3, TOTAL3, host ? `Connecting ${host}` : "Switching to workers.dev", async (s) => {
|
|
2678
|
+
if (host) await attachCustomDomain(ctx, account.id, next.name, host);
|
|
2679
|
+
s.update("Deploying");
|
|
2680
|
+
await deployWorker(ctx, next, configFile, licenseChanged ? { LICENSE_KEY: next.license_key, LICENSE_INSTANCE: next.instance_id } : null);
|
|
2681
|
+
if (install.custom_domain && install.custom_domain !== host) {
|
|
2682
|
+
s.update(`Detaching ${install.custom_domain}`);
|
|
2683
|
+
await detachCustomDomain(ctx, account.id, next.name, install.custom_domain).catch((err) => ctx.log.write(`detach ${install.custom_domain} failed: ${err.message}`));
|
|
2684
|
+
}
|
|
2685
|
+
return { label: "Deployed", detail: host || url.replace("https://", "") };
|
|
2686
|
+
});
|
|
2687
|
+
Object.assign(install, { custom_domain: host, url, version: manifest.version, updated_at: nowIso() });
|
|
2688
|
+
upsertInstall(ctx.cfg, install);
|
|
2689
|
+
save(ctx);
|
|
2690
|
+
const live = await waitLive(ctx, 4, TOTAL3, url, manifest.version, !!host);
|
|
2691
|
+
const lines = [import_picocolors3.default.cyan(url)];
|
|
2692
|
+
if (host) lines.push("", "Update the tracking snippet on your sites to load from here.", import_picocolors3.default.dim(`The old address keeps working: ${workersDevUrl(install.name, subdomain).replace("https://", "")}`));
|
|
2693
|
+
box(live ? "New address is live" : "New address is deployed", lines, { color: live ? import_picocolors3.default.green : import_picocolors3.default.yellow });
|
|
2694
|
+
end(live ? "Done." : "New certificates can take a few minutes. Try the address again shortly.");
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
// src/commands/status.mjs
|
|
2698
|
+
async function runStatus(ctx) {
|
|
2699
|
+
const installs = ctx.flags.name ? ctx.cfg.installs.filter((i) => i.name === ctx.flags.name) : ctx.cfg.installs;
|
|
2700
|
+
if (!installs.length) {
|
|
2701
|
+
line(ctx.flags.name ? `No install named "${ctx.flags.name}" on this computer.` : "No DataBull installs on this computer yet.");
|
|
2702
|
+
if (ctx.cfg.pending) line(`An install was started and didn't finish. Run \`${CMD}\` to pick it up.`);
|
|
2703
|
+
end(`Install with \`${CMD}\`. Installed on another computer? \`${CMD} update\` finds it.`);
|
|
2704
|
+
return;
|
|
2705
|
+
}
|
|
2706
|
+
const s = step(null, null, "Checking your installs");
|
|
2707
|
+
const [latest, health, setup] = await Promise.all([
|
|
2708
|
+
latestRelease(ctx).catch(() => null),
|
|
2709
|
+
Promise.all(installs.map((i) => fetchHealth(i.url))),
|
|
2710
|
+
Promise.all(installs.map((i) => setupPending(ctx, i)))
|
|
2711
|
+
]);
|
|
2712
|
+
s.done(latest ? `Latest DataBull is ${latest.version}` : "Checked your installs", latest ? "" : "couldn't reach databull.app for the latest version");
|
|
2713
|
+
installs.forEach((i, idx) => {
|
|
2714
|
+
const h2 = health[idx];
|
|
2715
|
+
const version = h2?.version || i.version || "?";
|
|
2716
|
+
const behind = latest && h2?.version && compareVersions(h2.version, latest.version) < 0;
|
|
2717
|
+
const state = h2 ? behind ? import_picocolors3.default.yellow(`${version} \xB7 update available`) : import_picocolors3.default.green(`${version} \xB7 online`) : import_picocolors3.default.red("not answering");
|
|
2718
|
+
box(i.name, [
|
|
2719
|
+
`${import_picocolors3.default.dim("Address ")} ${import_picocolors3.default.cyan(i.url)}`,
|
|
2720
|
+
`${import_picocolors3.default.dim("Status ")} ${state}`,
|
|
2721
|
+
`${import_picocolors3.default.dim("Account ")} ${i.account_name || i.account_id}`,
|
|
2722
|
+
`${import_picocolors3.default.dim("Database")} ${i.d1_name}`,
|
|
2723
|
+
`${import_picocolors3.default.dim("License ")} ${maskKey(i.license_key)}${i.license_email ? ` \xB7 ${i.license_email}` : ""}`,
|
|
2724
|
+
...setup[idx] ? ["", import_picocolors3.default.yellow("No login yet. Create it here:"), import_picocolors3.default.cyan(setupLink(i))] : []
|
|
2725
|
+
], { color: h2 ? behind ? import_picocolors3.default.yellow : import_picocolors3.default.green : import_picocolors3.default.red });
|
|
2726
|
+
});
|
|
2727
|
+
const anyBehind = latest && installs.some((i, idx) => health[idx]?.version && compareVersions(health[idx].version, latest.version) < 0);
|
|
2728
|
+
const anyDown = health.some((h2) => !h2);
|
|
2729
|
+
const anySetup = setup.some(Boolean);
|
|
2730
|
+
end(anySetup ? "Open the setup link above to create your login." : anyBehind ? `Run \`${CMD} update\` to get ${latest.version}.` : anyDown ? "An install isn\u2019t answering. Check it in the Cloudflare dashboard, or run update to redeploy." : "Everything looks good.");
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
// src/commands/license.mjs
|
|
2734
|
+
async function runLicense(ctx) {
|
|
2735
|
+
const install = await pickInstall(ctx, { verb: "see the license of" });
|
|
2736
|
+
if (!install) throw new Fail("No DataBull installs on this computer.", { fix: `Your key is in your receipt email. Install with \`${CMD}\`.` });
|
|
2737
|
+
if (!install.license_key || !install.instance_id) {
|
|
2738
|
+
box(install.name, [`${import_picocolors3.default.dim("License")} not activated on this computer`], { color: import_picocolors3.default.yellow });
|
|
2739
|
+
end(`Run \`${CMD} update\` to activate it.`);
|
|
2740
|
+
return;
|
|
2741
|
+
}
|
|
2742
|
+
const s = step(null, null, "Checking your license");
|
|
2743
|
+
const valid = await validate(ctx, install.license_key, install.instance_id).catch((err) => {
|
|
2744
|
+
ctx.log.write(`validate failed: ${err.message}`);
|
|
2745
|
+
return null;
|
|
2746
|
+
});
|
|
2747
|
+
if (valid === null) s.warn("Couldn't check with databull.app right now");
|
|
2748
|
+
else s.done(valid ? "License active" : "This activation is no longer valid");
|
|
2749
|
+
box(install.name, [
|
|
2750
|
+
`${import_picocolors3.default.dim("Key ")} ${maskKey(install.license_key)}`,
|
|
2751
|
+
`${import_picocolors3.default.dim("Activation")} ${install.instance_id}`,
|
|
2752
|
+
`${import_picocolors3.default.dim("Email ")} ${install.license_email || "\u2014"}`,
|
|
2753
|
+
`${import_picocolors3.default.dim("Address ")} ${install.url}`
|
|
2754
|
+
], { color: valid === false ? import_picocolors3.default.yellow : import_picocolors3.default.blue });
|
|
2755
|
+
if (!ctx.flags.deactivate) {
|
|
2756
|
+
end(`To free this activation (e.g. moving to a new Cloudflare account): \`${CMD} license --deactivate\``);
|
|
2757
|
+
return;
|
|
2758
|
+
}
|
|
2759
|
+
if (!ctx.flags.yes) {
|
|
2760
|
+
const ok = await askConfirm({
|
|
2761
|
+
message: `Free this activation? ${install.name} keeps running, but updates need the key again.`,
|
|
2762
|
+
initialValue: false,
|
|
2763
|
+
flag: "--yes"
|
|
2764
|
+
});
|
|
2765
|
+
if (!ok) {
|
|
2766
|
+
end("No changes made.");
|
|
2767
|
+
return;
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
await withStep(null, null, "Freeing the activation", async () => {
|
|
2771
|
+
await deactivate(ctx, install.license_key, install.instance_id);
|
|
2772
|
+
install.instance_id = null;
|
|
2773
|
+
upsertInstall(ctx.cfg, install);
|
|
2774
|
+
save(ctx);
|
|
2775
|
+
return { label: "Activation freed" };
|
|
2776
|
+
});
|
|
2777
|
+
end("Done. You can use the key on another install now.");
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2780
|
+
// src/commands/uninstall.mjs
|
|
2781
|
+
import fs7 from "node:fs";
|
|
2782
|
+
async function runUninstall(ctx) {
|
|
2783
|
+
const install = await pickInstall(ctx, { verb: "uninstall" });
|
|
2784
|
+
if (!install) throw new Fail("No DataBull installs on this computer.", { fix: "Delete it in the Cloudflare dashboard (Workers & Pages), or pass --name for an install this computer made." });
|
|
2785
|
+
warn(`This deletes the Worker ${import_picocolors3.default.bold(install.name)} (${install.url}). Your sites stop sending data to it.`);
|
|
2786
|
+
if (!ctx.flags.yes) {
|
|
2787
|
+
await askText({
|
|
2788
|
+
message: `Type ${import_picocolors3.default.bold(install.name)} to confirm`,
|
|
2789
|
+
validate: (v2) => v2?.trim() === install.name ? void 0 : `Type ${install.name} exactly, or press Ctrl-C to stop.`,
|
|
2790
|
+
flag: "--yes"
|
|
2791
|
+
});
|
|
2792
|
+
}
|
|
2793
|
+
let dropDb = !!ctx.flags.deleteDatabase;
|
|
2794
|
+
if (!dropDb && !ctx.flags.yes && canPrompt()) {
|
|
2795
|
+
dropDb = await askConfirm({
|
|
2796
|
+
message: `Also delete the database "${install.d1_name}"? This erases all visitors, sites and revenue history for good.`,
|
|
2797
|
+
initialValue: false
|
|
2798
|
+
});
|
|
2799
|
+
}
|
|
2800
|
+
const total = dropDb ? 4 : 3;
|
|
2801
|
+
const account = await connect(ctx, 1, total, { accountId: install.account_id });
|
|
2802
|
+
await withStep(2, total, "Deleting the Worker", async () => {
|
|
2803
|
+
const gone = await deleteWorker(ctx, account.id, install.name);
|
|
2804
|
+
return { label: gone ? "Worker deleted" : "Worker was already gone" };
|
|
2805
|
+
});
|
|
2806
|
+
let n = 3;
|
|
2807
|
+
if (dropDb) {
|
|
2808
|
+
await withStep(n++, total, "Deleting the database", async () => {
|
|
2809
|
+
const gone = await deleteD1(ctx, account.id, install.d1_id);
|
|
2810
|
+
return { label: gone ? "Database deleted" : "Database was already gone" };
|
|
2811
|
+
});
|
|
2812
|
+
}
|
|
2813
|
+
await withStep(n, total, "Freeing the license activation", async (s) => {
|
|
2814
|
+
if (!install.license_key || !install.instance_id) return { label: "No license activation to free" };
|
|
2815
|
+
try {
|
|
2816
|
+
await deactivate(ctx, install.license_key, install.instance_id);
|
|
2817
|
+
return { label: "License activation freed" };
|
|
2818
|
+
} catch (err) {
|
|
2819
|
+
s.warn("Couldn't free the license activation", `email adgan.business@gmail.com if you need it back`);
|
|
2820
|
+
ctx.log.write(`deactivate failed: ${err.message}`);
|
|
2821
|
+
return {};
|
|
2822
|
+
}
|
|
2823
|
+
});
|
|
2824
|
+
removeInstall(ctx.cfg, install);
|
|
2825
|
+
save(ctx);
|
|
2826
|
+
fs7.rmSync(ctx.paths.installDir(install.name), { recursive: true, force: true });
|
|
2827
|
+
box("Uninstalled", [
|
|
2828
|
+
`${install.name} is gone from ${install.account_name || "your account"}.`,
|
|
2829
|
+
dropDb ? "The database was deleted too." : `Your data is kept in the D1 database "${install.d1_name}".`,
|
|
2830
|
+
dropDb ? "" : import_picocolors3.default.dim(`Reinstall with \`${CMD}\` and the same name to pick it up again.`)
|
|
2831
|
+
].filter(Boolean), { color: import_picocolors3.default.gray });
|
|
2832
|
+
end("Done.");
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2835
|
+
// src/agent.mjs
|
|
2836
|
+
import os4 from "node:os";
|
|
2837
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
2838
|
+
var MIN_AGENT_VERSION = "1.1.0";
|
|
2839
|
+
var KEY_RE = /^dbk_[a-z0-9]{40}$/;
|
|
2840
|
+
var ENV_FIX = "Or set DATABULL_URL and DATABULL_API_KEY (create a key in your dashboard under Settings \u2192 API keys).";
|
|
2841
|
+
var cleanUrl = (u2) => String(u2).trim().replace(/\/+$/, "").replace(/\/mcp$/, "");
|
|
2842
|
+
function endpoint(url) {
|
|
2843
|
+
const mock = process.env.DATABULL_TEST_HEALTH_URL;
|
|
2844
|
+
return mock ? `${mock}/mcp?host=${encodeURIComponent(new URL(url).host)}` : `${url}/mcp`;
|
|
2845
|
+
}
|
|
2846
|
+
async function rpc(conn, method, params = {}, { timeoutMs = 45e3 } = {}) {
|
|
2847
|
+
let res;
|
|
2848
|
+
try {
|
|
2849
|
+
res = await fetchWithTimeout(endpoint(conn.url), {
|
|
2850
|
+
method: "POST",
|
|
2851
|
+
headers: { "Content-Type": "application/json", Accept: "application/json, text/event-stream", Authorization: `Bearer ${conn.key}`, "User-Agent": "databull-cli" },
|
|
2852
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params })
|
|
2853
|
+
}, timeoutMs);
|
|
2854
|
+
} catch {
|
|
2855
|
+
throw new Fail(`Couldn't reach ${conn.url}.`, { fix: "Check the address and your internet connection, then try again." });
|
|
2856
|
+
}
|
|
2857
|
+
const type = res.headers.get("content-type") || "";
|
|
2858
|
+
if (res.status === 401) {
|
|
2859
|
+
const body2 = await res.json().catch(() => null);
|
|
2860
|
+
throw new Fail(body2?.error?.message || "The API key was refused.", {
|
|
2861
|
+
fix: conn.install ? "Run the command again to create a new key for this computer." : "Check that DATABULL_API_KEY belongs to this address, or create a new key under Settings \u2192 API keys.",
|
|
2862
|
+
code: "unauthorized"
|
|
2863
|
+
});
|
|
2864
|
+
}
|
|
2865
|
+
if (!type.includes("application/json")) {
|
|
2866
|
+
const health = await fetchHealth(conn.url);
|
|
2867
|
+
if (!health) throw new Fail(`${conn.url} isn't a DataBull install.`, { fix: "Check the address: it's the one you open your dashboard at." });
|
|
2868
|
+
throw new Fail(`${conn.url} runs DataBull ${health.version}; the API needs ${MIN_AGENT_VERSION} or newer.`, { fix: "Update it with `npx databull@latest update`, then try again.", code: "too_old" });
|
|
2869
|
+
}
|
|
2870
|
+
const body = await res.json().catch(() => null);
|
|
2871
|
+
if (!body) throw new Fail(`${conn.url} sent an answer the CLI couldn't read.`, { fix: "Try again in a moment." });
|
|
2872
|
+
if (body.error) throw new Fail(body.error.message || "The request failed.");
|
|
2873
|
+
return body.result;
|
|
2874
|
+
}
|
|
2875
|
+
async function callTool(conn, name, args = {}) {
|
|
2876
|
+
const r2 = await rpc(conn, "tools/call", { name, arguments: args });
|
|
2877
|
+
if (r2?.isError) throw new Fail(r2.content?.[0]?.text || `${name} failed.`);
|
|
2878
|
+
if (r2?.structuredContent) return r2.structuredContent;
|
|
2879
|
+
try {
|
|
2880
|
+
return JSON.parse(r2.content[0].text);
|
|
2881
|
+
} catch {
|
|
2882
|
+
throw new Fail(`${name} answered in a shape the CLI doesn't understand.`, { fix: "Update the CLI: `npx databull@latest`." });
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
function newApiKey() {
|
|
2886
|
+
const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
2887
|
+
let s = "";
|
|
2888
|
+
while (s.length < 40) for (const b3 of randomBytes2(64)) if (b3 < 252 && s.length < 40) s += alphabet[b3 % 36];
|
|
2889
|
+
return `dbk_${s}`;
|
|
2890
|
+
}
|
|
2891
|
+
var sqlText = (s) => `'${String(s).replace(/[^\w .@()+#:/-]/g, "").slice(0, 60).replace(/'/g, "''")}'`;
|
|
2892
|
+
var hostName = () => os4.hostname().replace(/\.local$/, "").slice(0, 30) || "this computer";
|
|
2893
|
+
async function ensureAgentVersion(ctx, install, runUpdate2) {
|
|
2894
|
+
const health = await fetchHealth(install.url);
|
|
2895
|
+
if (!health) throw new Fail(`${install.url} isn't answering.`, { fix: "Check it with `npx databull@latest status`." });
|
|
2896
|
+
if (compareVersions(health.version, MIN_AGENT_VERSION) >= 0) return health.version;
|
|
2897
|
+
const why = `${install.name} runs DataBull ${health.version}; API keys and AI agents need ${MIN_AGENT_VERSION} or newer.`;
|
|
2898
|
+
if (!ctx.flags.yes) {
|
|
2899
|
+
if (!canPrompt()) throw new Fail(why, { fix: "Update it with `npx databull@latest update` (or pass --yes to update now)." });
|
|
2900
|
+
warn(why);
|
|
2901
|
+
if (!await askConfirm({ message: `Update ${install.name} now? Your data stays as it is.` })) throw new Cancelled();
|
|
2902
|
+
} else {
|
|
2903
|
+
info(`${why} Updating it first.`);
|
|
2904
|
+
}
|
|
2905
|
+
const before = ctx.flags.name;
|
|
2906
|
+
ctx.flags.name = install.name;
|
|
2907
|
+
try {
|
|
2908
|
+
await runUpdate2(ctx);
|
|
2909
|
+
} finally {
|
|
2910
|
+
ctx.flags.name = before;
|
|
2911
|
+
}
|
|
2912
|
+
const after = await fetchHealth(install.url);
|
|
2913
|
+
if (!after || compareVersions(after.version, MIN_AGENT_VERSION) < 0) throw new Fail(`${install.name} still isn't on ${MIN_AGENT_VERSION}.`, { fix: "Run `npx databull@latest update` and try again." });
|
|
2914
|
+
return after.version;
|
|
2915
|
+
}
|
|
2916
|
+
async function createKeyOnInstall(ctx, install, { label, pii = false, connect: connect2 }) {
|
|
2917
|
+
await connect2(ctx, null, null, { accountId: install.account_id });
|
|
2918
|
+
const key = newApiKey();
|
|
2919
|
+
const sql = `INSERT INTO api_keys (user_id, name, prefix, hash, pii, created_at) SELECT id, ${sqlText(label)}, '${key.slice(0, 12)}', '${sha256hex(key)}', ${pii ? 1 : 0}, ${Date.now()} FROM users ORDER BY id LIMIT 1;`;
|
|
2920
|
+
const configFile = ensureD1Config(ctx.paths.installDir(install.name), install);
|
|
2921
|
+
const r2 = await withStep(null, null, "Creating an API key", async () => {
|
|
2922
|
+
const res = await d1Execute(ctx, install, configFile, sql, "create API key");
|
|
2923
|
+
return { value: res, label: res.changes === 0 ? "No login yet" : "API key created", detail: res.changes === 0 ? "" : label };
|
|
2924
|
+
});
|
|
2925
|
+
if (r2.value.changes === 0) throw new Fail(`${install.name} has no login yet, so there's no account to give a key to.`, { fix: "Create your login first (`npx databull@latest status` shows the setup link), then try again." });
|
|
2926
|
+
return { key, prefix: key.slice(0, 12), label };
|
|
2927
|
+
}
|
|
2928
|
+
async function resolveConnection(ctx, { pickInstall: pickInstall2, connect: connect2, runUpdate: runUpdate2, save: save2 }) {
|
|
2929
|
+
const url = ctx.flags.url || process.env.DATABULL_URL;
|
|
2930
|
+
const key = ctx.flags.apiKey || process.env.DATABULL_API_KEY;
|
|
2931
|
+
if (url || key) {
|
|
2932
|
+
if (!url || !key) throw new Fail("Pass both the address and the API key.", { fix: "Set DATABULL_URL=https://\u2026 and DATABULL_API_KEY=dbk_\u2026 (or use --url and --api-key)." });
|
|
2933
|
+
if (!KEY_RE.test(key)) throw new Fail("That isn't a DataBull API key (they start with dbk_ and have 44 characters).", { fix: "Copy it again from Settings \u2192 API keys." });
|
|
2934
|
+
if (!/^https?:\/\//.test(url)) throw new Fail(`"${url}" isn't an address.`, { fix: "Use the full address, e.g. https://analytics.example.com." });
|
|
2935
|
+
return { url: cleanUrl(url), key };
|
|
2936
|
+
}
|
|
2937
|
+
const install = await pickInstall2(ctx, { verb: "read" });
|
|
2938
|
+
if (!install) throw new Fail("No DataBull install on this computer.", { fix: `Install it with \`npx databull@latest\`. ${ENV_FIX}` });
|
|
2939
|
+
if (install.api_key && KEY_RE.test(install.api_key)) return { url: install.url, key: install.api_key, install };
|
|
2940
|
+
await ensureAgentVersion(ctx, install, runUpdate2);
|
|
2941
|
+
if (!ctx.flags.yes) {
|
|
2942
|
+
if (!canPrompt()) throw new Fail(`This computer has no API key for ${install.name} yet.`, { fix: "Run the command in a terminal to create one, pass --yes, or create it with `npx databull@latest api-key create`." });
|
|
2943
|
+
info(`To read your stats, this computer needs an API key for ${install.name} (read-only).`);
|
|
2944
|
+
if (!await askConfirm({ message: "Create one now? It uses your Cloudflare login." })) throw new Cancelled();
|
|
2945
|
+
}
|
|
2946
|
+
const made = await createKeyOnInstall(ctx, install, { label: `CLI on ${hostName()}`, connect: connect2 });
|
|
2947
|
+
upsertInstall(ctx.cfg, { ...install, api_key: made.key });
|
|
2948
|
+
save2(ctx);
|
|
2949
|
+
return { url: install.url, key: made.key, install: { ...install, api_key: made.key } };
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2952
|
+
// src/commands/data.mjs
|
|
2953
|
+
var out2 = (s = "") => process.stdout.write(`${s}
|
|
2954
|
+
`);
|
|
2955
|
+
var strip = (s) => String(s).replace(/\x1b\[[0-9;?]*[A-Za-z]/g, "");
|
|
2956
|
+
function money(n, currency) {
|
|
2957
|
+
if (n === null || n === void 0) return import_picocolors3.default.dim("\u2013");
|
|
2958
|
+
const big = Math.abs(n) >= 1e3;
|
|
2959
|
+
try {
|
|
2960
|
+
return new Intl.NumberFormat("en-US", { style: "currency", currency, maximumFractionDigits: big ? 0 : 2, minimumFractionDigits: big || Number.isInteger(n) ? 0 : 2 }).format(n);
|
|
2961
|
+
} catch {
|
|
2962
|
+
return `${n} ${currency}`;
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
var num = (n) => n === null || n === void 0 ? import_picocolors3.default.dim("\u2013") : Number(n).toLocaleString("en-US");
|
|
2966
|
+
var pctText = (n) => n === null || n === void 0 ? "" : `${n}%`;
|
|
2967
|
+
function change(n) {
|
|
2968
|
+
if (n === null || n === void 0) return import_picocolors3.default.dim("new");
|
|
2969
|
+
if (n === 0) return import_picocolors3.default.dim("\xB10%");
|
|
2970
|
+
return n > 0 ? import_picocolors3.default.green(`+${n}%`) : import_picocolors3.default.red(`${n}%`);
|
|
2971
|
+
}
|
|
2972
|
+
function duration(s) {
|
|
2973
|
+
if (!s) return "0s";
|
|
2974
|
+
const m2 = Math.floor(s / 60);
|
|
2975
|
+
return m2 ? `${m2}m ${Math.round(s % 60)}s` : `${Math.round(s)}s`;
|
|
2976
|
+
}
|
|
2977
|
+
function ago(iso) {
|
|
2978
|
+
if (!iso) return "";
|
|
2979
|
+
const s = Math.max(0, (Date.now() - Date.parse(iso)) / 1e3);
|
|
2980
|
+
if (s < 60) return "just now";
|
|
2981
|
+
if (s < 3600) return `${Math.floor(s / 60)}m ago`;
|
|
2982
|
+
if (s < 86400) return `${Math.floor(s / 3600)}h ago`;
|
|
2983
|
+
if (s < 30 * 86400) return `${Math.floor(s / 86400)}d ago`;
|
|
2984
|
+
return iso.slice(0, 10);
|
|
2985
|
+
}
|
|
2986
|
+
function bar(value, max, width = 14) {
|
|
2987
|
+
if (!max || !value) return " ".repeat(width);
|
|
2988
|
+
const eighths = Math.max(1, Math.round(value / max * width * 8));
|
|
2989
|
+
const full = Math.floor(eighths / 8);
|
|
2990
|
+
const part = ["", "\u258F", "\u258E", "\u258D", "\u258C", "\u258B", "\u258A", "\u2589"][eighths % 8];
|
|
2991
|
+
return ("\u2588".repeat(full) + part).padEnd(width, " ").slice(0, width);
|
|
2992
|
+
}
|
|
2993
|
+
function spark(values) {
|
|
2994
|
+
const max = Math.max(...values, 0);
|
|
2995
|
+
if (!max) return import_picocolors3.default.dim("\u2581".repeat(values.length));
|
|
2996
|
+
return values.map((v2) => "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588"[Math.min(7, Math.round(v2 / max * 7))]).join("");
|
|
2997
|
+
}
|
|
2998
|
+
var short = (s, n = 34) => s && s.length > n ? `${s.slice(0, n - 1)}\u2026` : s || "";
|
|
2999
|
+
var regions = (() => {
|
|
3000
|
+
try {
|
|
3001
|
+
return new Intl.DisplayNames(["en"], { type: "region" });
|
|
3002
|
+
} catch {
|
|
3003
|
+
return null;
|
|
3004
|
+
}
|
|
3005
|
+
})();
|
|
3006
|
+
var countryName = (code) => {
|
|
3007
|
+
if (!code || !/^[A-Z]{2}$/.test(code)) return code;
|
|
3008
|
+
try {
|
|
3009
|
+
return `${regions.of(code)} (${code})`;
|
|
3010
|
+
} catch {
|
|
3011
|
+
return code;
|
|
3012
|
+
}
|
|
3013
|
+
};
|
|
3014
|
+
function rowName(x2, dim, n = 44) {
|
|
3015
|
+
if (x2.label) return import_picocolors3.default.italic(short(x2.label[0].toUpperCase() + x2.label.slice(1), n));
|
|
3016
|
+
return short(dim === "country" ? countryName(x2.name) : x2.name, n);
|
|
3017
|
+
}
|
|
3018
|
+
function table(rows, cols) {
|
|
3019
|
+
const cells = rows.map((r2) => cols.map((c) => String(c.get(r2) ?? "")));
|
|
3020
|
+
const widths = cols.map((c, i) => Math.max(strip(c.label).length, ...cells.map((row) => strip(row[i]).length)));
|
|
3021
|
+
const pad = (s, i) => cols[i].right ? " ".repeat(widths[i] - strip(s).length) + s : s + " ".repeat(widths[i] - strip(s).length);
|
|
3022
|
+
out2(` ${cols.map((c, i) => import_picocolors3.default.dim(pad(c.label, i))).join(" ")}`);
|
|
3023
|
+
for (const row of cells) out2(` ${row.map((s, i) => pad(s, i)).join(" ")}`);
|
|
3024
|
+
}
|
|
3025
|
+
function header(r2, title) {
|
|
3026
|
+
const range2 = r2.range ? import_picocolors3.default.dim(` ${r2.range.from === r2.range.to ? r2.range.from : `${r2.range.from} \u2192 ${r2.range.to}`}${r2.range.period && r2.range.period !== "custom" ? ` (${r2.range.period})` : ""}`) : "";
|
|
3027
|
+
out2("");
|
|
3028
|
+
out2(`${import_picocolors3.default.bold(title || r2.site?.domain || "")}${range2}`);
|
|
3029
|
+
out2("");
|
|
3030
|
+
}
|
|
3031
|
+
function filters(ctx) {
|
|
3032
|
+
const f = {};
|
|
3033
|
+
for (const item of ctx.flags.filter || []) {
|
|
3034
|
+
const i = item.indexOf("=");
|
|
3035
|
+
if (i < 1) throw new Fail(`--filter needs key=value (got "${item}").`, { fix: "Example: --filter referrer=news.ycombinator.com --filter country=US" });
|
|
3036
|
+
f[item.slice(0, i).trim()] = item.slice(i + 1).trim();
|
|
3037
|
+
}
|
|
3038
|
+
return Object.keys(f).length ? f : void 0;
|
|
3039
|
+
}
|
|
3040
|
+
function range(ctx) {
|
|
3041
|
+
const r2 = {};
|
|
3042
|
+
if (ctx.flags.period) r2.period = ctx.flags.period;
|
|
3043
|
+
if (ctx.flags.from) r2.from = ctx.flags.from;
|
|
3044
|
+
if (ctx.flags.to) r2.to = ctx.flags.to;
|
|
3045
|
+
const f = filters(ctx);
|
|
3046
|
+
if (f) r2.filters = f;
|
|
3047
|
+
return r2;
|
|
3048
|
+
}
|
|
3049
|
+
function limit(ctx, def) {
|
|
3050
|
+
if (ctx.flags.limit === void 0) return def;
|
|
3051
|
+
const n = Number(ctx.flags.limit);
|
|
3052
|
+
if (!Number.isInteger(n) || n < 1) throw new Fail(`--limit must be a whole number (got "${ctx.flags.limit}").`);
|
|
3053
|
+
return n;
|
|
3054
|
+
}
|
|
3055
|
+
var siteArg = (ctx, i = 0) => ctx.flags.site || ctx.args[i];
|
|
3056
|
+
function renderSites(r2) {
|
|
3057
|
+
out2("");
|
|
3058
|
+
if (!r2.sites.length) return out2(" No sites yet. Add one in your dashboard.");
|
|
3059
|
+
table(r2.sites, [
|
|
3060
|
+
{ label: "Site", get: (s) => import_picocolors3.default.bold(s.domain) },
|
|
3061
|
+
{ label: "Today", get: (s) => num(s.visitors_today), right: true },
|
|
3062
|
+
{ label: "Online", get: (s) => s.online_now ? import_picocolors3.default.green(`\u25CF ${s.online_now}`) : import_picocolors3.default.dim("0"), right: true },
|
|
3063
|
+
{ label: "7d visitors", get: (s) => `${num(s.last_7_days.visitors)} ${change(pctChange(s.last_7_days.visitors, s.last_7_days.previous.visitors))}`, right: true },
|
|
3064
|
+
{ label: "7d revenue", get: (s) => `${money(s.last_7_days.revenue, r2.currency)} ${change(pctChange(s.last_7_days.revenue, s.last_7_days.previous.revenue))}`, right: true },
|
|
3065
|
+
{ label: "30d revenue", get: (s) => money(s.last_30_days.revenue, r2.currency), right: true },
|
|
3066
|
+
{ label: "Id", get: (s) => import_picocolors3.default.dim(s.id) }
|
|
3067
|
+
]);
|
|
3068
|
+
out2("");
|
|
3069
|
+
}
|
|
3070
|
+
var pctChange = (cur, prev) => prev ? Math.round((cur - prev) / prev * 1e3) / 10 : cur ? null : 0;
|
|
3071
|
+
function renderOverview(r2) {
|
|
3072
|
+
header(r2);
|
|
3073
|
+
const t = r2.totals;
|
|
3074
|
+
const c = r2.change_pct;
|
|
3075
|
+
const rows = [
|
|
3076
|
+
["Visitors", num(t.visitors), change(c.visitors)],
|
|
3077
|
+
["Revenue", money(t.revenue, r2.currency), change(c.revenue)],
|
|
3078
|
+
["Sales", num(t.sales), change(c.sales)],
|
|
3079
|
+
["Conversion", pctText(t.conversion_rate_pct), change(c.conversion_rate_pct)],
|
|
3080
|
+
["Revenue/visitor", money(t.revenue_per_visitor, r2.currency), change(c.revenue_per_visitor)],
|
|
3081
|
+
["Pageviews", num(t.pageviews), change(c.pageviews)],
|
|
3082
|
+
["Bounce rate", pctText(t.bounce_rate_pct), ""],
|
|
3083
|
+
["Visit length", duration(t.avg_visit_seconds), ""]
|
|
3084
|
+
];
|
|
3085
|
+
if (t.refunds) rows.push(["Refunds", money(t.refunds, r2.currency), ""]);
|
|
3086
|
+
for (const [k3, v2, d3] of rows) out2(` ${import_picocolors3.default.dim(k3.padEnd(16))}${v2.padStart(strip(v2).length > 12 ? strip(v2).length : 12 + (v2.length - strip(v2).length))} ${d3}`);
|
|
3087
|
+
out2(` ${import_picocolors3.default.dim("Online now".padEnd(16))}${String(r2.online_now).padStart(12)} ${r2.online_now ? import_picocolors3.default.green("\u25CF") : ""}`);
|
|
3088
|
+
if (r2.series?.length > 1) {
|
|
3089
|
+
out2("");
|
|
3090
|
+
out2(` ${import_picocolors3.default.dim("Visitors".padEnd(16))}${import_picocolors3.default.blue(spark(r2.series.map((b3) => b3.visitors)))}`);
|
|
3091
|
+
out2(` ${import_picocolors3.default.dim("Revenue".padEnd(16))}${import_picocolors3.default.magenta(spark(r2.series.map((b3) => b3.revenue || 0)))}`);
|
|
3092
|
+
out2(` ${import_picocolors3.default.dim("".padEnd(16))}${import_picocolors3.default.dim(`${r2.series[0].date} \u2026 ${r2.series[r2.series.length - 1].date} (${r2.granularity})`)}`);
|
|
3093
|
+
}
|
|
3094
|
+
if (r2.spikes?.length) {
|
|
3095
|
+
out2("");
|
|
3096
|
+
for (const s of r2.spikes) out2(` ${import_picocolors3.default.yellow("\u25B2")} ${s.date}: ${import_picocolors3.default.bold(s.source)} sent ${s.times_usual}\xD7 its usual traffic (${num(s.visitors)} visitors)`);
|
|
3097
|
+
}
|
|
3098
|
+
out2("");
|
|
3099
|
+
}
|
|
3100
|
+
function renderBreakdown(r2) {
|
|
3101
|
+
header(r2, `${r2.site.domain} \xB7 ${r2.dimension.replace(/_/g, " ")}`);
|
|
3102
|
+
if (!r2.rows.length) return out2(` Nothing for this period.
|
|
3103
|
+
`);
|
|
3104
|
+
const byRevenue = r2.sorted_by === "revenue";
|
|
3105
|
+
const max = Math.max(...r2.rows.map((x2) => byRevenue ? x2.revenue || 0 : x2.visitors || x2.completions || 0));
|
|
3106
|
+
const hasMoney = r2.rows.some((x2) => x2.revenue !== void 0);
|
|
3107
|
+
const cols = [
|
|
3108
|
+
{ label: "#", get: (x2) => import_picocolors3.default.dim(String(r2.rows.indexOf(x2) + 1)), right: true },
|
|
3109
|
+
{ label: r2.dimension.replace(/_/g, " "), get: (x2) => rowName(x2, r2.dimension) + (x2.country ? import_picocolors3.default.dim(` ${x2.country}`) : "") },
|
|
3110
|
+
{ label: "", get: (x2) => (byRevenue ? import_picocolors3.default.magenta : import_picocolors3.default.blue)(bar(byRevenue ? x2.revenue || 0 : x2.visitors || x2.completions || 0, max)) },
|
|
3111
|
+
{ label: "Visitors", get: (x2) => num(x2.visitors), right: true }
|
|
3112
|
+
];
|
|
3113
|
+
if (r2.rows.some((x2) => x2.pageviews !== void 0)) cols.push({ label: "Pageviews", get: (x2) => num(x2.pageviews), right: true });
|
|
3114
|
+
if (r2.rows.some((x2) => x2.completions !== void 0)) cols.push({ label: "Completions", get: (x2) => num(x2.completions), right: true });
|
|
3115
|
+
if (hasMoney) {
|
|
3116
|
+
cols.push({ label: "Revenue", get: (x2) => x2.revenue ? money(x2.revenue, r2.currency) : import_picocolors3.default.dim("\u2013"), right: true });
|
|
3117
|
+
if (r2.rows.some((x2) => x2.sales !== void 0)) cols.push({ label: "Sales", get: (x2) => x2.sales ? num(x2.sales) : import_picocolors3.default.dim("\u2013"), right: true });
|
|
3118
|
+
}
|
|
3119
|
+
if (r2.rows.some((x2) => x2.conversion_rate_pct !== void 0)) cols.push({ label: "Conv.", get: (x2) => pctText(x2.conversion_rate_pct), right: true });
|
|
3120
|
+
table(r2.rows, cols);
|
|
3121
|
+
out2("");
|
|
3122
|
+
out2(import_picocolors3.default.dim(` ${num(r2.total_visitors)} visitors in total \xB7 showing ${r2.rows.length} of ${r2.rows_total} \xB7 sorted by ${r2.sorted_by}`));
|
|
3123
|
+
out2("");
|
|
3124
|
+
}
|
|
3125
|
+
function renderCompare(r2) {
|
|
3126
|
+
out2("");
|
|
3127
|
+
out2(`${import_picocolors3.default.bold(r2.site.domain)} ${r2.range.from} \u2192 ${r2.range.to} ${import_picocolors3.default.dim(`vs ${r2.previous_range.from} \u2192 ${r2.previous_range.to}`)}`);
|
|
3128
|
+
out2("");
|
|
3129
|
+
const t = r2.totals;
|
|
3130
|
+
const p2 = r2.previous_period;
|
|
3131
|
+
const row = (k3, a, b3, d3) => out2(` ${import_picocolors3.default.dim(k3.padEnd(16))}${a.padStart(12 + (a.length - strip(a).length))} ${import_picocolors3.default.dim("vs")} ${b3.padEnd(10 + (b3.length - strip(b3).length))} ${d3}`);
|
|
3132
|
+
row("Visitors", num(t.visitors), num(p2.visitors), change(r2.change_pct.visitors));
|
|
3133
|
+
row("Revenue", money(t.revenue, r2.currency), money(p2.revenue, r2.currency), change(r2.change_pct.revenue));
|
|
3134
|
+
row("Sales", num(t.sales), num(p2.sales), change(r2.change_pct.sales));
|
|
3135
|
+
row("Conversion", pctText(t.conversion_rate_pct), pctText(p2.conversion_rate_pct), change(r2.change_pct.conversion_rate_pct));
|
|
3136
|
+
const list = (title, items, field) => {
|
|
3137
|
+
if (!items.length) return;
|
|
3138
|
+
out2("");
|
|
3139
|
+
out2(` ${import_picocolors3.default.bold(title)}`);
|
|
3140
|
+
for (const m2 of items) {
|
|
3141
|
+
const delta = field === "revenue" ? money(Math.abs(m2.change), r2.currency) : num(Math.abs(m2.change));
|
|
3142
|
+
out2(` ${m2.change > 0 ? import_picocolors3.default.green(`\u25B2 +${delta}`) : import_picocolors3.default.red(`\u25BC -${delta}`)} ${rowName(m2, r2.dimension, 40)}${m2.country ? import_picocolors3.default.dim(` ${m2.country}`) : ""} ${import_picocolors3.default.dim(`(${field === "revenue" ? `${money(m2.previous_revenue, r2.currency)} \u2192 ${money(m2.revenue, r2.currency)}` : `${num(m2.previous_visitors)} \u2192 ${num(m2.visitors)}`})`)}`);
|
|
3143
|
+
}
|
|
3144
|
+
};
|
|
3145
|
+
const dim = r2.dimension.replace(/_/g, " ");
|
|
3146
|
+
list(`Revenue up, by ${dim}`, r2.gainers_by_revenue, "revenue");
|
|
3147
|
+
list(`Revenue down, by ${dim}`, r2.losers_by_revenue, "revenue");
|
|
3148
|
+
list(`Visitors up, by ${dim}`, r2.gainers_by_visitors, "visitors");
|
|
3149
|
+
list(`Visitors down, by ${dim}`, r2.losers_by_visitors, "visitors");
|
|
3150
|
+
out2("");
|
|
3151
|
+
}
|
|
3152
|
+
function sourceText(s) {
|
|
3153
|
+
if (!s) return import_picocolors3.default.dim("not matched to a visit");
|
|
3154
|
+
return [s.referrer || s.channel, s.utm_campaign ? import_picocolors3.default.dim(`campaign ${s.utm_campaign}`) : "", s.country ? import_picocolors3.default.dim(s.country) : ""].filter(Boolean).join(" \xB7 ");
|
|
3155
|
+
}
|
|
3156
|
+
function renderSales(r2) {
|
|
3157
|
+
header(r2);
|
|
3158
|
+
if (!r2.sales.length) return out2(" No sales in this period.\n");
|
|
3159
|
+
table(r2.sales, [
|
|
3160
|
+
{ label: "When", get: (s) => import_picocolors3.default.dim(ago(s.at)) },
|
|
3161
|
+
{ label: "Amount", get: (s) => s.refunded ? `${money(s.amount, r2.currency)} ${import_picocolors3.default.red(`-${money(s.refunded, r2.currency)}`)}` : money(s.amount, r2.currency), right: true },
|
|
3162
|
+
{ label: "Product", get: (s) => short(s.product, 26) },
|
|
3163
|
+
{ label: "Type", get: (s) => import_picocolors3.default.dim((s.charge_type || "").replace("_", "-")) },
|
|
3164
|
+
{ label: "Source", get: (s) => sourceText(s.source) },
|
|
3165
|
+
...r2.sales.some((s) => s.customer) ? [{ label: "Customer", get: (s) => s.customer?.email || "" }] : [],
|
|
3166
|
+
{ label: "Visitor", get: (s) => import_picocolors3.default.dim((s.visitor_id || "").slice(0, 10)) }
|
|
3167
|
+
]);
|
|
3168
|
+
out2("");
|
|
3169
|
+
const total = r2.by_product.reduce((a, p2) => a + p2.revenue, 0);
|
|
3170
|
+
out2(import_picocolors3.default.dim(` ${money(total, r2.currency)} from ${r2.by_product.reduce((a, p2) => a + p2.sales, 0)} sales${r2.by_product.length > 1 ? ` across ${r2.by_product.length} products` : ""}${r2.has_more ? " \xB7 more with --limit" : ""} \xB7 see a buyer's path: databull visitor <visitor id>`));
|
|
3171
|
+
out2("");
|
|
3172
|
+
}
|
|
3173
|
+
function renderVisitors(r2) {
|
|
3174
|
+
header(r2);
|
|
3175
|
+
if (!r2.visitors.length) return out2(" No visitors match.\n");
|
|
3176
|
+
table(r2.visitors, [
|
|
3177
|
+
{ label: "Last seen", get: (v2) => import_picocolors3.default.dim(ago(v2.last_seen)) },
|
|
3178
|
+
{ label: "Visitor", get: (v2) => v2.email || v2.name ? `${v2.name || v2.email}` : import_picocolors3.default.dim(v2.visitor_id.slice(0, 10)) },
|
|
3179
|
+
{ label: "From", get: (v2) => short(v2.source?.referrer || v2.source?.channel || "", 24) },
|
|
3180
|
+
{ label: "Path", get: (v2) => short((v2.path || []).join(" \u2192 "), 40) },
|
|
3181
|
+
{ label: "Visits", get: (v2) => num(v2.visits_in_range), right: true },
|
|
3182
|
+
{ label: "Time", get: (v2) => duration(v2.time_on_site_seconds), right: true },
|
|
3183
|
+
{ label: "Revenue", get: (v2) => v2.revenue ? import_picocolors3.default.magenta(money(v2.revenue, r2.currency)) : import_picocolors3.default.dim("\u2013"), right: true },
|
|
3184
|
+
{ label: "Where", get: (v2) => import_picocolors3.default.dim(short(v2.location || "", 24)) }
|
|
3185
|
+
]);
|
|
3186
|
+
out2("");
|
|
3187
|
+
out2(import_picocolors3.default.dim(` ${num(r2.total_visitors)} visitors \xB7 ${num(r2.paying_visitors)} paid${r2.has_more ? " \xB7 more with --page 1" : ""}`));
|
|
3188
|
+
out2("");
|
|
3189
|
+
}
|
|
3190
|
+
function renderVisitor(r2) {
|
|
3191
|
+
const v2 = r2.visitor;
|
|
3192
|
+
out2("");
|
|
3193
|
+
out2(`${import_picocolors3.default.bold(v2.name || v2.email || v2.visitor_id)} ${import_picocolors3.default.dim([r2.site.domain, `first seen ${ago(v2.first_seen)}`, `${v2.visits} visit${v2.visits === 1 ? "" : "s"}`, v2.location, v2.device].filter(Boolean).join(" \xB7 "))}`);
|
|
3194
|
+
out2("");
|
|
3195
|
+
if (r2.payments.length) {
|
|
3196
|
+
for (const p2 of r2.payments) out2(` ${import_picocolors3.default.magenta("$")} ${import_picocolors3.default.bold(money(p2.amount, r2.currency))} ${p2.product || ""} ${import_picocolors3.default.dim(`${ago(p2.at)} \xB7 credited to ${sourceText(p2.source)}`)}`);
|
|
3197
|
+
out2("");
|
|
3198
|
+
}
|
|
3199
|
+
const events = [...r2.events].reverse();
|
|
3200
|
+
for (const s of [...r2.visits].reverse()) {
|
|
3201
|
+
out2(` ${import_picocolors3.default.blue("\u25CF")} ${import_picocolors3.default.dim(s.started?.slice(0, 16).replace("T", " "))} ${import_picocolors3.default.bold(s.referrer || s.channel)}${s.campaign ? import_picocolors3.default.dim(` \xB7 ${s.campaign}`) : ""} ${import_picocolors3.default.dim(`${s.pageviews} page${s.pageviews === 1 ? "" : "s"} \xB7 ${duration(s.seconds)}`)}`);
|
|
3202
|
+
const inVisit = events.filter((e2) => e2.visit_id === s.visit_id);
|
|
3203
|
+
for (const e2 of inVisit.slice(0, 15)) out2(` ${e2.type === "pageview" ? import_picocolors3.default.dim("\u2192") : import_picocolors3.default.yellow("\u2605")} ${e2.type === "pageview" ? e2.page : `${e2.type}: ${e2.name || ""}`}`);
|
|
3204
|
+
}
|
|
3205
|
+
out2("");
|
|
3206
|
+
}
|
|
3207
|
+
function renderLive(r2) {
|
|
3208
|
+
out2("");
|
|
3209
|
+
out2(`${import_picocolors3.default.bold(r2.site.domain)} ${r2.online_now ? import_picocolors3.default.green(`\u25CF ${r2.online_now} online now`) : import_picocolors3.default.dim("nobody online right now")}`);
|
|
3210
|
+
out2("");
|
|
3211
|
+
if (r2.visitors.length) {
|
|
3212
|
+
table(r2.visitors.slice(0, 20), [
|
|
3213
|
+
{ label: "Page", get: (v2) => short(v2.page, 36) },
|
|
3214
|
+
{ label: "From", get: (v2) => short(v2.referrer || v2.channel || "", 26) },
|
|
3215
|
+
{ label: "Where", get: (v2) => import_picocolors3.default.dim(short(v2.location || "", 22)) },
|
|
3216
|
+
{ label: "Device", get: (v2) => import_picocolors3.default.dim(short(v2.device || "", 26)) }
|
|
3217
|
+
]);
|
|
3218
|
+
out2("");
|
|
3219
|
+
}
|
|
3220
|
+
if (r2.recent_sales.length) {
|
|
3221
|
+
for (const s of r2.recent_sales) out2(` ${import_picocolors3.default.magenta("$")} ${money(s.amount, s.currency)} ${s.product || ""} ${import_picocolors3.default.dim(`${ago(s.at)} \xB7 ${s.referrer || "direct"}${s.country ? ` \xB7 ${s.country}` : ""}`)}`);
|
|
3222
|
+
out2("");
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
function renderGoals(r2) {
|
|
3226
|
+
header(r2, `${r2.site.domain} \xB7 goals`);
|
|
3227
|
+
if (!r2.goals.length) return out2(" No goals completed in this period. Track one with data-fast-goal or datacheap('goal').\n");
|
|
3228
|
+
const max = Math.max(...r2.goals.map((g) => g.completions));
|
|
3229
|
+
table(r2.goals, [
|
|
3230
|
+
{ label: "Goal", get: (g) => import_picocolors3.default.bold(g.goal) },
|
|
3231
|
+
{ label: "", get: (g) => import_picocolors3.default.yellow(bar(g.completions, max)) },
|
|
3232
|
+
{ label: "Completions", get: (g) => num(g.completions), right: true },
|
|
3233
|
+
{ label: "Visitors", get: (g) => num(g.visitors), right: true },
|
|
3234
|
+
{ label: "Conv.", get: (g) => pctText(g.conversion_rate_pct), right: true },
|
|
3235
|
+
{ label: "Revenue", get: (g) => g.revenue ? money(g.revenue, r2.currency) : import_picocolors3.default.dim("\u2013"), right: true }
|
|
3236
|
+
]);
|
|
3237
|
+
out2("");
|
|
3238
|
+
}
|
|
3239
|
+
function renderFunnels(r2) {
|
|
3240
|
+
header(r2, `${r2.site.domain} \xB7 funnels`);
|
|
3241
|
+
if (!r2.funnels.length) return out2(" No funnels yet. Create one in the dashboard under Funnels.\n");
|
|
3242
|
+
for (const f of r2.funnels) {
|
|
3243
|
+
out2(` ${import_picocolors3.default.bold(f.name)} ${import_picocolors3.default.dim(`${f.overall_conversion_pct}% make it to the end`)}`);
|
|
3244
|
+
const max = Math.max(...f.steps.map((s) => s.visitors), 1);
|
|
3245
|
+
f.steps.forEach((s, i) => {
|
|
3246
|
+
out2(` ${import_picocolors3.default.dim(`${i + 1}.`)} ${short(s.step, 30).padEnd(30)} ${import_picocolors3.default.blue(bar(s.visitors, max, 20))} ${num(s.visitors).padStart(7)} ${i ? import_picocolors3.default.dim(`${s.from_previous_pct}% of previous`) : ""}${s.revenue ? ` ${money(s.revenue, r2.currency)}` : ""}`);
|
|
3247
|
+
});
|
|
3248
|
+
out2("");
|
|
3249
|
+
}
|
|
3250
|
+
if (r2.note) out2(import_picocolors3.default.dim(` ${r2.note}
|
|
3251
|
+
`));
|
|
3252
|
+
}
|
|
3253
|
+
function renderLtv(r2) {
|
|
3254
|
+
header(r2, `${r2.site.domain} \xB7 customer value by ${r2.dimension.replace(/_/g, " ")}`);
|
|
3255
|
+
if (!r2.by_source.length) return out2(" No customers whose first payment falls in this period.\n");
|
|
3256
|
+
table(r2.by_source, [
|
|
3257
|
+
{ label: "Source", get: (g) => rowName({ name: g.source, label: g.label }, r2.dimension, 34) },
|
|
3258
|
+
{ label: "Customers", get: (g) => num(g.customers), right: true },
|
|
3259
|
+
{ label: "Revenue", get: (g) => money(g.revenue, r2.currency), right: true },
|
|
3260
|
+
{ label: "Avg value", get: (g) => money(g.average_value, r2.currency), right: true },
|
|
3261
|
+
{ label: "Recurring", get: (g) => pctText(g.recurring_share_pct), right: true },
|
|
3262
|
+
{ label: "Repeat", get: (g) => pctText(g.repeat_rate_pct), right: true }
|
|
3263
|
+
]);
|
|
3264
|
+
out2("");
|
|
3265
|
+
out2(import_picocolors3.default.dim(` ${num(r2.totals.customers)} customers \xB7 ${money(r2.totals.revenue, r2.currency)} \xB7 ${money(r2.totals.average_value, r2.currency)} each on average`));
|
|
3266
|
+
out2("");
|
|
3267
|
+
}
|
|
3268
|
+
var COMMANDS2 = {
|
|
3269
|
+
sites: { tool: "list_sites", input: () => ({}), render: renderSites },
|
|
3270
|
+
stats: { tool: "get_overview", input: (ctx) => ({ site: siteArg(ctx), ...range(ctx) }), render: renderOverview },
|
|
3271
|
+
sources: {
|
|
3272
|
+
tool: "get_breakdown",
|
|
3273
|
+
input: (ctx) => ({ site: siteArg(ctx), dimension: ctx.flags.dim || "referrer", sort: ctx.flags.by || "revenue", limit: limit(ctx, 15), ...range(ctx) }),
|
|
3274
|
+
render: renderBreakdown
|
|
3275
|
+
},
|
|
3276
|
+
breakdown: {
|
|
3277
|
+
tool: "get_breakdown",
|
|
3278
|
+
input: (ctx) => {
|
|
3279
|
+
const dimension = ctx.flags.dim || ctx.args[0];
|
|
3280
|
+
if (!dimension) throw new Fail("Which dimension? e.g. `databull breakdown country`.", { fix: "Dimensions: channel, referrer, utm_source, utm_medium, utm_campaign, utm_term, utm_content, country, region, city, browser, os, device, entry_page, exit_page, hostname, page, goal, outbound." });
|
|
3281
|
+
return { site: ctx.flags.site || ctx.args[ctx.flags.dim ? 0 : 1], dimension, sort: ctx.flags.by || ctx.flags.sort || "visitors", limit: limit(ctx, 15), ...range(ctx) };
|
|
3282
|
+
},
|
|
3283
|
+
render: renderBreakdown
|
|
3284
|
+
},
|
|
3285
|
+
compare: { tool: "compare_periods", input: (ctx) => ({ site: siteArg(ctx), ...ctx.flags.dim || ctx.flags.by ? { dimension: ctx.flags.dim || ctx.flags.by } : {}, ...ctx.flags.limit ? { limit: limit(ctx, 5) } : {}, ...range(ctx), period: ctx.flags.period || (ctx.flags.from ? void 0 : "7d") }), render: renderCompare },
|
|
3286
|
+
sales: { tool: "list_sales", input: (ctx) => ({ site: siteArg(ctx), limit: limit(ctx, 20), ...ctx.flags.page ? { page: Number(ctx.flags.page) } : {}, ...range(ctx) }), render: renderSales },
|
|
3287
|
+
visitors: {
|
|
3288
|
+
tool: "list_visitors",
|
|
3289
|
+
input: (ctx) => ({ site: siteArg(ctx), sort: ctx.flags.sort || ctx.flags.by || "recent", ...ctx.flags.customers ? { customers_only: true } : {}, limit: limit(ctx, 20), ...ctx.flags.page ? { page: Number(ctx.flags.page) } : {}, ...range(ctx) }),
|
|
3290
|
+
render: renderVisitors
|
|
3291
|
+
},
|
|
3292
|
+
visitor: {
|
|
3293
|
+
tool: "get_visitor",
|
|
3294
|
+
input: (ctx) => {
|
|
3295
|
+
if (!ctx.args[0]) throw new Fail("Which visitor? e.g. `databull visitor 3f2a\u2026`", { fix: "Visitor ids are in `databull sales` and `databull visitors`." });
|
|
3296
|
+
return { site: ctx.flags.site || ctx.args[1], visitor_id: ctx.args[0] };
|
|
3297
|
+
},
|
|
3298
|
+
render: renderVisitor
|
|
3299
|
+
},
|
|
3300
|
+
live: { tool: "get_realtime", input: (ctx) => ({ site: siteArg(ctx) }), render: renderLive },
|
|
3301
|
+
goals: { tool: "get_goals", input: (ctx) => ({ site: siteArg(ctx), ...range(ctx) }), render: renderGoals },
|
|
3302
|
+
funnels: { tool: "get_funnels", input: (ctx) => ({ site: siteArg(ctx), ...ctx.flags.funnel ? { funnel: ctx.flags.funnel } : {}, ...range(ctx) }), render: renderFunnels },
|
|
3303
|
+
ltv: { tool: "get_customer_value", input: (ctx) => ({ site: siteArg(ctx), ...ctx.flags.dim || ctx.flags.by ? { dimension: ctx.flags.dim || ctx.flags.by } : {}, ...range(ctx) }), render: renderLtv }
|
|
3304
|
+
};
|
|
3305
|
+
async function withKey(ctx, deps, tool, input) {
|
|
3306
|
+
const conn = await resolveConnection(ctx, deps);
|
|
3307
|
+
try {
|
|
3308
|
+
return await callTool(conn, tool, input);
|
|
3309
|
+
} catch (err) {
|
|
3310
|
+
if (err.code !== "unauthorized" || !conn.install?.api_key) throw err;
|
|
3311
|
+
forgetInstallFields(ctx.cfg, conn.install, ["api_key"]);
|
|
3312
|
+
deps.save(ctx);
|
|
3313
|
+
process.stderr.write(`${import_picocolors3.default.yellow("\u25B2")} The API key saved on this computer no longer works (revoked?). Getting a new one.
|
|
3314
|
+
`);
|
|
3315
|
+
return callTool(await resolveConnection(ctx, deps), tool, input);
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
async function runData(ctx, deps) {
|
|
3319
|
+
if (ctx.command === "query") {
|
|
3320
|
+
const [tool, json2] = ctx.args;
|
|
3321
|
+
if (!tool) throw new Fail('Which tool? e.g. `databull query get_overview \'{"period":"7d"}\'`.', { fix: "Tools: list_sites, get_overview, get_breakdown, compare_periods, list_sales, list_visitors, get_visitor, get_realtime, get_goals, get_funnels, get_customer_value." });
|
|
3322
|
+
let args = {};
|
|
3323
|
+
if (json2) {
|
|
3324
|
+
try {
|
|
3325
|
+
args = JSON.parse(json2);
|
|
3326
|
+
} catch {
|
|
3327
|
+
throw new Fail("The tool's arguments must be JSON.", { fix: `Example: '{"site":"example.com","period":"7d"}'` });
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
3330
|
+
process.stdout.write(`${JSON.stringify(await withKey(ctx, deps, tool, args), null, 2)}
|
|
3331
|
+
`);
|
|
3332
|
+
return;
|
|
3333
|
+
}
|
|
3334
|
+
const cmd = COMMANDS2[ctx.command];
|
|
3335
|
+
const input = cmd.input(ctx);
|
|
3336
|
+
for (const k3 of Object.keys(input)) if (input[k3] === void 0) delete input[k3];
|
|
3337
|
+
const result = await withKey(ctx, deps, cmd.tool, input);
|
|
3338
|
+
if (ctx.flags.json) process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
3339
|
+
`);
|
|
3340
|
+
else cmd.render(result);
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
// src/commands/keys.mjs
|
|
3344
|
+
var fmtDate = (ms) => ms ? new Date(Number(ms)).toISOString().slice(0, 16).replace("T", " ") : "\u2013";
|
|
3345
|
+
async function runApiKey(ctx, { pickInstall: pickInstall2, connect: connect2, runUpdate: runUpdate2, save: save2 }) {
|
|
3346
|
+
const sub = ctx.args[0] || "list";
|
|
3347
|
+
if (!["create", "list", "revoke"].includes(sub)) throw new Fail(`Unknown api-key command "${sub}".`, { fix: `Use \`${CMD} api-key create\`, \`list\` or \`revoke <prefix>\`.` });
|
|
3348
|
+
const install = await pickInstall2(ctx, { verb: "manage API keys for" });
|
|
3349
|
+
if (!install) throw new Fail("No DataBull install on this computer.", { fix: `Manage keys in your dashboard under Settings \u2192 API keys, or install with \`${CMD}\`.` });
|
|
3350
|
+
await ensureAgentVersion(ctx, install, runUpdate2);
|
|
3351
|
+
if (sub === "create") {
|
|
3352
|
+
const label = (ctx.flags.label || `API key ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`).trim();
|
|
3353
|
+
const made = await createKeyOnInstall(ctx, install, { label, pii: !!ctx.flags.pii, connect: connect2 });
|
|
3354
|
+
box("Your API key (shown once)", [
|
|
3355
|
+
import_picocolors3.default.bold(made.key),
|
|
3356
|
+
"",
|
|
3357
|
+
`${import_picocolors3.default.dim("Address")} ${install.url}`,
|
|
3358
|
+
`${import_picocolors3.default.dim("Access ")} read-only${ctx.flags.pii ? ", including customer names and emails" : ", customer details hidden"}`,
|
|
3359
|
+
"",
|
|
3360
|
+
import_picocolors3.default.dim("Use it anywhere:"),
|
|
3361
|
+
` DATABULL_URL=${install.url} DATABULL_API_KEY=${made.key.slice(0, 8)}\u2026 ${CMD} stats`,
|
|
3362
|
+
import_picocolors3.default.dim(`AI agents: \`${CMD} mcp\` sets them up for you.`)
|
|
3363
|
+
], { color: import_picocolors3.default.green });
|
|
3364
|
+
end(`Store it somewhere safe. Revoke it any time with \`${CMD} api-key revoke ${made.prefix}\`.`);
|
|
3365
|
+
return;
|
|
3366
|
+
}
|
|
3367
|
+
await connect2(ctx, null, null, { accountId: install.account_id });
|
|
3368
|
+
const configFile = ensureD1Config(ctx.paths.installDir(install.name), install);
|
|
3369
|
+
if (sub === "list") {
|
|
3370
|
+
const r3 = await d1Execute(ctx, install, configFile, "SELECT id, name, prefix, pii, created_at, last_used_at FROM api_keys WHERE revoked_at IS NULL ORDER BY created_at DESC;", "list API keys");
|
|
3371
|
+
if (!r3.results.length) {
|
|
3372
|
+
end(`No API keys yet. Create one with \`${CMD} api-key create\` (or \`${CMD} mcp\` for AI agents).`);
|
|
3373
|
+
return;
|
|
3374
|
+
}
|
|
3375
|
+
box(`API keys on ${install.name}`, r3.results.map((k3) => `${import_picocolors3.default.bold(k3.prefix)}\u2026 ${String(k3.name).padEnd(28)} ${import_picocolors3.default.dim(`created ${fmtDate(k3.created_at)} \xB7 last used ${fmtDate(k3.last_used_at)}${k3.pii ? " \xB7 customer details" : ""}`)}`));
|
|
3376
|
+
end(`Revoke one with \`${CMD} api-key revoke <prefix>\`.`);
|
|
3377
|
+
return;
|
|
3378
|
+
}
|
|
3379
|
+
const which = ctx.args[1];
|
|
3380
|
+
if (!which) throw new Fail("Which key? Pass its prefix, e.g. `api-key revoke dbk_ab12cd34`.", { fix: `\`${CMD} api-key list\` shows them.` });
|
|
3381
|
+
const prefix = which.replace(/…$/, "").slice(0, 12);
|
|
3382
|
+
if (!/^dbk_[a-z0-9]{8}$/.test(prefix) && !/^\d+$/.test(which)) throw new Fail(`"${which}" isn't a key prefix (dbk_ + 8 characters) or id.`, { fix: `\`${CMD} api-key list\` shows them.` });
|
|
3383
|
+
const where = /^\d+$/.test(which) ? `id = ${Number(which)}` : `prefix = '${prefix}'`;
|
|
3384
|
+
const r2 = await d1Execute(ctx, install, configFile, `UPDATE api_keys SET revoked_at = ${Date.now()} WHERE revoked_at IS NULL AND ${where};`, "revoke API key");
|
|
3385
|
+
if (!r2.changes) throw new Fail(`No active key ${which} on ${install.name}.`, { fix: `\`${CMD} api-key list\` shows the active ones.` });
|
|
3386
|
+
const mine = ["api_key", "mcp_key"].filter((f) => install[f] && (install[f].startsWith(prefix) || /^\d+$/.test(which)));
|
|
3387
|
+
if (mine.length && !/^\d+$/.test(which)) {
|
|
3388
|
+
forgetInstallFields(ctx.cfg, install, mine);
|
|
3389
|
+
save2(ctx);
|
|
3390
|
+
}
|
|
3391
|
+
end(`Revoked. Anything using that key stops working right away.`);
|
|
3392
|
+
}
|
|
3393
|
+
|
|
3394
|
+
// src/commands/mcp.mjs
|
|
3395
|
+
import { spawnSync } from "node:child_process";
|
|
3396
|
+
var CLIENTS = ["claude", "cursor", "vscode", "codex", "windsurf", "other"];
|
|
3397
|
+
var claudeBin = () => process.env.DATABULL_CLAUDE_BIN || "claude";
|
|
3398
|
+
function hasClaude() {
|
|
3399
|
+
if (process.env.DATABULL_CLAUDE_BIN === "") return false;
|
|
3400
|
+
const r2 = spawnSync(claudeBin(), ["--version"], { stdio: "ignore", timeout: 1e4 });
|
|
3401
|
+
return r2.status === 0;
|
|
3402
|
+
}
|
|
3403
|
+
function snippets(url, key) {
|
|
3404
|
+
const header2 = `Authorization: Bearer ${key}`;
|
|
3405
|
+
const json2 = (o2) => JSON.stringify(o2, null, 2);
|
|
3406
|
+
return {
|
|
3407
|
+
claude: { title: "Claude Code", where: "Run in a terminal", text: `claude mcp add --transport http --scope user databull ${url} --header "${header2}"` },
|
|
3408
|
+
cursor: { title: "Cursor", where: "~/.cursor/mcp.json (or .cursor/mcp.json in a project)", text: json2({ mcpServers: { databull: { url, headers: { Authorization: `Bearer ${key}` } } } }) },
|
|
3409
|
+
vscode: { title: "VS Code (Copilot agent mode)", where: '.vscode/mcp.json, or "MCP: Open User Configuration"', text: json2({ servers: { databull: { type: "http", url, headers: { Authorization: `Bearer ${key}` } } } }) },
|
|
3410
|
+
codex: { title: "Codex", where: "~/.codex/config.toml", text: `[mcp_servers.databull]
|
|
3411
|
+
url = "${url}"
|
|
3412
|
+
http_headers = { "Authorization" = "Bearer ${key}" }` },
|
|
3413
|
+
windsurf: { title: "Windsurf", where: "~/.codeium/windsurf/mcp_config.json", text: json2({ mcpServers: { databull: { serverUrl: url, headers: { Authorization: `Bearer ${key}` } } } }) },
|
|
3414
|
+
other: { title: "Any other MCP client", where: "Streamable HTTP", text: `URL ${url}
|
|
3415
|
+
Header ${header2}` }
|
|
3416
|
+
};
|
|
3417
|
+
}
|
|
3418
|
+
function addToClaude(url, key) {
|
|
3419
|
+
const args = ["mcp", "add", "--transport", "http", "--scope", "user", "databull", url, "--header", `Authorization: Bearer ${key}`];
|
|
3420
|
+
let r2 = spawnSync(claudeBin(), args, { encoding: "utf8", timeout: 3e4 });
|
|
3421
|
+
if (r2.status !== 0 && /already exists/i.test(`${r2.stdout}${r2.stderr}`)) {
|
|
3422
|
+
spawnSync(claudeBin(), ["mcp", "remove", "databull", "--scope", "user"], { encoding: "utf8", timeout: 3e4 });
|
|
3423
|
+
r2 = spawnSync(claudeBin(), args, { encoding: "utf8", timeout: 3e4 });
|
|
3424
|
+
}
|
|
3425
|
+
return r2.status === 0 ? null : (r2.stderr || r2.stdout || "claude mcp add failed").trim().split("\n").pop();
|
|
3426
|
+
}
|
|
3427
|
+
async function runMcp(ctx, { pickInstall: pickInstall2, connect: connect2, runUpdate: runUpdate2, save: save2 }) {
|
|
3428
|
+
const client = ctx.flags.client?.toLowerCase();
|
|
3429
|
+
if (client && !CLIENTS.includes(client)) throw new Fail(`Unknown client "${ctx.flags.client}".`, { fix: `Use one of: ${CLIENTS.join(", ")}.` });
|
|
3430
|
+
let url;
|
|
3431
|
+
let key;
|
|
3432
|
+
let install = null;
|
|
3433
|
+
if (ctx.flags.url || process.env.DATABULL_URL) {
|
|
3434
|
+
url = String(ctx.flags.url || process.env.DATABULL_URL).replace(/\/+$/, "").replace(/\/mcp$/, "");
|
|
3435
|
+
key = ctx.flags.apiKey || process.env.DATABULL_API_KEY;
|
|
3436
|
+
if (!key) throw new Fail("With --url, pass the key too (--api-key or DATABULL_API_KEY).", { fix: "Create one in your dashboard under Settings \u2192 API keys." });
|
|
3437
|
+
} else {
|
|
3438
|
+
install = await pickInstall2(ctx, { verb: "connect AI agents to" });
|
|
3439
|
+
if (!install) throw new Fail("No DataBull install on this computer.", { fix: `Install it with \`${CMD}\`, or pass --url and --api-key (Settings \u2192 API keys in your dashboard).` });
|
|
3440
|
+
await ensureAgentVersion(ctx, install, runUpdate2);
|
|
3441
|
+
url = install.url;
|
|
3442
|
+
key = install.mcp_key;
|
|
3443
|
+
if (key) {
|
|
3444
|
+
try {
|
|
3445
|
+
await rpc({ url, key, install }, "ping");
|
|
3446
|
+
} catch {
|
|
3447
|
+
key = null;
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
if (!key) {
|
|
3451
|
+
const made = await createKeyOnInstall(ctx, install, { label: `AI agents on ${hostName()}`, pii: !!ctx.flags.pii, connect: connect2 });
|
|
3452
|
+
key = made.key;
|
|
3453
|
+
install = { ...install, mcp_key: key };
|
|
3454
|
+
upsertInstall(ctx.cfg, install);
|
|
3455
|
+
save2(ctx);
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
const tools = await withStep(null, null, "Checking the connection", async () => {
|
|
3459
|
+
const r2 = await rpc({ url, key, install }, "tools/list");
|
|
3460
|
+
return { value: r2.tools, label: `${r2.tools.length} tools ready`, detail: url.replace(/^https:\/\//, "") };
|
|
3461
|
+
}).then((r2) => r2.value);
|
|
3462
|
+
const mcpUrl = `${url}/mcp`;
|
|
3463
|
+
const all = snippets(mcpUrl, key);
|
|
3464
|
+
let addedToClaude = false;
|
|
3465
|
+
if ((!client || client === "claude") && hasClaude()) {
|
|
3466
|
+
const go = ctx.flags.yes || canPrompt() && await askConfirm({ message: "Claude Code is installed. Add DataBull to it now (for all your projects)?" });
|
|
3467
|
+
if (go) {
|
|
3468
|
+
const err = addToClaude(mcpUrl, key);
|
|
3469
|
+
if (err) warn(`Couldn't add it to Claude Code automatically: ${err}`, "Use the command below instead.");
|
|
3470
|
+
else {
|
|
3471
|
+
addedToClaude = true;
|
|
3472
|
+
success(`Added to Claude Code. Open Claude Code and run ${import_picocolors3.default.bold("/mcp")} to see "databull" connected.`);
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3476
|
+
const show = client ? [client] : CLIENTS.filter((c) => !(c === "claude" && addedToClaude));
|
|
3477
|
+
for (const c of show) {
|
|
3478
|
+
const s = all[c];
|
|
3479
|
+
box(`${s.title} ${import_picocolors3.default.dim(s.where)}`, s.text.split("\n"));
|
|
3480
|
+
}
|
|
3481
|
+
line();
|
|
3482
|
+
line(import_picocolors3.default.bold("Try asking your agent:"));
|
|
3483
|
+
for (const q2 of ["Which referrer made me the most money this month?", "Compare this week with last week. What changed?", "Show my last 5 sales and where each buyer came from.", "Walk me through the journey of my latest customer."]) line(` ${import_picocolors3.default.cyan(`\u201C${q2}\u201D`)}`);
|
|
3484
|
+
line();
|
|
3485
|
+
line(import_picocolors3.default.dim(`${tools.length} read-only tools: ${tools.map((t) => t.name).join(", ")}.`));
|
|
3486
|
+
end(`The key is read-only${install ? `; revoke it any time with \`${CMD} api-key revoke ${key.slice(0, 12)}\`` : ""}. Docs: https://databull.app/docs/mcp`);
|
|
3487
|
+
}
|
|
3488
|
+
|
|
3489
|
+
// src/index.mjs
|
|
3490
|
+
var VERSION = true ? "1.1.0" : "0.0.0-dev";
|
|
3491
|
+
var DEPS = { pickInstall, connect, runUpdate, save };
|
|
3492
|
+
var RUN = {
|
|
3493
|
+
install: runInstall,
|
|
3494
|
+
update: runUpdate,
|
|
3495
|
+
"reset-password": runReset,
|
|
3496
|
+
domain: runDomain,
|
|
3497
|
+
status: runStatus,
|
|
3498
|
+
license: runLicense,
|
|
3499
|
+
uninstall: runUninstall,
|
|
3500
|
+
mcp: (ctx) => runMcp(ctx, DEPS),
|
|
3501
|
+
"api-key": (ctx) => runApiKey(ctx, DEPS)
|
|
3502
|
+
};
|
|
3503
|
+
var NEEDS_WRANGLER = /* @__PURE__ */ new Set(["install", "update", "reset-password", "domain", "uninstall"]);
|
|
3504
|
+
var LAZY_WRANGLER = /* @__PURE__ */ new Set(["mcp", "api-key", ...DATA_COMMANDS]);
|
|
3505
|
+
var SUBTITLE = { install: "installer", update: "update", "reset-password": "reset password", domain: "custom domain", status: "status", license: "license", uninstall: "uninstall", mcp: "AI agents", "api-key": "API keys" };
|
|
3506
|
+
function help() {
|
|
3507
|
+
const c = import_picocolors3.default;
|
|
3508
|
+
const cmd = (name, text) => ` ${c.cyan(name.padEnd(22))}${text}`;
|
|
3509
|
+
const flag = (name, text) => ` ${c.yellow(name.padEnd(28))}${text}`;
|
|
3510
|
+
return [
|
|
3511
|
+
"",
|
|
3512
|
+
`${c.bgBlue(c.white(c.bold(" DataBull ")))} ${c.dim(`v${VERSION}`)}`,
|
|
3513
|
+
"",
|
|
3514
|
+
"Install and update DataBull on your own Cloudflare account, and read your stats from the terminal or an AI agent.",
|
|
3515
|
+
"",
|
|
3516
|
+
`${c.bold("Usage")} npx databull@latest ${c.dim("[command] [options]")}`,
|
|
3517
|
+
"",
|
|
3518
|
+
c.bold("Commands"),
|
|
3519
|
+
cmd("install", "Set up DataBull (the default when no command is given)"),
|
|
3520
|
+
cmd("update", "Update an install to the latest version"),
|
|
3521
|
+
cmd("reset-password", "Get a one-time link to set a new password"),
|
|
3522
|
+
cmd("domain", "Move an install to your own domain (or back to workers.dev)"),
|
|
3523
|
+
cmd("status", "Show your installs, their versions and whether they answer"),
|
|
3524
|
+
cmd("license", "Show the license an install uses; --deactivate frees it"),
|
|
3525
|
+
cmd("uninstall", "Delete an install from Cloudflare"),
|
|
3526
|
+
cmd("help", "Show this help"),
|
|
3527
|
+
"",
|
|
3528
|
+
c.bold("AI agents & API"),
|
|
3529
|
+
cmd("mcp", "Connect Claude Code, Cursor, VS Code, Codex\u2026 to your stats"),
|
|
3530
|
+
cmd("api-key", "create | list | revoke read-only API keys"),
|
|
3531
|
+
"",
|
|
3532
|
+
c.bold("Your stats") + c.dim(" (add --json for scripts and agents)"),
|
|
3533
|
+
cmd("sites", "Every site: today, online now, 7 and 30 days"),
|
|
3534
|
+
cmd("stats [site]", "Visitors, revenue, conversion\u2026 vs the previous period"),
|
|
3535
|
+
cmd("sources [site]", "Referrers ranked by revenue (--dim channel|utm_campaign\u2026)"),
|
|
3536
|
+
cmd("breakdown <dim>", "Rank any dimension: country, page, device, entry_page\u2026"),
|
|
3537
|
+
cmd("compare [site]", "What changed vs the previous period, and why"),
|
|
3538
|
+
cmd("sales [site]", "Recent sales and the source each one came from"),
|
|
3539
|
+
cmd("visitors [site]", "Recent visitors (--customers for buyers only)"),
|
|
3540
|
+
cmd("visitor <id>", "One visitor's journey: visits, pages, payments"),
|
|
3541
|
+
cmd("live [site]", "Who's on the site right now"),
|
|
3542
|
+
cmd("goals | funnels | ltv", "Goals, funnel steps, lifetime value by source"),
|
|
3543
|
+
cmd("query <tool> [json]", "Call any agent tool and print its JSON"),
|
|
3544
|
+
"",
|
|
3545
|
+
c.bold("Options"),
|
|
3546
|
+
flag("--key <key>", "License key (otherwise you are asked)"),
|
|
3547
|
+
flag("--account <id|name>", "Cloudflare account to use"),
|
|
3548
|
+
flag("--name <worker>", "Worker name (default: databull)"),
|
|
3549
|
+
flag("--domain <host|workers.dev>", "Address, e.g. analytics.example.com"),
|
|
3550
|
+
flag("--yes, -y", "Accept the defaults and skip confirmations"),
|
|
3551
|
+
flag("--no-open", "Don't open the browser; print links instead"),
|
|
3552
|
+
flag("--deactivate", "With license: free this activation"),
|
|
3553
|
+
flag("--delete-database", "With uninstall: also delete the database"),
|
|
3554
|
+
flag("--period, -p <range>", "today, yesterday, 24h, 7d, 30d, mtd, last_month, ytd, 12m, all"),
|
|
3555
|
+
flag("--from / --to <YYYY-MM-DD>", "Custom date range (in the site's time zone)"),
|
|
3556
|
+
flag("--filter, -f <key=value>", "Repeatable: referrer=x.com, country=US, page=/pricing\u2026"),
|
|
3557
|
+
flag("--by <metric>", "Sort: revenue, visitors, conversion (sources, breakdown, visitors)"),
|
|
3558
|
+
flag("--limit, -n <n>", "How many rows"),
|
|
3559
|
+
flag("--json", "Print the raw JSON (for scripts and agents)"),
|
|
3560
|
+
flag("--url, --api-key", "Read another install (or set DATABULL_URL, DATABULL_API_KEY)"),
|
|
3561
|
+
flag("--client <name>", "With mcp: claude, cursor, vscode, codex, windsurf, other"),
|
|
3562
|
+
flag("--label <text>, --pii", "With api-key create: a name; allow customer details"),
|
|
3563
|
+
flag("--bundle <file.tgz>", "Use a local release bundle instead of downloading"),
|
|
3564
|
+
flag("--api <url>", "License server (default https://databull.app, env DATABULL_API)"),
|
|
3565
|
+
flag("--home <dir>", "Where DataBull keeps its files (default ~/.databull, env DATABULL_HOME)"),
|
|
3566
|
+
flag("--version, -v", "Print the installer version"),
|
|
3567
|
+
"",
|
|
3568
|
+
`${c.dim("Docs")} https://databull.app/docs ${c.dim("Help")} adgan.business@gmail.com`,
|
|
3569
|
+
""
|
|
3570
|
+
].join("\n");
|
|
3571
|
+
}
|
|
3572
|
+
async function main(argv) {
|
|
3573
|
+
let args;
|
|
3574
|
+
try {
|
|
3575
|
+
args = parseArgs(argv);
|
|
3576
|
+
} catch (err) {
|
|
3577
|
+
printFailure(err, null);
|
|
3578
|
+
return 2;
|
|
3579
|
+
}
|
|
3580
|
+
if (args.command === "version") {
|
|
3581
|
+
process.stdout.write(`databull ${VERSION}
|
|
3582
|
+
`);
|
|
3583
|
+
return 0;
|
|
3584
|
+
}
|
|
3585
|
+
if (args.command === "help") {
|
|
3586
|
+
process.stdout.write(help() + "\n");
|
|
3587
|
+
return 0;
|
|
3588
|
+
}
|
|
3589
|
+
const major = Number(process.versions.node.split(".")[0]);
|
|
3590
|
+
if (major < 22) {
|
|
3591
|
+
process.stdout.write(`DataBull needs Node.js 22 or newer (you have ${process.versions.node}).
|
|
3592
|
+
Install the LTS version from https://nodejs.org (or \`brew install node\` on a Mac), open a new terminal, and run this again.
|
|
3593
|
+
`);
|
|
3594
|
+
return 1;
|
|
3595
|
+
}
|
|
3596
|
+
const paths = resolvePaths(args.flags.home);
|
|
3597
|
+
const log = createLog(paths.logs);
|
|
3598
|
+
const ctx = { version: VERSION, command: args.command, args: args.args, flags: args.flags, paths, log, api: apiBase(args.flags), cfg: null };
|
|
3599
|
+
if (NEEDS_WRANGLER.has(args.command)) ctx.wrangler = startTools(paths, log);
|
|
3600
|
+
else if (LAZY_WRANGLER.has(args.command)) {
|
|
3601
|
+
let tools = null;
|
|
3602
|
+
ctx.wrangler = () => (tools ||= startTools(paths, log))();
|
|
3603
|
+
Object.defineProperty(ctx.wrangler, "ready", { get: () => !!tools?.ready });
|
|
3604
|
+
} else ctx.wrangler = () => Promise.reject(new Error("wrangler not available for this command"));
|
|
3605
|
+
ctx.cfToken = tokenGetter(ctx);
|
|
3606
|
+
if (DATA_COMMANDS.includes(args.command)) return runDataCommand(ctx);
|
|
3607
|
+
const onSigint = () => {
|
|
3608
|
+
killChildren();
|
|
3609
|
+
cleanupTemp();
|
|
3610
|
+
printCancelled();
|
|
3611
|
+
process.exit(130);
|
|
3612
|
+
};
|
|
3613
|
+
process.on("SIGINT", onSigint);
|
|
3614
|
+
process.on("SIGTERM", onSigint);
|
|
3615
|
+
banner(VERSION, SUBTITLE[args.command]);
|
|
3616
|
+
try {
|
|
3617
|
+
ctx.cfg = readConfig(paths);
|
|
3618
|
+
await RUN[args.command](ctx);
|
|
3619
|
+
return 0;
|
|
3620
|
+
} catch (err) {
|
|
3621
|
+
if (err instanceof Cancelled) {
|
|
3622
|
+
killChildren();
|
|
3623
|
+
printCancelled();
|
|
3624
|
+
return 130;
|
|
3625
|
+
}
|
|
3626
|
+
log.section("failure");
|
|
3627
|
+
log.write(err?.stack || String(err));
|
|
3628
|
+
printFailure(err, log.path);
|
|
3629
|
+
return 1;
|
|
3630
|
+
} finally {
|
|
3631
|
+
process.off("SIGINT", onSigint);
|
|
3632
|
+
process.off("SIGTERM", onSigint);
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
async function runDataCommand(ctx) {
|
|
3636
|
+
process.stdout.on("error", (err) => {
|
|
3637
|
+
if (err.code === "EPIPE") process.exit(0);
|
|
3638
|
+
throw err;
|
|
3639
|
+
});
|
|
3640
|
+
const onSigint = () => {
|
|
3641
|
+
killChildren();
|
|
3642
|
+
process.exit(130);
|
|
3643
|
+
};
|
|
3644
|
+
process.on("SIGINT", onSigint);
|
|
3645
|
+
try {
|
|
3646
|
+
ctx.cfg = readConfig(ctx.paths);
|
|
3647
|
+
await runData(ctx, DEPS);
|
|
3648
|
+
return 0;
|
|
3649
|
+
} catch (err) {
|
|
3650
|
+
stopSpinner();
|
|
3651
|
+
if (err instanceof Cancelled) return 130;
|
|
3652
|
+
ctx.log.section("failure");
|
|
3653
|
+
ctx.log.write(err?.stack || String(err));
|
|
3654
|
+
const message = err instanceof Fail ? err.message : `Something unexpected went wrong: ${err?.message || err}`;
|
|
3655
|
+
const fix = err instanceof Fail ? err.fix : `Run it again; if it keeps happening, email adgan.business@gmail.com with ${ctx.log.path}.`;
|
|
3656
|
+
if (ctx.flags.json) process.stdout.write(`${JSON.stringify({ error: message, ...fix ? { fix } : {} })}
|
|
3657
|
+
`);
|
|
3658
|
+
process.stderr.write(`${import_picocolors3.default.red("\u2716")} ${message}
|
|
3659
|
+
${fix ? ` ${import_picocolors3.default.dim(fix)}
|
|
3660
|
+
` : ""}`);
|
|
3661
|
+
return 1;
|
|
3662
|
+
} finally {
|
|
3663
|
+
process.off("SIGINT", onSigint);
|
|
3664
|
+
}
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3667
|
+
// src/cli.mjs
|
|
3668
|
+
main(process.argv.slice(2)).then(
|
|
3669
|
+
(code) => {
|
|
3670
|
+
process.exitCode = code;
|
|
3671
|
+
},
|
|
3672
|
+
(err) => {
|
|
3673
|
+
process.stderr.write(`${err?.stack || err}
|
|
3674
|
+
`);
|
|
3675
|
+
process.exitCode = 1;
|
|
3676
|
+
}
|
|
3677
|
+
);
|