101 lines
2.5 KiB
JavaScript
101 lines
2.5 KiB
JavaScript
#!/usr/bin/env node
|
|
/* global console */
|
|
import { execFileSync, spawnSync } from 'node:child_process'
|
|
import {
|
|
existsSync,
|
|
mkdirSync,
|
|
readdirSync,
|
|
renameSync,
|
|
rmSync,
|
|
writeFileSync,
|
|
} from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
|
|
function run(command, args) {
|
|
const result = spawnSync(command, args, { stdio: 'inherit' })
|
|
if (result.error) throw result.error
|
|
if (result.status !== 0) {
|
|
throw new Error(`${command} exited with status ${result.status}`)
|
|
}
|
|
}
|
|
|
|
function parsePlan(planText) {
|
|
const entries = []
|
|
const seen = new Set()
|
|
|
|
for (const section of planText.split(/\n(?=\S)/)) {
|
|
const label = section.split('\n')[0]?.trim() ?? ''
|
|
const location = readField(section, 'Install location')
|
|
const url = readField(section, 'Download url')
|
|
rememberEntry(entries, seen, { label, location, url })
|
|
}
|
|
|
|
return entries
|
|
}
|
|
|
|
function readField(text, name) {
|
|
const match = text.match(new RegExp(`^\\s*${name}:\\s*(.+)$`, 'm'))
|
|
return match?.[1].trim() ?? ''
|
|
}
|
|
|
|
function rememberEntry(entries, seen, entry) {
|
|
const key = `${entry.location}\n${entry.url}`
|
|
if (!entry.location || !entry.url || seen.has(key)) return
|
|
seen.add(key)
|
|
entries.push(entry)
|
|
}
|
|
|
|
function installBrowser(entry) {
|
|
const markerPath = join(entry.location, 'INSTALLATION_COMPLETE')
|
|
if (existsSync(markerPath)) {
|
|
console.log(`Already installed: ${entry.label}`)
|
|
return
|
|
}
|
|
|
|
const workDir = join(tmpdir(), `playwright-${Date.now()}`)
|
|
const archivePath = join(workDir, 'browser.zip')
|
|
const extractDir = join(workDir, 'extract')
|
|
|
|
rmSync(entry.location, { force: true, recursive: true })
|
|
mkdirSync(extractDir, { recursive: true })
|
|
|
|
console.log(`Downloading ${entry.label}`)
|
|
run('curl', [
|
|
'-fL',
|
|
'--retry',
|
|
'3',
|
|
'--connect-timeout',
|
|
'20',
|
|
'-o',
|
|
archivePath,
|
|
entry.url,
|
|
])
|
|
|
|
console.log(`Extracting ${entry.label}`)
|
|
run('unzip', ['-q', archivePath, '-d', extractDir])
|
|
|
|
mkdirSync(entry.location, { recursive: true })
|
|
for (const child of readdirSync(extractDir)) {
|
|
renameSync(join(extractDir, child), join(entry.location, child))
|
|
}
|
|
|
|
writeFileSync(markerPath, '')
|
|
rmSync(workDir, { force: true, recursive: true })
|
|
}
|
|
|
|
const planText = execFileSync(
|
|
'npx',
|
|
['playwright', 'install', '--dry-run', 'chromium'],
|
|
{ encoding: 'utf8' },
|
|
)
|
|
const entries = parsePlan(planText)
|
|
|
|
if (entries.length === 0) {
|
|
throw new Error('Playwright did not report any browser archives to install')
|
|
}
|
|
|
|
for (const entry of entries) {
|
|
installBrowser(entry)
|
|
}
|