import { execFile } from 'node:child_process' import { createHash } from 'node:crypto' import { createReadStream, constants } from 'node:fs' import { access, mkdir, mkdtemp, readFile, rename, rm, writeFile } from 'node:fs/promises' import { dirname, join, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { promisify } from 'node:util' import { installEditorExtensions } from './install-editor-extensions.mjs' const execute = promisify(execFile) const pluginDirectory = dirname(dirname(fileURLToPath(import.meta.url))) const release = JSON.parse(await readFile(join(pluginDirectory, 'editor-release.json'), 'utf8')) const runtimeDirectory = join(pluginDirectory, 'runtime') const installationDirectory = join(runtimeDirectory, 'current') async function archiveDigest(path) { const hash = createHash('sha256') for await (const chunk of createReadStream(path)) hash.update(chunk) return hash.digest('hex') } async function isAlreadyInstalled() { let installed try { installed = JSON.parse(await readFile(join(installationDirectory, 'dsh-release.json'), 'utf8')) } catch (error) { if (error.code === 'ENOENT') return false throw error } if (installed.sha256 !== release.sha256) { throw new Error('A different editor runtime is installed. Remove runtime/current explicitly before changing versions.') } await access(join(installationDirectory, 'bin', 'openvscode-server'), constants.X_OK) return true } async function install() { const argumentsList = process.argv.slice(2) if (argumentsList.length !== 0 && (argumentsList.length !== 2 || argumentsList[0] !== '--archive')) { throw new Error('Usage: node scripts/install-editor.mjs [--archive /path/to/editor.tar.gz]') } const providedArchive = argumentsList.length === 2 ? resolve(argumentsList[1]) : null if (process.platform !== release.platform || process.arch !== release.architecture) { throw new Error(`This pinned runtime supports ${release.platform}/${release.architecture}, not ${process.platform}/${process.arch}.`) } if (await isAlreadyInstalled()) { console.log(`OpenVSCode Server ${release.version} is already installed. No server started.`) return } await mkdir(runtimeDirectory, { recursive: true }) const stagingDirectory = await mkdtemp(join(runtimeDirectory, '.install-')) try { const archive = providedArchive || join(stagingDirectory, 'editor.tar.gz') if (!providedArchive) { console.log(`Downloading OpenVSCode Server ${release.version} from the pinned official release…`) await execute('curl', ['--fail', '--location', '--silent', '--show-error', '--header', 'Accept: application/octet-stream', '--max-time', '180', '--output', archive, release.url], { timeout: 190000 }) } if (await archiveDigest(archive) !== release.sha256) { throw new Error('Editor archive SHA-256 mismatch; refusing to extract or execute it.') } await execute('tar', ['--extract', '--gzip', '--file', archive, '--directory', stagingDirectory, '--no-same-owner'], { timeout: 60000 }) const extractedDirectory = join(stagingDirectory, release.archiveDirectory) await access(join(extractedDirectory, 'bin', 'openvscode-server'), constants.X_OK) await writeFile(join(extractedDirectory, 'dsh-release.json'), `${JSON.stringify(release, null, 2)}\n`) await rename(extractedDirectory, installationDirectory) console.log(`Installed and SHA-256 verified OpenVSCode Server ${release.version}. No server started.`) } finally { await rm(stagingDirectory, { recursive: true, force: true }) } } await install() await installEditorExtensions()