You've already forked hugo-extended
mirror of
https://github.com/jakejarvis/hugo-extended.git
synced 2026-06-12 08:45:27 -04:00
fix: support hugo's new macos .pkg installer (https://github.com/gohugoio/hugo/issues/14135)
- Replaced `careful-downloader` with custom download and checksum verification functions. - Updated `log-symbols`, `read-pkg-up`, and added `adm-zip` and `tar` as dependencies. - Enhanced installation process for macOS and other platforms, including cleanup of downloaded files. - Adjusted binary path handling for macOS.
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
import globals from "globals";
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: [
|
||||
"vendor/**",
|
||||
"*.d.ts",
|
||||
],
|
||||
},
|
||||
{
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
sourceType: "module",
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.es6,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
"brace-style": "error",
|
||||
|
||||
camelcase: ["error", {
|
||||
properties: "never",
|
||||
ignoreDestructuring: true,
|
||||
}],
|
||||
|
||||
"comma-dangle": ["error", {
|
||||
arrays: "always-multiline",
|
||||
objects: "always-multiline",
|
||||
imports: "always-multiline",
|
||||
exports: "always-multiline",
|
||||
functions: "never",
|
||||
}],
|
||||
"comma-spacing": "error",
|
||||
"comma-style": "error",
|
||||
curly: ["error", "multi-line"],
|
||||
"func-call-spacing": "error",
|
||||
|
||||
"no-multiple-empty-lines": ["error", {
|
||||
max: 1,
|
||||
}],
|
||||
|
||||
"no-tabs": "error",
|
||||
"no-trailing-spaces": "error",
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
|
||||
"one-var": ["error", {
|
||||
var: "never",
|
||||
let: "never",
|
||||
const: "never",
|
||||
}],
|
||||
|
||||
"operator-linebreak": ["error", "after", { overrides: { "?": "before", ":": "before" } }],
|
||||
"padded-blocks": ["error", "never"],
|
||||
"quote-props": ["error", "as-needed"],
|
||||
|
||||
quotes: ["error", "double", {
|
||||
avoidEscape: true,
|
||||
allowTemplateLiterals: true,
|
||||
}],
|
||||
|
||||
semi: "error",
|
||||
"semi-spacing": "error",
|
||||
"space-before-blocks": "error",
|
||||
|
||||
"space-before-function-paren": ["error", {
|
||||
named: "never",
|
||||
anonymous: "always",
|
||||
asyncArrow: "always",
|
||||
}],
|
||||
|
||||
"spaced-comment": ["error", "always", {
|
||||
line: {
|
||||
markers: ["/"],
|
||||
exceptions: ["-", "+"],
|
||||
},
|
||||
|
||||
block: {
|
||||
markers: ["!"],
|
||||
exceptions: ["*"],
|
||||
balanced: true,
|
||||
},
|
||||
}],
|
||||
|
||||
"template-tag-spacing": ["error", "never"],
|
||||
|
||||
"arrow-parens": ["error", "always"],
|
||||
|
||||
"arrow-spacing": ["error", {
|
||||
before: true,
|
||||
after: true,
|
||||
}],
|
||||
|
||||
"no-confusing-arrow": ["error", {
|
||||
allowParens: true,
|
||||
}],
|
||||
|
||||
"no-var": "error",
|
||||
|
||||
"prefer-const": ["error", {
|
||||
destructuring: "any",
|
||||
ignoreReadBeforeAssign: true,
|
||||
}],
|
||||
|
||||
"prefer-destructuring": ["error", {
|
||||
VariableDeclarator: {
|
||||
array: false,
|
||||
object: true,
|
||||
},
|
||||
|
||||
AssignmentExpression: {
|
||||
array: true,
|
||||
object: false,
|
||||
},
|
||||
}],
|
||||
|
||||
"prefer-rest-params": "error",
|
||||
"prefer-spread": "error",
|
||||
"template-curly-spacing": "error",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["test/**/*.js"],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.mocha,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
+105
-21
@@ -1,7 +1,11 @@
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import { fileURLToPath } from "url";
|
||||
import downloader from "careful-downloader";
|
||||
import { spawnSync } from "child_process";
|
||||
import { pipeline } from "stream/promises";
|
||||
import crypto from "crypto";
|
||||
import * as tar from "tar";
|
||||
import AdmZip from "adm-zip";
|
||||
import logSymbols from "log-symbols";
|
||||
import {
|
||||
getPkgVersion,
|
||||
@@ -15,6 +19,41 @@ import {
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
async function downloadFile(url, dest) {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to download ${url}: ${response.statusText}`);
|
||||
}
|
||||
await pipeline(response.body, fs.createWriteStream(dest));
|
||||
}
|
||||
|
||||
async function verifyChecksum(filePath, checksumUrl, filename) {
|
||||
const response = await fetch(checksumUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to download checksums: ${response.statusText}`);
|
||||
}
|
||||
const checksums = await response.text();
|
||||
|
||||
// checksums file format: "sha256 filename"
|
||||
const expectedChecksum = checksums
|
||||
.split("\n")
|
||||
.find((line) => line.endsWith(filename))
|
||||
?.split(/\s+/)[0];
|
||||
|
||||
if (!expectedChecksum) {
|
||||
throw new Error(`Checksum for ${filename} not found in checksums file.`);
|
||||
}
|
||||
|
||||
const fileBuffer = fs.readFileSync(filePath);
|
||||
const hash = crypto.createHash("sha256");
|
||||
hash.update(fileBuffer);
|
||||
const actualChecksum = hash.digest("hex");
|
||||
|
||||
if (actualChecksum !== expectedChecksum) {
|
||||
throw new Error(`Checksum mismatch! Expected ${expectedChecksum}, got ${actualChecksum}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function install() {
|
||||
try {
|
||||
const version = getPkgVersion();
|
||||
@@ -22,36 +61,81 @@ async function install() {
|
||||
const checksumFile = getChecksumFilename(version);
|
||||
const binFile = getBinFilename();
|
||||
|
||||
// stop here if there's nothing we can download
|
||||
if (!releaseFile)
|
||||
if (!releaseFile) {
|
||||
throw new Error(`Are you sure this platform is supported? See: https://github.com/gohugoio/hugo/releases/tag/v${version}`);
|
||||
}
|
||||
|
||||
// warn if platform doesn't support Hugo Extended, proceed with vanilla Hugo
|
||||
if (!isExtended(releaseFile))
|
||||
if (!isExtended(releaseFile)) {
|
||||
console.warn(`${logSymbols.info} Hugo Extended isn't supported on this platform, downloading vanilla Hugo instead.`);
|
||||
}
|
||||
|
||||
// download release from GitHub and verify its checksum
|
||||
const download = await downloader(getReleaseUrl(version, releaseFile), {
|
||||
checksumUrl: getReleaseUrl(version, checksumFile),
|
||||
filename: releaseFile,
|
||||
destDir: path.join(__dirname, "..", "vendor"),
|
||||
algorithm: "sha256",
|
||||
extract: true,
|
||||
});
|
||||
// Prepare vendor directory
|
||||
const vendorDir = path.join(__dirname, "..", "vendor");
|
||||
if (!fs.existsSync(vendorDir)) {
|
||||
fs.mkdirSync(vendorDir, { recursive: true });
|
||||
}
|
||||
|
||||
// full path to the binary
|
||||
const installedToPath = path.join(download, binFile);
|
||||
const releaseUrl = getReleaseUrl(version, releaseFile);
|
||||
const checksumUrl = getReleaseUrl(version, checksumFile);
|
||||
const downloadPath = path.join(vendorDir, releaseFile);
|
||||
|
||||
// ensure hugo[.exe] is executable
|
||||
fs.chmodSync(installedToPath, 0o755);
|
||||
console.info(`${logSymbols.info} Downloading ${releaseFile}...`);
|
||||
await downloadFile(releaseUrl, downloadPath);
|
||||
|
||||
console.info(`${logSymbols.info} Verifying checksum...`);
|
||||
await verifyChecksum(downloadPath, checksumUrl, releaseFile);
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
console.info(`${logSymbols.info} Installing ${releaseFile} (requires sudo)...`);
|
||||
// Run MacOS installer
|
||||
const result = spawnSync("sudo", ["installer", "-pkg", downloadPath, "-target", "/"], {
|
||||
stdio: "inherit",
|
||||
});
|
||||
|
||||
if (result.error) throw result.error;
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`Installer failed with exit code ${result.status}`);
|
||||
}
|
||||
|
||||
// Cleanup downloaded pkg
|
||||
fs.unlinkSync(downloadPath);
|
||||
} else {
|
||||
console.info(`${logSymbols.info} Extracting...`);
|
||||
|
||||
if (releaseFile.endsWith(".zip")) {
|
||||
const zip = new AdmZip(downloadPath);
|
||||
zip.extractAllTo(vendorDir, true);
|
||||
|
||||
// Cleanup zip
|
||||
fs.unlinkSync(downloadPath);
|
||||
} else if (releaseFile.endsWith(".tar.gz")) {
|
||||
await tar.x({
|
||||
file: downloadPath,
|
||||
cwd: vendorDir,
|
||||
});
|
||||
|
||||
// Cleanup tar.gz
|
||||
fs.unlinkSync(downloadPath);
|
||||
}
|
||||
|
||||
const binPath = path.join(vendorDir, binFile);
|
||||
if (fs.existsSync(binPath)) {
|
||||
fs.chmodSync(binPath, 0o755);
|
||||
}
|
||||
}
|
||||
|
||||
console.info(`${logSymbols.success} Hugo installed successfully!`);
|
||||
console.info(getBinVersion(installedToPath));
|
||||
|
||||
// return the full path to our Hugo binary
|
||||
return installedToPath;
|
||||
// Check version
|
||||
if (process.platform === "darwin") {
|
||||
console.info(getBinVersion("/usr/local/bin/hugo"));
|
||||
return "/usr/local/bin/hugo";
|
||||
} else {
|
||||
const binPath = path.join(vendorDir, binFile);
|
||||
console.info(getBinVersion(binPath));
|
||||
return binPath;
|
||||
}
|
||||
} catch (error) {
|
||||
// pass whatever error occurred along the way to console
|
||||
console.error(`${logSymbols.error} Hugo installation failed. :(`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
+25
-21
@@ -2,7 +2,7 @@ import path from "path";
|
||||
import fs from "fs";
|
||||
import { fileURLToPath } from "url";
|
||||
import { execFileSync } from "child_process";
|
||||
import { readPackageUpSync } from "read-pkg-up";
|
||||
import { readPackageUpSync } from "read-package-up";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
@@ -26,11 +26,13 @@ export function getBinFilename() {
|
||||
|
||||
// Simple shortcut to ./vendor/hugo[.exe] from package root.
|
||||
export function getBinPath() {
|
||||
if (process.platform === "darwin") return "/usr/local/bin/hugo";
|
||||
|
||||
return path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"vendor",
|
||||
getBinFilename(),
|
||||
getBinFilename()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,12 +46,14 @@ export function getBinVersion(bin) {
|
||||
// Simply detect if the given file exists.
|
||||
export function doesBinExist(bin) {
|
||||
try {
|
||||
if (fs.existsSync(bin))
|
||||
if (fs.existsSync(bin)) {
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
// something bad happened besides Hugo not existing
|
||||
if (error.code !== "ENOENT")
|
||||
if (error.code !== "ENOENT") {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -63,33 +67,33 @@ export function getReleaseFilename(version) {
|
||||
|
||||
const filename =
|
||||
// macOS: as of 0.102.0, binaries are universal
|
||||
platform === "darwin" && arch === "x64" ?
|
||||
`hugo_extended_${version}_darwin-universal.tar.gz` :
|
||||
platform === "darwin" && arch === "arm64" ?
|
||||
`hugo_extended_${version}_darwin-universal.tar.gz` :
|
||||
platform === "darwin" && arch === "x64"
|
||||
? `hugo_extended_${version}_darwin-universal.pkg`
|
||||
: platform === "darwin" && arch === "arm64"
|
||||
? `hugo_extended_${version}_darwin-universal.pkg`
|
||||
|
||||
// Windows
|
||||
platform === "win32" && arch === "x64" ?
|
||||
`hugo_extended_${version}_windows-amd64.zip` :
|
||||
platform === "win32" && arch === "arm64" ?
|
||||
`hugo_${version}_windows-arm64.zip` :
|
||||
: platform === "win32" && arch === "x64"
|
||||
? `hugo_extended_${version}_windows-amd64.zip`
|
||||
: platform === "win32" && arch === "arm64"
|
||||
? `hugo_${version}_windows-arm64.zip`
|
||||
|
||||
// Linux
|
||||
platform === "linux" && arch === "x64" ?
|
||||
`hugo_extended_${version}_linux-amd64.tar.gz` :
|
||||
platform === "linux" && arch === "arm64" ?
|
||||
`hugo_extended_${version}_linux-arm64.tar.gz` :
|
||||
: platform === "linux" && arch === "x64"
|
||||
? `hugo_extended_${version}_linux-amd64.tar.gz`
|
||||
: platform === "linux" && arch === "arm64"
|
||||
? `hugo_extended_${version}_linux-arm64.tar.gz`
|
||||
|
||||
// FreeBSD
|
||||
platform === "freebsd" && arch === "x64" ?
|
||||
`hugo_${version}_freebsd-amd64.tar.gz` :
|
||||
: platform === "freebsd" && arch === "x64"
|
||||
? `hugo_${version}_freebsd-amd64.tar.gz`
|
||||
|
||||
// OpenBSD
|
||||
platform === "openbsd" && arch === "x64" ?
|
||||
`hugo_${version}_openbsd-amd64.tar.gz` :
|
||||
: platform === "openbsd" && arch === "x64"
|
||||
? `hugo_${version}_openbsd-amd64.tar.gz`
|
||||
|
||||
// not gonna work :(
|
||||
null;
|
||||
: null;
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
Generated
+922
-1882
File diff suppressed because it is too large
Load Diff
+10
-23
@@ -30,15 +30,17 @@
|
||||
"exports": "./index.js",
|
||||
"types": "./index.d.ts",
|
||||
"dependencies": {
|
||||
"careful-downloader": "^3.0.0",
|
||||
"log-symbols": "^5.1.0",
|
||||
"read-pkg-up": "^9.1.0"
|
||||
"adm-zip": "^0.5.16",
|
||||
"log-symbols": "^7.0.1",
|
||||
"read-package-up": "^11.0.0",
|
||||
"tar": "^7.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jakejarvis/eslint-config": "*",
|
||||
"del": "^7.1.0",
|
||||
"eslint": "^8.56.0",
|
||||
"mocha": "^10.3.0"
|
||||
"@types/adm-zip": "^0.5.7",
|
||||
"del": "^8.0.1",
|
||||
"eslint": "^9.39.2",
|
||||
"globals": "^16.5.0",
|
||||
"mocha": "^11.7.5"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "node postinstall.js",
|
||||
@@ -65,20 +67,5 @@
|
||||
"frontmatter",
|
||||
"go",
|
||||
"golang"
|
||||
],
|
||||
"eslintConfig": {
|
||||
"extends": "@jakejarvis/eslint-config",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"vendor/**",
|
||||
"*.d.ts"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+14
-4
@@ -1,4 +1,4 @@
|
||||
/* eslint-env node, mocha */
|
||||
/* global it, assert -- Globals defined by Mocha */
|
||||
import path from "path";
|
||||
import { execFile } from "child_process";
|
||||
import assert from "assert";
|
||||
@@ -12,8 +12,9 @@ it("Hugo exists and runs?", async function () {
|
||||
const hugoPath = await hugo();
|
||||
|
||||
assert(execFile(hugoPath, ["env"], function (error, stdout) {
|
||||
if (error)
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.log(stdout);
|
||||
}));
|
||||
@@ -22,14 +23,23 @@ it("Hugo exists and runs?", async function () {
|
||||
it("Hugo doesn't exist, install it instead of throwing an error", async function () {
|
||||
this.timeout(30000); // increase timeout to an excessive 30 seconds for CI
|
||||
|
||||
// On macOS, the binary is installed to /usr/local/bin, which we can't/shouldn't delete.
|
||||
// We also can't easily test the reinstall flow without sudo.
|
||||
if (process.platform === "darwin") {
|
||||
console.log("Skipping reinstall test on macOS (requires sudo/system modification)");
|
||||
this.skip();
|
||||
return;
|
||||
}
|
||||
|
||||
// delete binary to ensure it's auto-reinstalled
|
||||
await deleteAsync(path.dirname(getBinPath()));
|
||||
await deleteAsync(path.dirname(getBinPath()), { force: true });
|
||||
|
||||
const hugoPath = await hugo();
|
||||
|
||||
assert(execFile(hugoPath, ["version"], function (error, stdout) {
|
||||
if (error)
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.log(stdout);
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user