cortad 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/local.mjs +58 -1
- package/package.json +1 -1
package/local.mjs
CHANGED
|
@@ -346,6 +346,8 @@ async function startApp() {
|
|
|
346
346
|
const wanted = Number(flag("--port"));
|
|
347
347
|
if (wanted) {
|
|
348
348
|
if (!(await answers(wanted))) fail(`nothing is answering on port ${wanted}. Start your app first, then run this again.`);
|
|
349
|
+
const took = await takeOver(wanted);
|
|
350
|
+
if (took) return took;
|
|
349
351
|
say("your app was already running, so its request limits stay as they are; if it answers 429, stop it and run this without --port and they are raised for the session");
|
|
350
352
|
return { port: wanted, cmd: null };
|
|
351
353
|
}
|
|
@@ -366,7 +368,13 @@ async function startApp() {
|
|
|
366
368
|
if (up.exited !== null && /EADDRINUSE|address already in use|port.{0,40}(?:in use|already used|is taken|unavailable)/i.test(up.tail)) {
|
|
367
369
|
const ports = [...up.tail.matchAll(/(?::|port\s*[:=]?\s*)(\d{4,5})\b/gi)].map((m) => Number(m[1]));
|
|
368
370
|
for (const port of new Set(ports)) {
|
|
369
|
-
if (await answers(port)) {
|
|
371
|
+
if (await answers(port)) {
|
|
372
|
+
launched = null; child = null;
|
|
373
|
+
const took = await takeOver(port);
|
|
374
|
+
if (took) return took;
|
|
375
|
+
say(`your app is already running on port ${port}, so that one is used; its request limits stay as they are`);
|
|
376
|
+
return { port, cmd: null };
|
|
377
|
+
}
|
|
370
378
|
}
|
|
371
379
|
}
|
|
372
380
|
if (up.tail) console.error(up.tail);
|
|
@@ -374,6 +382,55 @@ async function startApp() {
|
|
|
374
382
|
return { port: null, said: up.tail, why: `${up.exited !== null ? "your app stopped before it answered" : "your app did not answer within three minutes"}; what it said is above.${wrongNode}` };
|
|
375
383
|
}
|
|
376
384
|
|
|
385
|
+
// An app the person started keeps the limits it started with, and a run needs more than a person
|
|
386
|
+
// uses in a month. With their yes, the command stops that app and starts it the way it would have,
|
|
387
|
+
// limits raised for the session. Only in a terminal, only when the code reads a limit, and only
|
|
388
|
+
// when the command knows how to start the app.
|
|
389
|
+
async function takeOver(port) {
|
|
390
|
+
const lifted = liftedLimits(envFiles, files.map((f) => join(root, f)));
|
|
391
|
+
const names = Object.keys(lifted);
|
|
392
|
+
if (!names.length || !process.stdin.isTTY) return null;
|
|
393
|
+
const plan = startPlan({ root, typed: flag("--start"), onPath });
|
|
394
|
+
if (!plan?.cmd) return null;
|
|
395
|
+
const pid = await listenerOn(port);
|
|
396
|
+
if (!pid) return null;
|
|
397
|
+
const answer = await ask(`your app is running with its own request limits (${names.join(", ")}). Restart it with them raised for this session? [Y/n] `);
|
|
398
|
+
if (answer && !/^y(?:es)?$/i.test(answer)) return null;
|
|
399
|
+
const top = await supervisorOf(pid);
|
|
400
|
+
step(`stopping your app (pid ${top}) to start it with higher limits`);
|
|
401
|
+
await stopApp(top);
|
|
402
|
+
for (let i = 0; i < 50 && (await answers(port)); i++) await new Promise((r) => setTimeout(r, 200));
|
|
403
|
+
if (await answers(port)) { say("your app did not stop, so it is used as it is"); return null; }
|
|
404
|
+
appDir = plan.cwd ?? root;
|
|
405
|
+
pinned = pinnedNode();
|
|
406
|
+
if (plan.within) say(`your app is in ${plan.within}, started there with: ${plan.cmd}`);
|
|
407
|
+
say(`higher request limits for this session: ${names.join(", ")}`);
|
|
408
|
+
launched = { cmd: plan.cmd, lifted };
|
|
409
|
+
step(`starting your app: ${plan.cmd}`);
|
|
410
|
+
const up = await launch(180_000);
|
|
411
|
+
if (up.port) return { port: up.port, cmd: plan.cmd, lifted: names };
|
|
412
|
+
if (up.tail) console.error(up.tail);
|
|
413
|
+
fail("your app did not come back after the restart. Start it yourself, then run this again with --port.");
|
|
414
|
+
}
|
|
415
|
+
// The process listening on a port.
|
|
416
|
+
async function listenerOn(port) {
|
|
417
|
+
try { return Number((await exec("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"])).stdout.trim().split("\n")[0]) || null; } catch { return null; }
|
|
418
|
+
}
|
|
419
|
+
// The top of the chain that runs the app: nodemon, npm, the sh -c under it. Climbs from the listener
|
|
420
|
+
// while the parent is a runner, never into the person's own shell or terminal.
|
|
421
|
+
const RUNNER = /^(?:\S*\/)?(?:node|npm|npx|pnpm|yarn|bun|deno|python[\d.]*|uvicorn|gunicorn|flask|tsx|ts-node|nodemon|concurrently|pm2)(?:\s|$)|^(?:\/bin\/)?sh -c\b/;
|
|
422
|
+
async function supervisorOf(pid) {
|
|
423
|
+
const rows = (await exec("ps", ["-axo", "pid=,ppid=,args="])).stdout.trim().split("\n").map((l) => l.trim());
|
|
424
|
+
const table = new Map(rows.map((l) => { const m = /^(\d+)\s+(\d+)\s+(.*)$/.exec(l); return m ? [Number(m[1]), { ppid: Number(m[2]), args: m[3] }] : [0, null]; }));
|
|
425
|
+
let top = pid;
|
|
426
|
+
for (let i = 0; i < 8; i++) {
|
|
427
|
+
const parent = table.get(table.get(top)?.ppid ?? 0);
|
|
428
|
+
if (!parent || !RUNNER.test(parent.args)) break;
|
|
429
|
+
top = table.get(top).ppid;
|
|
430
|
+
}
|
|
431
|
+
return top;
|
|
432
|
+
}
|
|
433
|
+
|
|
377
434
|
// Your app, started the way you start it, and watched until one of its own ports answers.
|
|
378
435
|
let launched = null;
|
|
379
436
|
// The Node this project pins, when the shell that ran this command has another. Strapi refuses
|