rechrome 1.25.0 → 1.26.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/package.json +1 -1
- package/rechrome.js +54 -16
- package/rechrome.ts +54 -16
- package/serve.js +6 -1
- package/serve.ts +6 -1
package/package.json
CHANGED
package/rechrome.js
CHANGED
|
@@ -5,7 +5,7 @@ import { file } from "bun";
|
|
|
5
5
|
import yargs from "yargs";
|
|
6
6
|
import { readExtensionTokenFromProfile } from "./extension-token.js";
|
|
7
7
|
import { randomBytes } from "crypto";
|
|
8
|
-
import { mkdirSync, appendFileSync, existsSync, realpathSync, accessSync, cpSync, unlinkSync, readFileSync, readdirSync, constants as fsConstants } from "fs";
|
|
8
|
+
import { mkdirSync, appendFileSync, existsSync, realpathSync, accessSync, cpSync, unlinkSync, readFileSync, readdirSync, renameSync, rmdirSync, constants as fsConstants } from "fs";
|
|
9
9
|
import { hostname, homedir, networkInterfaces } from "os";
|
|
10
10
|
import { isIPv4 } from "net";
|
|
11
11
|
import { join, basename, dirname } from "path";
|
|
@@ -16,14 +16,16 @@ import { oxmgrInstallCommand, pickDaemonManager, type DaemonManager } from "./da
|
|
|
16
16
|
|
|
17
17
|
export const ENV_KEY = "RECHROME_URL";
|
|
18
18
|
export const DEFAULT_PORT = 13775;
|
|
19
|
-
export const RECH_DIR = join(import.meta.dir, ".rech");
|
|
20
|
-
export const LOG_DIR = join(RECH_DIR, "logs");
|
|
21
|
-
|
|
22
19
|
// Home dir: HOME on POSIX, USERPROFILE on Windows (handled by os.homedir()).
|
|
23
20
|
export const HOME = homedir();
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
// All rechrome state (registry, listeners, extension, logs, daemon output) lives in
|
|
23
|
+
// ~/.rechrome, never next to the code: a bunx/npx install dir is a disposable cache.
|
|
24
|
+
export const RECH_DIR = join(HOME, ".rechrome");
|
|
25
|
+
export const LOG_DIR = join(RECH_DIR, "logs");
|
|
26
|
+
// Before the rename, logs and daemon output lived in <install dir>/.rech.
|
|
27
|
+
export const LEGACY_RECH_DIR = join(import.meta.dir, ".rech");
|
|
28
|
+
const TOKENS_FILE = join(RECH_DIR, "profiles.yaml");
|
|
27
29
|
|
|
28
30
|
type TokenEntry = { extensionId: string; token: string; profileDir: string; userDataDir?: string; loadExtension?: string };
|
|
29
31
|
|
|
@@ -38,7 +40,7 @@ function validateTokenRegistry(value: unknown): Record<string, TokenEntry> {
|
|
|
38
40
|
return value as Record<string, TokenEntry>;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
export async function writeTokenRegistry(registry: Record<string, TokenEntry>, directory =
|
|
43
|
+
export async function writeTokenRegistry(registry: Record<string, TokenEntry>, directory = RECH_DIR): Promise<void> {
|
|
42
44
|
validateTokenRegistry(registry);
|
|
43
45
|
await mkdir(directory, { recursive: true });
|
|
44
46
|
const path = join(directory, "profiles.yaml");
|
|
@@ -51,7 +53,7 @@ export async function writeTokenRegistry(registry: Record<string, TokenEntry>, d
|
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
export async function readTokenRegistry(directory =
|
|
56
|
+
export async function readTokenRegistry(directory = RECH_DIR): Promise<Record<string, TokenEntry>> {
|
|
55
57
|
const path = join(directory, "profiles.yaml");
|
|
56
58
|
let raw: string;
|
|
57
59
|
try { raw = await readFile(path, "utf8"); }
|
|
@@ -74,7 +76,7 @@ export async function readTokenRegistry(directory = RECH_HOME_DIR): Promise<Reco
|
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
async function saveTokenEntry(profileEmail: string, entry: TokenEntry): Promise<void> {
|
|
77
|
-
mkdirSync(
|
|
79
|
+
mkdirSync(RECH_DIR, { recursive: true });
|
|
78
80
|
const registry = await readTokenRegistry();
|
|
79
81
|
registry[profileEmail] = entry;
|
|
80
82
|
await writeTokenRegistry(registry);
|
|
@@ -241,6 +243,31 @@ export function log(msg: string) {
|
|
|
241
243
|
appendFileSync(logFile, line);
|
|
242
244
|
}
|
|
243
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Move logs and daemon output from the legacy <install dir>/.rech into RECH_DIR, entry by
|
|
248
|
+
* entry. An entry already present in RECH_DIR is kept and the legacy copy left in place,
|
|
249
|
+
* so nothing is overwritten. Best effort: a cross-device rename just leaves the old copy.
|
|
250
|
+
*/
|
|
251
|
+
export function migrateLegacyDataDir(legacy = LEGACY_RECH_DIR, target = RECH_DIR): string[] {
|
|
252
|
+
const moved: string[] = [];
|
|
253
|
+
if (!existsSync(legacy)) return moved;
|
|
254
|
+
mkdirSync(target, { recursive: true });
|
|
255
|
+
if (realpathSync(legacy) === realpathSync(target)) return moved;
|
|
256
|
+
for (const sub of ["logs", "output"]) {
|
|
257
|
+
const from = join(legacy, sub);
|
|
258
|
+
if (!existsSync(from)) continue;
|
|
259
|
+
mkdirSync(join(target, sub), { recursive: true });
|
|
260
|
+
for (const entry of readdirSync(from)) {
|
|
261
|
+
const to = join(target, sub, entry);
|
|
262
|
+
if (existsSync(to)) continue;
|
|
263
|
+
try { renameSync(join(from, entry), to); moved.push(`${sub}/${entry}`); } catch { /* keep the legacy copy */ }
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Remove only directories left empty (the unused tls/ included); rmdir refuses non-empty ones.
|
|
267
|
+
for (const dir of ["logs", "output", "tls", ""]) { try { rmdirSync(join(legacy, dir)); } catch { /* not empty */ } }
|
|
268
|
+
return moved;
|
|
269
|
+
}
|
|
270
|
+
|
|
244
271
|
export function parseUrl(raw: string) {
|
|
245
272
|
const u = new URL(raw);
|
|
246
273
|
const fragment = new URLSearchParams(u.hash.slice(1).replace(/^\?/, ""));
|
|
@@ -482,7 +509,15 @@ export function deriveIdentity(opts: {
|
|
|
482
509
|
return { key, label };
|
|
483
510
|
}
|
|
484
511
|
|
|
485
|
-
|
|
512
|
+
/**
|
|
513
|
+
* A project's own rechrome folder: <root>/.rechrome, where root is the same one the session
|
|
514
|
+
* key uses (the worktree root, submodules rolled up), or cwd in `cwd` mode / outside git.
|
|
515
|
+
*/
|
|
516
|
+
export function projectDataDir(opts: { mode: string; cwd: string; root?: string | null }): string {
|
|
517
|
+
return join(opts.mode === "cwd" ? opts.cwd : opts.root || opts.cwd, ".rechrome");
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
async function getClientIdentity(): Promise<{ key: string; label: string; dataDir: string; profile?: string }> {
|
|
486
521
|
const cwd = realpathSafe(process.cwd());
|
|
487
522
|
const mode = (process.env.RECH_IDENTITY || "worktree").toLowerCase();
|
|
488
523
|
let root: string | null = null;
|
|
@@ -508,7 +543,7 @@ async function getClientIdentity(): Promise<{ key: string; label: string; profil
|
|
|
508
543
|
if (remoteUrl) remote = normalizeRemote(remoteUrl);
|
|
509
544
|
}
|
|
510
545
|
|
|
511
|
-
return deriveIdentity({ mode, cwd, host: hostname(), root, remote, branch });
|
|
546
|
+
return { ...deriveIdentity({ mode, cwd, host: hostname(), root, remote, branch }), dataDir: projectDataDir({ mode, cwd, root }) };
|
|
512
547
|
}
|
|
513
548
|
|
|
514
549
|
// Profile precedence: an explicit `?profile=` in RECHROME_URL is authoritative; the
|
|
@@ -902,7 +937,8 @@ async function callServe(
|
|
|
902
937
|
const res = await fetch(serviceUrl(url, "run"), {
|
|
903
938
|
method: "POST",
|
|
904
939
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
|
|
905
|
-
|
|
940
|
+
// dataDir is a client-local path; the daemon (possibly remote) only needs the session identity.
|
|
941
|
+
body: JSON.stringify({ args, identity: { key: identity.key, label: identity.label, profile: identity.profile }, env }),
|
|
906
942
|
signal: AbortSignal.timeout(70_000),
|
|
907
943
|
}).catch(async (e) => {
|
|
908
944
|
if (throwOnFailure) throw new Error("Cannot reach the rechrome daemon. Check that it is running.");
|
|
@@ -1018,9 +1054,11 @@ async function run(url: string, args: string[], overrideEnv?: Record<string, str
|
|
|
1018
1054
|
if (stdout) process.stdout.write(stdout);
|
|
1019
1055
|
|
|
1020
1056
|
if (files?.length) {
|
|
1021
|
-
|
|
1057
|
+
// Saved files belong to the project: <project>/.rechrome/output. The folder also holds
|
|
1058
|
+
// .env.local (secrets), so it is git-ignored as a whole when first created.
|
|
1059
|
+
const dlDir = join(identity.dataDir, "output");
|
|
1022
1060
|
mkdirSync(dlDir, { recursive: true });
|
|
1023
|
-
const gitignorePath = join(
|
|
1061
|
+
const gitignorePath = join(identity.dataDir, ".gitignore");
|
|
1024
1062
|
if (!existsSync(gitignorePath)) await Bun.write(gitignorePath, "*\n");
|
|
1025
1063
|
for (const name of files) {
|
|
1026
1064
|
const fileRes = await fetch(serviceUrl(url, `files/${name}`), {
|
|
@@ -1467,7 +1505,7 @@ async function daemonUninstall(): Promise<void> {
|
|
|
1467
1505
|
// locate the binary and launch it detached (singleton via a pidfile).
|
|
1468
1506
|
// `rech tray hide` / the menu "Hide" item both kill the process;
|
|
1469
1507
|
// `rech tray show` starts a fresh one.
|
|
1470
|
-
const TRAY_PID_FILE = join(
|
|
1508
|
+
const TRAY_PID_FILE = join(RECH_DIR, "tray.pid");
|
|
1471
1509
|
|
|
1472
1510
|
// A desktop GUI must be present. Linux needs an X11/Wayland display; a headless
|
|
1473
1511
|
// box (SSH, CI, container) has neither, so the tray is skipped. macOS/Windows
|
|
@@ -1729,7 +1767,7 @@ async function provisionProfile(name: string, opts: { headed?: boolean } = {}):
|
|
|
1729
1767
|
process.exit(1);
|
|
1730
1768
|
}
|
|
1731
1769
|
const dist = await ensureExtensionDistInstalled();
|
|
1732
|
-
const userDataDir = join(
|
|
1770
|
+
const userDataDir = join(RECH_DIR, "profiles", name);
|
|
1733
1771
|
const token = randomBytes(32).toString("base64url");
|
|
1734
1772
|
|
|
1735
1773
|
console.log(`\n[1/3] Provisioning managed profile "${name}"`);
|
package/rechrome.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { file } from "bun";
|
|
|
5
5
|
import yargs from "yargs";
|
|
6
6
|
import { readExtensionTokenFromProfile } from "./extension-token.ts";
|
|
7
7
|
import { randomBytes } from "crypto";
|
|
8
|
-
import { mkdirSync, appendFileSync, existsSync, realpathSync, accessSync, cpSync, unlinkSync, readFileSync, readdirSync, constants as fsConstants } from "fs";
|
|
8
|
+
import { mkdirSync, appendFileSync, existsSync, realpathSync, accessSync, cpSync, unlinkSync, readFileSync, readdirSync, renameSync, rmdirSync, constants as fsConstants } from "fs";
|
|
9
9
|
import { hostname, homedir, networkInterfaces } from "os";
|
|
10
10
|
import { isIPv4 } from "net";
|
|
11
11
|
import { join, basename, dirname } from "path";
|
|
@@ -16,14 +16,16 @@ import { oxmgrInstallCommand, pickDaemonManager, type DaemonManager } from "./da
|
|
|
16
16
|
|
|
17
17
|
export const ENV_KEY = "RECHROME_URL";
|
|
18
18
|
export const DEFAULT_PORT = 13775;
|
|
19
|
-
export const RECH_DIR = join(import.meta.dir, ".rech");
|
|
20
|
-
export const LOG_DIR = join(RECH_DIR, "logs");
|
|
21
|
-
|
|
22
19
|
// Home dir: HOME on POSIX, USERPROFILE on Windows (handled by os.homedir()).
|
|
23
20
|
export const HOME = homedir();
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
// All rechrome state (registry, listeners, extension, logs, daemon output) lives in
|
|
23
|
+
// ~/.rechrome, never next to the code: a bunx/npx install dir is a disposable cache.
|
|
24
|
+
export const RECH_DIR = join(HOME, ".rechrome");
|
|
25
|
+
export const LOG_DIR = join(RECH_DIR, "logs");
|
|
26
|
+
// Before the rename, logs and daemon output lived in <install dir>/.rech.
|
|
27
|
+
export const LEGACY_RECH_DIR = join(import.meta.dir, ".rech");
|
|
28
|
+
const TOKENS_FILE = join(RECH_DIR, "profiles.yaml");
|
|
27
29
|
|
|
28
30
|
type TokenEntry = { extensionId: string; token: string; profileDir: string; userDataDir?: string; loadExtension?: string };
|
|
29
31
|
|
|
@@ -38,7 +40,7 @@ function validateTokenRegistry(value: unknown): Record<string, TokenEntry> {
|
|
|
38
40
|
return value as Record<string, TokenEntry>;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
export async function writeTokenRegistry(registry: Record<string, TokenEntry>, directory =
|
|
43
|
+
export async function writeTokenRegistry(registry: Record<string, TokenEntry>, directory = RECH_DIR): Promise<void> {
|
|
42
44
|
validateTokenRegistry(registry);
|
|
43
45
|
await mkdir(directory, { recursive: true });
|
|
44
46
|
const path = join(directory, "profiles.yaml");
|
|
@@ -51,7 +53,7 @@ export async function writeTokenRegistry(registry: Record<string, TokenEntry>, d
|
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
export async function readTokenRegistry(directory =
|
|
56
|
+
export async function readTokenRegistry(directory = RECH_DIR): Promise<Record<string, TokenEntry>> {
|
|
55
57
|
const path = join(directory, "profiles.yaml");
|
|
56
58
|
let raw: string;
|
|
57
59
|
try { raw = await readFile(path, "utf8"); }
|
|
@@ -74,7 +76,7 @@ export async function readTokenRegistry(directory = RECH_HOME_DIR): Promise<Reco
|
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
async function saveTokenEntry(profileEmail: string, entry: TokenEntry): Promise<void> {
|
|
77
|
-
mkdirSync(
|
|
79
|
+
mkdirSync(RECH_DIR, { recursive: true });
|
|
78
80
|
const registry = await readTokenRegistry();
|
|
79
81
|
registry[profileEmail] = entry;
|
|
80
82
|
await writeTokenRegistry(registry);
|
|
@@ -241,6 +243,31 @@ export function log(msg: string) {
|
|
|
241
243
|
appendFileSync(logFile, line);
|
|
242
244
|
}
|
|
243
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Move logs and daemon output from the legacy <install dir>/.rech into RECH_DIR, entry by
|
|
248
|
+
* entry. An entry already present in RECH_DIR is kept and the legacy copy left in place,
|
|
249
|
+
* so nothing is overwritten. Best effort: a cross-device rename just leaves the old copy.
|
|
250
|
+
*/
|
|
251
|
+
export function migrateLegacyDataDir(legacy = LEGACY_RECH_DIR, target = RECH_DIR): string[] {
|
|
252
|
+
const moved: string[] = [];
|
|
253
|
+
if (!existsSync(legacy)) return moved;
|
|
254
|
+
mkdirSync(target, { recursive: true });
|
|
255
|
+
if (realpathSync(legacy) === realpathSync(target)) return moved;
|
|
256
|
+
for (const sub of ["logs", "output"]) {
|
|
257
|
+
const from = join(legacy, sub);
|
|
258
|
+
if (!existsSync(from)) continue;
|
|
259
|
+
mkdirSync(join(target, sub), { recursive: true });
|
|
260
|
+
for (const entry of readdirSync(from)) {
|
|
261
|
+
const to = join(target, sub, entry);
|
|
262
|
+
if (existsSync(to)) continue;
|
|
263
|
+
try { renameSync(join(from, entry), to); moved.push(`${sub}/${entry}`); } catch { /* keep the legacy copy */ }
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Remove only directories left empty (the unused tls/ included); rmdir refuses non-empty ones.
|
|
267
|
+
for (const dir of ["logs", "output", "tls", ""]) { try { rmdirSync(join(legacy, dir)); } catch { /* not empty */ } }
|
|
268
|
+
return moved;
|
|
269
|
+
}
|
|
270
|
+
|
|
244
271
|
export function parseUrl(raw: string) {
|
|
245
272
|
const u = new URL(raw);
|
|
246
273
|
const fragment = new URLSearchParams(u.hash.slice(1).replace(/^\?/, ""));
|
|
@@ -482,7 +509,15 @@ export function deriveIdentity(opts: {
|
|
|
482
509
|
return { key, label };
|
|
483
510
|
}
|
|
484
511
|
|
|
485
|
-
|
|
512
|
+
/**
|
|
513
|
+
* A project's own rechrome folder: <root>/.rechrome, where root is the same one the session
|
|
514
|
+
* key uses (the worktree root, submodules rolled up), or cwd in `cwd` mode / outside git.
|
|
515
|
+
*/
|
|
516
|
+
export function projectDataDir(opts: { mode: string; cwd: string; root?: string | null }): string {
|
|
517
|
+
return join(opts.mode === "cwd" ? opts.cwd : opts.root || opts.cwd, ".rechrome");
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
async function getClientIdentity(): Promise<{ key: string; label: string; dataDir: string; profile?: string }> {
|
|
486
521
|
const cwd = realpathSafe(process.cwd());
|
|
487
522
|
const mode = (process.env.RECH_IDENTITY || "worktree").toLowerCase();
|
|
488
523
|
let root: string | null = null;
|
|
@@ -508,7 +543,7 @@ async function getClientIdentity(): Promise<{ key: string; label: string; profil
|
|
|
508
543
|
if (remoteUrl) remote = normalizeRemote(remoteUrl);
|
|
509
544
|
}
|
|
510
545
|
|
|
511
|
-
return deriveIdentity({ mode, cwd, host: hostname(), root, remote, branch });
|
|
546
|
+
return { ...deriveIdentity({ mode, cwd, host: hostname(), root, remote, branch }), dataDir: projectDataDir({ mode, cwd, root }) };
|
|
512
547
|
}
|
|
513
548
|
|
|
514
549
|
// Profile precedence: an explicit `?profile=` in RECHROME_URL is authoritative; the
|
|
@@ -902,7 +937,8 @@ async function callServe(
|
|
|
902
937
|
const res = await fetch(serviceUrl(url, "run"), {
|
|
903
938
|
method: "POST",
|
|
904
939
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
|
|
905
|
-
|
|
940
|
+
// dataDir is a client-local path; the daemon (possibly remote) only needs the session identity.
|
|
941
|
+
body: JSON.stringify({ args, identity: { key: identity.key, label: identity.label, profile: identity.profile }, env }),
|
|
906
942
|
signal: AbortSignal.timeout(70_000),
|
|
907
943
|
}).catch(async (e) => {
|
|
908
944
|
if (throwOnFailure) throw new Error("Cannot reach the rechrome daemon. Check that it is running.");
|
|
@@ -1018,9 +1054,11 @@ async function run(url: string, args: string[], overrideEnv?: Record<string, str
|
|
|
1018
1054
|
if (stdout) process.stdout.write(stdout);
|
|
1019
1055
|
|
|
1020
1056
|
if (files?.length) {
|
|
1021
|
-
|
|
1057
|
+
// Saved files belong to the project: <project>/.rechrome/output. The folder also holds
|
|
1058
|
+
// .env.local (secrets), so it is git-ignored as a whole when first created.
|
|
1059
|
+
const dlDir = join(identity.dataDir, "output");
|
|
1022
1060
|
mkdirSync(dlDir, { recursive: true });
|
|
1023
|
-
const gitignorePath = join(
|
|
1061
|
+
const gitignorePath = join(identity.dataDir, ".gitignore");
|
|
1024
1062
|
if (!existsSync(gitignorePath)) await Bun.write(gitignorePath, "*\n");
|
|
1025
1063
|
for (const name of files) {
|
|
1026
1064
|
const fileRes = await fetch(serviceUrl(url, `files/${name}`), {
|
|
@@ -1467,7 +1505,7 @@ async function daemonUninstall(): Promise<void> {
|
|
|
1467
1505
|
// locate the binary and launch it detached (singleton via a pidfile).
|
|
1468
1506
|
// `rech tray hide` / the menu "Hide" item both kill the process;
|
|
1469
1507
|
// `rech tray show` starts a fresh one.
|
|
1470
|
-
const TRAY_PID_FILE = join(
|
|
1508
|
+
const TRAY_PID_FILE = join(RECH_DIR, "tray.pid");
|
|
1471
1509
|
|
|
1472
1510
|
// A desktop GUI must be present. Linux needs an X11/Wayland display; a headless
|
|
1473
1511
|
// box (SSH, CI, container) has neither, so the tray is skipped. macOS/Windows
|
|
@@ -1729,7 +1767,7 @@ async function provisionProfile(name: string, opts: { headed?: boolean } = {}):
|
|
|
1729
1767
|
process.exit(1);
|
|
1730
1768
|
}
|
|
1731
1769
|
const dist = await ensureExtensionDistInstalled();
|
|
1732
|
-
const userDataDir = join(
|
|
1770
|
+
const userDataDir = join(RECH_DIR, "profiles", name);
|
|
1733
1771
|
const token = randomBytes(32).toString("base64url");
|
|
1734
1772
|
|
|
1735
1773
|
console.log(`\n[1/3] Provisioning managed profile "${name}"`);
|
package/serve.js
CHANGED
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
getOrCreateUrl,
|
|
10
10
|
authCheck,
|
|
11
11
|
RECH_DIR,
|
|
12
|
+
LEGACY_RECH_DIR,
|
|
13
|
+
migrateLegacyDataDir,
|
|
12
14
|
HOME,
|
|
13
15
|
PASSTHROUGH_ENV_KEYS,
|
|
14
16
|
resolvePlaywrightCli,
|
|
@@ -263,11 +265,14 @@ export function shouldExitOrphanedServe(opts: {
|
|
|
263
265
|
}
|
|
264
266
|
|
|
265
267
|
export async function serve() {
|
|
268
|
+
// The daemon owns logs/ and output/, so it migrates them before writing anything.
|
|
269
|
+
const migrated = migrateLegacyDataDir();
|
|
266
270
|
const url = await getOrCreateUrl();
|
|
267
271
|
const { key, port } = parseUrl(url);
|
|
268
272
|
|
|
269
273
|
const workDir = join(RECH_DIR, "output");
|
|
270
274
|
mkdirSync(workDir, { recursive: true });
|
|
275
|
+
if (migrated.length) log(`Moved ${migrated.length} legacy entries from ${LEGACY_RECH_DIR} to ${RECH_DIR}`);
|
|
271
276
|
|
|
272
277
|
// Foreground/orphan self-exit: a serve whose parent has been re-parented to init
|
|
273
278
|
// (ppid 1) is a leaked foreground serve. Poll for that, track the last real /run,
|
|
@@ -656,7 +661,7 @@ export async function serve() {
|
|
|
656
661
|
outputFiles.push(f);
|
|
657
662
|
} else {
|
|
658
663
|
const basename = f.split("/").pop()!;
|
|
659
|
-
for (const subdir of [".playwright-cli", ".
|
|
664
|
+
for (const subdir of [".playwright-cli", ".playwright-cli-multi-tab"]) {
|
|
660
665
|
// Forward-slash for the wire: join() would use "\" on the Windows daemon, which
|
|
661
666
|
// a POSIX client can't treat as a separator (it builds a literal-backslash path).
|
|
662
667
|
const subpath = `${subdir}/${basename}`;
|
package/serve.ts
CHANGED
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
getOrCreateUrl,
|
|
10
10
|
authCheck,
|
|
11
11
|
RECH_DIR,
|
|
12
|
+
LEGACY_RECH_DIR,
|
|
13
|
+
migrateLegacyDataDir,
|
|
12
14
|
HOME,
|
|
13
15
|
PASSTHROUGH_ENV_KEYS,
|
|
14
16
|
resolvePlaywrightCli,
|
|
@@ -263,11 +265,14 @@ export function shouldExitOrphanedServe(opts: {
|
|
|
263
265
|
}
|
|
264
266
|
|
|
265
267
|
export async function serve() {
|
|
268
|
+
// The daemon owns logs/ and output/, so it migrates them before writing anything.
|
|
269
|
+
const migrated = migrateLegacyDataDir();
|
|
266
270
|
const url = await getOrCreateUrl();
|
|
267
271
|
const { key, port } = parseUrl(url);
|
|
268
272
|
|
|
269
273
|
const workDir = join(RECH_DIR, "output");
|
|
270
274
|
mkdirSync(workDir, { recursive: true });
|
|
275
|
+
if (migrated.length) log(`Moved ${migrated.length} legacy entries from ${LEGACY_RECH_DIR} to ${RECH_DIR}`);
|
|
271
276
|
|
|
272
277
|
// Foreground/orphan self-exit: a serve whose parent has been re-parented to init
|
|
273
278
|
// (ppid 1) is a leaked foreground serve. Poll for that, track the last real /run,
|
|
@@ -656,7 +661,7 @@ export async function serve() {
|
|
|
656
661
|
outputFiles.push(f);
|
|
657
662
|
} else {
|
|
658
663
|
const basename = f.split("/").pop()!;
|
|
659
|
-
for (const subdir of [".playwright-cli", ".
|
|
664
|
+
for (const subdir of [".playwright-cli", ".playwright-cli-multi-tab"]) {
|
|
660
665
|
// Forward-slash for the wire: join() would use "\" on the Windows daemon, which
|
|
661
666
|
// a POSIX client can't treat as a separator (it builds a literal-backslash path).
|
|
662
667
|
const subpath = `${subdir}/${basename}`;
|