lopebase 0.2.0 → 0.3.1
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/LICENSE +21 -0
- package/README.md +36 -7
- package/dist/lopebase.js +1490 -296
- package/package.json +4 -2
package/dist/lopebase.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
2
3
|
var __create = Object.create;
|
|
3
4
|
var { getPrototypeOf: __getProtoOf, defineProperty: __defProp, getOwnPropertyNames: __getOwnPropNames } = Object;
|
|
4
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
@@ -13,7 +14,7 @@ var __toESMCache_node, __toESMCache_esm, __toESM = (mod, isNodeMode, target) =>
|
|
|
13
14
|
return cached;
|
|
14
15
|
}
|
|
15
16
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
16
|
-
let to = isNodeMode || !mod || !mod.__esModule
|
|
17
|
+
let to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: !0 }) : target;
|
|
17
18
|
if (mod && typeof mod === "object" || typeof mod === "function") {
|
|
18
19
|
for (let key of __getOwnPropNames(mod))
|
|
19
20
|
if (!__hasOwnProp.call(to, key))
|
|
@@ -27,6 +28,7 @@ var __toESMCache_node, __toESMCache_esm, __toESM = (mod, isNodeMode, target) =>
|
|
|
27
28
|
return to;
|
|
28
29
|
};
|
|
29
30
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
31
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
30
32
|
|
|
31
33
|
// ../../node_modules/.bun/sisteransi@1.0.5/node_modules/sisteransi/src/index.js
|
|
32
34
|
var require_src = __commonJS(function(exports, module) {
|
|
@@ -143,8 +145,364 @@ var require_picocolors = __commonJS(function(exports, module) {
|
|
|
143
145
|
module.exports.createColors = createColors;
|
|
144
146
|
});
|
|
145
147
|
|
|
148
|
+
// ../../node_modules/.bun/isexe@2.0.0/node_modules/isexe/windows.js
|
|
149
|
+
var require_windows = __commonJS(function(exports, module) {
|
|
150
|
+
module.exports = isexe;
|
|
151
|
+
isexe.sync = sync;
|
|
152
|
+
var fs = __require("fs");
|
|
153
|
+
function checkPathExt(path, options) {
|
|
154
|
+
var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
|
|
155
|
+
if (!pathext)
|
|
156
|
+
return !0;
|
|
157
|
+
if (pathext = pathext.split(";"), pathext.indexOf("") !== -1)
|
|
158
|
+
return !0;
|
|
159
|
+
for (var i = 0;i < pathext.length; i++) {
|
|
160
|
+
var p2 = pathext[i].toLowerCase();
|
|
161
|
+
if (p2 && path.substr(-p2.length).toLowerCase() === p2)
|
|
162
|
+
return !0;
|
|
163
|
+
}
|
|
164
|
+
return !1;
|
|
165
|
+
}
|
|
166
|
+
function checkStat(stat, path, options) {
|
|
167
|
+
if (!stat.isSymbolicLink() && !stat.isFile())
|
|
168
|
+
return !1;
|
|
169
|
+
return checkPathExt(path, options);
|
|
170
|
+
}
|
|
171
|
+
function isexe(path, options, cb) {
|
|
172
|
+
fs.stat(path, function(er, stat) {
|
|
173
|
+
cb(er, er ? !1 : checkStat(stat, path, options));
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
function sync(path, options) {
|
|
177
|
+
return checkStat(fs.statSync(path), path, options);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// ../../node_modules/.bun/isexe@2.0.0/node_modules/isexe/mode.js
|
|
182
|
+
var require_mode = __commonJS(function(exports, module) {
|
|
183
|
+
module.exports = isexe;
|
|
184
|
+
isexe.sync = sync;
|
|
185
|
+
var fs = __require("fs");
|
|
186
|
+
function isexe(path, options, cb) {
|
|
187
|
+
fs.stat(path, function(er, stat) {
|
|
188
|
+
cb(er, er ? !1 : checkStat(stat, options));
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
function sync(path, options) {
|
|
192
|
+
return checkStat(fs.statSync(path), options);
|
|
193
|
+
}
|
|
194
|
+
function checkStat(stat, options) {
|
|
195
|
+
return stat.isFile() && checkMode(stat, options);
|
|
196
|
+
}
|
|
197
|
+
function checkMode(stat, options) {
|
|
198
|
+
var { mode: mod, uid, gid } = stat, myUid = options.uid !== void 0 ? options.uid : process.getuid && process.getuid(), myGid = options.gid !== void 0 ? options.gid : process.getgid && process.getgid(), u2 = parseInt("100", 8), g2 = parseInt("010", 8), o2 = parseInt("001", 8), ug = u2 | g2, ret = mod & o2 || mod & g2 && gid === myGid || mod & u2 && uid === myUid || mod & ug && myUid === 0;
|
|
199
|
+
return ret;
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// ../../node_modules/.bun/isexe@2.0.0/node_modules/isexe/index.js
|
|
204
|
+
var require_isexe = __commonJS(function(exports, module) {
|
|
205
|
+
var fs = __require("fs"), core;
|
|
206
|
+
if (process.platform === "win32" || global.TESTING_WINDOWS)
|
|
207
|
+
core = require_windows();
|
|
208
|
+
else
|
|
209
|
+
core = require_mode();
|
|
210
|
+
module.exports = isexe;
|
|
211
|
+
isexe.sync = sync;
|
|
212
|
+
function isexe(path, options, cb) {
|
|
213
|
+
if (typeof options === "function")
|
|
214
|
+
cb = options, options = {};
|
|
215
|
+
if (!cb) {
|
|
216
|
+
if (typeof Promise !== "function")
|
|
217
|
+
throw TypeError("callback not provided");
|
|
218
|
+
return new Promise(function(resolve, reject) {
|
|
219
|
+
isexe(path, options || {}, function(er, is) {
|
|
220
|
+
if (er)
|
|
221
|
+
reject(er);
|
|
222
|
+
else
|
|
223
|
+
resolve(is);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
core(path, options || {}, function(er, is) {
|
|
228
|
+
if (er) {
|
|
229
|
+
if (er.code === "EACCES" || options && options.ignoreErrors)
|
|
230
|
+
er = null, is = !1;
|
|
231
|
+
}
|
|
232
|
+
cb(er, is);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
function sync(path, options) {
|
|
236
|
+
try {
|
|
237
|
+
return core.sync(path, options || {});
|
|
238
|
+
} catch (er) {
|
|
239
|
+
if (options && options.ignoreErrors || er.code === "EACCES")
|
|
240
|
+
return !1;
|
|
241
|
+
else
|
|
242
|
+
throw er;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// ../../node_modules/.bun/which@2.0.2/node_modules/which/which.js
|
|
248
|
+
var require_which = __commonJS(function(exports, module) {
|
|
249
|
+
var isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys", path = __require("path"), COLON = isWindows ? ";" : ":", isexe = require_isexe(), getNotFoundError = (cmd) => Object.assign(Error(`not found: ${cmd}`), { code: "ENOENT" }), getPathInfo = (cmd, opt) => {
|
|
250
|
+
let colon = opt.colon || COLON, pathEnv = cmd.match(/\//) || isWindows && cmd.match(/\\/) ? [""] : [
|
|
251
|
+
...isWindows ? [process.cwd()] : [],
|
|
252
|
+
...(opt.path || process.env.PATH || "").split(colon)
|
|
253
|
+
], pathExtExe = isWindows ? opt.pathExt || process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM" : "", pathExt = isWindows ? pathExtExe.split(colon) : [""];
|
|
254
|
+
if (isWindows) {
|
|
255
|
+
if (cmd.indexOf(".") !== -1 && pathExt[0] !== "")
|
|
256
|
+
pathExt.unshift("");
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
pathEnv,
|
|
260
|
+
pathExt,
|
|
261
|
+
pathExtExe
|
|
262
|
+
};
|
|
263
|
+
}, which = (cmd, opt, cb) => {
|
|
264
|
+
if (typeof opt === "function")
|
|
265
|
+
cb = opt, opt = {};
|
|
266
|
+
if (!opt)
|
|
267
|
+
opt = {};
|
|
268
|
+
let { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt), found = [], step = (i) => new Promise((resolve, reject) => {
|
|
269
|
+
if (i === pathEnv.length)
|
|
270
|
+
return opt.all && found.length ? resolve(found) : reject(getNotFoundError(cmd));
|
|
271
|
+
let ppRaw = pathEnv[i], pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw, pCmd = path.join(pathPart, cmd), p2 = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
|
|
272
|
+
resolve(subStep(p2, i, 0));
|
|
273
|
+
}), subStep = (p2, i, ii) => new Promise((resolve, reject) => {
|
|
274
|
+
if (ii === pathExt.length)
|
|
275
|
+
return resolve(step(i + 1));
|
|
276
|
+
let ext = pathExt[ii];
|
|
277
|
+
isexe(p2 + ext, { pathExt: pathExtExe }, (er, is) => {
|
|
278
|
+
if (!er && is)
|
|
279
|
+
if (opt.all)
|
|
280
|
+
found.push(p2 + ext);
|
|
281
|
+
else
|
|
282
|
+
return resolve(p2 + ext);
|
|
283
|
+
return resolve(subStep(p2, i, ii + 1));
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
return cb ? step(0).then((res) => cb(null, res), cb) : step(0);
|
|
287
|
+
}, whichSync = (cmd, opt) => {
|
|
288
|
+
opt = opt || {};
|
|
289
|
+
let { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt), found = [];
|
|
290
|
+
for (let i = 0;i < pathEnv.length; i++) {
|
|
291
|
+
let ppRaw = pathEnv[i], pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw, pCmd = path.join(pathPart, cmd), p2 = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
|
|
292
|
+
for (let j2 = 0;j2 < pathExt.length; j2++) {
|
|
293
|
+
let cur = p2 + pathExt[j2];
|
|
294
|
+
try {
|
|
295
|
+
if (isexe.sync(cur, { pathExt: pathExtExe }))
|
|
296
|
+
if (opt.all)
|
|
297
|
+
found.push(cur);
|
|
298
|
+
else
|
|
299
|
+
return cur;
|
|
300
|
+
} catch (ex) {}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (opt.all && found.length)
|
|
304
|
+
return found;
|
|
305
|
+
if (opt.nothrow)
|
|
306
|
+
return null;
|
|
307
|
+
throw getNotFoundError(cmd);
|
|
308
|
+
};
|
|
309
|
+
module.exports = which;
|
|
310
|
+
which.sync = whichSync;
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// ../../node_modules/.bun/path-key@3.1.1/node_modules/path-key/index.js
|
|
314
|
+
var require_path_key = __commonJS(function(exports, module) {
|
|
315
|
+
var pathKey = (options = {}) => {
|
|
316
|
+
let environment = options.env || process.env;
|
|
317
|
+
if ((options.platform || process.platform) !== "win32")
|
|
318
|
+
return "PATH";
|
|
319
|
+
return Object.keys(environment).reverse().find((key) => key.toUpperCase() === "PATH") || "Path";
|
|
320
|
+
};
|
|
321
|
+
module.exports = pathKey;
|
|
322
|
+
module.exports.default = pathKey;
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// ../../node_modules/.bun/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/resolveCommand.js
|
|
326
|
+
var require_resolveCommand = __commonJS(function(exports, module) {
|
|
327
|
+
var path = __require("path"), which = require_which(), getPathKey = require_path_key();
|
|
328
|
+
function resolveCommandAttempt(parsed, withoutPathExt) {
|
|
329
|
+
let env = parsed.options.env || process.env, cwd = process.cwd(), hasCustomCwd = parsed.options.cwd != null, shouldSwitchCwd = hasCustomCwd && process.chdir !== void 0 && !process.chdir.disabled;
|
|
330
|
+
if (shouldSwitchCwd)
|
|
331
|
+
try {
|
|
332
|
+
process.chdir(parsed.options.cwd);
|
|
333
|
+
} catch (err) {}
|
|
334
|
+
let resolved;
|
|
335
|
+
try {
|
|
336
|
+
resolved = which.sync(parsed.command, {
|
|
337
|
+
path: env[getPathKey({ env })],
|
|
338
|
+
pathExt: withoutPathExt ? path.delimiter : void 0
|
|
339
|
+
});
|
|
340
|
+
} catch (e2) {} finally {
|
|
341
|
+
if (shouldSwitchCwd)
|
|
342
|
+
process.chdir(cwd);
|
|
343
|
+
}
|
|
344
|
+
if (resolved)
|
|
345
|
+
resolved = path.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
|
|
346
|
+
return resolved;
|
|
347
|
+
}
|
|
348
|
+
function resolveCommand(parsed) {
|
|
349
|
+
return resolveCommandAttempt(parsed) || resolveCommandAttempt(parsed, !0);
|
|
350
|
+
}
|
|
351
|
+
module.exports = resolveCommand;
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// ../../node_modules/.bun/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/escape.js
|
|
355
|
+
var require_escape = __commonJS(function(exports, module) {
|
|
356
|
+
var metaCharsRegExp = /([()\][%!^"`<>&|;, *?])/g;
|
|
357
|
+
function escapeCommand(arg) {
|
|
358
|
+
return arg = arg.replace(metaCharsRegExp, "^$1"), arg;
|
|
359
|
+
}
|
|
360
|
+
function escapeArgument(arg, doubleEscapeMetaChars) {
|
|
361
|
+
if (arg = `${arg}`, arg = arg.replace(/(?=(\\+?)?)\1"/g, "$1$1\\\""), arg = arg.replace(/(?=(\\+?)?)\1$/, "$1$1"), arg = `"${arg}"`, arg = arg.replace(metaCharsRegExp, "^$1"), doubleEscapeMetaChars)
|
|
362
|
+
arg = arg.replace(metaCharsRegExp, "^$1");
|
|
363
|
+
return arg;
|
|
364
|
+
}
|
|
365
|
+
exports.command = escapeCommand;
|
|
366
|
+
exports.argument = escapeArgument;
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// ../../node_modules/.bun/shebang-regex@3.0.0/node_modules/shebang-regex/index.js
|
|
370
|
+
var require_shebang_regex = __commonJS(function(exports, module) {
|
|
371
|
+
module.exports = /^#!(.*)/;
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// ../../node_modules/.bun/shebang-command@2.0.0/node_modules/shebang-command/index.js
|
|
375
|
+
var require_shebang_command = __commonJS(function(exports, module) {
|
|
376
|
+
var shebangRegex = require_shebang_regex();
|
|
377
|
+
module.exports = (string = "") => {
|
|
378
|
+
let match = string.match(shebangRegex);
|
|
379
|
+
if (!match)
|
|
380
|
+
return null;
|
|
381
|
+
let [path, argument] = match[0].replace(/#! ?/, "").split(" "), binary = path.split("/").pop();
|
|
382
|
+
if (binary === "env")
|
|
383
|
+
return argument;
|
|
384
|
+
return argument ? `${binary} ${argument}` : binary;
|
|
385
|
+
};
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// ../../node_modules/.bun/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.js
|
|
389
|
+
var require_readShebang = __commonJS(function(exports, module) {
|
|
390
|
+
var fs = __require("fs"), shebangCommand = require_shebang_command();
|
|
391
|
+
function readShebang(command) {
|
|
392
|
+
let buffer = Buffer.alloc(150), fd;
|
|
393
|
+
try {
|
|
394
|
+
fd = fs.openSync(command, "r"), fs.readSync(fd, buffer, 0, 150, 0), fs.closeSync(fd);
|
|
395
|
+
} catch (e2) {}
|
|
396
|
+
return shebangCommand(buffer.toString());
|
|
397
|
+
}
|
|
398
|
+
module.exports = readShebang;
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// ../../node_modules/.bun/cross-spawn@7.0.6/node_modules/cross-spawn/lib/parse.js
|
|
402
|
+
var require_parse = __commonJS(function(exports, module) {
|
|
403
|
+
var path = __require("path"), resolveCommand = require_resolveCommand(), escape = require_escape(), readShebang = require_readShebang(), isWin = process.platform === "win32", isExecutableRegExp = /\.(?:com|exe)$/i, isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
|
|
404
|
+
function detectShebang(parsed) {
|
|
405
|
+
parsed.file = resolveCommand(parsed);
|
|
406
|
+
let shebang = parsed.file && readShebang(parsed.file);
|
|
407
|
+
if (shebang)
|
|
408
|
+
return parsed.args.unshift(parsed.file), parsed.command = shebang, resolveCommand(parsed);
|
|
409
|
+
return parsed.file;
|
|
410
|
+
}
|
|
411
|
+
function parseNonShell(parsed) {
|
|
412
|
+
if (!isWin)
|
|
413
|
+
return parsed;
|
|
414
|
+
let commandFile = detectShebang(parsed), needsShell = !isExecutableRegExp.test(commandFile);
|
|
415
|
+
if (parsed.options.forceShell || needsShell) {
|
|
416
|
+
let needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
|
|
417
|
+
parsed.command = path.normalize(parsed.command), parsed.command = escape.command(parsed.command), parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars));
|
|
418
|
+
let shellCommand = [parsed.command].concat(parsed.args).join(" ");
|
|
419
|
+
parsed.args = ["/d", "/s", "/c", `"${shellCommand}"`], parsed.command = process.env.comspec || "cmd.exe", parsed.options.windowsVerbatimArguments = !0;
|
|
420
|
+
}
|
|
421
|
+
return parsed;
|
|
422
|
+
}
|
|
423
|
+
function parse(command, args, options) {
|
|
424
|
+
if (args && !Array.isArray(args))
|
|
425
|
+
options = args, args = null;
|
|
426
|
+
args = args ? args.slice(0) : [], options = Object.assign({}, options);
|
|
427
|
+
let parsed = {
|
|
428
|
+
command,
|
|
429
|
+
args,
|
|
430
|
+
options,
|
|
431
|
+
file: void 0,
|
|
432
|
+
original: {
|
|
433
|
+
command,
|
|
434
|
+
args
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
return options.shell ? parsed : parseNonShell(parsed);
|
|
438
|
+
}
|
|
439
|
+
module.exports = parse;
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// ../../node_modules/.bun/cross-spawn@7.0.6/node_modules/cross-spawn/lib/enoent.js
|
|
443
|
+
var require_enoent = __commonJS(function(exports, module) {
|
|
444
|
+
var isWin = process.platform === "win32";
|
|
445
|
+
function notFoundError(original, syscall) {
|
|
446
|
+
return Object.assign(Error(`${syscall} ${original.command} ENOENT`), {
|
|
447
|
+
code: "ENOENT",
|
|
448
|
+
errno: "ENOENT",
|
|
449
|
+
syscall: `${syscall} ${original.command}`,
|
|
450
|
+
path: original.command,
|
|
451
|
+
spawnargs: original.args
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
function hookChildProcess(cp, parsed) {
|
|
455
|
+
if (!isWin)
|
|
456
|
+
return;
|
|
457
|
+
let originalEmit = cp.emit;
|
|
458
|
+
cp.emit = function(name, arg1) {
|
|
459
|
+
if (name === "exit") {
|
|
460
|
+
let err = verifyENOENT(arg1, parsed);
|
|
461
|
+
if (err)
|
|
462
|
+
return originalEmit.call(cp, "error", err);
|
|
463
|
+
}
|
|
464
|
+
return originalEmit.apply(cp, arguments);
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
function verifyENOENT(status, parsed) {
|
|
468
|
+
if (isWin && status === 1 && !parsed.file)
|
|
469
|
+
return notFoundError(parsed.original, "spawn");
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
function verifyENOENTSync(status, parsed) {
|
|
473
|
+
if (isWin && status === 1 && !parsed.file)
|
|
474
|
+
return notFoundError(parsed.original, "spawnSync");
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
module.exports = {
|
|
478
|
+
hookChildProcess,
|
|
479
|
+
verifyENOENT,
|
|
480
|
+
verifyENOENTSync,
|
|
481
|
+
notFoundError
|
|
482
|
+
};
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
// ../../node_modules/.bun/cross-spawn@7.0.6/node_modules/cross-spawn/index.js
|
|
486
|
+
var require_cross_spawn = __commonJS(function(exports, module) {
|
|
487
|
+
var cp = __require("child_process"), parse = require_parse(), enoent = require_enoent();
|
|
488
|
+
function spawn(command, args, options) {
|
|
489
|
+
let parsed = parse(command, args, options), spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
|
|
490
|
+
return enoent.hookChildProcess(spawned, parsed), spawned;
|
|
491
|
+
}
|
|
492
|
+
function spawnSync(command, args, options) {
|
|
493
|
+
let parsed = parse(command, args, options), result = cp.spawnSync(parsed.command, parsed.args, parsed.options);
|
|
494
|
+
return result.error = result.error || enoent.verifyENOENTSync(result.status, parsed), result;
|
|
495
|
+
}
|
|
496
|
+
module.exports = spawn;
|
|
497
|
+
module.exports.spawn = spawn;
|
|
498
|
+
module.exports.sync = spawnSync;
|
|
499
|
+
module.exports._parse = parse;
|
|
500
|
+
module.exports._enoent = enoent;
|
|
501
|
+
});
|
|
502
|
+
|
|
146
503
|
// bin/lopebase.ts
|
|
147
|
-
import { spawn
|
|
504
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
505
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
148
506
|
|
|
149
507
|
// ../../node_modules/.bun/@clack+prompts@0.11.0/node_modules/@clack/prompts/dist/index.mjs
|
|
150
508
|
import { stripVTControlCharacters as S2 } from "node:util";
|
|
@@ -474,6 +832,24 @@ class x {
|
|
|
474
832
|
}
|
|
475
833
|
}
|
|
476
834
|
}
|
|
835
|
+
|
|
836
|
+
class dD extends x {
|
|
837
|
+
get cursor() {
|
|
838
|
+
return this.value ? 0 : 1;
|
|
839
|
+
}
|
|
840
|
+
get _value() {
|
|
841
|
+
return this.cursor === 0;
|
|
842
|
+
}
|
|
843
|
+
constructor(u) {
|
|
844
|
+
super(u, !1), this.value = !!u.initialValue, this.on("value", () => {
|
|
845
|
+
this.value = this._value;
|
|
846
|
+
}), this.on("confirm", (t) => {
|
|
847
|
+
this.output.write(import_sisteransi.cursor.move(0, -1)), this.value = t, this.state = "submit", this.close();
|
|
848
|
+
}), this.on("cursor", () => {
|
|
849
|
+
this.value = !this.value;
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
}
|
|
477
853
|
var A;
|
|
478
854
|
A = /* @__PURE__ */ new WeakMap;
|
|
479
855
|
var kD = Object.defineProperty, $D = (e, u, t) => (u in e) ? kD(e, u, { enumerable: !0, configurable: !0, writable: !0, value: t }) : e[u] = t, H = (e, u, t) => ($D(e, typeof u != "symbol" ? u + "" : u, t), t), SD = class extends x {
|
|
@@ -592,17 +968,17 @@ var V2 = ce(), u = (t, n) => V2 ? t : n, le = u("◆", "*"), L2 = u("■", "x"),
|
|
|
592
968
|
return import_picocolors2.default.green(C);
|
|
593
969
|
}
|
|
594
970
|
}, G2 = (t) => {
|
|
595
|
-
let { cursor: n, options:
|
|
596
|
-
n >=
|
|
597
|
-
let $ = a <
|
|
598
|
-
return
|
|
599
|
-
let
|
|
600
|
-
return
|
|
971
|
+
let { 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)), l2 = 0;
|
|
972
|
+
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));
|
|
973
|
+
let $2 = a < r2.length && l2 > 0, g2 = a < r2.length && l2 + a < r2.length;
|
|
974
|
+
return r2.slice(l2, l2 + a).map((p2, v2, f) => {
|
|
975
|
+
let j2 = v2 === 0 && $2, E = v2 === f.length - 1 && g2;
|
|
976
|
+
return j2 || E ? import_picocolors2.default.dim("...") : i(p2, v2 + l2 === n);
|
|
601
977
|
});
|
|
602
978
|
}, he = (t) => new RD({ validate: t.validate, placeholder: t.placeholder, defaultValue: t.defaultValue, initialValue: t.initialValue, render() {
|
|
603
979
|
let n = `${import_picocolors2.default.gray(o)}
|
|
604
980
|
${b2(this.state)} ${t.message}
|
|
605
|
-
`,
|
|
981
|
+
`, 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;
|
|
606
982
|
switch (this.state) {
|
|
607
983
|
case "error":
|
|
608
984
|
return `${n.trim()}
|
|
@@ -622,7 +998,7 @@ ${import_picocolors2.default.cyan(d2)}
|
|
|
622
998
|
} }).prompt(), ge = (t) => new MD({ validate: t.validate, mask: t.mask ?? $e, render() {
|
|
623
999
|
let n = `${import_picocolors2.default.gray(o)}
|
|
624
1000
|
${b2(this.state)} ${t.message}
|
|
625
|
-
`,
|
|
1001
|
+
`, r2 = this.valueWithCursor, i = this.masked;
|
|
626
1002
|
switch (this.state) {
|
|
627
1003
|
case "error":
|
|
628
1004
|
return `${n.trim()}
|
|
@@ -635,19 +1011,36 @@ ${import_picocolors2.default.yellow(d2)} ${import_picocolors2.default.yellow(th
|
|
|
635
1011
|
return `${n}${import_picocolors2.default.gray(o)} ${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(i ?? ""))}${i ? `
|
|
636
1012
|
${import_picocolors2.default.gray(o)}` : ""}`;
|
|
637
1013
|
default:
|
|
638
|
-
return `${n}${import_picocolors2.default.cyan(o)} ${
|
|
1014
|
+
return `${n}${import_picocolors2.default.cyan(o)} ${r2}
|
|
639
1015
|
${import_picocolors2.default.cyan(d2)}
|
|
640
1016
|
`;
|
|
641
1017
|
}
|
|
642
|
-
} }).prompt()
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
let
|
|
1018
|
+
} }).prompt(), ye = (t) => {
|
|
1019
|
+
let n = t.active ?? "Yes", r2 = t.inactive ?? "No";
|
|
1020
|
+
return new dD({ active: n, inactive: r2, initialValue: t.initialValue ?? !0, render() {
|
|
1021
|
+
let i = `${import_picocolors2.default.gray(o)}
|
|
1022
|
+
${b2(this.state)} ${t.message}
|
|
1023
|
+
`, s = this.value ? n : r2;
|
|
1024
|
+
switch (this.state) {
|
|
1025
|
+
case "submit":
|
|
1026
|
+
return `${i}${import_picocolors2.default.gray(o)} ${import_picocolors2.default.dim(s)}`;
|
|
1027
|
+
case "cancel":
|
|
1028
|
+
return `${i}${import_picocolors2.default.gray(o)} ${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(s))}
|
|
1029
|
+
${import_picocolors2.default.gray(o)}`;
|
|
1030
|
+
default:
|
|
1031
|
+
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}`}
|
|
1032
|
+
${import_picocolors2.default.cyan(d2)}
|
|
1033
|
+
`;
|
|
1034
|
+
}
|
|
1035
|
+
} }).prompt();
|
|
1036
|
+
}, ve = (t) => {
|
|
1037
|
+
let n = (r2, i) => {
|
|
1038
|
+
let s = r2.label ?? String(r2.value);
|
|
646
1039
|
switch (i) {
|
|
647
1040
|
case "selected":
|
|
648
1041
|
return `${import_picocolors2.default.dim(s)}`;
|
|
649
1042
|
case "active":
|
|
650
|
-
return `${import_picocolors2.default.green(k2)} ${s} ${
|
|
1043
|
+
return `${import_picocolors2.default.green(k2)} ${s} ${r2.hint ? import_picocolors2.default.dim(`(${r2.hint})`) : ""}`;
|
|
651
1044
|
case "cancelled":
|
|
652
1045
|
return `${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(s))}`;
|
|
653
1046
|
default:
|
|
@@ -655,17 +1048,17 @@ var ve = (t) => {
|
|
|
655
1048
|
}
|
|
656
1049
|
};
|
|
657
1050
|
return new LD({ options: t.options, initialValue: t.initialValue, render() {
|
|
658
|
-
let
|
|
1051
|
+
let r2 = `${import_picocolors2.default.gray(o)}
|
|
659
1052
|
${b2(this.state)} ${t.message}
|
|
660
1053
|
`;
|
|
661
1054
|
switch (this.state) {
|
|
662
1055
|
case "submit":
|
|
663
|
-
return `${
|
|
1056
|
+
return `${r2}${import_picocolors2.default.gray(o)} ${n(this.options[this.cursor], "selected")}`;
|
|
664
1057
|
case "cancel":
|
|
665
|
-
return `${
|
|
1058
|
+
return `${r2}${import_picocolors2.default.gray(o)} ${n(this.options[this.cursor], "cancelled")}
|
|
666
1059
|
${import_picocolors2.default.gray(o)}`;
|
|
667
1060
|
default:
|
|
668
|
-
return `${
|
|
1061
|
+
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(`
|
|
669
1062
|
${import_picocolors2.default.cyan(o)} `)}
|
|
670
1063
|
${import_picocolors2.default.cyan(d2)}
|
|
671
1064
|
`;
|
|
@@ -673,16 +1066,16 @@ ${import_picocolors2.default.cyan(d2)}
|
|
|
673
1066
|
} }).prompt();
|
|
674
1067
|
};
|
|
675
1068
|
var fe = (t) => {
|
|
676
|
-
let n = (
|
|
677
|
-
let s =
|
|
678
|
-
return i === "active" ? `${import_picocolors2.default.cyan(A2)} ${s} ${
|
|
1069
|
+
let n = (r2, i) => {
|
|
1070
|
+
let s = r2.label ?? String(r2.value);
|
|
1071
|
+
return i === "active" ? `${import_picocolors2.default.cyan(A2)} ${s} ${r2.hint ? import_picocolors2.default.dim(`(${r2.hint})`) : ""}` : i === "selected" ? `${import_picocolors2.default.green(T)} ${import_picocolors2.default.dim(s)} ${r2.hint ? import_picocolors2.default.dim(`(${r2.hint})`) : ""}` : i === "cancelled" ? `${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(s))}` : i === "active-selected" ? `${import_picocolors2.default.green(T)} ${s} ${r2.hint ? import_picocolors2.default.dim(`(${r2.hint})`) : ""}` : i === "submitted" ? `${import_picocolors2.default.dim(s)}` : `${import_picocolors2.default.dim(F)} ${import_picocolors2.default.dim(s)}`;
|
|
679
1072
|
};
|
|
680
|
-
return new SD({ options: t.options, initialValues: t.initialValues, required: t.required ?? !0, cursorAt: t.cursorAt, validate(
|
|
681
|
-
if (this.required &&
|
|
1073
|
+
return new SD({ options: t.options, initialValues: t.initialValues, required: t.required ?? !0, cursorAt: t.cursorAt, validate(r2) {
|
|
1074
|
+
if (this.required && r2.length === 0)
|
|
682
1075
|
return `Please select at least one option.
|
|
683
1076
|
${import_picocolors2.default.reset(import_picocolors2.default.dim(`Press ${import_picocolors2.default.gray(import_picocolors2.default.bgWhite(import_picocolors2.default.inverse(" space ")))} to select, ${import_picocolors2.default.gray(import_picocolors2.default.bgWhite(import_picocolors2.default.inverse(" enter ")))} to submit`))}`;
|
|
684
1077
|
}, render() {
|
|
685
|
-
let
|
|
1078
|
+
let r2 = `${import_picocolors2.default.gray(o)}
|
|
686
1079
|
${b2(this.state)} ${t.message}
|
|
687
1080
|
`, i = (s, c) => {
|
|
688
1081
|
let a = this.value.includes(s.value);
|
|
@@ -690,23 +1083,23 @@ ${b2(this.state)} ${t.message}
|
|
|
690
1083
|
};
|
|
691
1084
|
switch (this.state) {
|
|
692
1085
|
case "submit":
|
|
693
|
-
return `${
|
|
1086
|
+
return `${r2}${import_picocolors2.default.gray(o)} ${this.options.filter(({ value: s }) => this.value.includes(s)).map((s) => n(s, "submitted")).join(import_picocolors2.default.dim(", ")) || import_picocolors2.default.dim("none")}`;
|
|
694
1087
|
case "cancel": {
|
|
695
1088
|
let s = this.options.filter(({ value: c }) => this.value.includes(c)).map((c) => n(c, "cancelled")).join(import_picocolors2.default.dim(", "));
|
|
696
|
-
return `${
|
|
1089
|
+
return `${r2}${import_picocolors2.default.gray(o)} ${s.trim() ? `${s}
|
|
697
1090
|
${import_picocolors2.default.gray(o)}` : ""}`;
|
|
698
1091
|
}
|
|
699
1092
|
case "error": {
|
|
700
1093
|
let s = this.error.split(`
|
|
701
1094
|
`).map((c, a) => a === 0 ? `${import_picocolors2.default.yellow(d2)} ${import_picocolors2.default.yellow(c)}` : ` ${c}`).join(`
|
|
702
1095
|
`);
|
|
703
|
-
return `${
|
|
1096
|
+
return `${r2 + import_picocolors2.default.yellow(o)} ${G2({ options: this.options, cursor: this.cursor, maxItems: t.maxItems, style: i }).join(`
|
|
704
1097
|
${import_picocolors2.default.yellow(o)} `)}
|
|
705
1098
|
${s}
|
|
706
1099
|
`;
|
|
707
1100
|
}
|
|
708
1101
|
default:
|
|
709
|
-
return `${
|
|
1102
|
+
return `${r2}${import_picocolors2.default.cyan(o)} ${G2({ options: this.options, cursor: this.cursor, maxItems: t.maxItems, style: i }).join(`
|
|
710
1103
|
${import_picocolors2.default.cyan(o)} `)}
|
|
711
1104
|
${import_picocolors2.default.cyan(d2)}
|
|
712
1105
|
`;
|
|
@@ -714,13 +1107,13 @@ ${import_picocolors2.default.cyan(d2)}
|
|
|
714
1107
|
} }).prompt();
|
|
715
1108
|
};
|
|
716
1109
|
var Me = (t = "", n = "") => {
|
|
717
|
-
let
|
|
1110
|
+
let r2 = `
|
|
718
1111
|
${t}
|
|
719
1112
|
`.split(`
|
|
720
|
-
`), i = S2(n).length, s = Math.max(
|
|
721
|
-
let $ = S2(
|
|
722
|
-
return
|
|
723
|
-
}, 0), i) + 2, c =
|
|
1113
|
+
`), i = S2(n).length, s = Math.max(r2.reduce((a, l2) => {
|
|
1114
|
+
let $2 = S2(l2);
|
|
1115
|
+
return $2.length > a ? $2.length : a;
|
|
1116
|
+
}, 0), i) + 2, c = r2.map((a) => `${import_picocolors2.default.gray(o)} ${import_picocolors2.default.dim(a)}${" ".repeat(s - S2(a).length)}${import_picocolors2.default.gray(o)}`).join(`
|
|
724
1117
|
`);
|
|
725
1118
|
process.stdout.write(`${import_picocolors2.default.gray(o)}
|
|
726
1119
|
${import_picocolors2.default.green(C)} ${import_picocolors2.default.reset(n)} ${import_picocolors2.default.gray(_2.repeat(Math.max(s - i - 1, 1)) + me)}
|
|
@@ -740,13 +1133,13 @@ ${import_picocolors2.default.gray(d2)} ${t}
|
|
|
740
1133
|
|
|
741
1134
|
`);
|
|
742
1135
|
}, M2 = { message: (t = "", { symbol: n = import_picocolors2.default.gray(o) } = {}) => {
|
|
743
|
-
let
|
|
1136
|
+
let r2 = [`${import_picocolors2.default.gray(o)}`];
|
|
744
1137
|
if (t) {
|
|
745
1138
|
let [i, ...s] = t.split(`
|
|
746
1139
|
`);
|
|
747
|
-
|
|
1140
|
+
r2.push(`${n} ${i}`, ...s.map((c) => `${import_picocolors2.default.gray(o)} ${c}`));
|
|
748
1141
|
}
|
|
749
|
-
process.stdout.write(`${
|
|
1142
|
+
process.stdout.write(`${r2.join(`
|
|
750
1143
|
`)}
|
|
751
1144
|
`);
|
|
752
1145
|
}, info: (t) => {
|
|
@@ -763,52 +1156,52 @@ ${import_picocolors2.default.gray(d2)} ${t}
|
|
|
763
1156
|
M2.message(t, { symbol: import_picocolors2.default.red(K2) });
|
|
764
1157
|
} }, J2 = `${import_picocolors2.default.gray(o)} `;
|
|
765
1158
|
var Y2 = ({ indicator: t = "dots" } = {}) => {
|
|
766
|
-
let n = V2 ? ["◒", "◐", "◓", "◑"] : ["•", "o", "O", "0"],
|
|
767
|
-
let
|
|
768
|
-
a &&
|
|
769
|
-
},
|
|
770
|
-
process.on("uncaughtExceptionMonitor",
|
|
1159
|
+
let n = V2 ? ["◒", "◐", "◓", "◑"] : ["•", "o", "O", "0"], r2 = V2 ? 80 : 120, i = process.env.CI === "true", s, c, a = !1, l2 = "", $2, g2 = performance.now(), p2 = (m2) => {
|
|
1160
|
+
let h2 = m2 > 1 ? "Something went wrong" : "Canceled";
|
|
1161
|
+
a && N2(h2, m2);
|
|
1162
|
+
}, v2 = () => p2(2), f = () => p2(1), j2 = () => {
|
|
1163
|
+
process.on("uncaughtExceptionMonitor", v2), process.on("unhandledRejection", v2), process.on("SIGINT", f), process.on("SIGTERM", f), process.on("exit", p2);
|
|
771
1164
|
}, E = () => {
|
|
772
|
-
process.removeListener("uncaughtExceptionMonitor",
|
|
773
|
-
},
|
|
774
|
-
if ($ === void 0)
|
|
1165
|
+
process.removeListener("uncaughtExceptionMonitor", v2), process.removeListener("unhandledRejection", v2), process.removeListener("SIGINT", f), process.removeListener("SIGTERM", f), process.removeListener("exit", p2);
|
|
1166
|
+
}, B2 = () => {
|
|
1167
|
+
if ($2 === void 0)
|
|
775
1168
|
return;
|
|
776
1169
|
i && process.stdout.write(`
|
|
777
1170
|
`);
|
|
778
|
-
let
|
|
1171
|
+
let m2 = $2.split(`
|
|
779
1172
|
`);
|
|
780
|
-
process.stdout.write(import_sisteransi2.cursor.move(-999,
|
|
781
|
-
},
|
|
782
|
-
let
|
|
783
|
-
return
|
|
784
|
-
},
|
|
785
|
-
a = !0, s = fD(),
|
|
1173
|
+
process.stdout.write(import_sisteransi2.cursor.move(-999, m2.length - 1)), process.stdout.write(import_sisteransi2.erase.down(m2.length));
|
|
1174
|
+
}, R2 = (m2) => m2.replace(/\.+$/, ""), O2 = (m2) => {
|
|
1175
|
+
let h2 = (performance.now() - m2) / 1000, w2 = Math.floor(h2 / 60), I2 = Math.floor(h2 % 60);
|
|
1176
|
+
return w2 > 0 ? `[${w2}m ${I2}s]` : `[${I2}s]`;
|
|
1177
|
+
}, H2 = (m2 = "") => {
|
|
1178
|
+
a = !0, s = fD(), l2 = R2(m2), g2 = performance.now(), process.stdout.write(`${import_picocolors2.default.gray(o)}
|
|
786
1179
|
`);
|
|
787
|
-
let
|
|
788
|
-
|
|
789
|
-
if (i &&
|
|
1180
|
+
let h2 = 0, w2 = 0;
|
|
1181
|
+
j2(), c = setInterval(() => {
|
|
1182
|
+
if (i && l2 === $2)
|
|
790
1183
|
return;
|
|
791
|
-
|
|
792
|
-
let
|
|
1184
|
+
B2(), $2 = l2;
|
|
1185
|
+
let I2 = import_picocolors2.default.magenta(n[h2]);
|
|
793
1186
|
if (i)
|
|
794
|
-
process.stdout.write(`${
|
|
1187
|
+
process.stdout.write(`${I2} ${l2}...`);
|
|
795
1188
|
else if (t === "timer")
|
|
796
|
-
process.stdout.write(`${
|
|
1189
|
+
process.stdout.write(`${I2} ${l2} ${O2(g2)}`);
|
|
797
1190
|
else {
|
|
798
|
-
let
|
|
799
|
-
process.stdout.write(`${
|
|
1191
|
+
let z2 = ".".repeat(Math.floor(w2)).slice(0, 3);
|
|
1192
|
+
process.stdout.write(`${I2} ${l2}${z2}`);
|
|
800
1193
|
}
|
|
801
|
-
|
|
802
|
-
},
|
|
803
|
-
},
|
|
804
|
-
a = !1, clearInterval(c),
|
|
805
|
-
let
|
|
806
|
-
|
|
807
|
-
`) : process.stdout.write(`${
|
|
1194
|
+
h2 = h2 + 1 < n.length ? h2 + 1 : 0, w2 = w2 < n.length ? w2 + 0.125 : 0;
|
|
1195
|
+
}, r2);
|
|
1196
|
+
}, N2 = (m2 = "", h2 = 0) => {
|
|
1197
|
+
a = !1, clearInterval(c), B2();
|
|
1198
|
+
let w2 = h2 === 0 ? import_picocolors2.default.green(C) : h2 === 1 ? import_picocolors2.default.red(L2) : import_picocolors2.default.red(W2);
|
|
1199
|
+
l2 = R2(m2 ?? l2), t === "timer" ? process.stdout.write(`${w2} ${l2} ${O2(g2)}
|
|
1200
|
+
`) : process.stdout.write(`${w2} ${l2}
|
|
808
1201
|
`), E(), s();
|
|
809
1202
|
};
|
|
810
|
-
return { start:
|
|
811
|
-
|
|
1203
|
+
return { start: H2, stop: N2, message: (m2 = "") => {
|
|
1204
|
+
l2 = R2(m2 ?? l2);
|
|
812
1205
|
} };
|
|
813
1206
|
};
|
|
814
1207
|
|
|
@@ -816,7 +1209,7 @@ var Y2 = ({ indicator: t = "dots" } = {}) => {
|
|
|
816
1209
|
var SIGNATURE_HEADER = "lopebase-signature";
|
|
817
1210
|
var enc = /* @__PURE__ */ new TextEncoder;
|
|
818
1211
|
function hex(bytes) {
|
|
819
|
-
return [...new Uint8Array(bytes)].map((
|
|
1212
|
+
return [...new Uint8Array(bytes)].map((b3) => b3.toString(16).padStart(2, "0")).join("");
|
|
820
1213
|
}
|
|
821
1214
|
async function sha256Hex(data) {
|
|
822
1215
|
return hex(await crypto.subtle.digest("SHA-256", data));
|
|
@@ -827,8 +1220,8 @@ async function hmacHex(secret, message) {
|
|
|
827
1220
|
}
|
|
828
1221
|
function newSigningSecret() {
|
|
829
1222
|
let bytes = crypto.getRandomValues(new Uint8Array(32)), bin = "";
|
|
830
|
-
for (let
|
|
831
|
-
bin += String.fromCharCode(
|
|
1223
|
+
for (let b3 of bytes)
|
|
1224
|
+
bin += String.fromCharCode(b3);
|
|
832
1225
|
return "lbs_" + btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
833
1226
|
}
|
|
834
1227
|
function payload(t, method, pathAndQuery, bodyHash) {
|
|
@@ -865,10 +1258,11 @@ var SENSITIVE_PARTS = /* @__PURE__ */ new Set([
|
|
|
865
1258
|
]), SENSITIVE_JOINED = /apikey|privatekey|secretkey|accesskey|accountnumber|routingnumber|stripecustomer|paymentmethod|recoverycode/;
|
|
866
1259
|
function isSensitiveFieldName(name) {
|
|
867
1260
|
let parts = name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
868
|
-
if (parts.some((
|
|
1261
|
+
if (parts.some((p2) => SENSITIVE_PARTS.has(p2)))
|
|
869
1262
|
return !0;
|
|
870
1263
|
return SENSITIVE_JOINED.test(parts.join(""));
|
|
871
1264
|
}
|
|
1265
|
+
var PERSON_WORDS = "user|customer|client|member|contact|person|people|owner|author|sender|recipient|requester|assignee|reporter|buyer|guest|subscriber|lead|employee|student|patient|attendee|signer|holder", PERSON_NAME = new RegExp(`(${PERSON_WORDS})s?name$`), PEOPLE_RESOURCE = new RegExp(`^(${PERSON_WORDS})s?$`);
|
|
872
1266
|
|
|
873
1267
|
// ../admin-adapter/src/adapter.ts
|
|
874
1268
|
var DEFAULT_BASE_PATH = "/api/lopebase";
|
|
@@ -883,12 +1277,12 @@ function conformanceChecks(opts) {
|
|
|
883
1277
|
let remote = !opts.adapter, base = opts.baseUrl ? opts.baseUrl.replace(/\/$/, "") : `${opts.origin ?? "https://property.test"}${(opts.basePath ?? opts.adapter?.config.basePath ?? DEFAULT_BASE_PATH).replace(/\/$/, "")}`, send = (req) => opts.adapter ? opts.adapter.handle(req) : fetch(req);
|
|
884
1278
|
async function signedHeaders(url, method, bodyText, timestamp) {
|
|
885
1279
|
if (opts.secret) {
|
|
886
|
-
let
|
|
1280
|
+
let u2 = new URL(url);
|
|
887
1281
|
return {
|
|
888
1282
|
[SIGNATURE_HEADER]: await signRequest({
|
|
889
1283
|
secret: opts.secret,
|
|
890
1284
|
method,
|
|
891
|
-
pathAndQuery:
|
|
1285
|
+
pathAndQuery: u2.pathname + u2.search,
|
|
892
1286
|
body: bodyText,
|
|
893
1287
|
timestamp
|
|
894
1288
|
})
|
|
@@ -906,15 +1300,14 @@ function conformanceChecks(opts) {
|
|
|
906
1300
|
...init?.headers
|
|
907
1301
|
}
|
|
908
1302
|
}));
|
|
909
|
-
}, resources = () => authed("/resources").then((
|
|
910
|
-
return [
|
|
1303
|
+
}, resources = () => authed("/resources").then((r2) => body(r2)), checks = [
|
|
911
1304
|
{
|
|
912
1305
|
name: "GET /health returns { ok } without auth",
|
|
913
1306
|
async run() {
|
|
914
1307
|
let res = await send(new Request(`${base}/health`));
|
|
915
1308
|
assert.equal(res.status, 200);
|
|
916
|
-
let
|
|
917
|
-
assert.equal(typeof
|
|
1309
|
+
let b3 = await body(res);
|
|
1310
|
+
assert.equal(typeof b3.ok, "boolean");
|
|
918
1311
|
}
|
|
919
1312
|
},
|
|
920
1313
|
{
|
|
@@ -929,10 +1322,10 @@ function conformanceChecks(opts) {
|
|
|
929
1322
|
async run() {
|
|
930
1323
|
let res = await authed("/resources");
|
|
931
1324
|
assert.equal(res.status, 200);
|
|
932
|
-
let
|
|
933
|
-
assert.ok(Array.isArray(
|
|
934
|
-
for (let
|
|
935
|
-
assert.ok(
|
|
1325
|
+
let b3 = await body(res);
|
|
1326
|
+
assert.ok(Array.isArray(b3.resources));
|
|
1327
|
+
for (let r2 of b3.resources)
|
|
1328
|
+
assert.ok(r2.name && r2.label && r2.readCapability), assert.ok(Array.isArray(r2.fields));
|
|
936
1329
|
}
|
|
937
1330
|
},
|
|
938
1331
|
{
|
|
@@ -940,9 +1333,9 @@ function conformanceChecks(opts) {
|
|
|
940
1333
|
async run() {
|
|
941
1334
|
let res = await authed("/actions");
|
|
942
1335
|
assert.equal(res.status, 200);
|
|
943
|
-
let
|
|
944
|
-
assert.ok(Array.isArray(
|
|
945
|
-
for (let a of
|
|
1336
|
+
let b3 = await body(res);
|
|
1337
|
+
assert.ok(Array.isArray(b3.actions));
|
|
1338
|
+
for (let a of b3.actions)
|
|
946
1339
|
assert.ok(a.name && a.label && a.capability), assert.ok(Array.isArray(a.params));
|
|
947
1340
|
}
|
|
948
1341
|
},
|
|
@@ -961,7 +1354,7 @@ function conformanceChecks(opts) {
|
|
|
961
1354
|
{
|
|
962
1355
|
name: "GET /resources/:name/:id returns a row or 404-null",
|
|
963
1356
|
async run() {
|
|
964
|
-
let cat = await resources(), meta = opts.sampleResource ? cat.resources.find((
|
|
1357
|
+
let cat = await resources(), meta = opts.sampleResource ? cat.resources.find((r2) => r2.name === opts.sampleResource) : cat.resources[0];
|
|
965
1358
|
if (!meta)
|
|
966
1359
|
return;
|
|
967
1360
|
let first = (await body(await authed(`/resources/${meta.name}?limit=1`))).rows[0];
|
|
@@ -976,8 +1369,8 @@ function conformanceChecks(opts) {
|
|
|
976
1369
|
async run() {
|
|
977
1370
|
let res = await authed("/audit");
|
|
978
1371
|
assert.equal(res.status, 200);
|
|
979
|
-
let
|
|
980
|
-
assert.ok(Array.isArray(
|
|
1372
|
+
let b3 = await body(res);
|
|
1373
|
+
assert.ok(Array.isArray(b3.rows));
|
|
981
1374
|
}
|
|
982
1375
|
},
|
|
983
1376
|
{
|
|
@@ -991,24 +1384,24 @@ function conformanceChecks(opts) {
|
|
|
991
1384
|
name: "no field in the catalog looks like a secret unless marked safe",
|
|
992
1385
|
async run() {
|
|
993
1386
|
let cat = await resources();
|
|
994
|
-
for (let
|
|
995
|
-
for (let f of
|
|
996
|
-
assert.ok(!isSensitiveFieldName(f.name) || f.sensitive === "allow", `${
|
|
1387
|
+
for (let r2 of cat.resources)
|
|
1388
|
+
for (let f of r2.fields)
|
|
1389
|
+
assert.ok(!isSensitiveFieldName(f.name) || f.sensitive === "allow", `${r2.name}.${f.name} looks sensitive`);
|
|
997
1390
|
}
|
|
998
1391
|
},
|
|
999
1392
|
{
|
|
1000
1393
|
name: "rows contain only declared fields, and a page is at most 100",
|
|
1001
1394
|
async run() {
|
|
1002
1395
|
let cat = await resources();
|
|
1003
|
-
for (let
|
|
1004
|
-
let res = await authed(`/resources/${
|
|
1396
|
+
for (let r2 of cat.resources) {
|
|
1397
|
+
let res = await authed(`/resources/${r2.name}?limit=1000`);
|
|
1005
1398
|
assert.equal(res.status, 200);
|
|
1006
1399
|
let page = await body(res);
|
|
1007
|
-
assert.ok(page.rows.length <= 100, `${
|
|
1008
|
-
let declared = /* @__PURE__ */ new Set(["id", ...
|
|
1400
|
+
assert.ok(page.rows.length <= 100, `${r2.name} returned ${page.rows.length} rows`);
|
|
1401
|
+
let declared = /* @__PURE__ */ new Set(["id", ...r2.fields.map((f) => f.name)]);
|
|
1009
1402
|
for (let row of page.rows)
|
|
1010
|
-
for (let
|
|
1011
|
-
assert.ok(declared.has(
|
|
1403
|
+
for (let k3 of Object.keys(row))
|
|
1404
|
+
assert.ok(declared.has(k3), `${r2.name} returned undeclared field ${k3}`);
|
|
1012
1405
|
}
|
|
1013
1406
|
}
|
|
1014
1407
|
},
|
|
@@ -1026,7 +1419,7 @@ function conformanceChecks(opts) {
|
|
|
1026
1419
|
{
|
|
1027
1420
|
name: "a mutation writes an audit row (PATCH round-trips into /audit)",
|
|
1028
1421
|
async run() {
|
|
1029
|
-
let writable = (await resources()).resources.find((
|
|
1422
|
+
let writable = (await resources()).resources.find((r2) => r2.writeCapability && r2.fields.some((f) => f.editable));
|
|
1030
1423
|
if (!writable || remote)
|
|
1031
1424
|
return;
|
|
1032
1425
|
let row = (await body(await authed(`/resources/${writable.name}?limit=1`))).rows[0];
|
|
@@ -1045,9 +1438,10 @@ function conformanceChecks(opts) {
|
|
|
1045
1438
|
}
|
|
1046
1439
|
}
|
|
1047
1440
|
];
|
|
1441
|
+
return remote ? checks.filter((c) => !c.name.startsWith("a mutation writes")) : checks;
|
|
1048
1442
|
}
|
|
1049
1443
|
// bin/lopebase.ts
|
|
1050
|
-
var
|
|
1444
|
+
var import_picocolors4 = __toESM(require_picocolors(), 1);
|
|
1051
1445
|
|
|
1052
1446
|
// src/adapter-check.ts
|
|
1053
1447
|
async function checkAdapter(input) {
|
|
@@ -1061,8 +1455,8 @@ async function checkAdapter(input) {
|
|
|
1061
1455
|
})
|
|
1062
1456
|
} : { authorization: `Bearer ${input.token ?? ""}` };
|
|
1063
1457
|
status = (await fetch(url, { headers })).status;
|
|
1064
|
-
} catch (
|
|
1065
|
-
let error = `Could not reach ${base} (${
|
|
1458
|
+
} catch (e2) {
|
|
1459
|
+
let error = `Could not reach ${base} (${e2 instanceof Error ? e2.message : String(e2)}).`;
|
|
1066
1460
|
return log(`FAIL ${error}`), { passed, failed: [{ name: "reachable", error }] };
|
|
1067
1461
|
}
|
|
1068
1462
|
if (status === 401) {
|
|
@@ -1080,22 +1474,23 @@ async function checkAdapter(input) {
|
|
|
1080
1474
|
}))
|
|
1081
1475
|
try {
|
|
1082
1476
|
await c.run(), passed++, log(`pass ${c.name}`);
|
|
1083
|
-
} catch (
|
|
1084
|
-
let error =
|
|
1085
|
-
`)[0] : String(
|
|
1477
|
+
} catch (e2) {
|
|
1478
|
+
let error = e2 instanceof Error ? e2.message.split(`
|
|
1479
|
+
`)[0] : String(e2);
|
|
1086
1480
|
failed.push({ name: c.name, error }), log(`FAIL ${c.name}: ${error}`);
|
|
1087
1481
|
}
|
|
1088
1482
|
return { passed, failed };
|
|
1089
1483
|
}
|
|
1090
1484
|
|
|
1091
1485
|
// src/agents.ts
|
|
1486
|
+
var import_cross_spawn = __toESM(require_cross_spawn(), 1);
|
|
1092
1487
|
import { spawnSync } from "node:child_process";
|
|
1093
1488
|
var AGENTS = [
|
|
1094
1489
|
{
|
|
1095
1490
|
id: "claude",
|
|
1096
1491
|
label: "Claude Code",
|
|
1097
1492
|
bin: "claude",
|
|
1098
|
-
args: (
|
|
1493
|
+
args: (p2) => [p2],
|
|
1099
1494
|
mcpAdd: (argv) => [
|
|
1100
1495
|
"mcp",
|
|
1101
1496
|
"add",
|
|
@@ -1110,32 +1505,48 @@ var AGENTS = [
|
|
|
1110
1505
|
id: "codex",
|
|
1111
1506
|
label: "Codex",
|
|
1112
1507
|
bin: "codex",
|
|
1113
|
-
args: (
|
|
1508
|
+
args: (p2) => [p2],
|
|
1114
1509
|
mcpAdd: (argv) => ["mcp", "add", "lopebase", "--", ...argv]
|
|
1115
1510
|
},
|
|
1116
1511
|
{
|
|
1117
1512
|
id: "gemini",
|
|
1118
1513
|
label: "Gemini CLI",
|
|
1119
1514
|
bin: "gemini",
|
|
1120
|
-
args: (
|
|
1515
|
+
args: (p2) => ["-i", p2],
|
|
1121
1516
|
mcpAdd: (argv) => ["mcp", "add", "lopebase", ...argv]
|
|
1122
1517
|
},
|
|
1123
1518
|
{
|
|
1124
1519
|
id: "opencode",
|
|
1125
1520
|
label: "opencode",
|
|
1126
1521
|
bin: "opencode",
|
|
1127
|
-
args: (
|
|
1522
|
+
args: (p2) => ["--prompt", p2]
|
|
1128
1523
|
},
|
|
1129
1524
|
{
|
|
1130
1525
|
id: "cursor-agent",
|
|
1131
1526
|
label: "Cursor Agent",
|
|
1132
1527
|
bin: "cursor-agent",
|
|
1133
|
-
args: (
|
|
1528
|
+
args: (p2) => [p2]
|
|
1134
1529
|
}
|
|
1135
1530
|
];
|
|
1136
1531
|
function isInstalled(bin) {
|
|
1137
|
-
|
|
1138
|
-
|
|
1532
|
+
return whereIs(bin) !== null;
|
|
1533
|
+
}
|
|
1534
|
+
function whereIs(bin) {
|
|
1535
|
+
let probe = process.platform === "win32" ? "where" : "which", res = spawnSync(probe, [bin], { encoding: "utf8" });
|
|
1536
|
+
if (res.status !== 0)
|
|
1537
|
+
return null;
|
|
1538
|
+
return res.stdout.split(/\r?\n/)[0]?.trim() || null;
|
|
1539
|
+
}
|
|
1540
|
+
function portableArgs(bin, args, platform = process.platform, resolved = platform === "win32" ? whereIs(bin) : null) {
|
|
1541
|
+
if (platform !== "win32" || !resolved || !/\.(cmd|bat)$/i.test(resolved))
|
|
1542
|
+
return args;
|
|
1543
|
+
return args.map((a) => a.replace(/[ \t]*\r?\n\s*/g, " "));
|
|
1544
|
+
}
|
|
1545
|
+
function spawnAgent(bin, args, options) {
|
|
1546
|
+
return import_cross_spawn.default(bin, portableArgs(bin, args), options);
|
|
1547
|
+
}
|
|
1548
|
+
function spawnAgentSync(bin, args, options) {
|
|
1549
|
+
return import_cross_spawn.default.sync(bin, portableArgs(bin, args), options);
|
|
1139
1550
|
}
|
|
1140
1551
|
function installedAgents() {
|
|
1141
1552
|
return AGENTS.filter((a) => isInstalled(a.bin));
|
|
@@ -1152,6 +1563,22 @@ import {
|
|
|
1152
1563
|
import os from "node:os";
|
|
1153
1564
|
import path from "node:path";
|
|
1154
1565
|
var DEFAULT_URL = "https://app.lopebase.com";
|
|
1566
|
+
function workspaceUrl(input) {
|
|
1567
|
+
let v2 = input.trim().replace(/\/+$/, "");
|
|
1568
|
+
if (!v2)
|
|
1569
|
+
return null;
|
|
1570
|
+
if (/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.test(v2))
|
|
1571
|
+
return `https://${v2.toLowerCase()}.lopebase.com`;
|
|
1572
|
+
let withScheme = /^https?:\/\//i.test(v2) ? v2 : `https://${v2}`;
|
|
1573
|
+
try {
|
|
1574
|
+
let u2 = new URL(withScheme);
|
|
1575
|
+
if (!u2.hostname.includes(".") && u2.hostname !== "localhost")
|
|
1576
|
+
return null;
|
|
1577
|
+
return `${u2.origin}${u2.pathname.replace(/\/+$/, "")}`;
|
|
1578
|
+
} catch {
|
|
1579
|
+
return null;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1155
1582
|
function configDir() {
|
|
1156
1583
|
let base = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
1157
1584
|
return path.join(base, "lopebase");
|
|
@@ -1181,21 +1608,21 @@ function saveAuth(auth) {
|
|
|
1181
1608
|
var mcpEndpoint = (url) => `${url.replace(/\/+$/, "")}/api/mcp`;
|
|
1182
1609
|
|
|
1183
1610
|
// src/detect.ts
|
|
1184
|
-
var APP_SIDE_ENV = /^(NEXT_PUBLIC_|PUBLIC_|VITE_|NUXT_PUBLIC_|EXPO_PUBLIC_)|_DSN
|
|
1611
|
+
var APP_SIDE_ENV = /^(NEXT_PUBLIC_|PUBLIC_|VITE_|NUXT_PUBLIC_|EXPO_PUBLIC_|REACT_APP_|GATSBY_)|_DSN$/, file = (name) => new RegExp(`(^|/)${name.replace(/[.]/g, "\\.")}$`);
|
|
1185
1612
|
function evidenceFor(facts, rule) {
|
|
1186
1613
|
let out = [], weak = [];
|
|
1187
1614
|
for (let re of rule.deps ?? []) {
|
|
1188
|
-
let hit = facts.dependencies.find((
|
|
1615
|
+
let hit = facts.dependencies.find((d3) => re.test(d3));
|
|
1189
1616
|
if (hit)
|
|
1190
|
-
out.push(`uses ${hit}`);
|
|
1617
|
+
out.push(`uses ${hit.replace(/^(py|gem|composer|go|cargo):/, "")}`);
|
|
1191
1618
|
}
|
|
1192
1619
|
for (let re of rule.files ?? []) {
|
|
1193
1620
|
let hit = facts.files.find((f) => re.test(f));
|
|
1194
1621
|
if (hit)
|
|
1195
|
-
out.push(`has ${hit}`);
|
|
1622
|
+
out.push(`has ${hit.replace(/#.*$/, "")}`);
|
|
1196
1623
|
}
|
|
1197
1624
|
for (let re of rule.env ?? []) {
|
|
1198
|
-
let hit = facts.envKeys.find((
|
|
1625
|
+
let hit = facts.envKeys.find((k3) => re.test(k3));
|
|
1199
1626
|
if (hit)
|
|
1200
1627
|
(APP_SIDE_ENV.test(hit) ? out : weak).push(`env ${hit}`);
|
|
1201
1628
|
}
|
|
@@ -1204,15 +1631,31 @@ function evidenceFor(facts, rule) {
|
|
|
1204
1631
|
var SOURCE_RULES = {
|
|
1205
1632
|
posthog: {
|
|
1206
1633
|
label: "PostHog",
|
|
1207
|
-
rule: {
|
|
1634
|
+
rule: {
|
|
1635
|
+
deps: [
|
|
1636
|
+
/^posthog-(js|node|react-native)$/,
|
|
1637
|
+
/^py:posthog$/,
|
|
1638
|
+
/^gem:posthog-ruby$/,
|
|
1639
|
+
/^composer:posthog\//,
|
|
1640
|
+
/^go:github\.com\/posthog\//
|
|
1641
|
+
],
|
|
1642
|
+
env: [/POSTHOG/]
|
|
1643
|
+
},
|
|
1208
1644
|
why: "Signups, active users, and funnels on your dashboard."
|
|
1209
1645
|
},
|
|
1210
1646
|
ga4: {
|
|
1211
1647
|
label: "Google Analytics",
|
|
1212
1648
|
rule: {
|
|
1213
|
-
deps: [
|
|
1649
|
+
deps: [
|
|
1650
|
+
/^@next\/third-parties$/,
|
|
1651
|
+
/^react-ga4$/,
|
|
1652
|
+
/^vue-gtag/,
|
|
1653
|
+
/^nuxt-gtag$/,
|
|
1654
|
+
/^gatsby-plugin-google-(gtag|analytics)$/,
|
|
1655
|
+
/^@analytics\/google-analytics$/
|
|
1656
|
+
],
|
|
1214
1657
|
env: [
|
|
1215
|
-
/^(NEXT_PUBLIC_|PUBLIC_|VITE_)?GA(4)?_(MEASUREMENT_)?ID$/,
|
|
1658
|
+
/^(NEXT_PUBLIC_|PUBLIC_|VITE_|GATSBY_|REACT_APP_)?GA(4)?_(MEASUREMENT_|TRACKING_)?ID$/,
|
|
1216
1659
|
/GTM_ID/
|
|
1217
1660
|
]
|
|
1218
1661
|
},
|
|
@@ -1221,7 +1664,14 @@ var SOURCE_RULES = {
|
|
|
1221
1664
|
sentry: {
|
|
1222
1665
|
label: "Sentry",
|
|
1223
1666
|
rule: {
|
|
1224
|
-
deps: [
|
|
1667
|
+
deps: [
|
|
1668
|
+
/^@sentry\//,
|
|
1669
|
+
/^py:sentry-sdk$/,
|
|
1670
|
+
/^gem:sentry-(ruby|rails)$/,
|
|
1671
|
+
/^composer:sentry\//,
|
|
1672
|
+
/^go:github\.com\/getsentry\//,
|
|
1673
|
+
/^cargo:sentry$/
|
|
1674
|
+
],
|
|
1225
1675
|
files: [/sentry\.(client|server|edge)\.config/],
|
|
1226
1676
|
env: [/SENTRY_DSN/]
|
|
1227
1677
|
},
|
|
@@ -1229,80 +1679,392 @@ var SOURCE_RULES = {
|
|
|
1229
1679
|
},
|
|
1230
1680
|
stripe: {
|
|
1231
1681
|
label: "Stripe",
|
|
1232
|
-
rule: {
|
|
1682
|
+
rule: {
|
|
1683
|
+
deps: [
|
|
1684
|
+
/^stripe$/,
|
|
1685
|
+
/^@stripe\//,
|
|
1686
|
+
/^py:stripe$/,
|
|
1687
|
+
/^gem:stripe$/,
|
|
1688
|
+
/^composer:stripe\//,
|
|
1689
|
+
/^go:github\.com\/stripe\//
|
|
1690
|
+
],
|
|
1691
|
+
env: [/^STRIPE_/, /^(NEXT_PUBLIC_|PUBLIC_|VITE_)STRIPE_/]
|
|
1692
|
+
},
|
|
1233
1693
|
why: "Revenue, new customers, and churn per product."
|
|
1694
|
+
},
|
|
1695
|
+
polar: {
|
|
1696
|
+
label: "Polar",
|
|
1697
|
+
rule: { deps: [/^@polar-sh\//, /^py:polar-sdk$/], env: [/^POLAR_/] },
|
|
1698
|
+
why: "Revenue and subscriptions from Polar per product."
|
|
1699
|
+
},
|
|
1700
|
+
supabase: {
|
|
1701
|
+
label: "Supabase",
|
|
1702
|
+
rule: {
|
|
1703
|
+
deps: [/^@supabase\//, /^py:supabase$/, /^composer:supabase\//],
|
|
1704
|
+
files: [file("supabase/config.toml")],
|
|
1705
|
+
env: [/SUPABASE_URL$/]
|
|
1706
|
+
},
|
|
1707
|
+
why: "Database health, security warnings, disk, and backups next to everything else."
|
|
1708
|
+
},
|
|
1709
|
+
resend: {
|
|
1710
|
+
label: "Resend",
|
|
1711
|
+
rule: {
|
|
1712
|
+
deps: [/^resend$/, /^py:resend$/, /^gem:resend$/, /^composer:resend\//],
|
|
1713
|
+
env: [/^RESEND_/]
|
|
1714
|
+
},
|
|
1715
|
+
why: "Bounce and complaint rates, so you know your emails land."
|
|
1716
|
+
},
|
|
1717
|
+
sequenzy: {
|
|
1718
|
+
label: "Sequenzy",
|
|
1719
|
+
rule: { deps: [/^@?sequenzy/], env: [/SEQUENZY/] },
|
|
1720
|
+
why: "Your audience and running email sequences at a glance."
|
|
1234
1721
|
}
|
|
1235
1722
|
}, STACK_RULES = [
|
|
1236
|
-
{
|
|
1237
|
-
|
|
1723
|
+
{
|
|
1724
|
+
kind: "framework",
|
|
1725
|
+
name: "Next.js",
|
|
1726
|
+
rule: { deps: [/^next$/], files: [/(^|\/)next\.config\./] }
|
|
1727
|
+
},
|
|
1728
|
+
{
|
|
1729
|
+
kind: "framework",
|
|
1730
|
+
name: "Astro",
|
|
1731
|
+
rule: { deps: [/^astro$/], files: [/(^|\/)astro\.config\./] }
|
|
1732
|
+
},
|
|
1238
1733
|
{
|
|
1239
1734
|
kind: "framework",
|
|
1240
1735
|
name: "SvelteKit",
|
|
1241
1736
|
rule: { deps: [/^@sveltejs\/kit$/] }
|
|
1242
1737
|
},
|
|
1738
|
+
{ kind: "framework", name: "Remix", rule: { deps: [/^@remix-run\//] } },
|
|
1243
1739
|
{
|
|
1244
1740
|
kind: "framework",
|
|
1245
|
-
name: "
|
|
1246
|
-
rule: { deps: [/^@
|
|
1741
|
+
name: "React Router",
|
|
1742
|
+
rule: { deps: [/^@react-router\/dev$/] }
|
|
1247
1743
|
},
|
|
1248
|
-
{
|
|
1744
|
+
{
|
|
1745
|
+
kind: "framework",
|
|
1746
|
+
name: "Nuxt",
|
|
1747
|
+
rule: { deps: [/^nuxt$/], files: [/(^|\/)nuxt\.config\./] }
|
|
1748
|
+
},
|
|
1749
|
+
{ kind: "framework", name: "Gatsby", rule: { deps: [/^gatsby$/] } },
|
|
1750
|
+
{
|
|
1751
|
+
kind: "framework",
|
|
1752
|
+
name: "Docusaurus",
|
|
1753
|
+
rule: { deps: [/^@docusaurus\/core$/] }
|
|
1754
|
+
},
|
|
1755
|
+
{
|
|
1756
|
+
kind: "framework",
|
|
1757
|
+
name: "Eleventy",
|
|
1758
|
+
rule: {
|
|
1759
|
+
deps: [/^@11ty\/eleventy$/],
|
|
1760
|
+
files: [/(^|\/)(\.eleventy\.js|eleventy\.config\.)/]
|
|
1761
|
+
}
|
|
1762
|
+
},
|
|
1763
|
+
{
|
|
1764
|
+
kind: "framework",
|
|
1765
|
+
name: "Hugo",
|
|
1766
|
+
rule: { files: [/(^|\/)hugo\.(toml|ya?ml|json)$/] }
|
|
1767
|
+
},
|
|
1768
|
+
{ kind: "framework", name: "Jekyll", rule: { deps: [/^gem:jekyll$/] } },
|
|
1769
|
+
{ kind: "framework", name: "Angular", rule: { deps: [/^@angular\/core$/] } },
|
|
1770
|
+
{
|
|
1771
|
+
kind: "framework",
|
|
1772
|
+
name: "SolidStart",
|
|
1773
|
+
rule: { deps: [/^@solidjs\/start$/] }
|
|
1774
|
+
},
|
|
1775
|
+
{
|
|
1776
|
+
kind: "framework",
|
|
1777
|
+
name: "Qwik",
|
|
1778
|
+
rule: { deps: [/^@builder\.io\/qwik-city$/] }
|
|
1779
|
+
},
|
|
1780
|
+
{
|
|
1781
|
+
kind: "framework",
|
|
1782
|
+
name: "TanStack Start",
|
|
1783
|
+
rule: { deps: [/^@tanstack\/(react-)?start$/] }
|
|
1784
|
+
},
|
|
1785
|
+
{ kind: "framework", name: "Expo", rule: { deps: [/^expo$/] } },
|
|
1786
|
+
{ kind: "framework", name: "Electron", rule: { deps: [/^electron$/] } },
|
|
1249
1787
|
{ kind: "framework", name: "Hono", rule: { deps: [/^hono$/] } },
|
|
1788
|
+
{ kind: "framework", name: "Express", rule: { deps: [/^express$/] } },
|
|
1789
|
+
{ kind: "framework", name: "Fastify", rule: { deps: [/^fastify$/] } },
|
|
1790
|
+
{ kind: "framework", name: "NestJS", rule: { deps: [/^@nestjs\/core$/] } },
|
|
1791
|
+
{
|
|
1792
|
+
kind: "framework",
|
|
1793
|
+
name: "Django",
|
|
1794
|
+
rule: { deps: [/^py:django$/], files: [/(^|\/)manage\.py$/] }
|
|
1795
|
+
},
|
|
1796
|
+
{ kind: "framework", name: "Flask", rule: { deps: [/^py:flask$/] } },
|
|
1797
|
+
{ kind: "framework", name: "FastAPI", rule: { deps: [/^py:fastapi$/] } },
|
|
1798
|
+
{ kind: "framework", name: "Rails", rule: { deps: [/^gem:rails$/] } },
|
|
1799
|
+
{
|
|
1800
|
+
kind: "framework",
|
|
1801
|
+
name: "Laravel",
|
|
1802
|
+
rule: {
|
|
1803
|
+
deps: [/^composer:laravel\/framework$/],
|
|
1804
|
+
files: [/(^|\/)artisan$/]
|
|
1805
|
+
}
|
|
1806
|
+
},
|
|
1807
|
+
{
|
|
1808
|
+
kind: "framework",
|
|
1809
|
+
name: "WordPress",
|
|
1810
|
+
rule: { files: [/(^|\/)wp-config(-sample)?\.php$/, /(^|\/)wp-content$/] }
|
|
1811
|
+
},
|
|
1812
|
+
{
|
|
1813
|
+
kind: "framework",
|
|
1814
|
+
name: "Shopify app",
|
|
1815
|
+
rule: { files: [/(^|\/)shopify\.app\.toml$/] }
|
|
1816
|
+
},
|
|
1817
|
+
{ kind: "framework", name: "Vue", rule: { deps: [/^vue$/] } },
|
|
1818
|
+
{ kind: "framework", name: "React", rule: { deps: [/^react$/] } },
|
|
1819
|
+
{ kind: "framework", name: "Vite", rule: { deps: [/^vite$/] } },
|
|
1820
|
+
{ kind: "language", name: "TypeScript", rule: { deps: [/^typescript$/] } },
|
|
1821
|
+
{
|
|
1822
|
+
kind: "language",
|
|
1823
|
+
name: "Python",
|
|
1824
|
+
rule: { files: [/(^|\/)(requirements\.txt|pyproject\.toml|Pipfile)$/] }
|
|
1825
|
+
},
|
|
1826
|
+
{ kind: "language", name: "Ruby", rule: { files: [/(^|\/)Gemfile$/] } },
|
|
1827
|
+
{ kind: "language", name: "PHP", rule: { files: [/(^|\/)composer\.json$/] } },
|
|
1828
|
+
{ kind: "language", name: "Go", rule: { files: [/(^|\/)go\.mod$/] } },
|
|
1829
|
+
{ kind: "language", name: "Rust", rule: { files: [/(^|\/)Cargo\.toml$/] } },
|
|
1250
1830
|
{
|
|
1251
1831
|
kind: "hosting",
|
|
1252
1832
|
name: "Cloudflare Workers",
|
|
1253
1833
|
rule: {
|
|
1254
1834
|
files: [/(^|\/)wrangler\.(toml|jsonc?)$/],
|
|
1255
|
-
deps: [
|
|
1835
|
+
deps: [
|
|
1836
|
+
/^@opennextjs\/cloudflare$/,
|
|
1837
|
+
/^@astrojs\/cloudflare$/,
|
|
1838
|
+
/^@sveltejs\/adapter-cloudflare/
|
|
1839
|
+
]
|
|
1840
|
+
}
|
|
1841
|
+
},
|
|
1842
|
+
{
|
|
1843
|
+
kind: "hosting",
|
|
1844
|
+
name: "Vercel",
|
|
1845
|
+
rule: {
|
|
1846
|
+
files: [file("vercel.json")],
|
|
1847
|
+
deps: [
|
|
1848
|
+
/^@vercel\/(analytics|speed-insights|og)$/,
|
|
1849
|
+
/^@astrojs\/vercel$/,
|
|
1850
|
+
/^@sveltejs\/adapter-vercel$/
|
|
1851
|
+
]
|
|
1256
1852
|
}
|
|
1257
1853
|
},
|
|
1258
|
-
{ kind: "hosting", name: "Vercel", rule: { files: [/(^|\/)vercel\.json$/] } },
|
|
1259
1854
|
{
|
|
1260
1855
|
kind: "hosting",
|
|
1261
1856
|
name: "Netlify",
|
|
1262
|
-
rule: {
|
|
1857
|
+
rule: {
|
|
1858
|
+
files: [file("netlify.toml")],
|
|
1859
|
+
deps: [/^@netlify\//, /^@astrojs\/netlify$/]
|
|
1860
|
+
}
|
|
1263
1861
|
},
|
|
1264
|
-
{ kind: "hosting", name: "Fly.io", rule: { files: [
|
|
1862
|
+
{ kind: "hosting", name: "Fly.io", rule: { files: [file("fly.toml")] } },
|
|
1265
1863
|
{
|
|
1266
|
-
kind: "
|
|
1267
|
-
name: "
|
|
1268
|
-
rule: {
|
|
1864
|
+
kind: "hosting",
|
|
1865
|
+
name: "Render",
|
|
1866
|
+
rule: { files: [/(^|\/)render\.ya?ml$/] }
|
|
1867
|
+
},
|
|
1868
|
+
{
|
|
1869
|
+
kind: "hosting",
|
|
1870
|
+
name: "Railway",
|
|
1871
|
+
rule: { files: [/(^|\/)railway\.(json|toml)$/] }
|
|
1872
|
+
},
|
|
1873
|
+
{
|
|
1874
|
+
kind: "hosting",
|
|
1875
|
+
name: "Firebase Hosting",
|
|
1876
|
+
rule: { files: [file("firebase.json")] }
|
|
1877
|
+
},
|
|
1878
|
+
{
|
|
1879
|
+
kind: "hosting",
|
|
1880
|
+
name: "AWS Amplify",
|
|
1881
|
+
rule: { files: [/(^|\/)amplify\.ya?ml$/] }
|
|
1269
1882
|
},
|
|
1883
|
+
{ kind: "hosting", name: "Heroku", rule: { files: [file("Procfile")] } },
|
|
1884
|
+
{ kind: "hosting", name: "GitHub Pages", rule: { files: [file("CNAME")] } },
|
|
1885
|
+
{
|
|
1886
|
+
kind: "hosting",
|
|
1887
|
+
name: "Docker",
|
|
1888
|
+
rule: { files: [file("Dockerfile"), /(^|\/)(docker-)?compose\.ya?ml$/] }
|
|
1889
|
+
},
|
|
1890
|
+
{ kind: "database", name: "Supabase", rule: SOURCE_RULES.supabase.rule },
|
|
1270
1891
|
{
|
|
1271
1892
|
kind: "database",
|
|
1272
1893
|
name: "Convex",
|
|
1273
|
-
rule: { deps: [/^convex$/], files: [
|
|
1894
|
+
rule: { deps: [/^convex$/], files: [file("convex/schema.ts")] }
|
|
1274
1895
|
},
|
|
1896
|
+
{ kind: "database", name: "Neon", rule: { deps: [/^@neondatabase\//] } },
|
|
1275
1897
|
{
|
|
1276
1898
|
kind: "database",
|
|
1277
|
-
name: "
|
|
1278
|
-
rule: {
|
|
1899
|
+
name: "Postgres",
|
|
1900
|
+
rule: {
|
|
1901
|
+
deps: [
|
|
1902
|
+
/^postgres$/,
|
|
1903
|
+
/^pg$/,
|
|
1904
|
+
/^py:psycopg2?(-binary)?$/,
|
|
1905
|
+
/^py:asyncpg$/,
|
|
1906
|
+
/^gem:pg$/
|
|
1907
|
+
]
|
|
1908
|
+
}
|
|
1279
1909
|
},
|
|
1280
1910
|
{
|
|
1281
1911
|
kind: "database",
|
|
1282
|
-
name: "
|
|
1283
|
-
rule: { deps: [/^
|
|
1912
|
+
name: "MySQL",
|
|
1913
|
+
rule: { deps: [/^mysql2?$/, /^py:(mysqlclient|pymysql)$/, /^gem:mysql2$/] }
|
|
1914
|
+
},
|
|
1915
|
+
{
|
|
1916
|
+
kind: "database",
|
|
1917
|
+
name: "SQLite",
|
|
1918
|
+
rule: { deps: [/^better-sqlite3$/, /^gem:sqlite3$/] }
|
|
1919
|
+
},
|
|
1920
|
+
{
|
|
1921
|
+
kind: "database",
|
|
1922
|
+
name: "MongoDB",
|
|
1923
|
+
rule: { deps: [/^mongodb$/, /^mongoose$/, /^py:pymongo$/, /^py:motor$/] }
|
|
1924
|
+
},
|
|
1925
|
+
{
|
|
1926
|
+
kind: "database",
|
|
1927
|
+
name: "Firebase",
|
|
1928
|
+
rule: { deps: [/^firebase(-admin)?$/, /^py:firebase-admin$/] }
|
|
1929
|
+
},
|
|
1930
|
+
{
|
|
1931
|
+
kind: "database",
|
|
1932
|
+
name: "PlanetScale",
|
|
1933
|
+
rule: { deps: [/^@planetscale\/database$/] }
|
|
1934
|
+
},
|
|
1935
|
+
{ kind: "database", name: "Turso", rule: { deps: [/^@libsql\/client$/] } },
|
|
1936
|
+
{
|
|
1937
|
+
kind: "database",
|
|
1938
|
+
name: "Redis",
|
|
1939
|
+
rule: { deps: [/^@upstash\/redis$/, /^ioredis$/, /^redis$/, /^py:redis$/] }
|
|
1940
|
+
},
|
|
1941
|
+
{
|
|
1942
|
+
kind: "database",
|
|
1943
|
+
name: "Prisma",
|
|
1944
|
+
rule: {
|
|
1945
|
+
deps: [/^@prisma\/client$/],
|
|
1946
|
+
files: [file("prisma/schema.prisma")]
|
|
1947
|
+
}
|
|
1284
1948
|
},
|
|
1285
|
-
{ kind: "database", name: "Prisma", rule: { deps: [/^@prisma\/client$/] } },
|
|
1286
1949
|
{ kind: "database", name: "Drizzle", rule: { deps: [/^drizzle-orm$/] } },
|
|
1287
1950
|
{
|
|
1288
1951
|
kind: "database",
|
|
1289
1952
|
name: "Cloudflare D1",
|
|
1290
1953
|
rule: { files: [/(^|\/)wrangler\.(toml|jsonc?)#d1$/] }
|
|
1291
1954
|
},
|
|
1955
|
+
{ kind: "email", name: "Resend", rule: SOURCE_RULES.resend.rule },
|
|
1956
|
+
{ kind: "email", name: "Sequenzy", rule: SOURCE_RULES.sequenzy.rule },
|
|
1957
|
+
{
|
|
1958
|
+
kind: "email",
|
|
1959
|
+
name: "SendGrid",
|
|
1960
|
+
rule: { deps: [/^@sendgrid\/mail$/, /^py:sendgrid$/], env: [/^SENDGRID_/] }
|
|
1961
|
+
},
|
|
1962
|
+
{
|
|
1963
|
+
kind: "email",
|
|
1964
|
+
name: "Postmark",
|
|
1965
|
+
rule: { deps: [/^postmark$/, /^py:postmarker$/], env: [/^POSTMARK_/] }
|
|
1966
|
+
},
|
|
1292
1967
|
{
|
|
1293
1968
|
kind: "email",
|
|
1294
|
-
name: "
|
|
1295
|
-
rule: { deps: [/^
|
|
1969
|
+
name: "Mailgun",
|
|
1970
|
+
rule: { deps: [/^mailgun\.js$/], env: [/^MAILGUN_/] }
|
|
1296
1971
|
},
|
|
1972
|
+
{
|
|
1973
|
+
kind: "email",
|
|
1974
|
+
name: "Loops",
|
|
1975
|
+
rule: { deps: [/^loops$/], env: [/^LOOPS_/] }
|
|
1976
|
+
},
|
|
1977
|
+
{
|
|
1978
|
+
kind: "email",
|
|
1979
|
+
name: "Amazon SES",
|
|
1980
|
+
rule: { deps: [/^@aws-sdk\/client-sesv?2?$/] }
|
|
1981
|
+
},
|
|
1982
|
+
{ kind: "email", name: "Nodemailer", rule: { deps: [/^nodemailer$/] } },
|
|
1983
|
+
{ kind: "billing", name: "Stripe", rule: SOURCE_RULES.stripe.rule },
|
|
1984
|
+
{ kind: "billing", name: "Polar", rule: SOURCE_RULES.polar.rule },
|
|
1297
1985
|
{
|
|
1298
1986
|
kind: "billing",
|
|
1299
|
-
name: "
|
|
1300
|
-
rule: { deps: [/^@
|
|
1987
|
+
name: "Lemon Squeezy",
|
|
1988
|
+
rule: { deps: [/^@lemonsqueezy\//], env: [/^LEMON_?SQUEEZY_/] }
|
|
1989
|
+
},
|
|
1990
|
+
{
|
|
1991
|
+
kind: "billing",
|
|
1992
|
+
name: "Paddle",
|
|
1993
|
+
rule: { deps: [/^@paddle\//], env: [/^PADDLE_/] }
|
|
1301
1994
|
},
|
|
1302
1995
|
{ kind: "auth", name: "Clerk", rule: { deps: [/^@clerk\//] } },
|
|
1303
|
-
{
|
|
1304
|
-
|
|
1305
|
-
|
|
1996
|
+
{
|
|
1997
|
+
kind: "auth",
|
|
1998
|
+
name: "WorkOS",
|
|
1999
|
+
rule: { deps: [/^@workos-inc\//, /^py:workos$/] }
|
|
2000
|
+
},
|
|
2001
|
+
{ kind: "auth", name: "Better Auth", rule: { deps: [/^better-auth$/] } },
|
|
2002
|
+
{
|
|
2003
|
+
kind: "auth",
|
|
2004
|
+
name: "Auth.js",
|
|
2005
|
+
rule: { deps: [/^next-auth$/, /^@auth\/core$/] }
|
|
2006
|
+
},
|
|
2007
|
+
{ kind: "auth", name: "Auth0", rule: { deps: [/^@auth0\//] } },
|
|
2008
|
+
{ kind: "auth", name: "Kinde", rule: { deps: [/^@kinde-oss\//] } },
|
|
2009
|
+
{ kind: "auth", name: "Lucia", rule: { deps: [/^lucia$/] } },
|
|
2010
|
+
{ kind: "auth", name: "Devise", rule: { deps: [/^gem:devise$/] } },
|
|
2011
|
+
{
|
|
2012
|
+
kind: "analytics",
|
|
2013
|
+
name: "Vercel Analytics",
|
|
2014
|
+
rule: { deps: [/^@vercel\/analytics$/] }
|
|
2015
|
+
},
|
|
2016
|
+
{
|
|
2017
|
+
kind: "analytics",
|
|
2018
|
+
name: "Plausible",
|
|
2019
|
+
rule: { deps: [/plausible/], env: [/PLAUSIBLE/] }
|
|
2020
|
+
},
|
|
2021
|
+
{ kind: "analytics", name: "Umami", rule: { env: [/UMAMI/] } },
|
|
2022
|
+
{ kind: "analytics", name: "Fathom", rule: { deps: [/^fathom-client$/] } },
|
|
2023
|
+
{
|
|
2024
|
+
kind: "analytics",
|
|
2025
|
+
name: "Mixpanel",
|
|
2026
|
+
rule: { deps: [/^mixpanel(-browser)?$/] }
|
|
2027
|
+
},
|
|
2028
|
+
{ kind: "analytics", name: "Amplitude", rule: { deps: [/^@amplitude\//] } },
|
|
2029
|
+
{
|
|
2030
|
+
kind: "cms",
|
|
2031
|
+
name: "Sanity",
|
|
2032
|
+
rule: { deps: [/^(next-)?sanity$/, /^@sanity\/client$/] }
|
|
2033
|
+
},
|
|
2034
|
+
{ kind: "cms", name: "Contentful", rule: { deps: [/^contentful$/] } },
|
|
2035
|
+
{ kind: "cms", name: "Payload", rule: { deps: [/^payload$/] } },
|
|
2036
|
+
{ kind: "cms", name: "Strapi", rule: { deps: [/^@strapi\/strapi$/] } },
|
|
2037
|
+
{ kind: "cms", name: "Keystatic", rule: { deps: [/^@keystatic\/core$/] } },
|
|
2038
|
+
{
|
|
2039
|
+
kind: "cms",
|
|
2040
|
+
name: "MDX",
|
|
2041
|
+
rule: { deps: [/^@next\/mdx$/, /^@astrojs\/mdx$/, /^@mdx-js\//] }
|
|
2042
|
+
}
|
|
2043
|
+
], WEB_FRAMEWORKS = /* @__PURE__ */ new Set([
|
|
2044
|
+
"Next.js",
|
|
2045
|
+
"Astro",
|
|
2046
|
+
"SvelteKit",
|
|
2047
|
+
"Remix",
|
|
2048
|
+
"React Router",
|
|
2049
|
+
"Nuxt",
|
|
2050
|
+
"Gatsby",
|
|
2051
|
+
"Docusaurus",
|
|
2052
|
+
"Eleventy",
|
|
2053
|
+
"Hugo",
|
|
2054
|
+
"Jekyll",
|
|
2055
|
+
"Angular",
|
|
2056
|
+
"SolidStart",
|
|
2057
|
+
"Qwik",
|
|
2058
|
+
"TanStack Start",
|
|
2059
|
+
"Django",
|
|
2060
|
+
"Flask",
|
|
2061
|
+
"Rails",
|
|
2062
|
+
"Laravel",
|
|
2063
|
+
"WordPress",
|
|
2064
|
+
"Vue",
|
|
2065
|
+
"React",
|
|
2066
|
+
"Vite"
|
|
2067
|
+
]);
|
|
1306
2068
|
function detectStack(facts) {
|
|
1307
2069
|
let stack = [];
|
|
1308
2070
|
for (let s of STACK_RULES) {
|
|
@@ -1310,7 +2072,13 @@ function detectStack(facts) {
|
|
|
1310
2072
|
if (ev.length)
|
|
1311
2073
|
stack.push({ kind: s.kind, name: s.name, evidence: ev[0] });
|
|
1312
2074
|
}
|
|
1313
|
-
|
|
2075
|
+
if (!stack.some((s) => s.kind === "framework") && facts.files.some((f) => /(^|\/)index\.html$/.test(f)))
|
|
2076
|
+
stack.unshift({
|
|
2077
|
+
kind: "framework",
|
|
2078
|
+
name: "Static site",
|
|
2079
|
+
evidence: "has index.html"
|
|
2080
|
+
});
|
|
2081
|
+
let tidy = stack.filter((s) => s.kind === "framework" && !["React", "Vite", "Vue"].includes(s.name)).length ? stack.filter((s) => !["React", "Vite", "Vue"].includes(s.name)) : stack, sources = [];
|
|
1314
2082
|
for (let [id, s] of Object.entries(SOURCE_RULES)) {
|
|
1315
2083
|
let { strong, weak } = evidenceFor(facts, s.rule);
|
|
1316
2084
|
if (strong.length || weak.length)
|
|
@@ -1322,7 +2090,15 @@ function detectStack(facts) {
|
|
|
1322
2090
|
why: s.why
|
|
1323
2091
|
});
|
|
1324
2092
|
}
|
|
1325
|
-
|
|
2093
|
+
if (facts.repo)
|
|
2094
|
+
sources.push({
|
|
2095
|
+
source: "github",
|
|
2096
|
+
label: "GitHub",
|
|
2097
|
+
status: "found",
|
|
2098
|
+
evidence: [`repo ${facts.repo}`],
|
|
2099
|
+
why: "Open pull requests, issues, and failing Actions runs."
|
|
2100
|
+
});
|
|
2101
|
+
let has = (id) => sources.some((s) => s.source === id && s.status === "found"), listed = (id) => sources.some((s) => s.source === id), isWebsite = tidy.some((s) => s.kind === "framework" && (WEB_FRAMEWORKS.has(s.name) || s.name === "Static site")) || !!facts.siteUrl, gaps = [];
|
|
1326
2102
|
if (isWebsite)
|
|
1327
2103
|
sources.push({
|
|
1328
2104
|
source: "gsc",
|
|
@@ -1336,9 +2112,15 @@ function detectStack(facts) {
|
|
|
1336
2112
|
status: "suggested",
|
|
1337
2113
|
evidence: [],
|
|
1338
2114
|
why: "Bing and ChatGPT search traffic. Imports from Search Console in one click."
|
|
2115
|
+
}, {
|
|
2116
|
+
source: "uptime",
|
|
2117
|
+
label: "Uptime check",
|
|
2118
|
+
status: "suggested",
|
|
2119
|
+
evidence: [],
|
|
2120
|
+
why: "Know the moment the site goes down. Nothing to install."
|
|
1339
2121
|
});
|
|
1340
|
-
if (!has("posthog") && !has("ga4")) {
|
|
1341
|
-
if (gaps.push("No product analytics found.
|
|
2122
|
+
if (isWebsite && !has("posthog") && !has("ga4")) {
|
|
2123
|
+
if (gaps.push("No product analytics found."), !listed("posthog"))
|
|
1342
2124
|
sources.push({
|
|
1343
2125
|
source: "posthog",
|
|
1344
2126
|
label: "PostHog",
|
|
@@ -1348,7 +2130,7 @@ function detectStack(facts) {
|
|
|
1348
2130
|
});
|
|
1349
2131
|
}
|
|
1350
2132
|
if (!has("sentry")) {
|
|
1351
|
-
if (gaps.push("No error
|
|
2133
|
+
if (gaps.push("No error logging found."), !listed("sentry"))
|
|
1352
2134
|
sources.push({
|
|
1353
2135
|
source: "sentry",
|
|
1354
2136
|
label: "Sentry",
|
|
@@ -1357,7 +2139,7 @@ function detectStack(facts) {
|
|
|
1357
2139
|
why: SOURCE_RULES.sentry.why
|
|
1358
2140
|
});
|
|
1359
2141
|
}
|
|
1360
|
-
return { stack, sources, gaps };
|
|
2142
|
+
return { stack: tidy, sources, gaps };
|
|
1361
2143
|
}
|
|
1362
2144
|
|
|
1363
2145
|
// src/device-login.ts
|
|
@@ -1374,27 +2156,27 @@ async function startDeviceLogin(url, opts = {}) {
|
|
|
1374
2156
|
});
|
|
1375
2157
|
if (!res.ok)
|
|
1376
2158
|
return null;
|
|
1377
|
-
let
|
|
1378
|
-
if (typeof
|
|
2159
|
+
let b3 = await res.json();
|
|
2160
|
+
if (typeof b3.device_code !== "string" || typeof b3.user_code !== "string")
|
|
1379
2161
|
return null;
|
|
1380
2162
|
return {
|
|
1381
|
-
deviceCode:
|
|
1382
|
-
userCode:
|
|
1383
|
-
verifyUrl: String(
|
|
1384
|
-
interval: Number(
|
|
1385
|
-
expiresIn: Number(
|
|
2163
|
+
deviceCode: b3.device_code,
|
|
2164
|
+
userCode: b3.user_code,
|
|
2165
|
+
verifyUrl: String(b3.verification_uri_complete ?? b3.verification_uri),
|
|
2166
|
+
interval: Number(b3.interval) || 5,
|
|
2167
|
+
expiresIn: Number(b3.expires_in) || 600
|
|
1386
2168
|
};
|
|
1387
2169
|
} catch {
|
|
1388
2170
|
return null;
|
|
1389
2171
|
}
|
|
1390
2172
|
}
|
|
1391
2173
|
async function pollDeviceLogin(url, start, opts = {}) {
|
|
1392
|
-
let f = opts.fetchImpl ?? fetch, sleep = opts.sleep ?? ((ms) => new Promise((
|
|
2174
|
+
let f = opts.fetchImpl ?? fetch, sleep = opts.sleep ?? ((ms) => new Promise((r2) => setTimeout(r2, ms))), interval = start.interval, deadline = Date.now() + start.expiresIn * 1000;
|
|
1393
2175
|
while (Date.now() < deadline) {
|
|
1394
2176
|
await sleep(interval * 1000);
|
|
1395
|
-
let
|
|
2177
|
+
let body2;
|
|
1396
2178
|
try {
|
|
1397
|
-
|
|
2179
|
+
body2 = await (await f(`${url}/api/oauth/token`, {
|
|
1398
2180
|
method: "POST",
|
|
1399
2181
|
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
1400
2182
|
body: new URLSearchParams({
|
|
@@ -1405,14 +2187,14 @@ async function pollDeviceLogin(url, start, opts = {}) {
|
|
|
1405
2187
|
} catch {
|
|
1406
2188
|
continue;
|
|
1407
2189
|
}
|
|
1408
|
-
if (typeof
|
|
2190
|
+
if (typeof body2.access_token === "string")
|
|
1409
2191
|
return {
|
|
1410
2192
|
ok: !0,
|
|
1411
|
-
token:
|
|
1412
|
-
url: typeof
|
|
1413
|
-
scope: String(
|
|
2193
|
+
token: body2.access_token,
|
|
2194
|
+
url: typeof body2.workspace_url === "string" ? body2.workspace_url : url,
|
|
2195
|
+
scope: String(body2.scope ?? "")
|
|
1414
2196
|
};
|
|
1415
|
-
switch (
|
|
2197
|
+
switch (body2.error) {
|
|
1416
2198
|
case "authorization_pending":
|
|
1417
2199
|
continue;
|
|
1418
2200
|
case "slow_down":
|
|
@@ -1434,7 +2216,7 @@ async function pollDeviceLogin(url, start, opts = {}) {
|
|
|
1434
2216
|
return {
|
|
1435
2217
|
ok: !1,
|
|
1436
2218
|
reason: "error",
|
|
1437
|
-
message: String(
|
|
2219
|
+
message: String(body2.error_description ?? body2.error ?? "Sign-in failed.")
|
|
1438
2220
|
};
|
|
1439
2221
|
}
|
|
1440
2222
|
}
|
|
@@ -1503,7 +2285,7 @@ async function runMcpProxy(auth, fetchImpl = fetch) {
|
|
|
1503
2285
|
}
|
|
1504
2286
|
process.stdout.write(`${text}
|
|
1505
2287
|
`);
|
|
1506
|
-
} catch (
|
|
2288
|
+
} catch (e2) {
|
|
1507
2289
|
if (id === null)
|
|
1508
2290
|
return;
|
|
1509
2291
|
write({
|
|
@@ -1511,7 +2293,7 @@ async function runMcpProxy(auth, fetchImpl = fetch) {
|
|
|
1511
2293
|
id,
|
|
1512
2294
|
error: {
|
|
1513
2295
|
code: -32000,
|
|
1514
|
-
message: `Could not reach LopeBase at ${endpoint}: ${String(
|
|
2296
|
+
message: `Could not reach LopeBase at ${endpoint}: ${String(e2)}`
|
|
1515
2297
|
}
|
|
1516
2298
|
});
|
|
1517
2299
|
}
|
|
@@ -1543,116 +2325,471 @@ function onboardingPrompt(input) {
|
|
|
1543
2325
|
}
|
|
1544
2326
|
|
|
1545
2327
|
// src/scan.ts
|
|
1546
|
-
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "node:fs";
|
|
2328
|
+
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
1547
2329
|
import path2 from "node:path";
|
|
1548
|
-
var MONOREPO_DIRS = ["apps", "packages", "services", "web", "site"],
|
|
1549
|
-
"
|
|
1550
|
-
"
|
|
1551
|
-
"
|
|
1552
|
-
"
|
|
1553
|
-
"
|
|
1554
|
-
"
|
|
2330
|
+
var MONOREPO_DIRS = ["apps", "packages", "services", "sites", "web", "site"], SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
2331
|
+
"node_modules",
|
|
2332
|
+
".git",
|
|
2333
|
+
".next",
|
|
2334
|
+
".nuxt",
|
|
2335
|
+
".svelte-kit",
|
|
2336
|
+
".turbo",
|
|
2337
|
+
".vercel",
|
|
2338
|
+
".wrangler",
|
|
2339
|
+
"dist",
|
|
2340
|
+
"build",
|
|
2341
|
+
"out",
|
|
2342
|
+
"vendor",
|
|
2343
|
+
"venv",
|
|
2344
|
+
".venv",
|
|
2345
|
+
"target"
|
|
2346
|
+
]), MAX_PACKAGES = 200, MARKERS = [
|
|
2347
|
+
/^wrangler\.(toml|jsonc?)$/,
|
|
2348
|
+
/^vercel\.json$/,
|
|
2349
|
+
/^netlify\.toml$/,
|
|
2350
|
+
/^fly\.toml$/,
|
|
2351
|
+
/^render\.ya?ml$/,
|
|
2352
|
+
/^railway\.(json|toml)$/,
|
|
2353
|
+
/^firebase\.json$/,
|
|
2354
|
+
/^amplify\.ya?ml$/,
|
|
2355
|
+
/^app\.ya?ml$/,
|
|
2356
|
+
/^Dockerfile$/,
|
|
2357
|
+
/^docker-compose\.ya?ml$/,
|
|
2358
|
+
/^compose\.ya?ml$/,
|
|
2359
|
+
/^Procfile$/,
|
|
2360
|
+
/^CNAME$/,
|
|
2361
|
+
/^index\.html$/,
|
|
2362
|
+
/^(next|astro|nuxt|svelte|vite|remix|gatsby|docusaurus|tailwind|drizzle|sentry\.(client|server|edge))\.config\.(js|cjs|mjs|ts|mts)$/,
|
|
2363
|
+
/^gatsby-config\.(js|ts)$/,
|
|
2364
|
+
/^(\.eleventy\.js|eleventy\.config\.(js|cjs|mjs))$/,
|
|
2365
|
+
/^hugo\.(toml|ya?ml|json)$/,
|
|
2366
|
+
/^_config\.ya?ml$/,
|
|
2367
|
+
/^instrumentation(-client)?\.(ts|js)$/,
|
|
2368
|
+
/^manage\.py$/,
|
|
2369
|
+
/^artisan$/,
|
|
2370
|
+
/^wp-config(-sample)?\.php$/,
|
|
2371
|
+
/^shopify\.app\.toml$/,
|
|
2372
|
+
/^app\.json$/,
|
|
2373
|
+
/^(requirements\.txt|pyproject\.toml|Pipfile|Gemfile|composer\.json|go\.mod|Cargo\.toml)$/,
|
|
2374
|
+
/^(bun\.lockb?|pnpm-lock\.yaml|yarn\.lock|package-lock\.json)$/
|
|
2375
|
+
], NESTED_MARKERS = [
|
|
1555
2376
|
"supabase/config.toml",
|
|
1556
2377
|
"convex/schema.ts",
|
|
1557
|
-
"
|
|
1558
|
-
"
|
|
1559
|
-
"
|
|
1560
|
-
"
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
2378
|
+
"prisma/schema.prisma",
|
|
2379
|
+
"src/instrumentation.ts",
|
|
2380
|
+
"wp-content",
|
|
2381
|
+
".github/workflows",
|
|
2382
|
+
"content/config.ts"
|
|
2383
|
+
], EXAMPLE_ENV = /^(\.env\.(example|sample|template|dist|local\.example|development\.example|production\.example)|example\.env|\.dev\.vars\.example)$/;
|
|
2384
|
+
function readText(file2) {
|
|
2385
|
+
try {
|
|
2386
|
+
return readFileSync2(file2, "utf8");
|
|
2387
|
+
} catch {
|
|
2388
|
+
return null;
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
function readJson(file2) {
|
|
2392
|
+
let text = readText(file2);
|
|
2393
|
+
if (text === null)
|
|
2394
|
+
return null;
|
|
1568
2395
|
try {
|
|
1569
|
-
return JSON.parse(
|
|
2396
|
+
return JSON.parse(text);
|
|
1570
2397
|
} catch {
|
|
1571
2398
|
return null;
|
|
1572
2399
|
}
|
|
1573
2400
|
}
|
|
2401
|
+
function isDir(p2) {
|
|
2402
|
+
try {
|
|
2403
|
+
return statSync(p2).isDirectory();
|
|
2404
|
+
} catch {
|
|
2405
|
+
return !1;
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
function listDir(dir) {
|
|
2409
|
+
try {
|
|
2410
|
+
return readdirSync(dir);
|
|
2411
|
+
} catch {
|
|
2412
|
+
return [];
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
function isPackage(dir) {
|
|
2416
|
+
return [
|
|
2417
|
+
"package.json",
|
|
2418
|
+
"pyproject.toml",
|
|
2419
|
+
"requirements.txt",
|
|
2420
|
+
"Gemfile",
|
|
2421
|
+
"composer.json",
|
|
2422
|
+
"go.mod",
|
|
2423
|
+
"Cargo.toml"
|
|
2424
|
+
].some((f) => existsSync2(path2.join(dir, f)));
|
|
2425
|
+
}
|
|
2426
|
+
function workspaceGlobs(root) {
|
|
2427
|
+
let globs = [], ws = readJson(path2.join(root, "package.json"))?.workspaces, list = Array.isArray(ws) ? ws : ws && typeof ws === "object" && Array.isArray(ws.packages) ? ws.packages : [];
|
|
2428
|
+
for (let g2 of list)
|
|
2429
|
+
if (typeof g2 === "string")
|
|
2430
|
+
globs.push(g2);
|
|
2431
|
+
let pnpm = readText(path2.join(root, "pnpm-workspace.yaml"));
|
|
2432
|
+
if (pnpm)
|
|
2433
|
+
for (let m2 of pnpm.matchAll(/^\s*-\s*["']?([^"'\s#]+)["']?/gm))
|
|
2434
|
+
globs.push(m2[1]);
|
|
2435
|
+
return globs.filter((g2) => !g2.startsWith("!"));
|
|
2436
|
+
}
|
|
2437
|
+
function expandGlob(root, glob) {
|
|
2438
|
+
let clean = glob.replace(/\/\*\*$/, "/*").replace(/\/$/, "");
|
|
2439
|
+
if (!clean.includes("*")) {
|
|
2440
|
+
let dir = path2.join(root, clean);
|
|
2441
|
+
return isDir(dir) ? [dir] : [];
|
|
2442
|
+
}
|
|
2443
|
+
let [base] = clean.split("*"), parent = path2.join(root, base);
|
|
2444
|
+
return listDir(parent).filter((e2) => !SKIP_DIRS.has(e2) && !e2.startsWith(".")).map((e2) => path2.join(parent, e2)).filter(isDir);
|
|
2445
|
+
}
|
|
1574
2446
|
function packageDirs(root) {
|
|
1575
|
-
let dirs = [root];
|
|
2447
|
+
let dirs = /* @__PURE__ */ new Set([root]);
|
|
2448
|
+
for (let g2 of workspaceGlobs(root))
|
|
2449
|
+
for (let d3 of expandGlob(root, g2))
|
|
2450
|
+
if (isPackage(d3))
|
|
2451
|
+
dirs.add(d3);
|
|
1576
2452
|
for (let group of MONOREPO_DIRS) {
|
|
1577
|
-
let
|
|
1578
|
-
if (!
|
|
2453
|
+
let g2 = path2.join(root, group);
|
|
2454
|
+
if (!isDir(g2))
|
|
1579
2455
|
continue;
|
|
1580
|
-
if (
|
|
1581
|
-
dirs.
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
}
|
|
2456
|
+
if (isPackage(g2))
|
|
2457
|
+
dirs.add(g2);
|
|
2458
|
+
for (let entry of listDir(g2)) {
|
|
2459
|
+
let d3 = path2.join(g2, entry);
|
|
2460
|
+
if (!SKIP_DIRS.has(entry) && isDir(d3) && isPackage(d3))
|
|
2461
|
+
dirs.add(d3);
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
return [...dirs].slice(0, MAX_PACKAGES);
|
|
2465
|
+
}
|
|
2466
|
+
function pythonRequirements(text) {
|
|
2467
|
+
let out = [];
|
|
2468
|
+
for (let line of text.split(/\r?\n/)) {
|
|
2469
|
+
let m2 = /^\s*([A-Za-z0-9][A-Za-z0-9._-]*)/.exec(line.replace(/#.*/, ""));
|
|
2470
|
+
if (m2 && !line.trim().startsWith("-"))
|
|
2471
|
+
out.push(m2[1].toLowerCase());
|
|
1587
2472
|
}
|
|
1588
|
-
return
|
|
2473
|
+
return out;
|
|
2474
|
+
}
|
|
2475
|
+
function pythonProjectDeps(text) {
|
|
2476
|
+
let out = /* @__PURE__ */ new Set;
|
|
2477
|
+
for (let block of text.matchAll(/(?:^|\n)\s*dependencies\s*=\s*\[([\s\S]*?)\]/g))
|
|
2478
|
+
for (let m2 of block[1].matchAll(/["']([A-Za-z0-9][A-Za-z0-9._-]*)/g))
|
|
2479
|
+
out.add(m2[1].toLowerCase());
|
|
2480
|
+
for (let table of text.matchAll(/\[(?:tool\.poetry\.(?:group\.[a-z]+\.)?dependencies|(?:dev-)?packages)\]([\s\S]*?)(?=\n\[|$)/g))
|
|
2481
|
+
for (let m2 of table[1].matchAll(/^\s*([A-Za-z0-9][A-Za-z0-9._-]*)\s*=/gm))
|
|
2482
|
+
out.add(m2[1].toLowerCase());
|
|
2483
|
+
return out.delete("python"), [...out];
|
|
2484
|
+
}
|
|
2485
|
+
function gemfileDeps(text) {
|
|
2486
|
+
return [...text.matchAll(/^\s*gem\s+["']([^"']+)["']/gm)].map((m2) => m2[1]);
|
|
2487
|
+
}
|
|
2488
|
+
function goModDeps(text) {
|
|
2489
|
+
let out = [];
|
|
2490
|
+
for (let m2 of text.matchAll(/^\s*(?:require\s+)?([a-z0-9.-]+\.[a-z]+\/[^\s]+)\s+v[0-9]/gm))
|
|
2491
|
+
out.push(m2[1]);
|
|
2492
|
+
return out;
|
|
2493
|
+
}
|
|
2494
|
+
function cargoDeps(text) {
|
|
2495
|
+
let out = [];
|
|
2496
|
+
for (let table of text.matchAll(/\[(?:dev-|build-)?dependencies\]([\s\S]*?)(?=\n\[|$)/g))
|
|
2497
|
+
for (let m2 of table[1].matchAll(/^\s*([A-Za-z0-9_-]+)\s*=/gm))
|
|
2498
|
+
out.push(m2[1]);
|
|
2499
|
+
return out;
|
|
2500
|
+
}
|
|
2501
|
+
function manifestDeps(dir) {
|
|
2502
|
+
let deps = [], add = (prefix, names) => {
|
|
2503
|
+
for (let n of names)
|
|
2504
|
+
deps.push(`${prefix}:${n}`);
|
|
2505
|
+
}, req = readText(path2.join(dir, "requirements.txt"));
|
|
2506
|
+
if (req)
|
|
2507
|
+
add("py", pythonRequirements(req));
|
|
2508
|
+
for (let f of ["pyproject.toml", "Pipfile"]) {
|
|
2509
|
+
let t = readText(path2.join(dir, f));
|
|
2510
|
+
if (t)
|
|
2511
|
+
add("py", pythonProjectDeps(t));
|
|
2512
|
+
}
|
|
2513
|
+
let gem = readText(path2.join(dir, "Gemfile"));
|
|
2514
|
+
if (gem)
|
|
2515
|
+
add("gem", gemfileDeps(gem));
|
|
2516
|
+
let composer = readJson(path2.join(dir, "composer.json"));
|
|
2517
|
+
for (let k3 of ["require", "require-dev"]) {
|
|
2518
|
+
let d3 = composer?.[k3];
|
|
2519
|
+
if (d3 && typeof d3 === "object")
|
|
2520
|
+
add("composer", Object.keys(d3));
|
|
2521
|
+
}
|
|
2522
|
+
let gomod = readText(path2.join(dir, "go.mod"));
|
|
2523
|
+
if (gomod)
|
|
2524
|
+
add("go", goModDeps(gomod));
|
|
2525
|
+
let cargo = readText(path2.join(dir, "Cargo.toml"));
|
|
2526
|
+
if (cargo)
|
|
2527
|
+
add("cargo", cargoDeps(cargo));
|
|
2528
|
+
return deps;
|
|
1589
2529
|
}
|
|
1590
2530
|
function siteFromWrangler(text) {
|
|
1591
2531
|
let first = [
|
|
1592
2532
|
...text.matchAll(/(?:pattern|route)["']?\s*[:=]\s*["']([^"'*/\s]+)/g),
|
|
1593
2533
|
...text.matchAll(/custom_domain["']?\s*[:=]\s*["']([^"'*/\s]+)/g)
|
|
1594
|
-
].map((
|
|
2534
|
+
].map((m2) => m2[1].toLowerCase()).filter((h2) => h2.includes(".") && !h2.endsWith("workers.dev"))[0];
|
|
1595
2535
|
if (!first)
|
|
1596
2536
|
return;
|
|
1597
2537
|
return `https://${first.replace(/^(app|www|api|console)\./, "")}`;
|
|
1598
2538
|
}
|
|
2539
|
+
var NOT_A_SITE = /(^|\.)(localhost|example\.(com|org)|your-?(site|domain)\.[a-z]+|vercel\.app|netlify\.app|pages\.dev|workers\.dev|github\.com|gitlab\.com|npmjs\.com)$/;
|
|
2540
|
+
function publicSite(url) {
|
|
2541
|
+
try {
|
|
2542
|
+
let u2 = new URL(url.startsWith("http") ? url : `https://${url}`);
|
|
2543
|
+
if (!u2.hostname.includes(".") || NOT_A_SITE.test(u2.hostname))
|
|
2544
|
+
return;
|
|
2545
|
+
return `https://${u2.hostname.replace(/^www\./, "")}`;
|
|
2546
|
+
} catch {
|
|
2547
|
+
return;
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
function siteFromConfig(text) {
|
|
2551
|
+
let m2 = /\b(?:site|siteUrl|baseURL|baseUrl|url)\s*[:=]\s*["'`](https?:\/\/[^"'`\s]+)["'`]/.exec(text);
|
|
2552
|
+
return m2 ? publicSite(m2[1]) : void 0;
|
|
2553
|
+
}
|
|
2554
|
+
function githubRepo(gitConfig) {
|
|
2555
|
+
let remotes = [
|
|
2556
|
+
...gitConfig.matchAll(/\[remote "([^"]+)"\][^[]*?url\s*=\s*(\S+)/g)
|
|
2557
|
+
].sort((a, b3) => Number(b3[1] === "origin") - Number(a[1] === "origin"));
|
|
2558
|
+
for (let [, , url] of remotes) {
|
|
2559
|
+
let m2 = /github\.com[:/]([\w.-]+)\/([\w.-]+?)(?:\.git)?\/?$/.exec(url);
|
|
2560
|
+
if (m2)
|
|
2561
|
+
return `${m2[1]}/${m2[2]}`;
|
|
2562
|
+
}
|
|
2563
|
+
return;
|
|
2564
|
+
}
|
|
2565
|
+
function readGitConfig(root) {
|
|
2566
|
+
let dotGit = path2.join(root, ".git");
|
|
2567
|
+
if (isDir(dotGit))
|
|
2568
|
+
return readText(path2.join(dotGit, "config"));
|
|
2569
|
+
let pointer = /gitdir:\s*(.+)/.exec(readText(dotGit) ?? "")?.[1]?.trim();
|
|
2570
|
+
if (!pointer)
|
|
2571
|
+
return null;
|
|
2572
|
+
let gitDir = path2.resolve(root, pointer);
|
|
2573
|
+
return readText(path2.join(gitDir, "config")) ?? readText(path2.join(gitDir, "..", "..", "config"));
|
|
2574
|
+
}
|
|
2575
|
+
function siteFromHtml(html) {
|
|
2576
|
+
let m2 = /<link[^>]+rel=["']canonical["'][^>]+href=["'](https?:\/\/[^"']+)/i.exec(html) ?? /<link[^>]+href=["'](https?:\/\/[^"']+)["'][^>]+rel=["']canonical["']/i.exec(html) ?? /<meta[^>]+property=["']og:url["'][^>]+content=["'](https?:\/\/[^"']+)/i.exec(html);
|
|
2577
|
+
return m2 ? publicSite(m2[1]) : void 0;
|
|
2578
|
+
}
|
|
1599
2579
|
function envKeyNames(text) {
|
|
1600
2580
|
let out = [];
|
|
1601
2581
|
for (let line of text.split(/\r?\n/)) {
|
|
1602
|
-
let
|
|
1603
|
-
if (
|
|
1604
|
-
out.push(
|
|
2582
|
+
let m2 = /^\s*(?:#\s*)?(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/.exec(line);
|
|
2583
|
+
if (m2)
|
|
2584
|
+
out.push(m2[1]);
|
|
1605
2585
|
}
|
|
1606
2586
|
return out;
|
|
1607
2587
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
2588
|
+
var SITE_CONFIGS = /^(astro\.config\.\w+|next-sitemap\.config\.\w+|gatsby-config\.\w+|docusaurus\.config\.\w+|hugo\.toml|config\.toml|_config\.ya?ml|nuxt\.config\.\w+)$/;
|
|
2589
|
+
function scanProject(root, onProgress) {
|
|
2590
|
+
let deps = /* @__PURE__ */ new Set, files = /* @__PURE__ */ new Set, envKeys = /* @__PURE__ */ new Set, name, siteUrl, dirs = packageDirs(root), relDir = (d3) => path2.relative(root, d3).split(path2.sep).join("/") || ".";
|
|
2591
|
+
onProgress?.({
|
|
2592
|
+
label: `Found ${dirs.length} ${dirs.length === 1 ? "package" : "packages"}`,
|
|
2593
|
+
detail: dirs.length > 1 ? dirs.map(relDir).join(", ") : void 0
|
|
2594
|
+
});
|
|
2595
|
+
let envFiles = 0;
|
|
2596
|
+
for (let dir of dirs) {
|
|
2597
|
+
let rel = (f) => (path2.relative(root, path2.join(dir, f)) || f).split(path2.sep).join("/"), pkg = readJson(path2.join(dir, "package.json"));
|
|
1612
2598
|
if (pkg) {
|
|
1613
2599
|
if (dir === root && typeof pkg.name === "string")
|
|
1614
2600
|
name = pkg.name;
|
|
1615
2601
|
if (!siteUrl && typeof pkg.homepage === "string")
|
|
1616
|
-
siteUrl = pkg.homepage;
|
|
1617
|
-
for (let
|
|
1618
|
-
let
|
|
1619
|
-
if (
|
|
1620
|
-
for (let n of Object.keys(
|
|
2602
|
+
siteUrl = publicSite(pkg.homepage);
|
|
2603
|
+
for (let k3 of ["dependencies", "devDependencies"]) {
|
|
2604
|
+
let d3 = pkg[k3];
|
|
2605
|
+
if (d3 && typeof d3 === "object")
|
|
2606
|
+
for (let n of Object.keys(d3))
|
|
1621
2607
|
deps.add(n);
|
|
1622
2608
|
}
|
|
1623
2609
|
}
|
|
1624
|
-
for (let
|
|
1625
|
-
|
|
2610
|
+
for (let d3 of manifestDeps(dir))
|
|
2611
|
+
deps.add(d3);
|
|
2612
|
+
for (let entry of listDir(dir)) {
|
|
2613
|
+
if (EXAMPLE_ENV.test(entry)) {
|
|
2614
|
+
let text = readText(path2.join(dir, entry));
|
|
2615
|
+
if (text) {
|
|
2616
|
+
envFiles++;
|
|
2617
|
+
for (let k3 of envKeyNames(text))
|
|
2618
|
+
envKeys.add(k3);
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
if (!MARKERS.some((re) => re.test(entry)))
|
|
1626
2622
|
continue;
|
|
1627
|
-
if (files.add(rel(
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
2623
|
+
if (files.add(rel(entry)), entry.startsWith("wrangler.")) {
|
|
2624
|
+
let text = readText(path2.join(dir, entry)) ?? "";
|
|
2625
|
+
if (/d1_databases/.test(text))
|
|
2626
|
+
files.add(`${rel(entry)}#d1`);
|
|
2627
|
+
if (!siteUrl)
|
|
2628
|
+
siteUrl = siteFromWrangler(text);
|
|
2629
|
+
} else if (entry === "CNAME" && !siteUrl)
|
|
2630
|
+
siteUrl = publicSite((readText(path2.join(dir, entry)) ?? "").trim());
|
|
2631
|
+
}
|
|
2632
|
+
for (let f of NESTED_MARKERS)
|
|
2633
|
+
if (existsSync2(path2.join(dir, f)))
|
|
2634
|
+
files.add(rel(f));
|
|
2635
|
+
if (!siteUrl)
|
|
2636
|
+
for (let entry of listDir(dir)) {
|
|
2637
|
+
if (!SITE_CONFIGS.test(entry))
|
|
2638
|
+
continue;
|
|
2639
|
+
if (siteUrl = siteFromConfig(readText(path2.join(dir, entry)) ?? ""), siteUrl)
|
|
2640
|
+
break;
|
|
2641
|
+
}
|
|
2642
|
+
if (!siteUrl) {
|
|
2643
|
+
for (let f of ["index.html", "public/index.html", "src/index.html"])
|
|
2644
|
+
if (siteUrl = siteFromHtml(readText(path2.join(dir, f)) ?? ""), siteUrl)
|
|
2645
|
+
break;
|
|
1635
2646
|
}
|
|
1636
|
-
for (let f of EXAMPLE_ENV)
|
|
1637
|
-
try {
|
|
1638
|
-
for (let k of envKeyNames(readFileSync2(path2.join(dir, f), "utf8")))
|
|
1639
|
-
envKeys.add(k);
|
|
1640
|
-
} catch {}
|
|
1641
2647
|
}
|
|
2648
|
+
onProgress?.({
|
|
2649
|
+
label: `Read ${deps.size} ${deps.size === 1 ? "dependency" : "dependencies"}`
|
|
2650
|
+
}), onProgress?.({
|
|
2651
|
+
label: `Found ${files.size} config ${files.size === 1 ? "file" : "files"}`,
|
|
2652
|
+
detail: [...files].filter((f) => !f.includes("#")).slice(0, 8).join(", ") || void 0
|
|
2653
|
+
}), onProgress?.({
|
|
2654
|
+
label: envFiles ? `Read ${envKeys.size} variable names from ${envFiles} example env ${envFiles === 1 ? "file" : "files"}` : "No example env files to read",
|
|
2655
|
+
detail: "Names only. Your real .env files are never opened."
|
|
2656
|
+
});
|
|
2657
|
+
let gitConfig = readGitConfig(root), repo = gitConfig ? githubRepo(gitConfig) : void 0;
|
|
2658
|
+
if (repo)
|
|
2659
|
+
onProgress?.({ label: `GitHub repo ${repo}` });
|
|
1642
2660
|
return {
|
|
1643
2661
|
name,
|
|
1644
2662
|
siteUrl,
|
|
2663
|
+
repo,
|
|
1645
2664
|
dependencies: [...deps].sort(),
|
|
1646
2665
|
files: [...files].sort(),
|
|
1647
2666
|
envKeys: [...envKeys].sort()
|
|
1648
2667
|
};
|
|
1649
2668
|
}
|
|
1650
2669
|
|
|
2670
|
+
// src/ui.ts
|
|
2671
|
+
var import_picocolors3 = __toESM(require_picocolors(), 1), MARK = [
|
|
2672
|
+
" ▗▖ ▗▖ ",
|
|
2673
|
+
" ▐█▖▗█▌ ",
|
|
2674
|
+
" ▜██▛ ",
|
|
2675
|
+
" ▟██▙ ",
|
|
2676
|
+
" ▟████▙ ",
|
|
2677
|
+
" ▟██████▙ ",
|
|
2678
|
+
" ▄▄▄▄▄▄▄▄▄▄ ",
|
|
2679
|
+
" ▄▄▄▄▄▄ "
|
|
2680
|
+
], HORIZON = /* @__PURE__ */ new Set([6, 7]);
|
|
2681
|
+
function motionAllowed(argv = process.argv, env = process.env, isTTY = Boolean(process.stdout.isTTY)) {
|
|
2682
|
+
return isTTY && !env.CI && !env.LOPEBASE_NO_MOTION && env.TERM !== "dumb" && !argv.includes("--no-motion");
|
|
2683
|
+
}
|
|
2684
|
+
var sleep = (ms) => new Promise((r2) => setTimeout(r2, ms));
|
|
2685
|
+
function logoLines(opts) {
|
|
2686
|
+
let right = {
|
|
2687
|
+
3: `${import_picocolors3.default.bold("Lope")}${import_picocolors3.default.dim("Base")}${opts.version ? import_picocolors3.default.dim(` v${opts.version}`) : ""}`,
|
|
2688
|
+
4: import_picocolors3.default.dim(opts.tagline)
|
|
2689
|
+
};
|
|
2690
|
+
return MARK.map((row, i) => {
|
|
2691
|
+
let mark = HORIZON.has(i) ? import_picocolors3.default.yellow(import_picocolors3.default.dim(row)) : import_picocolors3.default.yellow(row);
|
|
2692
|
+
return right[i] ? `${mark} ${right[i]}` : mark;
|
|
2693
|
+
});
|
|
2694
|
+
}
|
|
2695
|
+
async function showLogo(opts) {
|
|
2696
|
+
let write = opts.write ?? ((s) => process.stdout.write(s)), lines = logoLines(opts);
|
|
2697
|
+
if (!opts.motion) {
|
|
2698
|
+
write(`
|
|
2699
|
+
${lines.join(`
|
|
2700
|
+
`)}
|
|
2701
|
+
|
|
2702
|
+
`);
|
|
2703
|
+
return;
|
|
2704
|
+
}
|
|
2705
|
+
let blank = MARK.map((r2) => " ".repeat(r2.length)), frame = (shown) => MARK.map((row, i) => shown.has(i) ? HORIZON.has(i) ? import_picocolors3.default.yellow(import_picocolors3.default.dim(row)) : import_picocolors3.default.yellow(row) : blank[i]), up = `\x1B[${MARK.length}A`, onInterrupt = () => {
|
|
2706
|
+
write(`\x1B[?25h
|
|
2707
|
+
`), process.exit(130);
|
|
2708
|
+
};
|
|
2709
|
+
process.once("SIGINT", onInterrupt), write(`
|
|
2710
|
+
\x1B[?25l`);
|
|
2711
|
+
try {
|
|
2712
|
+
let shown = /* @__PURE__ */ new Set;
|
|
2713
|
+
write(`${frame(shown).join(`
|
|
2714
|
+
`)}
|
|
2715
|
+
`);
|
|
2716
|
+
for (let i = MARK.length - 1;i >= 0; i--)
|
|
2717
|
+
shown.add(i), write(`${up}\r${frame(shown).join(`
|
|
2718
|
+
`)}
|
|
2719
|
+
`), await sleep(HORIZON.has(i) ? 70 : 35);
|
|
2720
|
+
write(`${up}\r${lines.join(`
|
|
2721
|
+
`)}
|
|
2722
|
+
|
|
2723
|
+
`);
|
|
2724
|
+
} finally {
|
|
2725
|
+
process.off("SIGINT", onInterrupt), write("\x1B[?25h");
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
async function pace(motion, ms = 90) {
|
|
2729
|
+
if (motion)
|
|
2730
|
+
await sleep(ms);
|
|
2731
|
+
}
|
|
2732
|
+
var KIND_LABEL = {
|
|
2733
|
+
framework: "Framework",
|
|
2734
|
+
language: "Language",
|
|
2735
|
+
hosting: "Hosting",
|
|
2736
|
+
database: "Data",
|
|
2737
|
+
email: "Email",
|
|
2738
|
+
billing: "Billing",
|
|
2739
|
+
auth: "Sign-in",
|
|
2740
|
+
analytics: "Analytics",
|
|
2741
|
+
cms: "Content"
|
|
2742
|
+
};
|
|
2743
|
+
function stackRows(detection, facts) {
|
|
2744
|
+
let byKind = /* @__PURE__ */ new Map;
|
|
2745
|
+
for (let s of detection.stack)
|
|
2746
|
+
byKind.set(s.kind, [...byKind.get(s.kind) ?? [], s.name]);
|
|
2747
|
+
let rows = [];
|
|
2748
|
+
for (let kind of Object.keys(KIND_LABEL)) {
|
|
2749
|
+
let names = byKind.get(kind);
|
|
2750
|
+
if (names)
|
|
2751
|
+
rows.push([KIND_LABEL[kind], names.join(", ")]);
|
|
2752
|
+
}
|
|
2753
|
+
if (facts.siteUrl)
|
|
2754
|
+
rows.push(["Site", facts.siteUrl]);
|
|
2755
|
+
if (facts.repo)
|
|
2756
|
+
rows.push(["Repo", facts.repo]);
|
|
2757
|
+
if (!rows.length)
|
|
2758
|
+
return [import_picocolors3.default.dim("Nothing recognized yet. Your agent will look closer.")];
|
|
2759
|
+
let width = Math.max(...rows.map(([k3]) => k3.length));
|
|
2760
|
+
return rows.map(([k3, v2]) => `${import_picocolors3.default.dim(k3.padEnd(width))} ${v2}`);
|
|
2761
|
+
}
|
|
2762
|
+
var PLAN = [
|
|
2763
|
+
{
|
|
2764
|
+
title: "Scan this folder",
|
|
2765
|
+
detail: "Reads dependency and file names only. Never your .env or code."
|
|
2766
|
+
},
|
|
2767
|
+
{
|
|
2768
|
+
title: "Connect this computer",
|
|
2769
|
+
detail: "Sign in in the browser and confirm a short code."
|
|
2770
|
+
},
|
|
2771
|
+
{
|
|
2772
|
+
title: "Choose what to connect",
|
|
2773
|
+
detail: "What your code already uses is ticked. New things are optional."
|
|
2774
|
+
},
|
|
2775
|
+
{
|
|
2776
|
+
title: "Hand over to your coding agent",
|
|
2777
|
+
detail: "It does the setup and asks you when it needs a key or a yes."
|
|
2778
|
+
}
|
|
2779
|
+
];
|
|
2780
|
+
function planLines() {
|
|
2781
|
+
return PLAN.map((s, i) => `${import_picocolors3.default.yellow(String(i + 1))} ${import_picocolors3.default.bold(s.title)}
|
|
2782
|
+
${import_picocolors3.default.dim(s.detail)}`);
|
|
2783
|
+
}
|
|
2784
|
+
function stepTitle(n) {
|
|
2785
|
+
return `${import_picocolors3.default.dim(`Step ${n} of ${PLAN.length}`)} ${import_picocolors3.default.bold(PLAN[n - 1].title)}`;
|
|
2786
|
+
}
|
|
2787
|
+
|
|
1651
2788
|
// bin/lopebase.ts
|
|
1652
2789
|
var [command = "init", ...rest] = process.argv.slice(2), HELP = `
|
|
1653
|
-
${
|
|
2790
|
+
${import_picocolors4.default.bgYellow(import_picocolors4.default.black(" LopeBase "))} Set up LopeBase from this project's folder.
|
|
1654
2791
|
|
|
1655
|
-
${
|
|
2792
|
+
${import_picocolors4.default.bold("Usage")}
|
|
1656
2793
|
npx lopebase scan, connect, and hand over to your agent
|
|
1657
2794
|
npx lopebase login connect this computer (--paste for a token)
|
|
1658
2795
|
npx lopebase scan [--json] show what the scan found
|
|
@@ -1661,13 +2798,24 @@ ${import_picocolors3.default.bold("Usage")}
|
|
|
1661
2798
|
npx lopebase adapter secret print a new @lopebase/adapter signing secret
|
|
1662
2799
|
npx lopebase adapter check <url> test a running adapter (reads LOPEBASE_SIGNING_SECRET)
|
|
1663
2800
|
|
|
1664
|
-
${
|
|
1665
|
-
|
|
2801
|
+
${import_picocolors4.default.bold("Options")}
|
|
2802
|
+
--print print the agent prompt instead of starting an agent
|
|
2803
|
+
--no-motion no animation (also LOPEBASE_NO_MOTION=1)
|
|
2804
|
+
|
|
2805
|
+
${import_picocolors4.default.bold("Learn more")}
|
|
2806
|
+
${import_picocolors4.default.cyan("https://lopebase.com")}
|
|
1666
2807
|
Help: contact@jackalope.digital
|
|
1667
2808
|
`, flag = (name) => rest.includes(`--${name}`), option = (name) => {
|
|
1668
2809
|
let i = rest.indexOf(`--${name}`);
|
|
1669
2810
|
return i >= 0 ? rest[i + 1] : rest.find((a) => a.startsWith(`--${name}=`))?.split("=")[1];
|
|
1670
|
-
}, cwd = process.cwd();
|
|
2811
|
+
}, cwd = process.cwd(), motion = motionAllowed(), TAGLINE = "Every product, one place.";
|
|
2812
|
+
function version() {
|
|
2813
|
+
try {
|
|
2814
|
+
return JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
2815
|
+
} catch {
|
|
2816
|
+
return;
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
1671
2819
|
function bail(message) {
|
|
1672
2820
|
xe(message), process.exit(1);
|
|
1673
2821
|
}
|
|
@@ -1677,8 +2825,11 @@ function guard(value) {
|
|
|
1677
2825
|
return value;
|
|
1678
2826
|
}
|
|
1679
2827
|
function openBrowser(url) {
|
|
1680
|
-
let cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "
|
|
1681
|
-
|
|
2828
|
+
let [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["rundll32", ["url.dll,FileProtocolHandler", url]] : ["xdg-open", [url]];
|
|
2829
|
+
try {
|
|
2830
|
+
let child = spawn2(cmd, args, { stdio: "ignore", detached: !0 });
|
|
2831
|
+
child.on("error", () => {}), child.unref();
|
|
2832
|
+
} catch {}
|
|
1682
2833
|
}
|
|
1683
2834
|
async function checkToken(auth) {
|
|
1684
2835
|
try {
|
|
@@ -1697,40 +2848,40 @@ async function checkToken(auth) {
|
|
|
1697
2848
|
});
|
|
1698
2849
|
if (res.status === 401)
|
|
1699
2850
|
return { ok: !1, message: "That token is not valid or has expired." };
|
|
1700
|
-
let
|
|
1701
|
-
return { ok: !0, products: JSON.parse(
|
|
1702
|
-
} catch (
|
|
2851
|
+
let body2 = await res.json();
|
|
2852
|
+
return { ok: !0, products: JSON.parse(body2.result?.content?.[0]?.text ?? "[]").length };
|
|
2853
|
+
} catch (e2) {
|
|
1703
2854
|
return {
|
|
1704
2855
|
ok: !1,
|
|
1705
|
-
message: `Could not reach ${auth.url} (${String(
|
|
2856
|
+
message: `Could not reach ${auth.url} (${String(e2)}).`
|
|
1706
2857
|
};
|
|
1707
2858
|
}
|
|
1708
2859
|
}
|
|
1709
2860
|
async function login() {
|
|
1710
|
-
let url = guard(await he({
|
|
1711
|
-
message: "Your LopeBase workspace address",
|
|
1712
|
-
placeholder: "https://yourteam.lopebase.com",
|
|
1713
|
-
initialValue:
|
|
1714
|
-
validate: (
|
|
1715
|
-
}))
|
|
2861
|
+
let known = option("url") ?? loadAuth()?.url, url = workspaceUrl(guard(await he({
|
|
2862
|
+
message: "Your LopeBase workspace (name or address)",
|
|
2863
|
+
placeholder: "yourteam or https://yourteam.lopebase.com",
|
|
2864
|
+
initialValue: known ?? "",
|
|
2865
|
+
validate: (v2) => workspaceUrl(v2 ?? "") ? void 0 : "Type your workspace name, like yourteam, or its full address"
|
|
2866
|
+
})));
|
|
1716
2867
|
if (!flag("paste")) {
|
|
1717
2868
|
let device = await startDeviceLogin(url);
|
|
1718
2869
|
if (device) {
|
|
1719
2870
|
Me([
|
|
1720
|
-
`Your code: ${
|
|
2871
|
+
`Your code: ${import_picocolors4.default.bold(import_picocolors4.default.yellow(device.userCode))}`,
|
|
1721
2872
|
"",
|
|
1722
2873
|
"We opened your workspace in the browser. Sign in if asked,",
|
|
1723
2874
|
"check the code matches, and press Connect."
|
|
1724
2875
|
].join(`
|
|
1725
|
-
`), "Connect this computer"), openBrowser(device.verifyUrl), M2.info(
|
|
2876
|
+
`), "Connect this computer"), openBrowser(device.verifyUrl), M2.info(import_picocolors4.default.dim(device.verifyUrl));
|
|
1726
2877
|
let s = Y2();
|
|
1727
2878
|
s.start("Waiting for you to approve in the browser");
|
|
1728
2879
|
let res = await pollDeviceLogin(url, device);
|
|
1729
2880
|
if (res.ok) {
|
|
1730
|
-
let
|
|
1731
|
-
return s.stop(`Connected (${res.scope}). Saved to ${
|
|
2881
|
+
let file2 = saveAuth({ url: res.url, token: res.token });
|
|
2882
|
+
return s.stop(`Connected (${res.scope}). Saved to ${import_picocolors4.default.dim(file2)}`), { url: res.url, token: res.token };
|
|
1732
2883
|
}
|
|
1733
|
-
if (s.stop(
|
|
2884
|
+
if (s.stop(import_picocolors4.default.red(res.message)), res.reason === "denied")
|
|
1734
2885
|
bail("Nothing was connected.");
|
|
1735
2886
|
M2.warn("Falling back to pasting a token.");
|
|
1736
2887
|
}
|
|
@@ -1741,47 +2892,59 @@ async function pasteLogin(url) {
|
|
|
1741
2892
|
let settings = `${url}/settings#agent-access`;
|
|
1742
2893
|
Me([
|
|
1743
2894
|
"1. Sign in (or sign up) in the browser tab we open.",
|
|
1744
|
-
`2. Under Settings, Agent access, press ${
|
|
2895
|
+
`2. Under Settings, Agent access, press ${import_picocolors4.default.bold("Create agent access")}.`,
|
|
1745
2896
|
"3. Copy the token and paste it here. It is saved on this computer only."
|
|
1746
2897
|
].join(`
|
|
1747
|
-
`), "Get an agent token"), openBrowser(settings), M2.info(
|
|
2898
|
+
`), "Get an agent token"), openBrowser(settings), M2.info(import_picocolors4.default.dim(settings));
|
|
1748
2899
|
for (;; ) {
|
|
1749
2900
|
let token = guard(await ge({
|
|
1750
2901
|
message: "Paste the agent token",
|
|
1751
|
-
validate: (
|
|
2902
|
+
validate: (v2) => v2?.startsWith("lb_agent_") ? void 0 : "Tokens start with lb_agent_"
|
|
1752
2903
|
})).trim(), s = Y2();
|
|
1753
2904
|
s.start("Checking the token");
|
|
1754
2905
|
let check = await checkToken({ url, token });
|
|
1755
2906
|
if (check.ok) {
|
|
1756
|
-
let
|
|
1757
|
-
return s.stop(`Signed in. ${check.products} product(s) in this workspace. Saved to ${
|
|
2907
|
+
let file2 = saveAuth({ url, token });
|
|
2908
|
+
return s.stop(`Signed in. ${check.products} product(s) in this workspace. Saved to ${import_picocolors4.default.dim(file2)}`), { url, token };
|
|
1758
2909
|
}
|
|
1759
|
-
s.stop(
|
|
2910
|
+
s.stop(import_picocolors4.default.red(check.message ?? "That did not work."));
|
|
1760
2911
|
}
|
|
1761
2912
|
}
|
|
1762
|
-
function printDetection(
|
|
1763
|
-
|
|
1764
|
-
`)
|
|
1765
|
-
|
|
1766
|
-
let found = d.sources.filter((s) => s.status === "found");
|
|
2913
|
+
async function printDetection(d3, facts) {
|
|
2914
|
+
Me(stackRows(d3, facts).join(`
|
|
2915
|
+
`), "Your stack"), await pace(motion, 150);
|
|
2916
|
+
let found = d3.sources.filter((s) => s.status === "found");
|
|
1767
2917
|
if (found.length)
|
|
1768
|
-
Me(found.map((s) => `${
|
|
1769
|
-
`), "Already in your code, ready to connect");
|
|
1770
|
-
let possible =
|
|
2918
|
+
Me(found.map((s) => `${import_picocolors4.default.green("●")} ${import_picocolors4.default.bold(s.label)} ${import_picocolors4.default.dim(s.evidence.join(", "))}`).join(`
|
|
2919
|
+
`), "Already in your code, ready to connect"), await pace(motion, 150);
|
|
2920
|
+
let possible = d3.sources.filter((s) => s.status === "possible");
|
|
1771
2921
|
if (possible.length)
|
|
1772
|
-
Me(possible.map((s) => `${
|
|
1773
|
-
`), "Maybe in use, your agent will check");
|
|
1774
|
-
for (let gap of
|
|
1775
|
-
M2.
|
|
2922
|
+
Me(possible.map((s) => `${import_picocolors4.default.yellow("?")} ${import_picocolors4.default.bold(s.label)} ${import_picocolors4.default.dim(`only ${s.evidence.join(", ")}`)}`).join(`
|
|
2923
|
+
`), "Maybe in use, your agent will check"), await pace(motion, 150);
|
|
2924
|
+
for (let gap of d3.gaps)
|
|
2925
|
+
M2.info(import_picocolors4.default.dim(gap));
|
|
2926
|
+
}
|
|
2927
|
+
async function scanWithProgress() {
|
|
2928
|
+
let s = Y2();
|
|
2929
|
+
s.start("Reading this folder");
|
|
2930
|
+
let steps = [], facts = scanProject(cwd, (step) => steps.push(step));
|
|
2931
|
+
await pace(motion, 400), s.stop("Read this folder");
|
|
2932
|
+
for (let step of steps)
|
|
2933
|
+
M2.success(step.detail ? `${step.label}
|
|
2934
|
+
${import_picocolors4.default.dim(step.detail)}` : step.label), await pace(motion, 120);
|
|
2935
|
+
let detection = detectStack(facts);
|
|
2936
|
+
return { facts, detection };
|
|
1776
2937
|
}
|
|
1777
2938
|
function selfCommand() {
|
|
1778
2939
|
let self = process.argv[1] ?? "";
|
|
1779
|
-
|
|
2940
|
+
if (self && !self.includes("node_modules"))
|
|
2941
|
+
return [process.execPath, self, "mcp"];
|
|
2942
|
+
return process.platform === "win32" ? ["cmd", "/c", "npx", "-y", "lopebase", "mcp"] : ["npx", "-y", "lopebase", "mcp"];
|
|
1780
2943
|
}
|
|
1781
2944
|
function registerMcp(agent) {
|
|
1782
2945
|
if (!agent.mcpAdd)
|
|
1783
2946
|
return !1;
|
|
1784
|
-
let argv = agent.mcpAdd(selfCommand()), res =
|
|
2947
|
+
let argv = agent.mcpAdd(selfCommand()), res = spawnAgentSync(agent.bin, argv, {
|
|
1785
2948
|
stdio: "pipe",
|
|
1786
2949
|
cwd,
|
|
1787
2950
|
encoding: "utf8"
|
|
@@ -1789,29 +2952,45 @@ function registerMcp(agent) {
|
|
|
1789
2952
|
return res.status === 0 || /already exists/i.test(`${res.stderr}${res.stdout}`);
|
|
1790
2953
|
}
|
|
1791
2954
|
async function init() {
|
|
1792
|
-
console.clear(), Ie(`${
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
2955
|
+
if (console.clear(), await showLogo({ tagline: TAGLINE, version: version(), motion }), Ie(`${import_picocolors4.default.bgYellow(import_picocolors4.default.black(" LopeBase "))} set up this project`), Me(planLines().join(`
|
|
2956
|
+
`), "Here is what happens"), !guard(await ye({
|
|
2957
|
+
message: `Scan ${import_picocolors4.default.bold(cwd)}?`,
|
|
2958
|
+
initialValue: !0
|
|
2959
|
+
})))
|
|
2960
|
+
xe("Nothing was read. Run lopebase again any time."), process.exit(0);
|
|
2961
|
+
M2.step(stepTitle(1));
|
|
2962
|
+
let { facts, detection } = await scanWithProgress();
|
|
2963
|
+
await printDetection(detection, facts), M2.step(stepTitle(2));
|
|
2964
|
+
let auth = loadAuth();
|
|
2965
|
+
if (auth) {
|
|
2966
|
+
let s2 = Y2();
|
|
2967
|
+
s2.start(`Checking your connection to ${auth.url}`);
|
|
2968
|
+
let check = await checkToken(auth);
|
|
2969
|
+
if (check.ok)
|
|
2970
|
+
s2.stop(`Connected to ${import_picocolors4.default.bold(auth.url)}. ${check.products} ${check.products === 1 ? "product" : "products"} so far.`);
|
|
2971
|
+
else
|
|
2972
|
+
s2.stop(import_picocolors4.default.yellow("Your saved connection no longer works.")), auth = null;
|
|
2973
|
+
}
|
|
2974
|
+
auth ??= await login(), M2.step(stepTitle(3));
|
|
1800
2975
|
let productName = guard(await he({
|
|
1801
2976
|
message: "What is this product called?",
|
|
1802
2977
|
initialValue: facts.name?.replace(/^@[^/]+\//, "") ?? ""
|
|
1803
2978
|
})), chosen = guard(await fe({
|
|
1804
|
-
message: "What should we connect? (space to toggle)",
|
|
2979
|
+
message: "What should we connect? (space to toggle, enter to go on)",
|
|
1805
2980
|
options: detection.sources.map((src) => ({
|
|
1806
2981
|
value: src.label,
|
|
1807
|
-
label: src.status === "found" ? src.label : src.status === "possible" ? `${src.label} ${
|
|
2982
|
+
label: src.status === "found" ? src.label : src.status === "possible" ? `${src.label} ${import_picocolors4.default.dim("(maybe in use, we'll check)")}` : `${src.label} ${import_picocolors4.default.dim("(not set up yet, we'll help)")}`,
|
|
1808
2983
|
hint: src.why
|
|
1809
2984
|
})),
|
|
1810
|
-
initialValues: detection.sources.filter((src) => src.status === "found" || src.source === "gsc").map((src) => src.label),
|
|
2985
|
+
initialValues: detection.sources.filter((src) => src.status === "found" || src.source === "gsc" || src.source === "uptime").map((src) => src.label),
|
|
1811
2986
|
required: !1
|
|
1812
|
-
}))
|
|
2987
|
+
}));
|
|
2988
|
+
M2.step(stepTitle(4));
|
|
2989
|
+
let prompt = onboardingPrompt({ facts, detection, chosen, productName }), agents = installedAgents();
|
|
1813
2990
|
if (!agents.length || flag("print")) {
|
|
1814
|
-
|
|
2991
|
+
if (!agents.length)
|
|
2992
|
+
M2.info("No coding agent found on this computer (Claude Code, Codex, and others). Paste the prompt below into the one you use.");
|
|
2993
|
+
Me(prompt, "Paste this into your coding agent"), Se(`Add the MCP server first: ${import_picocolors4.default.cyan("npx lopebase mcp")} (stdio)`);
|
|
1815
2994
|
return;
|
|
1816
2995
|
}
|
|
1817
2996
|
let agentId = guard(await ve({
|
|
@@ -1825,24 +3004,39 @@ async function init() {
|
|
|
1825
3004
|
Me(prompt, "Paste this into your coding agent"), Se("Done.");
|
|
1826
3005
|
return;
|
|
1827
3006
|
}
|
|
1828
|
-
let agent = agents.find((a) => a.id === agentId);
|
|
1829
|
-
|
|
1830
|
-
|
|
3007
|
+
let agent = agents.find((a) => a.id === agentId), s = Y2();
|
|
3008
|
+
s.start(`Adding the LopeBase tools to ${agent.label}`);
|
|
3009
|
+
let registered = registerMcp(agent);
|
|
3010
|
+
if (await pace(motion, 300), registered)
|
|
3011
|
+
s.stop(`Added the LopeBase tools to ${agent.label} for this folder.`);
|
|
1831
3012
|
else
|
|
1832
|
-
|
|
1833
|
-
|
|
3013
|
+
s.stop(import_picocolors4.default.yellow(`Add an MCP server to ${agent.label} that runs: npx -y lopebase mcp`));
|
|
3014
|
+
Me([
|
|
3015
|
+
`${import_picocolors4.default.bold(agent.label)} will now:`,
|
|
3016
|
+
` ${import_picocolors4.default.dim("1.")} add ${import_picocolors4.default.bold(productName || "this product")} to your workspace`,
|
|
3017
|
+
` ${import_picocolors4.default.dim("2.")} connect ${chosen.length ? chosen.join(", ") : "what it recommends"}`,
|
|
3018
|
+
` ${import_picocolors4.default.dim("3.")} stop and ask you for any sign-in, key, or yes`,
|
|
3019
|
+
"",
|
|
3020
|
+
import_picocolors4.default.dim("You can follow along in your workspace as each one turns green.")
|
|
3021
|
+
].join(`
|
|
3022
|
+
`), "Handing over"), Se(`Starting ${agent.label}. Press Ctrl+C any time to stop.`);
|
|
3023
|
+
let child = spawnAgent(agent.bin, agent.args(prompt), {
|
|
3024
|
+
stdio: "inherit",
|
|
3025
|
+
cwd
|
|
3026
|
+
});
|
|
3027
|
+
child.on("error", (e2) => bail(`Could not start ${agent.label}: ${e2.message}`)), child.on("exit", (code) => process.exit(code ?? 0));
|
|
1834
3028
|
}
|
|
1835
3029
|
async function main() {
|
|
1836
3030
|
switch (command) {
|
|
1837
3031
|
case "init":
|
|
1838
3032
|
return init();
|
|
1839
3033
|
case "login":
|
|
1840
|
-
return Ie(`${
|
|
3034
|
+
return await showLogo({ tagline: TAGLINE, version: version(), motion }), Ie(`${import_picocolors4.default.bgYellow(import_picocolors4.default.black(" LopeBase "))} sign in`), await login(), Se("Ready. Run lopebase in any project.");
|
|
1841
3035
|
case "scan": {
|
|
1842
3036
|
let facts = scanProject(cwd), detection = detectStack(facts);
|
|
1843
3037
|
if (flag("json"))
|
|
1844
3038
|
return console.log(JSON.stringify({ facts, detection }, null, 2));
|
|
1845
|
-
return printDetection(detection);
|
|
3039
|
+
return Ie(`${import_picocolors4.default.bgYellow(import_picocolors4.default.black(" LopeBase "))} scan`), await printDetection(detection, facts), Se(`Run ${import_picocolors4.default.cyan("npx lopebase")} to connect what it found.`);
|
|
1846
3040
|
}
|
|
1847
3041
|
case "prompt": {
|
|
1848
3042
|
let facts = scanProject(cwd), detection = detectStack(facts);
|
|
@@ -1862,9 +3056,9 @@ async function main() {
|
|
|
1862
3056
|
baseUrl: rest[1],
|
|
1863
3057
|
secret,
|
|
1864
3058
|
token: secret ? void 0 : token,
|
|
1865
|
-
log: (
|
|
3059
|
+
log: (l2) => console.log(l2.startsWith("FAIL") ? import_picocolors4.default.red(l2) : import_picocolors4.default.green(l2))
|
|
1866
3060
|
});
|
|
1867
|
-
console.log(res.failed.length ?
|
|
3061
|
+
console.log(res.failed.length ? import_picocolors4.default.red(`${res.failed.length} failed, ${res.passed} passed`) : import_picocolors4.default.green(`All ${res.passed} checks passed. Ready to connect.`)), process.exit(res.failed.length ? 1 : 0);
|
|
1868
3062
|
}
|
|
1869
3063
|
return console.log(`Usage:
|
|
1870
3064
|
lopebase adapter secret print a new signing secret
|