@oh-my-pi/pi-utils 18.1.19 → 18.1.21
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/CHANGELOG.md +11 -0
- package/dist/types/dirs.d.ts +4 -0
- package/package.json +2 -2
- package/src/browsers.ts +39 -25
- package/src/dirs.ts +7 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.1.21] - 2026-09-14
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added `getBrowserProfilesDir()` (`~/.omp/browser-profiles`; XDG: `$XDG_STATE_HOME/omp/browser-profiles`) for profiles of Chromium browsers spawned by the browser tool.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Timed out stalled Chrome-for-Testing metadata requests after 30 seconds when looking up download metadata
|
|
14
|
+
- Concurrent browser installations share one download without replacing a running browser, and stalled downloads time out with partial files cleaned up for retry.
|
|
15
|
+
|
|
5
16
|
## [18.1.19] - 2026-09-12
|
|
6
17
|
|
|
7
18
|
### Added
|
package/dist/types/dirs.d.ts
CHANGED
|
@@ -40,6 +40,8 @@ export declare function normalizeProfileName(profile: string | undefined): strin
|
|
|
40
40
|
* {@link normalizeProfileName} (which throws on a syntactically invalid value).
|
|
41
41
|
*/
|
|
42
42
|
export declare function resolveProfileEnv(omp: string | undefined, pi: string | undefined): string | undefined;
|
|
43
|
+
/** Profile-independent config root (~/.omp), shared by every omp profile. */
|
|
44
|
+
export declare function getBaseConfigRoot(): string;
|
|
43
45
|
export declare function resolveEquivalentPath(inputPath: string): string;
|
|
44
46
|
export declare function normalizePathForComparison(inputPath: string): string;
|
|
45
47
|
export declare function pathIsWithin(root: string, candidate: string): boolean;
|
|
@@ -176,6 +178,8 @@ export declare function getPythonGatewayDir(): string;
|
|
|
176
178
|
export declare function getPuppeteerDir(): string;
|
|
177
179
|
/** Get the browser relay extension install directory (~/.omp/browser-relay). */
|
|
178
180
|
export declare function getBrowserRelayDir(): string;
|
|
181
|
+
/** Get the profile root for Chromium browsers the browser tool spawns via `app.path` (~/.omp/browser-profiles). */
|
|
182
|
+
export declare function getBrowserProfilesDir(): string;
|
|
179
183
|
/** Get DOCS_RS cache directory () */
|
|
180
184
|
export declare function getDocsRsCacheDir(): string;
|
|
181
185
|
/** Get the auto-QA grievances SQLite database path (~/.omp/autoqa.db; XDG: $XDG_DATA_HOME/omp/autoqa.db). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-utils",
|
|
3
|
-
"version": "18.1.
|
|
3
|
+
"version": "18.1.21",
|
|
4
4
|
"description": "Shared utilities for pi packages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@oh-my-pi/pi-natives": "18.1.
|
|
57
|
+
"@oh-my-pi/pi-natives": "18.1.21"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/bun": "^1.3.14"
|
package/src/browsers.ts
CHANGED
|
@@ -5,9 +5,13 @@ import * as fsp from "node:fs/promises";
|
|
|
5
5
|
import * as os from "node:os";
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import { type ArchiveLimits, extractArchive } from "./ar";
|
|
8
|
+
import { withFileLock } from "./file-lock";
|
|
8
9
|
|
|
9
10
|
const CHROME_FOR_TESTING_BASE_URL = "https://storage.googleapis.com/chrome-for-testing-public";
|
|
10
11
|
const CHROME_METADATA_BASE_URL = "https://googlechromelabs.github.io/chrome-for-testing";
|
|
12
|
+
// Installation is outside browser/Eval operation deadlines, but a stalled CDN
|
|
13
|
+
// must still release the cached install promise and permit a later attempt.
|
|
14
|
+
const DOWNLOAD_TIMEOUT_MS = 5 * 60_000;
|
|
11
15
|
|
|
12
16
|
/**
|
|
13
17
|
* Archive ceilings for the managed browser download. The default archive
|
|
@@ -251,29 +255,39 @@ export async function install(options: InstallOptions): Promise<InstalledBrowser
|
|
|
251
255
|
return { browser: options.browser, buildId: options.buildId, platform, path: installPath, executablePath };
|
|
252
256
|
}
|
|
253
257
|
|
|
254
|
-
await fsp.mkdir(
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
258
|
+
await fsp.mkdir(path.dirname(installPath), { recursive: true });
|
|
259
|
+
return withFileLock(
|
|
260
|
+
`${installPath}.install`,
|
|
261
|
+
async () => {
|
|
262
|
+
// The in-process launch promise cannot serialize separate OMP sessions.
|
|
263
|
+
// Recheck under the OS lock: never replace a winner's running Chrome.
|
|
264
|
+
if (!(await pathExists(executablePath))) {
|
|
265
|
+
const nonce = `${process.pid}-${crypto.randomUUID()}`;
|
|
266
|
+
const archivePath = path.join(options.cacheDir, `.browser-${nonce}.zip`);
|
|
267
|
+
const stagingPath = path.join(options.cacheDir, `.browser-${nonce}`);
|
|
268
|
+
try {
|
|
269
|
+
await downloadArchive(
|
|
270
|
+
getDownloadUrl(options.browser, platform, options.buildId, options.baseUrl),
|
|
271
|
+
archivePath,
|
|
272
|
+
options.downloadProgressCallback,
|
|
273
|
+
);
|
|
274
|
+
await extractArchive(archivePath, stagingPath, { limits: BROWSER_ARCHIVE_LIMITS });
|
|
275
|
+
if (!(await pathExists(path.join(stagingPath, path.relative(installPath, executablePath))))) {
|
|
276
|
+
throw new Error(`Browser archive did not contain its expected executable: ${executablePath}`);
|
|
277
|
+
}
|
|
278
|
+
await fsp.rm(installPath, { recursive: true, force: true });
|
|
279
|
+
await fsp.rename(stagingPath, installPath);
|
|
280
|
+
} finally {
|
|
281
|
+
await Promise.all([
|
|
282
|
+
fsp.rm(archivePath, { force: true }).catch(() => {}),
|
|
283
|
+
fsp.rm(stagingPath, { recursive: true, force: true }).catch(() => {}),
|
|
284
|
+
]);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return { browser: options.browser, buildId: options.buildId, platform, path: installPath, executablePath };
|
|
288
|
+
},
|
|
289
|
+
{ retries: Math.ceil(DOWNLOAD_TIMEOUT_MS / 100) + 1, retryDelayMs: 100 },
|
|
290
|
+
);
|
|
277
291
|
}
|
|
278
292
|
|
|
279
293
|
function chromeChannelName(tag: string): string | undefined {
|
|
@@ -292,7 +306,7 @@ function chromeChannelName(tag: string): string | undefined {
|
|
|
292
306
|
}
|
|
293
307
|
|
|
294
308
|
async function fetchMetadata<T>(filename: string): Promise<T> {
|
|
295
|
-
const response = await fetch(`${CHROME_METADATA_BASE_URL}/${filename}
|
|
309
|
+
const response = await fetch(`${CHROME_METADATA_BASE_URL}/${filename}`, { signal: AbortSignal.timeout(30_000) });
|
|
296
310
|
if (!response.ok)
|
|
297
311
|
throw new Error(`Failed to fetch Chrome-for-Testing metadata (${response.status} ${response.statusText})`);
|
|
298
312
|
return (await response.json()) as T;
|
|
@@ -345,7 +359,7 @@ async function downloadArchive(
|
|
|
345
359
|
destination: string,
|
|
346
360
|
onProgress: ((progress: BrowserDownloadProgress) => void) | undefined,
|
|
347
361
|
): Promise<void> {
|
|
348
|
-
const response = await fetch(url);
|
|
362
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) });
|
|
349
363
|
if (!response.ok || !response.body) {
|
|
350
364
|
throw new Error(`Browser download failed (${response.status} ${response.statusText}) from ${url}`);
|
|
351
365
|
}
|
package/src/dirs.ts
CHANGED
|
@@ -107,7 +107,8 @@ function readProfileFromEnvSafe(): string | undefined {
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
/** Profile-independent config root (~/.omp), shared by every omp profile. */
|
|
111
|
+
export function getBaseConfigRoot(): string {
|
|
111
112
|
return path.join(os.homedir(), getConfigDirName());
|
|
112
113
|
}
|
|
113
114
|
|
|
@@ -707,6 +708,11 @@ export function getBrowserRelayDir(): string {
|
|
|
707
708
|
return dirs.rootSubdir("browser-relay", "data");
|
|
708
709
|
}
|
|
709
710
|
|
|
711
|
+
/** Get the profile root for Chromium browsers the browser tool spawns via `app.path` (~/.omp/browser-profiles). */
|
|
712
|
+
export function getBrowserProfilesDir(): string {
|
|
713
|
+
return dirs.rootSubdir("browser-profiles", "state");
|
|
714
|
+
}
|
|
715
|
+
|
|
710
716
|
/** Get DOCS_RS cache directory () */
|
|
711
717
|
export function getDocsRsCacheDir(): string {
|
|
712
718
|
return dirs.rootSubdir("webcache", "cache");
|