chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
import { FileHelper } from '@/helpers/file-helper'
|
||||
import { SystemHelper } from '@/helpers/system-helper'
|
||||
|
||||
import { createSetupStatus } from './setup-status'
|
||||
|
||||
const MOVE_FALLBACK_ERROR_CODES = new Set(['EXDEV', 'EPERM', 'EBUSY', 'EACCES'])
|
||||
|
||||
function readManifest(manifestPath) {
|
||||
if (!fs.existsSync(manifestPath)) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(manifestPath, 'utf8'))
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function removePath(targetPath) {
|
||||
await fs.promises.rm(targetPath, { recursive: true, force: true })
|
||||
}
|
||||
|
||||
function isMoveFallbackError(error) {
|
||||
return (
|
||||
error instanceof Error &&
|
||||
'code' in error &&
|
||||
MOVE_FALLBACK_ERROR_CODES.has(error.code)
|
||||
)
|
||||
}
|
||||
|
||||
async function movePath(sourcePath, destinationPath) {
|
||||
try {
|
||||
await fs.promises.rename(sourcePath, destinationPath)
|
||||
} catch (error) {
|
||||
if (!isMoveFallbackError(error)) {
|
||||
throw error
|
||||
}
|
||||
|
||||
await fs.promises.cp(sourcePath, destinationPath, {
|
||||
recursive: true,
|
||||
force: true
|
||||
})
|
||||
await removePath(sourcePath)
|
||||
}
|
||||
}
|
||||
|
||||
async function moveDirectoryContents(sourcePath, destinationPath) {
|
||||
await fs.promises.mkdir(destinationPath, { recursive: true })
|
||||
|
||||
const entries = await fs.promises.readdir(sourcePath, {
|
||||
withFileTypes: true
|
||||
})
|
||||
|
||||
await Promise.all(
|
||||
entries.map((entry) =>
|
||||
movePath(
|
||||
path.join(sourcePath, entry.name),
|
||||
path.join(destinationPath, entry.name)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
async function cleanInstallDirectory(basePath) {
|
||||
await fs.promises.mkdir(basePath, { recursive: true })
|
||||
|
||||
const entries = await fs.promises.readdir(basePath, {
|
||||
withFileTypes: true
|
||||
})
|
||||
|
||||
await Promise.all(
|
||||
entries
|
||||
.filter((entry) => entry.name !== 'versions.json')
|
||||
.map((entry) => removePath(path.join(basePath, entry.name)))
|
||||
)
|
||||
}
|
||||
|
||||
async function getFlattenedExtractionRoot(extractionPath) {
|
||||
const entries = await fs.promises.readdir(extractionPath, {
|
||||
withFileTypes: true
|
||||
})
|
||||
|
||||
if (entries.length === 1 && entries[0]?.isDirectory()) {
|
||||
return path.join(extractionPath, entries[0].name)
|
||||
}
|
||||
|
||||
return extractionPath
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a portable runtime into Leon's managed `bin/` directory from either
|
||||
* an archive asset or a direct executable download.
|
||||
*/
|
||||
export async function setupRuntimeBinary({
|
||||
name,
|
||||
version,
|
||||
basePath,
|
||||
installPath,
|
||||
manifestPath,
|
||||
binaryPath,
|
||||
downloadURL,
|
||||
archiveFileName = null
|
||||
}) {
|
||||
const status = createSetupStatus(`Downloading and setting up ${name}...`).start()
|
||||
const manifest = readManifest(manifestPath)
|
||||
|
||||
if (manifest?.version === version && fs.existsSync(binaryPath)) {
|
||||
status.succeed(`${name}: ${version}`)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
await cleanInstallDirectory(basePath)
|
||||
|
||||
try {
|
||||
status.pause()
|
||||
|
||||
if (archiveFileName) {
|
||||
const archivePath = path.join(basePath, archiveFileName)
|
||||
const extractionPath = await fs.promises.mkdtemp(
|
||||
path.join(basePath, `${name.toLowerCase()}-extract-`)
|
||||
)
|
||||
|
||||
try {
|
||||
await FileHelper.downloadFile(downloadURL, archivePath, {
|
||||
cliProgress: true,
|
||||
parallelStreams: 3,
|
||||
skipExisting: false
|
||||
})
|
||||
status.text = `Installing ${name}...`
|
||||
status.start()
|
||||
|
||||
await FileHelper.extractArchive(archivePath, extractionPath)
|
||||
|
||||
const flattenedRootPath = await getFlattenedExtractionRoot(extractionPath)
|
||||
|
||||
await moveDirectoryContents(flattenedRootPath, installPath)
|
||||
} finally {
|
||||
await Promise.all([
|
||||
fs.promises.rm(archivePath, { force: true }),
|
||||
fs.promises.rm(extractionPath, { recursive: true, force: true })
|
||||
])
|
||||
}
|
||||
} else {
|
||||
await fs.promises.mkdir(path.dirname(binaryPath), { recursive: true })
|
||||
|
||||
await FileHelper.downloadFile(downloadURL, binaryPath, {
|
||||
cliProgress: true,
|
||||
parallelStreams: 3,
|
||||
skipExisting: false
|
||||
})
|
||||
status.text = `Finalizing ${name}...`
|
||||
status.start()
|
||||
}
|
||||
|
||||
if (!SystemHelper.isWindows()) {
|
||||
await fs.promises.chmod(binaryPath, 0o755)
|
||||
}
|
||||
|
||||
if (!fs.existsSync(binaryPath)) {
|
||||
throw new Error(`Cannot find ${name} binary at "${binaryPath}"`)
|
||||
}
|
||||
|
||||
await FileHelper.createManifestFile(manifestPath, name.toLowerCase(), version, {
|
||||
os: SystemHelper.getInformation().type,
|
||||
architecture: SystemHelper.getInformation().cpuArchitecture
|
||||
})
|
||||
|
||||
status.succeed(`${name}: ${version}`)
|
||||
} catch (error) {
|
||||
if (status.isSpinning) {
|
||||
status.fail(`Failed to set up ${name}`)
|
||||
}
|
||||
|
||||
throw new Error(`Failed to set up ${name}: ${error}`)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user