UNPKG

hugo-installer

Version:
866 lines (862 loc) 22.8 kB
import { spawn } from 'node:child_process'; import * as crypto from 'node:crypto'; import decompress from 'decompress'; import { deleteAsync } from 'del'; import * as fs from 'node:fs'; import got from 'got'; import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent'; import * as path from 'node:path'; import semver from 'semver'; const binaries = [ { os: "win32", arch: "x64", fileNamePatternHistory: [ { version: "0.103.0", fileNamePattern: "{{variant}}_{{version}}_windows-amd64.zip" }, { version: "0.20.4", fileNamePattern: "{{variant}}_{{version}}_Windows-64bit.zip" }, { version: "0.20.3", fileNamePattern: "{{variant}}_v{{version}}_Windows-64bit.zip" }, { version: "0.17", fileNamePattern: "{{variant}}_{{version}}_Windows-64bit.zip" }, { version: "0.16", fileNamePattern: "{{variant}}_{{version}}_windows-64bit.zip" }, { version: "0.9", fileNamePattern: "{{variant}}_{{version}}_windows_amd64.zip" }, { version: "0.8", fileNamePattern: "{{variant}}-windows-amd64" }, { version: "0.7", fileNamePattern: "{{variant}}-{{version}}-windows-x64" } ] }, { os: "win32", arch: "x86", fileNamePatternHistory: [ { version: "0.102.0", fileNamePattern: null }, { version: "0.20.4", fileNamePattern: "{{variant}}_{{version}}_Windows-32bit.zip" }, { version: "0.20.3", fileNamePattern: "{{variant}}_v{{version}}_Windows-32bit.zip" }, { version: "0.17", fileNamePattern: "{{variant}}_{{version}}_Windows-32bit.zip" }, { version: "0.16", fileNamePattern: "{{variant}}_{{version}}_windows-32bit.zip" }, { version: "0.15", fileNamePattern: "{{variant}}_{{version}}_windows_386_32-bit-only.zip" }, { version: "0.9", fileNamePattern: "{{variant}}_{{version}}_windows_386.zip" }, { version: "0.8", fileNamePattern: "{{variant}}-windows-386" }, { version: "0.7", fileNamePattern: "{{variant}}-{{version}}-windows-386" } ] }, { os: "darwin", arch: "x64", fileNamePatternHistory: [ { version: "0.103.0", fileNamePattern: "{{variant}}_{{version}}_darwin-universal.tar.gz" }, { version: "0.102.0", fileNamePattern: "{{variant}}_{{version}}_macOS-universal.tar.gz" }, { version: "0.89.1", fileNamePattern: "{{variant}}_{{version}}_macOS-64bit.tar.gz" }, { version: "0.89.0", fileNamePattern: null }, { version: "0.20.4", fileNamePattern: "{{variant}}_{{version}}_macOS-64bit.tar.gz" }, { version: "0.20.3", fileNamePattern: "{{variant}}_v{{version}}_macOS-64bit.tar.gz" }, { version: "0.18", fileNamePattern: "{{variant}}_{{version}}_macOS-64bit.zip" }, { version: "0.17", fileNamePattern: "{{variant}}_{{version}}_MacOS-64bit.zip" }, { version: "0.16", fileNamePattern: "{{variant}}_{{version}}_osx-64bit.tgz" }, { version: "0.9", fileNamePattern: "{{variant}}_{{version}}_darwin_amd64.zip" }, { version: "0.8", fileNamePattern: "{{variant}}-darwin-amd64" }, { version: "0.7", fileNamePattern: "{{variant}}-{{version}}-darwin-x64" } ] }, { os: "darwin", arch: "x86", fileNamePatternHistory: [ { version: "0.75.0", fileNamePattern: null }, { version: "0.20.4", fileNamePattern: "{{variant}}_{{version}}_macOS-32bit.tar.gz" }, { version: "0.20.3", fileNamePattern: "{{variant}}_v{{version}}_macOS-32bit.tar.gz" }, { version: "0.18", fileNamePattern: "{{variant}}_{{version}}_macOS-32bit.zip" }, { version: "0.17", fileNamePattern: "{{variant}}_{{version}}_MacOS-32bit.zip" }, { version: "0.16", fileNamePattern: "{{variant}}_{{version}}_osx-32bit.tgz" }, { version: "0.9", fileNamePattern: "{{variant}}_{{version}}_darwin_386.zip" }, { version: "0.8", fileNamePattern: "{{variant}}-darwin-386" }, { version: "0.7", fileNamePattern: "{{variant}}-{{version}}-darwin-386" } ] }, { os: "darwin", arch: "arm", fileNamePatternHistory: [ { version: "0.17", fileNamePattern: null }, { version: "0.16", fileNamePattern: "{{variant}}_{{version}}_darwin-arm32.tgz" }, { version: "0.7", fileNamePattern: null } ] }, { os: "darwin", arch: "arm64", fileNamePatternHistory: [ { version: "0.103.0", fileNamePattern: "{{variant}}_{{version}}_darwin-universal.tar.gz" }, { version: "0.102.0", fileNamePattern: "{{variant}}_{{version}}_macOS-universal.tar.gz" }, { version: "0.89.1", fileNamePattern: "{{variant}}_{{version}}_macOS-ARM64.tar.gz" }, { version: "0.89.0", fileNamePattern: null }, { version: "0.88.1", fileNamePattern: "{{variant}}_{{version}}_macOS-ARM64.tar.gz" }, { version: "0.88.0", fileNamePattern: null }, { version: "0.81.0", fileNamePattern: "{{variant}}_{{version}}_macOS-ARM64.tar.gz" }, { version: "0.7", fileNamePattern: null } ] }, { os: "linux", arch: "x64", fileNamePatternHistory: [ { version: "0.20.4", fileNamePattern: "{{variant}}_{{version}}_Linux-64bit.tar.gz" }, { version: "0.20.3", fileNamePattern: "{{variant}}_v{{version}}_Linux-64bit.tar.gz" }, { version: "0.17", fileNamePattern: "{{variant}}_{{version}}_Linux-64bit.tar.gz" }, { version: "0.16", fileNamePattern: "{{variant}}_{{version}}_linux-64bit.tgz" }, { version: "0.9", fileNamePattern: "{{variant}}_{{version}}_linux_amd64.tar.gz" }, { version: "0.8", fileNamePattern: "{{variant}}-linux-amd64" }, { version: "0.7", fileNamePattern: "{{variant}}-{{version}}-linux-x64" } ] }, { os: "linux", arch: "x86", fileNamePatternHistory: [ { version: "0.102.0", fileNamePattern: null }, { version: "0.20.4", fileNamePattern: "{{variant}}_{{version}}_Linux-32bit.tar.gz" }, { version: "0.20.3", fileNamePattern: "{{variant}}_v{{version}}_Linux-32bit.tar.gz" }, { version: "0.17", fileNamePattern: "{{variant}}_{{version}}_Linux-32bit.tar.gz" }, { version: "0.16", fileNamePattern: "{{variant}}_{{version}}_linux-32bit.tgz" }, { version: "0.9", fileNamePattern: "{{variant}}_{{version}}_linux_386.tar.gz" }, { version: "0.8", fileNamePattern: "{{variant}}-linux-386" }, { version: "0.7", fileNamePattern: "{{variant}}-{{version}}-linux-386" } ] }, { os: "linux", arch: "arm", fileNamePatternHistory: [ { version: "0.103.0", fileNamePattern: "{{variant}}_{{version}}_linux-arm.tar.gz" }, { version: "0.20.4", fileNamePattern: "{{variant}}_{{version}}_Linux-ARM.tar.gz" }, { version: "0.20.3", fileNamePattern: "{{variant}}_v{{version}}_Linux-ARM.tar.gz" }, { version: "0.18", fileNamePattern: "{{variant}}_{{version}}_Linux_ARM.tar.gz" }, { version: "0.17", fileNamePattern: "{{variant}}_{{version}}_linux_arm.tar.gz" }, { version: "0.16", fileNamePattern: "{{variant}}_{{version}}_linux-arm32.tgz" }, { version: "0.9", fileNamePattern: "{{variant}}_{{version}}_linux_arm.tar.gz" }, { version: "0.8", fileNamePattern: "{{variant}}-linux-arm" }, { version: "0.7", fileNamePattern: null } ] }, { os: "linux", arch: "arm64", fileNamePatternHistory: [ { version: "0.103.0", fileNamePattern: "{{variant}}_{{version}}_linux-arm64.tar.gz" }, { version: "0.20.4", fileNamePattern: "{{variant}}_{{version}}_Linux-ARM64.tar.gz" }, { version: "0.20.3", fileNamePattern: "{{variant}}_v{{version}}_Linux-ARM64.tar.gz" }, { version: "0.17", fileNamePattern: null }, { version: "0.16", fileNamePattern: "{{variant}}_{{version}}_linux-arm64.tgz" }, { version: "0.7", fileNamePattern: null } ] }, { os: "freebsd", arch: "x64", fileNamePatternHistory: [ { version: "0.103.0", fileNamePattern: "{{variant}}_{{version}}_freebsd-amd64.tar.gz" }, { version: "0.20.4", fileNamePattern: "{{variant}}_{{version}}_FreeBSD-64bit.tar.gz" }, { version: "0.20.3", fileNamePattern: "{{variant}}_v{{version}}_FreeBSD-64bit.tar.gz" }, { version: "0.17", fileNamePattern: "{{variant}}_{{version}}_FreeBSD-64bit.zip" }, { version: "0.16", fileNamePattern: "{{variant}}_{{version}}_freebsd-64bit.tgz" }, { version: "0.9", fileNamePattern: "{{variant}}_{{version}}_freebsd_amd64.zip" }, { version: "0.8", fileNamePattern: "{{variant}}-freebsd-amd64" }, { version: "0.7", fileNamePattern: "{{variant}}-{{version}}-freebsd-x64" } ] }, { os: "freebsd", arch: "x86", fileNamePatternHistory: [ { version: "0.89.0", fileNamePattern: null }, { version: "0.20.4", fileNamePattern: "{{variant}}_{{version}}_FreeBSD-32bit.tar.gz" }, { version: "0.20.3", fileNamePattern: "{{variant}}_v{{version}}_FreeBSD-32bit.tar.gz" }, { version: "0.17", fileNamePattern: "{{variant}}_{{version}}_FreeBSD-32bit.zip" }, { version: "0.16", fileNamePattern: "{{variant}}_{{version}}_freebsd-32bit.tgz" }, { version: "0.9", fileNamePattern: "{{variant}}_{{version}}_freebsd_386.zip" }, { version: "0.8", fileNamePattern: "{{variant}}-freebsd-386" }, { version: "0.7", fileNamePattern: "{{variant}}-{{version}}-freebsd-386" } ] }, { os: "freebsd", arch: "arm", fileNamePatternHistory: [ { version: "0.89.0", fileNamePattern: null }, { version: "0.20.4", fileNamePattern: "{{variant}}_{{version}}_FreeBSD-ARM.tar.gz" }, { version: "0.20.3", fileNamePattern: "{{variant}}_v{{version}}_FreeBSD-ARM.tar.gz" }, { version: "0.18", fileNamePattern: "{{variant}}_{{version}}_FreeBSD_ARM.zip" }, { version: "0.17", fileNamePattern: "{{variant}}_{{version}}_freebsd_arm.zip" }, { version: "0.16", fileNamePattern: "{{variant}}_{{version}}_freebsd-arm32.tgz" }, { version: "0.9", fileNamePattern: "{{variant}}_{{version}}_freebsd_arm.zip" }, { version: "0.8", fileNamePattern: "{{variant}}-freebsd-arm" }, { version: "0.7", fileNamePattern: null } ] }, { os: "freebsd", arch: "arm64", fileNamePatternHistory: [ { version: "0.89.0", fileNamePattern: null }, { version: "0.81.0", fileNamePattern: "{{variant}}_{{version}}_FreeBSD-ARM64.tar.gz" }, { version: "0.7", fileNamePattern: null } ] }, { os: "openbsd", arch: "x64", fileNamePatternHistory: [ { version: "0.103.0", fileNamePattern: "{{variant}}_{{version}}_openbsd-amd64.tar.gz" }, { version: "0.20.4", fileNamePattern: "{{variant}}_{{version}}_OpenBSD-64bit.tar.gz" }, { version: "0.20.3", fileNamePattern: "{{variant}}_v{{version}}_OpenBSD-64bit.tar.gz" }, { version: "0.17", fileNamePattern: "{{variant}}_{{version}}_OpenBSD-64bit.zip" }, { version: "0.16", fileNamePattern: "{{variant}}_{{version}}_openbsd-64bit.tgz" }, { version: "0.15", fileNamePattern: "{{variant}}_{{version}}_openbsd_amd64.zip" }, { version: "0.10", fileNamePattern: null }, { version: "0.9", fileNamePattern: "{{variant}}_{{version}}_openbsd_amd64.zip" }, { version: "0.7", fileNamePattern: null } ] }, { os: "openbsd", arch: "x86", fileNamePatternHistory: [ { version: "0.89.0", fileNamePattern: null }, { version: "0.20.4", fileNamePattern: "{{variant}}_{{version}}_OpenBSD-32bit.tar.gz" }, { version: "0.20.3", fileNamePattern: "{{variant}}_v{{version}}_OpenBSD-32bit.tar.gz" }, { version: "0.17", fileNamePattern: "{{variant}}_{{version}}_OpenBSD-32bit.zip" }, { version: "0.16", fileNamePattern: "{{variant}}_{{version}}_openbsd-32bit.tgz" }, { version: "0.15", fileNamePattern: "{{variant}}_{{version}}_openbsd_386.zip" }, { version: "0.10", fileNamePattern: null }, { version: "0.9", fileNamePattern: "{{variant}}_{{version}}_openbsd_386.zip" }, { version: "0.7", fileNamePattern: null } ] }, { os: "openbsd", arch: "arm", fileNamePatternHistory: [ { version: "0.89.0", fileNamePattern: null }, { version: "0.48", fileNamePattern: "{{variant}}_{{version}}_OpenBSD-ARM.tar.gz" }, { version: "0.7", fileNamePattern: null } ] }, { os: "freebsd", arch: "arm64", fileNamePatternHistory: [ { version: "0.89.0", fileNamePattern: null }, { version: "0.81.0", fileNamePattern: "{{variant}}_{{version}}_FreeBSD-ARM64.tar.gz" }, { version: "0.7", fileNamePattern: null } ] } ]; const checksumFilePatternHistory = [ { version: "0.56.0", fileNamePattern: "{{variant}}_{{version}}_checksums.txt", useSpecificVariant: false }, { version: "0.43", fileNamePattern: "{{variant}}_{{version}}_checksums.txt", useSpecificVariant: true }, { version: "0.27", fileNamePattern: "{{variant}}_{{version}}_checksums.txt", useSpecificVariant: false }, { version: "0.20.3", fileNamePattern: "{{variant}}_checksums.txt", useSpecificVariant: false }, { version: "0.7", fileNamePattern: null, useSpecificVariant: false } ]; var hugoReleasesMeta = { binaries: binaries, checksumFilePatternHistory: checksumFilePatternHistory }; const createFetchOptions = ({ httpProxy, httpsProxy, }) => { return { agent: Object.assign(Object.assign({}, (httpProxy === null ? {} : { http: new HttpProxyAgent({ proxy: httpProxy, }), })), (httpsProxy === null ? {} : { https: new HttpsProxyAgent({ proxy: httpsProxy, }), })), }; }; const cleanupAndPrepareDestination = async ({ destination }) => { await deleteAsync(path.join(destination, '**')); await fs.promises.mkdir(destination, { recursive: true }); }; const fetchBinary = async ({ arch, downloadUrl, extended, httpProxy, httpsProxy, os, version, }) => { const binaryMeta = hugoReleasesMeta.binaries.find((binary) => { return binary.arch === arch && binary.os === os; }) || null; if (binaryMeta === null) { throw new Error(`A hugo release for os="${os}" and arch="${arch}" is not known to "hugo-installer". If you think this is a bug, feel free to open a GitHub issue here: https://github.com/dominique-mueller/hugo-installer/issues/new.`); } const binaryFileNamePattern = binaryMeta.fileNamePatternHistory.reverse().reduce((fileNamePattern, fileNamePatternHistoryItem) => { return semver.gte(semver.coerce(version), semver.coerce(fileNamePatternHistoryItem.version)) ? fileNamePatternHistoryItem : fileNamePattern; }, null); if (binaryFileNamePattern === null || binaryFileNamePattern.fileNamePattern === null) { throw new Error(`A Hugo binary file for version="${version}"${extended ? ' (extended)' : ''}, os="${os}" and arch="${arch}" is not known to "hugo-installer". If you think this is a bug, feel free to open a GitHub issue here: https://github.com/dominique-mueller/hugo-installer/issues/new.`); } const binaryFileName = binaryFileNamePattern.fileNamePattern .replace('{{variant}}', extended ? 'hugo_extended' : 'hugo') .replace('{{version}}', version); const binaryUrl = new URL(`v${version}/${binaryFileName}`, downloadUrl).toString(); console.log(`> Downloading binary from "${binaryUrl}"`); let binaryAsBuffer; try { const binaryResponse = await got(binaryUrl, createFetchOptions({ httpProxy, httpsProxy })); binaryAsBuffer = binaryResponse.rawBody; } catch (error) { throw new Error(`An error occured while trying to download the binary from "${binaryUrl}". Details: ${error.message}`); } return { binaryAsBuffer, binaryFileName, }; }; const verifyBinaryChecksum = async (binaryAsBuffer, binaryFileName, { downloadUrl, extended, httpProxy, httpsProxy, version, }) => { const checksumFileNamePattern = hugoReleasesMeta.checksumFilePatternHistory .reverse() .reduce((fileNamePattern, fileNamePatternHistoryItem) => { return semver.gte(semver.coerce(version), semver.coerce(fileNamePatternHistoryItem.version)) ? fileNamePatternHistoryItem : fileNamePattern; }, null); if (checksumFileNamePattern == null || checksumFileNamePattern.fileNamePattern === null) { throw new Error(`A Hugo checksum file for version="${version}"${extended ? ' (extended)' : ''} is not known to "hugo-installer". If you think this is a bug, feel free to open a GitHub issue here: https://github.com/dominique-mueller/hugo-installer/issues/new.`); } const checksumFileName = checksumFileNamePattern.fileNamePattern .replace('{{variant}}', extended && checksumFileNamePattern.useSpecificVariant ? 'hugo_extended' : 'hugo') .replace('{{version}}', version); const checksumUrl = new URL(`v${version}/${checksumFileName}`, downloadUrl).toString(); console.log(`> Downloading checksum from "${checksumUrl}"`); let rawChecksums; try { const checksumResponse = await got(checksumUrl, createFetchOptions({ httpProxy, httpsProxy })); rawChecksums = checksumResponse.body; } catch (error) { throw new Error(`An error occured while trying to download the checksum. Details: ${error.message}`); } const rawChecksumLine = rawChecksums.split('\n').find((rawChecksumLine) => { return rawChecksumLine.endsWith(binaryFileName); }) || null; if (rawChecksumLine === null) { throw new Error(`An error occured while trying to find the checksum for version "${version}" the checksum.`); } const expectedChecksum = rawChecksumLine.split(' ')[0]; const actualChecksum = crypto.createHash('sha256').update(binaryAsBuffer).digest('hex'); console.log(`> Verifying binary checksum`); if (actualChecksum !== expectedChecksum) { throw new Error(`The binary file could not be verified by its checksum. Expected: "${expectedChecksum}". Actual: "${actualChecksum}"`); } }; const writeBinaryToDisk = async (binaryAsBuffer, { destination }) => { console.log(`> Extracting binary to disk`); const decompressedFiles = await decompress(binaryAsBuffer, destination); await Promise.all(decompressedFiles.map((decompressedFile) => { return fs.promises.chmod(path.join(destination, decompressedFile.path), 0o755); })); }; const verifyBinaryHealth = async ({ destination }) => { console.log(`> Verifying binary health`); return new Promise((resolve, reject) => { let hugoVersionConsoleOutput = null; const childProcess = spawn(path.join(destination, 'hugo'), ['version']); childProcess.stdout.on('data', (data) => { hugoVersionConsoleOutput = data.toString().replace(/\r?\n|\r/g, ''); }); childProcess.on('close', () => { resolve(hugoVersionConsoleOutput); }); childProcess.on('error', (error) => { reject(`An error occured while verifiy the binary health. Details: ${error.message}`); }); }); }; const generateAndWriteVersionToDisk = async ({ arch, destination, extended, os, version, }) => { const versionFileContent = { arch, extended, os, version, }; await fs.promises.writeFile(path.join(destination, 'version.json'), JSON.stringify(versionFileContent, null, ' ')); }; const checkForExistingBinary = async ({ arch, destination, extended, os, version, }) => { console.log('> Checking for existing binary'); try { const versionFileContentRaw = await fs.promises.readFile(path.join(destination, 'version.json'), { encoding: 'utf-8' }); const versionFileContent = JSON.parse(versionFileContentRaw); const doesBinaryAlreadyExist = versionFileContent.arch === arch && versionFileContent.extended === extended && versionFileContent.os === os && versionFileContent.version === version; if (doesBinaryAlreadyExist) { console.log(' Binary already exists!'); } return doesBinaryAlreadyExist; } catch (error) { return false; } }; async function installHugo({ arch, downloadUrl, destination, extended, force, httpProxy, httpsProxy, os, skipChecksumCheck, skipHealthCheck, version, }) { console.log(''); console.log('Hugo Installer'); console.log(''); const doesBinaryAlreadyExist = force ? false : await checkForExistingBinary({ arch, destination, extended, os, version, }); if (!doesBinaryAlreadyExist) { await cleanupAndPrepareDestination({ destination }); const { binaryAsBuffer, binaryFileName } = await fetchBinary({ arch, downloadUrl, extended, httpProxy, httpsProxy, os, version }); if (!skipChecksumCheck) { await verifyBinaryChecksum(binaryAsBuffer, binaryFileName, { downloadUrl, extended, httpProxy, httpsProxy, version }); } await writeBinaryToDisk(binaryAsBuffer, { destination }); } let versionOutput = null; if (!skipHealthCheck) { versionOutput = await verifyBinaryHealth({ destination }); } await generateAndWriteVersionToDisk({ arch, destination, extended, os, version, }); console.log(''); console.log(`Hugo is now available in "${destination}".`); console.log(''); console.log(`- Version ${version}`); console.log(`- Extended ${extended ? 'Yes' : 'No'}`); console.log(`- OS ${os}`); console.log(`- Architecture ${arch}`); if (versionOutput) { console.log(''); console.log(versionOutput); } console.log(''); } export { installHugo }; //# sourceMappingURL=index.js.map