roughbook 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +166 -2
- package/dist/agents/claude.js +214 -0
- package/dist/agents/claude.js.map +1 -0
- package/dist/agents/codex.js +179 -0
- package/dist/agents/codex.js.map +1 -0
- package/dist/agents/command.js +47 -0
- package/dist/agents/command.js.map +1 -0
- package/dist/agents/index.js +21 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/process.js +76 -0
- package/dist/agents/process.js.map +1 -0
- package/dist/agents/session.js +26 -0
- package/dist/agents/session.js.map +1 -0
- package/dist/agents/types.js +2 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/api/client.js +138 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/errors.js +40 -0
- package/dist/api/errors.js.map +1 -0
- package/dist/api/outbox.js +105 -0
- package/dist/api/outbox.js.map +1 -0
- package/dist/api/types.js +4 -0
- package/dist/api/types.js.map +1 -0
- package/dist/cli.js +10 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/agents.js +39 -0
- package/dist/commands/agents.js.map +1 -0
- package/dist/commands/context.js +27 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/done.js +125 -0
- package/dist/commands/done.js.map +1 -0
- package/dist/commands/index.js +101 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/init.js +203 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/login.js +93 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.js +34 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/promote.js +105 -0
- package/dist/commands/promote.js.map +1 -0
- package/dist/commands/pull.js +45 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/revert.js +39 -0
- package/dist/commands/revert.js.map +1 -0
- package/dist/commands/review.js +73 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/commands/run.js +147 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/shared.js +81 -0
- package/dist/commands/shared.js.map +1 -0
- package/dist/commands/start.js +119 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/status.js +62 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/config/credentials.js +17 -0
- package/dist/config/credentials.js.map +1 -0
- package/dist/config/server.js +29 -0
- package/dist/config/server.js.map +1 -0
- package/dist/config/settings.js +21 -0
- package/dist/config/settings.js.map +1 -0
- package/dist/config/userDir.js +18 -0
- package/dist/config/userDir.js.map +1 -0
- package/dist/fs/json.js +44 -0
- package/dist/fs/json.js.map +1 -0
- package/dist/fs/lock.js +38 -0
- package/dist/fs/lock.js.map +1 -0
- package/dist/io.js +26 -0
- package/dist/io.js.map +1 -0
- package/dist/launch/open.js +48 -0
- package/dist/launch/open.js.map +1 -0
- package/dist/launch/platform.js +38 -0
- package/dist/launch/platform.js.map +1 -0
- package/dist/launch/pty.js +61 -0
- package/dist/launch/pty.js.map +1 -0
- package/dist/launch/terminal.js +194 -0
- package/dist/launch/terminal.js.map +1 -0
- package/dist/launch/which.js +59 -0
- package/dist/launch/which.js.map +1 -0
- package/dist/repo/diff.js +26 -0
- package/dist/repo/diff.js.map +1 -0
- package/dist/repo/framework.js +41 -0
- package/dist/repo/framework.js.map +1 -0
- package/dist/repo/link.js +28 -0
- package/dist/repo/link.js.map +1 -0
- package/dist/repo/prep.js +447 -0
- package/dist/repo/prep.js.map +1 -0
- package/dist/repo/preview.js +144 -0
- package/dist/repo/preview.js.map +1 -0
- package/dist/repo/root.js +48 -0
- package/dist/repo/root.js.map +1 -0
- package/dist/repo/scratch.js +111 -0
- package/dist/repo/scratch.js.map +1 -0
- package/dist/runs/checks.js +160 -0
- package/dist/runs/checks.js.map +1 -0
- package/dist/runs/lifecycle.js +142 -0
- package/dist/runs/lifecycle.js.map +1 -0
- package/dist/runs/promote.js +185 -0
- package/dist/runs/promote.js.map +1 -0
- package/dist/runs/pull.js +182 -0
- package/dist/runs/pull.js.map +1 -0
- package/dist/runs/reviews.js +34 -0
- package/dist/runs/reviews.js.map +1 -0
- package/dist/runs/slots.js +96 -0
- package/dist/runs/slots.js.map +1 -0
- package/dist/runs/snapshot.js +265 -0
- package/dist/runs/snapshot.js.map +1 -0
- package/dist/runs/store.js +38 -0
- package/dist/runs/store.js.map +1 -0
- package/dist/runs/types.js +3 -0
- package/dist/runs/types.js.map +1 -0
- package/dist/runs/watch.js +42 -0
- package/dist/runs/watch.js.map +1 -0
- package/dist/screenshot/browser.js +163 -0
- package/dist/screenshot/browser.js.map +1 -0
- package/dist/screenshot/iteration.js +37 -0
- package/dist/screenshot/iteration.js.map +1 -0
- package/dist/server/app.js +321 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/security.js +44 -0
- package/dist/server/security.js.map +1 -0
- package/dist/server/studio.js +618 -0
- package/dist/server/studio.js.map +1 -0
- package/dist/server/terminals.js +142 -0
- package/dist/server/terminals.js.map +1 -0
- package/dist/ui/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/dist/ui/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/dist/ui/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/dist/ui/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/dist/ui/assets/index-D-TSXfvz.css +1 -0
- package/dist/ui/assets/index-HGV8FKwI.js +27 -0
- package/dist/ui/assets/inter-latin-400-normal-C38fXH4l.woff2 +0 -0
- package/dist/ui/assets/inter-latin-400-normal-CyCys3Eg.woff +0 -0
- package/dist/ui/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
- package/dist/ui/assets/inter-latin-500-normal-Cerq10X2.woff2 +0 -0
- package/dist/ui/assets/space-grotesk-latin-400-normal-BnQMeOim.woff +0 -0
- package/dist/ui/assets/space-grotesk-latin-400-normal-CJ-V5oYT.woff2 +0 -0
- package/dist/ui/assets/space-grotesk-latin-500-normal-CNSSEhBt.woff +0 -0
- package/dist/ui/assets/space-grotesk-latin-500-normal-lFbtlQH6.woff2 +0 -0
- package/dist/ui/index.html +14 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/package.json +68 -4
package/dist/fs/lock.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { mkdir, open, rm, stat } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Runs `work` while holding `<file>.lock`, so the CLI and the local server never write the same
|
|
5
|
+
* state file at once. Creating the lock with `wx` is atomic on every OS.
|
|
6
|
+
*/
|
|
7
|
+
export async function withLock(file, work, { staleMs = 60_000, waitMs = 60_000 } = {}) {
|
|
8
|
+
const lock = `${file}.lock`;
|
|
9
|
+
await mkdir(path.dirname(lock), { recursive: true });
|
|
10
|
+
const started = Date.now();
|
|
11
|
+
for (;;) {
|
|
12
|
+
try {
|
|
13
|
+
const handle = await open(lock, 'wx');
|
|
14
|
+
await handle.writeFile(String(process.pid));
|
|
15
|
+
await handle.close();
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
if (error.code !== 'EEXIST')
|
|
20
|
+
throw error;
|
|
21
|
+
const held = await stat(lock).catch(() => null);
|
|
22
|
+
if (held && Date.now() - held.mtimeMs > staleMs) {
|
|
23
|
+
await rm(lock, { force: true });
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (Date.now() - started > waitMs)
|
|
27
|
+
throw new Error(`Another Roughbook process holds ${lock}.`);
|
|
28
|
+
await new Promise(resolve => setTimeout(resolve, 25));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
return await work();
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
await rm(lock, { force: true });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.js","sourceRoot":"","sources":["../../src/fs/lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,IAAI,MAAM,WAAW,CAAC;AAS7B;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,IAAY,EACZ,IAAsB,EACtB,EAAE,OAAO,GAAG,MAAM,EAAE,MAAM,GAAG,MAAM,KAAkB,EAAE;IAEvD,MAAM,IAAI,GAAG,GAAG,IAAI,OAAO,CAAC;IAC5B,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,SAAS,CAAC;QACR,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACtC,MAAM,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;YAC5C,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;YACrB,MAAM;QACR,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,KAAK,CAAC;YACpE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;YAChD,IAAI,IAAI,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,GAAG,OAAO,EAAE,CAAC;gBAChD,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBAChC,SAAS;YACX,CAAC;YACD,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,GAAG,MAAM;gBAC/B,MAAM,IAAI,KAAK,CAAC,mCAAmC,IAAI,GAAG,CAAC,CAAC;YAC9D,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;IACD,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,EAAE,CAAC;IACtB,CAAC;YAAS,CAAC;QACT,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAClC,CAAC;AACH,CAAC"}
|
package/dist/io.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline/promises';
|
|
2
|
+
export function terminalIo() {
|
|
3
|
+
return {
|
|
4
|
+
out: line => void process.stdout.write(`${line}\n`),
|
|
5
|
+
err: line => void process.stderr.write(`${line}\n`),
|
|
6
|
+
async ask(question) {
|
|
7
|
+
if (!process.stdin.isTTY)
|
|
8
|
+
return null;
|
|
9
|
+
const readline = createInterface({ input: process.stdin, output: process.stdout });
|
|
10
|
+
try {
|
|
11
|
+
return (await readline.question(question)).trim();
|
|
12
|
+
}
|
|
13
|
+
finally {
|
|
14
|
+
readline.close();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** Asks a yes or no question; `fallback` when the answer is empty or nobody can answer. */
|
|
20
|
+
export async function confirm(io, question, fallback) {
|
|
21
|
+
const answer = await io.ask(`${question} ${fallback ? '[Y/n]' : '[y/N]'} `);
|
|
22
|
+
if (!answer)
|
|
23
|
+
return fallback;
|
|
24
|
+
return /^y(es)?$/i.test(answer);
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=io.js.map
|
package/dist/io.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"io.js","sourceRoot":"","sources":["../src/io.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAUzD,MAAM,UAAU,UAAU;IACxB,OAAO;QACL,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC;QACnD,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC;QACnD,KAAK,CAAC,GAAG,CAAC,QAAQ;YAChB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK;gBAAE,OAAO,IAAI,CAAC;YACtC,MAAM,QAAQ,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;YACnF,IAAI,CAAC;gBACH,OAAO,CAAC,MAAM,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACpD,CAAC;oBAAS,CAAC;gBACT,QAAQ,CAAC,KAAK,EAAE,CAAC;YACnB,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,2FAA2F;AAC3F,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,EAAM,EAAE,QAAgB,EAAE,QAAiB;IACvE,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,QAAQ,IAAI,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC;IAC5E,IAAI,CAAC,MAAM;QAAE,OAAO,QAAQ,CAAC;IAC7B,OAAO,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
/**
|
|
3
|
+
* How each OS opens a URL in the default browser, as an argument array: no shell, so nothing in
|
|
4
|
+
* the URL is ever interpreted. Null where the studio knows no opener.
|
|
5
|
+
*/
|
|
6
|
+
export function openerFor(url, platform = process.platform) {
|
|
7
|
+
if (platform === 'win32')
|
|
8
|
+
return { command: 'rundll32', args: ['url.dll,FileProtocolHandler', url] };
|
|
9
|
+
if (platform === 'darwin')
|
|
10
|
+
return { command: 'open', args: [url] };
|
|
11
|
+
if (platform === 'linux' || platform === 'freebsd' || platform === 'openbsd') {
|
|
12
|
+
return { command: 'xdg-open', args: [url] };
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
/** Opens an http(s) URL in the default browser. Resolves false when it could not. */
|
|
17
|
+
export function openUrl(url) {
|
|
18
|
+
let parsed;
|
|
19
|
+
try {
|
|
20
|
+
parsed = new URL(url);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return Promise.resolve(false);
|
|
24
|
+
}
|
|
25
|
+
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:')
|
|
26
|
+
return Promise.resolve(false);
|
|
27
|
+
const opener = openerFor(parsed.href);
|
|
28
|
+
if (!opener)
|
|
29
|
+
return Promise.resolve(false);
|
|
30
|
+
return new Promise(resolve => {
|
|
31
|
+
try {
|
|
32
|
+
const child = spawn(opener.command, opener.args, {
|
|
33
|
+
detached: true,
|
|
34
|
+
stdio: 'ignore',
|
|
35
|
+
windowsHide: true
|
|
36
|
+
});
|
|
37
|
+
child.once('error', () => resolve(false));
|
|
38
|
+
child.once('spawn', () => {
|
|
39
|
+
child.unref();
|
|
40
|
+
resolve(true);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
resolve(false);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=open.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open.js","sourceRoot":"","sources":["../../src/launch/open.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C;;;GAGG;AACH,MAAM,UAAU,SAAS,CACvB,GAAW,EACX,WAA4B,OAAO,CAAC,QAAQ;IAE5C,IAAI,QAAQ,KAAK,OAAO;QACtB,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,6BAA6B,EAAE,GAAG,CAAC,EAAE,CAAC;IAC7E,IAAI,QAAQ,KAAK,QAAQ;QAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;IACnE,IAAI,QAAQ,KAAK,OAAO,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC7E,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;IAC9C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,OAAO,CAAC,GAAW;IACjC,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC/F,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,CAAC,MAAM;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC3C,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;QAC3B,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE;gBAC/C,QAAQ,EAAE,IAAI;gBACd,KAAK,EAAE,QAAQ;gBACf,WAAW,EAAE,IAAI;aAClB,CAAC,CAAC;YACH,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;YAC1C,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;gBACvB,KAAK,CAAC,KAAK,EAAE,CAAC;gBACd,OAAO,CAAC,IAAI,CAAC,CAAC;YAChB,CAAC,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
export function currentOs(platform = process.platform) {
|
|
3
|
+
if (platform === 'win32')
|
|
4
|
+
return 'windows';
|
|
5
|
+
if (platform === 'darwin')
|
|
6
|
+
return 'macos';
|
|
7
|
+
if (platform === 'linux')
|
|
8
|
+
return 'linux';
|
|
9
|
+
return 'other';
|
|
10
|
+
}
|
|
11
|
+
/** This machine's platform, for code outside launch/ and screenshot/ that takes one as input. */
|
|
12
|
+
export function hostPlatform() {
|
|
13
|
+
return process.platform;
|
|
14
|
+
}
|
|
15
|
+
export const diskProbe = {
|
|
16
|
+
exists(file) {
|
|
17
|
+
try {
|
|
18
|
+
// lstat: Windows app execution aliases (wt.exe) are links that existsSync cannot follow.
|
|
19
|
+
fs.lstatSync(file);
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
dirs(dir) {
|
|
27
|
+
try {
|
|
28
|
+
return fs
|
|
29
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
30
|
+
.filter(entry => entry.isDirectory())
|
|
31
|
+
.map(entry => entry.name);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=platform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../../src/launch/platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AAKzB,MAAM,UAAU,SAAS,CAAC,WAA4B,OAAO,CAAC,QAAQ;IACpE,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,SAAS,CAAC;IAC3C,IAAI,QAAQ,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAC1C,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,OAAO,CAAC;IACzC,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,YAAY;IAC1B,OAAO,OAAO,CAAC,QAAQ,CAAC;AAC1B,CAAC;AASD,MAAM,CAAC,MAAM,SAAS,GAAU;IAC9B,MAAM,CAAC,IAAI;QACT,IAAI,CAAC;YACH,yFAAyF;YACzF,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YACnB,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,IAAI,CAAC,GAAG;QACN,IAAI,CAAC;YACH,OAAO,EAAE;iBACN,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;iBACzC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;iBACpC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// The built-in terminal's pseudo-terminal: node-pty, an optional dependency with binaries for
|
|
2
|
+
// Windows, macOS and glibc Linux (S1 step 5). Where it is missing, does not load, or would crash
|
|
3
|
+
// Node (musl Linux), the studio opens the designer's own terminal instead.
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
/** The glibc version Node runs on, or null on musl and every other platform. */
|
|
8
|
+
function glibcVersion() {
|
|
9
|
+
const report = process.report?.getReport();
|
|
10
|
+
return report?.header?.glibcVersionRuntime ?? null;
|
|
11
|
+
}
|
|
12
|
+
/** Starts and ends one terminal in a separate Node, so a crash there cannot take this one down. */
|
|
13
|
+
function survivesProbe(modulePath) {
|
|
14
|
+
const script = `
|
|
15
|
+
const pty = require(${JSON.stringify(modulePath)});
|
|
16
|
+
const child = pty.spawn(process.execPath, ['-e', ''], { name: 'xterm-256color', cols: 80, rows: 24, cwd: process.cwd(), env: process.env });
|
|
17
|
+
child.onExit(({ exitCode }) => process.exit(exitCode === 0 ? 0 : 1));
|
|
18
|
+
setTimeout(() => process.exit(2), 8000);
|
|
19
|
+
`;
|
|
20
|
+
const result = spawnSync(process.execPath, ['-e', script], { stdio: 'ignore', timeout: 10_000 });
|
|
21
|
+
return result.status === 0;
|
|
22
|
+
}
|
|
23
|
+
let cached = null;
|
|
24
|
+
/**
|
|
25
|
+
* Whether the built-in terminal can run here, and the module when it can. Checked once per
|
|
26
|
+
* process. `ROUGHBOOK_TERMINAL=external` turns it off.
|
|
27
|
+
*/
|
|
28
|
+
export function loadPty(env) {
|
|
29
|
+
if (env.ROUGHBOOK_TERMINAL === 'external') {
|
|
30
|
+
return { ok: false, reason: 'ROUGHBOOK_TERMINAL=external chooses your own terminal.' };
|
|
31
|
+
}
|
|
32
|
+
if (cached)
|
|
33
|
+
return cached;
|
|
34
|
+
cached = probe();
|
|
35
|
+
return cached;
|
|
36
|
+
}
|
|
37
|
+
function probe() {
|
|
38
|
+
if (process.platform === 'linux' && !glibcVersion()) {
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
reason: 'The built-in terminal needs glibc; on musl Linux (Alpine) it would crash Node.'
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
let modulePath;
|
|
45
|
+
let pty;
|
|
46
|
+
try {
|
|
47
|
+
modulePath = require.resolve('node-pty');
|
|
48
|
+
pty = require(modulePath);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return {
|
|
52
|
+
ok: false,
|
|
53
|
+
reason: 'node-pty is not installed here, so runs open in your own terminal.'
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (process.platform === 'linux' && !survivesProbe(modulePath)) {
|
|
57
|
+
return { ok: false, reason: 'node-pty did not start a terminal here.' };
|
|
58
|
+
}
|
|
59
|
+
return { ok: true, pty };
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=pty.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pty.js","sourceRoot":"","sources":["../../src/launch/pty.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,iGAAiG;AACjG,2EAA2E;AAC3E,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA2B5C,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C,gFAAgF;AAChF,SAAS,YAAY;IACnB,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,SAAS,EAAmD,CAAC;IAC5F,OAAO,MAAM,EAAE,MAAM,EAAE,mBAAmB,IAAI,IAAI,CAAC;AACrD,CAAC;AAED,mGAAmG;AACnG,SAAS,aAAa,CAAC,UAAkB;IACvC,MAAM,MAAM,GAAG;sBACK,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC;;;;CAI/C,CAAC;IACA,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IACjG,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC;AAC7B,CAAC;AAED,IAAI,MAAM,GAAsB,IAAI,CAAC;AAErC;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,GAAsB;IAC5C,IAAI,GAAG,CAAC,kBAAkB,KAAK,UAAU,EAAE,CAAC;QAC1C,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,wDAAwD,EAAE,CAAC;IACzF,CAAC;IACD,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,MAAM,GAAG,KAAK,EAAE,CAAC;IACjB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,KAAK;IACZ,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;QACpD,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,gFAAgF;SACzF,CAAC;IACJ,CAAC;IACD,IAAI,UAAkB,CAAC;IACvB,IAAI,GAAc,CAAC;IACnB,IAAI,CAAC;QACH,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACzC,GAAG,GAAG,OAAO,CAAC,UAAU,CAAc,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,oEAAoE;SAC7E,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,yCAAyC,EAAE,CAAC;IAC1E,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// Opens a terminal in the repo running an agent with its one-line instruction: a terminal
|
|
2
|
+
// session the designer watches and steers. Windows uses Windows Terminal (one tab per session)
|
|
3
|
+
// or a console window; macOS opens Terminal, or iTerm when ROUGHBOOK_TERMINAL=iterm; Linux tries
|
|
4
|
+
// $TERMINAL, then x-terminal-emulator, GNOME Terminal, Konsole and xterm. Where none can be
|
|
5
|
+
// opened, the plan says so and gives the command to paste. Planning is pure; launching is the
|
|
6
|
+
// only side effect. Ported and extended from roughbook runner/terminal.ts (v2-local), which was
|
|
7
|
+
// Windows only.
|
|
8
|
+
import { spawn } from 'node:child_process';
|
|
9
|
+
import { chmod, mkdtemp, writeFile } from 'node:fs/promises';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { diskProbe } from './platform.js';
|
|
13
|
+
import { findOnPath } from './which.js';
|
|
14
|
+
/** What may appear in a title or argument. Prompt paths are slugs and ids, so nothing real is refused. */
|
|
15
|
+
const SAFE_TEXT = /^[A-Za-z0-9 ./_:,()=@+-]+$/;
|
|
16
|
+
/** What may appear in a path handed to a terminal: no quotes, no shell or cmd metacharacters. */
|
|
17
|
+
const SAFE_PATH = /^[^"'`$%^&|<>;\r\n]+$/;
|
|
18
|
+
export const MAX_SESSIONS = 6;
|
|
19
|
+
/** A title from free text: only characters a launch can pass through safely. */
|
|
20
|
+
export function sessionTitle(text) {
|
|
21
|
+
return (text
|
|
22
|
+
.replace(/[^A-Za-z0-9 ./_:,()-]+/g, ' ')
|
|
23
|
+
.replace(/\s+/g, ' ')
|
|
24
|
+
.trim()
|
|
25
|
+
.slice(0, 60) || 'Roughbook');
|
|
26
|
+
}
|
|
27
|
+
/** The same session as a command to paste, for when no terminal can be opened. */
|
|
28
|
+
export function manualCommand(platform, repoRoot, session) {
|
|
29
|
+
if (platform === 'win32') {
|
|
30
|
+
return `cd /d "${repoRoot}" && ${[session.command, ...session.args].map(part => `"${part}"`).join(' ')}`;
|
|
31
|
+
}
|
|
32
|
+
return `cd '${repoRoot}' && ${[session.command, ...session.args].map(part => `'${part}'`).join(' ')}`;
|
|
33
|
+
}
|
|
34
|
+
function problem(repoRoot, sessions) {
|
|
35
|
+
if (!sessions.length)
|
|
36
|
+
return 'Nothing to run.';
|
|
37
|
+
if (sessions.length > MAX_SESSIONS)
|
|
38
|
+
return `At most ${MAX_SESSIONS} sessions at once.`;
|
|
39
|
+
if (!SAFE_PATH.test(repoRoot)) {
|
|
40
|
+
return "The repo's path has characters that cannot be passed to a terminal safely.";
|
|
41
|
+
}
|
|
42
|
+
for (const session of sessions) {
|
|
43
|
+
if (!SAFE_PATH.test(session.command)) {
|
|
44
|
+
return "The agent's path has characters that cannot be passed to a terminal safely.";
|
|
45
|
+
}
|
|
46
|
+
if (!SAFE_TEXT.test(session.title) || !session.args.every(arg => SAFE_PATH.test(arg))) {
|
|
47
|
+
return 'A session has characters that cannot be passed to a terminal safely.';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
/** Windows Terminal, when installed. Its launcher is an app alias, found by lstat. */
|
|
53
|
+
export function findWindowsTerminal(env, probe = diskProbe) {
|
|
54
|
+
const candidates = [
|
|
55
|
+
env.LOCALAPPDATA ? path.win32.join(env.LOCALAPPDATA, 'Microsoft', 'WindowsApps', 'wt.exe') : '',
|
|
56
|
+
...(env.PATH ?? env.Path ?? '')
|
|
57
|
+
.split(';')
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
.map(dir => path.win32.join(dir, 'wt.exe'))
|
|
60
|
+
].filter(Boolean);
|
|
61
|
+
return candidates.find(candidate => probe.exists(candidate)) ?? null;
|
|
62
|
+
}
|
|
63
|
+
/** How each Linux terminal is told the folder and the program; most take xterm's -e. */
|
|
64
|
+
function linuxArgs(terminal, repoRoot, session) {
|
|
65
|
+
const program = [session.command, ...session.args];
|
|
66
|
+
switch (path.posix.basename(terminal)) {
|
|
67
|
+
case 'gnome-terminal':
|
|
68
|
+
return ['--title', session.title, '--working-directory', repoRoot, '--', ...program];
|
|
69
|
+
case 'konsole':
|
|
70
|
+
return ['--workdir', repoRoot, '-e', ...program];
|
|
71
|
+
case 'kitty':
|
|
72
|
+
return ['--title', session.title, '--directory', repoRoot, ...program];
|
|
73
|
+
case 'alacritty':
|
|
74
|
+
return ['--title', session.title, '--working-directory', repoRoot, '-e', ...program];
|
|
75
|
+
case 'wezterm':
|
|
76
|
+
return ['start', '--cwd', repoRoot, '--', ...program];
|
|
77
|
+
default:
|
|
78
|
+
return ['-T', session.title, '-e', ...program];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const LINUX_TERMINALS = ['x-terminal-emulator', 'gnome-terminal', 'konsole', 'xterm'];
|
|
82
|
+
/** A POSIX shell script that runs one session; every part was checked by SAFE_PATH. */
|
|
83
|
+
export function sessionScript(repoRoot, session) {
|
|
84
|
+
const quoted = [session.command, ...session.args].map(part => `'${part}'`).join(' ');
|
|
85
|
+
return `#!/bin/sh\n# ${session.title}\ncd '${repoRoot}' || exit 1\nexec ${quoted}\n`;
|
|
86
|
+
}
|
|
87
|
+
export function planTerminal({ platform, env, repoRoot, sessions, probe = diskProbe }) {
|
|
88
|
+
const refused = problem(repoRoot, sessions);
|
|
89
|
+
if (refused)
|
|
90
|
+
return { kind: 'manual', reason: refused };
|
|
91
|
+
if (platform === 'win32') {
|
|
92
|
+
const wt = findWindowsTerminal(env, probe);
|
|
93
|
+
if (wt) {
|
|
94
|
+
// One new window, one tab per session. ";" separates Windows Terminal's own subcommands.
|
|
95
|
+
const args = ['-w', 'new'];
|
|
96
|
+
sessions.forEach((session, index) => {
|
|
97
|
+
if (index > 0)
|
|
98
|
+
args.push(';');
|
|
99
|
+
args.push('new-tab', '--title', session.title, '-d', repoRoot, session.command, ...session.args);
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
kind: 'spawn',
|
|
103
|
+
terminal: 'Windows Terminal',
|
|
104
|
+
launches: [{ command: wt, args, cwd: repoRoot }]
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
// One console window per session. `start` takes the title as its first quoted argument.
|
|
108
|
+
return {
|
|
109
|
+
kind: 'spawn',
|
|
110
|
+
terminal: 'a console window',
|
|
111
|
+
launches: sessions.map(session => ({
|
|
112
|
+
command: 'cmd.exe',
|
|
113
|
+
args: [
|
|
114
|
+
`/d /s /c "start "${session.title}" /D "${repoRoot}" ${[session.command, ...session.args]
|
|
115
|
+
.map(part => `"${part}"`)
|
|
116
|
+
.join(' ')}"`
|
|
117
|
+
],
|
|
118
|
+
cwd: repoRoot,
|
|
119
|
+
verbatim: true
|
|
120
|
+
}))
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (platform === 'darwin') {
|
|
124
|
+
const iterm = env.ROUGHBOOK_TERMINAL?.toLowerCase() === 'iterm';
|
|
125
|
+
return {
|
|
126
|
+
kind: 'scripts',
|
|
127
|
+
terminal: iterm ? 'iTerm' : 'Terminal',
|
|
128
|
+
app: iterm ? 'iTerm' : 'Terminal',
|
|
129
|
+
scripts: sessions.map(session => sessionScript(repoRoot, session))
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (platform === 'linux' || platform === 'freebsd' || platform === 'openbsd') {
|
|
133
|
+
const lookup = { env, platform, home: '', node: '', probe };
|
|
134
|
+
const chosen = env.TERMINAL?.trim();
|
|
135
|
+
const terminal = (chosen &&
|
|
136
|
+
(path.posix.isAbsolute(chosen)
|
|
137
|
+
? probe.exists(chosen)
|
|
138
|
+
? chosen
|
|
139
|
+
: null
|
|
140
|
+
: findOnPath(chosen, lookup))) ||
|
|
141
|
+
LINUX_TERMINALS.map(name => findOnPath(name, lookup)).find(Boolean) ||
|
|
142
|
+
null;
|
|
143
|
+
if (!terminal) {
|
|
144
|
+
return {
|
|
145
|
+
kind: 'manual',
|
|
146
|
+
reason: 'No terminal was found. Set TERMINAL, or run the command below yourself.'
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
kind: 'spawn',
|
|
151
|
+
terminal: path.posix.basename(terminal),
|
|
152
|
+
launches: sessions.map(session => ({
|
|
153
|
+
command: terminal,
|
|
154
|
+
args: linuxArgs(terminal, repoRoot, session),
|
|
155
|
+
cwd: repoRoot
|
|
156
|
+
}))
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
return { kind: 'manual', reason: 'Opening a terminal is not supported on this system.' };
|
|
160
|
+
}
|
|
161
|
+
/** Resolves once the process has started; rejects if it could not. */
|
|
162
|
+
function start(launch) {
|
|
163
|
+
return new Promise((resolve, reject) => {
|
|
164
|
+
const child = spawn(launch.command, launch.args, {
|
|
165
|
+
cwd: launch.cwd,
|
|
166
|
+
detached: true,
|
|
167
|
+
stdio: 'ignore',
|
|
168
|
+
windowsHide: false,
|
|
169
|
+
windowsVerbatimArguments: launch.verbatim ?? false
|
|
170
|
+
});
|
|
171
|
+
child.once('error', reject);
|
|
172
|
+
child.once('spawn', () => {
|
|
173
|
+
child.unref();
|
|
174
|
+
resolve();
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
/** Starts the plan; the terminals belong to the designer from here. */
|
|
179
|
+
export async function launchTerminal(plan) {
|
|
180
|
+
if (plan.kind === 'spawn') {
|
|
181
|
+
for (const launch of plan.launches)
|
|
182
|
+
await start(launch);
|
|
183
|
+
}
|
|
184
|
+
else if (plan.kind === 'scripts') {
|
|
185
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), 'roughbook-launch-'));
|
|
186
|
+
for (const [index, script] of plan.scripts.entries()) {
|
|
187
|
+
const file = path.join(dir, `session-${index + 1}.command`);
|
|
188
|
+
await writeFile(file, script);
|
|
189
|
+
await chmod(file, 0o755);
|
|
190
|
+
await start({ command: 'open', args: ['-a', plan.app, file], cwd: dir });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=terminal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal.js","sourceRoot":"","sources":["../../src/launch/terminal.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { diskProbe } from './platform.js';
|
|
3
|
+
function pathApi(platform) {
|
|
4
|
+
return platform === 'win32' ? path.win32 : path.posix;
|
|
5
|
+
}
|
|
6
|
+
/** The folders on PATH, in order. */
|
|
7
|
+
export function pathDirs(env, platform) {
|
|
8
|
+
return (env.PATH ?? env.Path ?? '').split(platform === 'win32' ? ';' : ':').filter(Boolean);
|
|
9
|
+
}
|
|
10
|
+
/** A program on PATH by name, as an executable file (with .exe or .com on Windows). */
|
|
11
|
+
export function findOnPath(name, lookup) {
|
|
12
|
+
const p = pathApi(lookup.platform);
|
|
13
|
+
const probe = lookup.probe ?? diskProbe;
|
|
14
|
+
const names = lookup.platform === 'win32' ? [`${name}.exe`, `${name}.com`] : [name];
|
|
15
|
+
for (const dir of pathDirs(lookup.env, lookup.platform)) {
|
|
16
|
+
for (const candidate of names) {
|
|
17
|
+
const file = p.join(dir, candidate);
|
|
18
|
+
if (probe.exists(file))
|
|
19
|
+
return file;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* An npm-installed command: on Windows, the `.cmd` shim's package script run with Node; elsewhere
|
|
26
|
+
* the shim on PATH is itself executable, so findOnPath already found it.
|
|
27
|
+
*/
|
|
28
|
+
export function findNpmScript(name, npmPackage, script, lookup) {
|
|
29
|
+
if (lookup.platform !== 'win32')
|
|
30
|
+
return null;
|
|
31
|
+
const p = path.win32;
|
|
32
|
+
const probe = lookup.probe ?? diskProbe;
|
|
33
|
+
for (const dir of pathDirs(lookup.env, lookup.platform)) {
|
|
34
|
+
const target = p.join(dir, 'node_modules', ...npmPackage.split('/'), ...script.split('/'));
|
|
35
|
+
if (probe.exists(p.join(dir, `${name}.cmd`)) && probe.exists(target)) {
|
|
36
|
+
return { command: lookup.node, prefixArgs: [target], source: 'npm' };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
/** `ROUGHBOOK_<NAME>_BIN`, when it names a file that exists. */
|
|
42
|
+
export function fromEnv(variable, lookup) {
|
|
43
|
+
const explicit = lookup.env[variable]?.trim();
|
|
44
|
+
if (!explicit || !(lookup.probe ?? diskProbe).exists(explicit))
|
|
45
|
+
return null;
|
|
46
|
+
return { command: explicit, prefixArgs: [], source: variable };
|
|
47
|
+
}
|
|
48
|
+
/** Compares dotted version strings numerically: 2.1.286 > 2.1.9. */
|
|
49
|
+
export function compareVersions(a, b) {
|
|
50
|
+
const left = a.split('.').map(part => Number.parseInt(part, 10) || 0);
|
|
51
|
+
const right = b.split('.').map(part => Number.parseInt(part, 10) || 0);
|
|
52
|
+
for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
|
|
53
|
+
const difference = (left[index] ?? 0) - (right[index] ?? 0);
|
|
54
|
+
if (difference !== 0)
|
|
55
|
+
return difference;
|
|
56
|
+
}
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=which.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"which.js","sourceRoot":"","sources":["../../src/launch/which.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lines an edit takes out and puts in, in order, for showing a planned change. Not a minimal
|
|
3
|
+
* diff: lines are matched from both ends, and what lies between is shown as replaced.
|
|
4
|
+
*/
|
|
5
|
+
export function changedLines(before, after) {
|
|
6
|
+
const a = lines(before);
|
|
7
|
+
const b = lines(after);
|
|
8
|
+
let start = 0;
|
|
9
|
+
while (start < a.length && start < b.length && a[start] === b[start])
|
|
10
|
+
start++;
|
|
11
|
+
let endA = a.length;
|
|
12
|
+
let endB = b.length;
|
|
13
|
+
while (endA > start && endB > start && a[endA - 1] === b[endB - 1]) {
|
|
14
|
+
endA--;
|
|
15
|
+
endB--;
|
|
16
|
+
}
|
|
17
|
+
return { removed: a.slice(start, endA), added: b.slice(start, endB) };
|
|
18
|
+
}
|
|
19
|
+
/** A text's lines, without the empty one after a final newline. */
|
|
20
|
+
function lines(text) {
|
|
21
|
+
const all = text.split(/\r?\n/);
|
|
22
|
+
if (all[all.length - 1] === '')
|
|
23
|
+
all.pop();
|
|
24
|
+
return all;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/repo/diff.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC1B,MAAc,EACd,KAAa;IAEb,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IACxB,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;IACvB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,OAAO,KAAK,GAAG,CAAC,CAAC,MAAM,IAAI,KAAK,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC;QAAE,KAAK,EAAE,CAAC;IAC9E,IAAI,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC;IACpB,IAAI,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC;IACpB,OAAO,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,EAAE,CAAC;QACnE,IAAI,EAAE,CAAC;QACP,IAAI,EAAE,CAAC;IACT,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC;AACxE,CAAC;AAED,mEAAmE;AACnE,SAAS,KAAK,CAAC,IAAY;IACzB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAChC,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE;QAAE,GAAG,CAAC,GAAG,EAAE,CAAC;IAC1C,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { searchUp } from './root.js';
|
|
4
|
+
/** Checked in this order, since a Next.js or Astro app may also list Vite. */
|
|
5
|
+
const FRAMEWORKS = [
|
|
6
|
+
['next', 'next'],
|
|
7
|
+
['astro', 'astro'],
|
|
8
|
+
['nuxt', 'nuxt'],
|
|
9
|
+
['@sveltejs/kit', 'sveltekit'],
|
|
10
|
+
['@remix-run/react', 'remix'],
|
|
11
|
+
['react-router', 'react-router'],
|
|
12
|
+
['vite', 'vite']
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* The app's framework and major version from package.json, such as `next-16`: what the link
|
|
16
|
+
* records, so ratings can be compared by setup. Null when none is recognized.
|
|
17
|
+
*/
|
|
18
|
+
export function detectFramework(repo) {
|
|
19
|
+
let pkg;
|
|
20
|
+
try {
|
|
21
|
+
pkg = JSON.parse(fs.readFileSync(path.join(repo, 'package.json'), 'utf8'));
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const deps = { ...pkg.devDependencies, ...pkg.dependencies };
|
|
27
|
+
const found = FRAMEWORKS.find(([dependency]) => typeof deps[dependency] === 'string');
|
|
28
|
+
if (!found)
|
|
29
|
+
return null;
|
|
30
|
+
const major = /(\d+)/.exec(deps[found[0]])?.[1];
|
|
31
|
+
return major ? `${found[1]}-${major}` : found[1];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The app `init` run from `start` means: the nearest folder upwards with a package.json, within
|
|
35
|
+
* the same git repo. `start` itself when there is none, for checkRepo to refuse.
|
|
36
|
+
*/
|
|
37
|
+
export function appRoot(start, home) {
|
|
38
|
+
return (searchUp(start, dir => fs.existsSync(path.join(dir, 'package.json')), home) ??
|
|
39
|
+
path.resolve(start));
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=framework.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"framework.js","sourceRoot":"","sources":["../../src/repo/framework.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,8EAA8E;AAC9E,MAAM,UAAU,GAAyC;IACvD,CAAC,MAAM,EAAE,MAAM,CAAC;IAChB,CAAC,OAAO,EAAE,OAAO,CAAC;IAClB,CAAC,MAAM,EAAE,MAAM,CAAC;IAChB,CAAC,eAAe,EAAE,WAAW,CAAC;IAC9B,CAAC,kBAAkB,EAAE,OAAO,CAAC;IAC7B,CAAC,cAAc,EAAE,cAAc,CAAC;IAChC,CAAC,MAAM,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,IAAI,GAAwF,CAAC;IAC7F,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IAC7E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,IAAI,GAAG,EAAE,GAAG,GAAG,CAAC,eAAe,EAAE,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC;IAC7D,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC,OAAO,IAAI,CAAC,UAAU,CAAC,KAAK,QAAQ,CAAC,CAAC;IACtF,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACjD,OAAO,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,KAAa,EAAE,IAAa;IAClD,OAAO,CACL,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,EAAE,IAAI,CAAC;QAC3E,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CACpB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readJson, writeJsonAtomic } from '../fs/json.js';
|
|
2
|
+
import { LINK_FILE, roughPath, statePath } from './root.js';
|
|
3
|
+
export async function readRepoLink(repoRoot) {
|
|
4
|
+
const link = await readJson(roughPath(repoRoot, LINK_FILE));
|
|
5
|
+
if (!link)
|
|
6
|
+
return null;
|
|
7
|
+
if (link.version !== 1 || !link.orgId || !link.project) {
|
|
8
|
+
throw new Error(`rough/${LINK_FILE} is not one this studio can read. Run roughbook init again.`);
|
|
9
|
+
}
|
|
10
|
+
return link;
|
|
11
|
+
}
|
|
12
|
+
/** Ensures the linked repo's link file reads, for commands that need its project. */
|
|
13
|
+
export async function requireRepoLink(repoRoot) {
|
|
14
|
+
const link = await readRepoLink(repoRoot);
|
|
15
|
+
if (!link)
|
|
16
|
+
throw new Error('This repo is not linked. Run roughbook init.');
|
|
17
|
+
return link;
|
|
18
|
+
}
|
|
19
|
+
export function renderRepoLink(link) {
|
|
20
|
+
return `${JSON.stringify(link, null, 2)}\n`;
|
|
21
|
+
}
|
|
22
|
+
export async function readLocalLink(repoRoot) {
|
|
23
|
+
return readJson(statePath(repoRoot, 'link.json'));
|
|
24
|
+
}
|
|
25
|
+
export async function writeLocalLink(repoRoot, link) {
|
|
26
|
+
await writeJsonAtomic(statePath(repoRoot, 'link.json'), link);
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=link.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link.js","sourceRoot":"","sources":["../../src/repo/link.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAmB5D,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,QAAgB;IACjD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAW,SAAS,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC;IACtE,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,IAAI,IAAI,CAAC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QACvD,MAAM,IAAI,KAAK,CACb,SAAS,SAAS,6DAA6D,CAChF,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,qFAAqF;AACrF,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,QAAgB;IACpD,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAC;IAC1C,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAC3E,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAc;IAC3C,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAC9C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAAgB;IAClD,OAAO,QAAQ,CAAY,SAAS,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,QAAgB,EAAE,IAAe;IACpE,MAAM,eAAe,CAAC,SAAS,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,IAAI,CAAC,CAAC;AAChE,CAAC"}
|