cortad 0.1.0 → 0.1.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/local.mjs +29 -3
- package/package.json +8 -1
package/local.mjs
CHANGED
|
@@ -32,12 +32,19 @@ const say = (line) => console.log(`cortad ${line}`);
|
|
|
32
32
|
const fail = (line) => { console.error(`cortad ${line}`); process.exit(1); };
|
|
33
33
|
|
|
34
34
|
const explain = argv.includes("--explain");
|
|
35
|
+
// What is happening right now, on one line that rewrites itself. npx spends its own seconds fetching
|
|
36
|
+
// this package before anything here runs, and the first thing we printed used to be after the whole
|
|
37
|
+
// upload: a minute or more of a cursor sitting still, which reads as nothing happening.
|
|
38
|
+
const step = (line) => { if (process.stdout.isTTY) process.stdout.write(`\rcortad ${line}\x1b[K`); };
|
|
39
|
+
const clearStep = () => { if (process.stdout.isTTY) process.stdout.write("\r\x1b[K"); };
|
|
40
|
+
const stepDone = (line) => { clearStep(); say(line); };
|
|
35
41
|
if (!explain && !/^[A-Z0-9]{8}$/.test(code)) fail("usage: npx cortad <code from the connect screen> [--port N] [--start \"cmd\"] | npx cortad --explain");
|
|
36
42
|
// Where Brainsless is. The host is not on the command line: a code cannot point at an impostor.
|
|
37
43
|
const origin = new URL(process.env.CORTAD_ORIGIN || "https://cortad.com");
|
|
38
44
|
// Ours, and only ours. brainsless.com is the same service under its earlier name and stays trusted
|
|
39
45
|
// while people still hold links to it.
|
|
40
|
-
|
|
46
|
+
// The last is our own Pages project: its subdomains are our branch deploys, staging among them.
|
|
47
|
+
const OURS = ["cortad.com", "brainsless.com", "brainsless-frontend.pages.dev"];
|
|
41
48
|
const trusted = (origin.protocol === "https:" && OURS.some((host) => origin.hostname === host || origin.hostname.endsWith(`.${host}`)))
|
|
42
49
|
|| origin.hostname === "localhost" || origin.hostname === "127.0.0.1";
|
|
43
50
|
if (!trusted) fail(`refusing: ${origin.host} is not Cortad.`);
|
|
@@ -304,6 +311,7 @@ async function startApp() {
|
|
|
304
311
|
const lifted = liftedLimits(envFiles);
|
|
305
312
|
if (Object.keys(lifted).length) say(`your own request limits are raised for this session: ${Object.keys(lifted).join(", ")}`);
|
|
306
313
|
launched = { cmd, lifted };
|
|
314
|
+
step(`starting your app: ${cmd}`);
|
|
307
315
|
const up = await launch(180_000);
|
|
308
316
|
if (up.port) return { port: up.port, cmd, lifted: Object.keys(lifted) };
|
|
309
317
|
// Already running: a second start dies on the port the first one holds. The one that is running
|
|
@@ -417,6 +425,8 @@ async function stopApp(pid) {
|
|
|
417
425
|
}
|
|
418
426
|
|
|
419
427
|
// ---- go
|
|
428
|
+
say("starting");
|
|
429
|
+
step("reading this folder");
|
|
420
430
|
const envFiles = [];
|
|
421
431
|
const files = [];
|
|
422
432
|
walk(root, 0, files, envFiles, 0);
|
|
@@ -464,19 +474,34 @@ key = attach.data.key;
|
|
|
464
474
|
|
|
465
475
|
const list = join(work, "files.txt");
|
|
466
476
|
writeFileSync(list, files.join("\n") + "\n");
|
|
477
|
+
// Which code this is, from what the files say rather than from the archive: gzip stamps the time into
|
|
478
|
+
// every archive, so the same folder packed twice never matched and every reconnect bought a whole new
|
|
479
|
+
// read of code that had not changed, emptying the page's counts and its price while it ran.
|
|
480
|
+
const treeDigest = (() => {
|
|
481
|
+
const h = createHash("sha256");
|
|
482
|
+
for (const rel of [...files].sort()) {
|
|
483
|
+
let body = Buffer.alloc(0);
|
|
484
|
+
try { body = readFileSync(join(root, rel)); } catch { continue; }
|
|
485
|
+
h.update(rel).update("\0").update(createHash("sha256").update(body).digest("hex")).update("\n");
|
|
486
|
+
}
|
|
487
|
+
return h.digest("hex");
|
|
488
|
+
})();
|
|
467
489
|
const archive = join(work, "tree.tgz");
|
|
490
|
+
step(`packing ${files.length} files`);
|
|
468
491
|
await exec("tar", ["-czf", archive, "-C", root, "-T", list]);
|
|
469
492
|
const bytes = readFileSync(archive);
|
|
470
493
|
const PART = 1_500_000;
|
|
471
494
|
let resumed = false;
|
|
495
|
+
const parts = Math.max(1, Math.ceil(bytes.length / PART));
|
|
472
496
|
for (let off = 0; off < bytes.length; off += PART) {
|
|
473
497
|
const last = off + PART >= bytes.length;
|
|
474
|
-
|
|
498
|
+
step(parts > 1 ? `sending your code, part ${Math.floor(off / PART) + 1} of ${parts}` : "sending your code");
|
|
499
|
+
const put = await call("PUT", `/local/${box}/tree?last=${last ? 1 : 0}${last ? `&digest=${treeDigest}` : ""}`, bytes.subarray(off, off + PART), { raw: true, timeoutMs: 120_000 });
|
|
475
500
|
if (!put.ok) fail(put.data?.error ?? `upload failed (${put.status})`);
|
|
476
501
|
if (last) resumed = put.data?.resumed === true;
|
|
477
502
|
}
|
|
478
503
|
// Unchanged code has already been read: coming back says so instead of claiming a second read.
|
|
479
|
-
|
|
504
|
+
stepDone(resumed ? "connected · your code is unchanged, picking up where you left off" : `connected · reading your code (${files.length} files, ${Math.round(bytes.length / 1024)} KB)`);
|
|
480
505
|
|
|
481
506
|
lock = await makeLock({ root, work });
|
|
482
507
|
if (lock && !lockHolds(lock, root)) lock = null;
|
|
@@ -517,6 +542,7 @@ async function appLife() {
|
|
|
517
542
|
say("saw your change, starting your app again");
|
|
518
543
|
}
|
|
519
544
|
const told = await announce();
|
|
545
|
+
clearStep();
|
|
520
546
|
if (!told.ok) fail(told.data?.error ?? `could not register your app (${told.status})`);
|
|
521
547
|
say(`your app is answering on port ${app.port}${app.cmd ? ` · ${app.cmd}` : ""}`);
|
|
522
548
|
say("leave this open. Go back to the browser; Ctrl-C disconnects.");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cortad",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Runs Cortad test conversations against the app on your machine. Env values never leave it. Zero dependencies, no install scripts.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"cortad": "local.mjs"
|
|
@@ -25,5 +25,12 @@
|
|
|
25
25
|
"homepage": "https://cortad.com",
|
|
26
26
|
"publishConfig": {
|
|
27
27
|
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/brainsless/cortad.git"
|
|
32
|
+
},
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/brainsless/cortad/issues"
|
|
28
35
|
}
|
|
29
36
|
}
|