mirror of
https://github.com/jakejarvis/hugo-extended.git
synced 2026-07-29 18:45:20 -04:00
fix: update macOS installation process to extract Hugo binary without sudo (#184)
This commit is contained in:
+14
-13
@@ -219,7 +219,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "Extended version verified"
|
echo "Extended version verified"
|
||||||
|
|
||||||
# macOS-specific tests (symlink behavior, pkg installation)
|
# macOS-specific tests (pkg extraction via pkgutil, no sudo required)
|
||||||
macos-quirks:
|
macos-quirks:
|
||||||
name: macOS quirks
|
name: macOS quirks
|
||||||
needs: unit
|
needs: unit
|
||||||
@@ -252,27 +252,28 @@ jobs:
|
|||||||
- name: Run postinstall
|
- name: Run postinstall
|
||||||
run: node postinstall.js
|
run: node postinstall.js
|
||||||
|
|
||||||
- name: Verify symlink structure
|
- name: Verify binary is regular file (not symlink)
|
||||||
run: |
|
run: |
|
||||||
if [ ! -L "bin/hugo" ]; then
|
if [ -L "bin/hugo" ]; then
|
||||||
echo "bin/hugo is not a symlink!"
|
echo "bin/hugo should not be a symlink (we use pkgutil extraction now)!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
TARGET=$(readlink bin/hugo)
|
if [ ! -f "bin/hugo" ]; then
|
||||||
echo "Symlink target: $TARGET"
|
echo "bin/hugo is not a regular file!"
|
||||||
if [ "$TARGET" != "/usr/local/bin/hugo" ]; then
|
|
||||||
echo "Unexpected symlink target!"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "Symlink structure verified"
|
echo "Binary is a regular file (extracted from .pkg via pkgutil)"
|
||||||
|
|
||||||
- name: Verify system Hugo binary
|
- name: Verify executable permissions
|
||||||
run: |
|
run: |
|
||||||
if [ ! -f "/usr/local/bin/hugo" ]; then
|
if [ ! -x "bin/hugo" ]; then
|
||||||
echo "System Hugo binary not found!"
|
echo "bin/hugo is not executable!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
/usr/local/bin/hugo version
|
echo "Permissions verified"
|
||||||
|
|
||||||
|
- name: Verify binary runs
|
||||||
|
run: ./bin/hugo version
|
||||||
|
|
||||||
# Linux- tests (tar.gz extraction, permissions)
|
# Linux- tests (tar.gz extraction, permissions)
|
||||||
linux-quirks:
|
linux-quirks:
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ Notes for LLM coding agents working on `hugo-extended`.
|
|||||||
|
|
||||||
- **Binary installation**: `src/lib/install.ts`
|
- **Binary installation**: `src/lib/install.ts`
|
||||||
- Downloads Hugo release assets and verifies SHA-256 checksums.
|
- Downloads Hugo release assets and verifies SHA-256 checksums.
|
||||||
- **macOS v0.153.0+**: uses `sudo installer -pkg ... -target /` (and then symlinks `bin/hugo` -> `/usr/local/bin/hugo`).
|
- **macOS v0.153.0+**: uses `pkgutil --expand-full` to extract the binary from the `.pkg` file (no sudo required).
|
||||||
- **macOS pre-v0.153.0**: extracts `.tar.gz` archive into `bin/` (no sudo required).
|
- **macOS pre-v0.153.0**: extracts `.tar.gz` archive into `bin/`.
|
||||||
- **non-macOS**: extracts archive into `bin/` and `chmod +x`.
|
- **non-macOS**: extracts archive into `bin/` and `chmod +x`.
|
||||||
|
|
||||||
- **Environment variables**: `src/lib/env.ts`
|
- **Environment variables**: `src/lib/env.ts`
|
||||||
@@ -139,6 +139,6 @@ Some variables have aliases (e.g., `HUGO_FORCE_STANDARD` → `HUGO_NO_EXTENDED`,
|
|||||||
|
|
||||||
### Version-dependent behavior
|
### Version-dependent behavior
|
||||||
|
|
||||||
- **macOS v0.153.0+**: Hugo ships as `.pkg` installer, requires `sudo`.
|
- **macOS v0.153.0+**: Hugo ships as `.pkg` installer, extracted locally using `pkgutil --expand-full` (no sudo required).
|
||||||
- **macOS pre-v0.153.0**: Hugo ships as `.tar.gz`, extracted to `bin/` directly.
|
- **macOS pre-v0.153.0**: Hugo ships as `.tar.gz`, extracted to `bin/` directly.
|
||||||
- The `usesMacOSPkg(version)` and `compareVersions(a, b)` utilities in `src/lib/utils.ts` handle this.
|
- The `usesMacOSPkg(version)` and `compareVersions(a, b)` utilities in `src/lib/utils.ts` handle this.
|
||||||
|
|||||||
@@ -252,9 +252,9 @@ If Hugo seems to disappear (rare edge case), it will be automatically reinstalle
|
|||||||
npm rebuild hugo-extended
|
npm rebuild hugo-extended
|
||||||
```
|
```
|
||||||
|
|
||||||
### Permission issues on macOS
|
### macOS installation
|
||||||
|
|
||||||
As of [v0.153.0](https://github.com/gohugoio/hugo/releases/tag/v0.153.0), Hugo is distributed as a full installer for macOS, rather than a simple binary/executable file. This package will make its best effort to run the installer for you (which includes prompting you for `sudo` access) but this method introduces infinitely more opportunities for things to go wrong. Please [open an issue](https://github.com/jakejarvis/hugo-extended/issues/new) if you encounter any issues.
|
As of [v0.153.0](https://github.com/gohugoio/hugo/releases/tag/v0.153.0), Hugo is distributed as a `.pkg` installer for macOS. This package extracts the binary locally using `pkgutil --expand-full`, so **no sudo or global installation is required**. The Hugo binary stays in `node_modules` just like on other platforms.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -47,7 +47,7 @@ export const getHugoBinary = async (): Promise<string> => {
|
|||||||
// See: https://github.com/jakejarvis/hugo-extended/issues/81
|
// See: https://github.com/jakejarvis/hugo-extended/issues/81
|
||||||
if (!doesBinExist(bin)) {
|
if (!doesBinExist(bin)) {
|
||||||
// Hugo isn't there for some reason. Try re-installing.
|
// Hugo isn't there for some reason. Try re-installing.
|
||||||
logger.info("⚠️ Hugo is missing, reinstalling now...");
|
logger.warn("Hugo is missing, reinstalling now...");
|
||||||
await install();
|
await install();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+80
-64
@@ -1,6 +1,7 @@
|
|||||||
import { spawnSync } from "node:child_process";
|
import { execSync } from "node:child_process";
|
||||||
import crypto from "node:crypto";
|
import crypto from "node:crypto";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { Readable } from "node:stream";
|
import { Readable } from "node:stream";
|
||||||
import { pipeline } from "node:stream/promises";
|
import { pipeline } from "node:stream/promises";
|
||||||
@@ -85,6 +86,52 @@ async function downloadFile(url: string, dest: string): Promise<void> {
|
|||||||
await pipeline(Readable.fromWeb(response.body), fs.createWriteStream(dest));
|
await pipeline(Readable.fromWeb(response.body), fs.createWriteStream(dest));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts a Hugo binary from a macOS .pkg file without requiring sudo.
|
||||||
|
*
|
||||||
|
* Uses `pkgutil --expand-full` to expand the package, then locates and copies
|
||||||
|
* the Hugo binary from the payload to the destination directory.
|
||||||
|
*
|
||||||
|
* The Hugo .pkg structure after expansion contains:
|
||||||
|
* - A "Payload" directory containing the hugo binary directly
|
||||||
|
* - Or a component package directory with Payload inside
|
||||||
|
*
|
||||||
|
* @param pkgPath - The path to the .pkg file to extract
|
||||||
|
* @param destDir - The directory where the hugo binary should be placed
|
||||||
|
* @throws {Error} If extraction fails, Payload is not found, or hugo binary is missing
|
||||||
|
* @see https://github.com/jmooring/hvm/commit/16eb55ae4965b5d2e414061085490a90fe7ea73e
|
||||||
|
*/
|
||||||
|
export function extractPkg(pkgPath: string, destDir: string): void {
|
||||||
|
// Create a temporary directory for expansion
|
||||||
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "hugo-pkg-"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const expansionDir = path.join(tempDir, "expanded");
|
||||||
|
|
||||||
|
// Use pkgutil to expand the package without installing
|
||||||
|
execSync(`pkgutil --expand-full "${pkgPath}" "${expansionDir}"`, {
|
||||||
|
stdio: "pipe",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find the hugo binary in the expanded package
|
||||||
|
const hugoPayload = path.join(expansionDir, "Payload", "hugo");
|
||||||
|
|
||||||
|
if (!fs.existsSync(hugoPayload)) {
|
||||||
|
throw new Error(
|
||||||
|
"Could not find hugo binary in expanded .pkg. Expected path: */Payload/hugo",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the binary to the destination
|
||||||
|
const destPath = path.join(destDir, getBinFilename());
|
||||||
|
fs.copyFileSync(hugoPayload, destPath);
|
||||||
|
fs.chmodSync(destPath, 0o755);
|
||||||
|
} finally {
|
||||||
|
// Clean up the temp directory
|
||||||
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies that a downloaded file matches its expected SHA-256 checksum.
|
* Verifies that a downloaded file matches its expected SHA-256 checksum.
|
||||||
*
|
*
|
||||||
@@ -133,9 +180,11 @@ async function verifyChecksum(
|
|||||||
* - Determines the correct Hugo release file for the current platform and architecture
|
* - Determines the correct Hugo release file for the current platform and architecture
|
||||||
* - Downloads the release file and checksums from GitHub (or custom mirror)
|
* - Downloads the release file and checksums from GitHub (or custom mirror)
|
||||||
* - Verifies the integrity of the downloaded file using SHA-256 checksums (unless HUGO_SKIP_CHECKSUM is set)
|
* - Verifies the integrity of the downloaded file using SHA-256 checksums (unless HUGO_SKIP_CHECKSUM is set)
|
||||||
* - Extracts or installs the binary (platform-specific):
|
* - Extracts the binary (platform-specific):
|
||||||
* - macOS: Uses `sudo installer` to install the .pkg file to /usr/local/bin
|
* - macOS v0.153.0+: Extracts from .pkg using pkgutil (no sudo required)
|
||||||
* - Windows/Linux: Extracts the .zip or .tar.gz archive to the local bin directory
|
* - macOS pre-v0.153.0: Extracts from .tar.gz archive
|
||||||
|
* - Windows: Extracts from .zip archive
|
||||||
|
* - Linux/BSD: Extracts from .tar.gz archive
|
||||||
* - Sets appropriate file permissions on Unix-like systems
|
* - Sets appropriate file permissions on Unix-like systems
|
||||||
* - Displays the installed Hugo version
|
* - Displays the installed Hugo version
|
||||||
*
|
*
|
||||||
@@ -166,10 +215,10 @@ async function install(): Promise<string> {
|
|||||||
|
|
||||||
if (!isExtended(releaseFile)) {
|
if (!isExtended(releaseFile)) {
|
||||||
if (envConfig.forceStandard) {
|
if (envConfig.forceStandard) {
|
||||||
logger.info("ℹ️ Installing vanilla Hugo (HUGO_NO_EXTENDED is set).");
|
logger.info("Installing vanilla Hugo (HUGO_NO_EXTENDED is set).");
|
||||||
} else {
|
} else {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
"ℹ️ Hugo Extended isn't supported on this platform, downloading vanilla Hugo instead.",
|
"Hugo Extended isn't supported on this platform, downloading vanilla Hugo instead.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,83 +238,50 @@ async function install(): Promise<string> {
|
|||||||
|
|
||||||
if (envConfig.skipChecksum) {
|
if (envConfig.skipChecksum) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
"⚠️ Skipping checksum verification (HUGO_SKIP_CHECKSUM is set).",
|
"Skipping checksum verification (HUGO_SKIP_CHECKSUM is set).",
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
logger.info("🕵️ Verifying checksum...");
|
logger.info("🕵️ Verifying checksum...");
|
||||||
await verifyChecksum(downloadPath, checksumUrl, releaseFile);
|
await verifyChecksum(downloadPath, checksumUrl, releaseFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All other platforms and macOS pre-0.153.0 (tar.gz) use archive extraction
|
||||||
|
logger.info("📦 Extracting...");
|
||||||
const archiveType = getArchiveType(releaseFile);
|
const archiveType = getArchiveType(releaseFile);
|
||||||
|
|
||||||
// macOS .pkg files require special handling with sudo installer
|
// macOS .pkg files: extract using pkgutil (no sudo required)
|
||||||
if (process.platform === "darwin" && archiveType === "pkg") {
|
if (archiveType === "pkg") {
|
||||||
logger.info(`💾 Installing ${releaseFile} (requires sudo)...`);
|
extractPkg(downloadPath, binDir);
|
||||||
// Run MacOS installer
|
} else if (archiveType === "zip") {
|
||||||
const result = spawnSync(
|
const zip = new AdmZip(downloadPath);
|
||||||
"sudo",
|
zip.extractAllTo(binDir, true);
|
||||||
["installer", "-pkg", downloadPath, "-target", "/"],
|
} else if (archiveType === "tar.gz") {
|
||||||
{
|
await tar.x({
|
||||||
stdio: envConfig.quiet ? "pipe" : "inherit",
|
file: downloadPath,
|
||||||
},
|
cwd: binDir,
|
||||||
);
|
});
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
// Create symlink in local bin dir for consistency with other platforms
|
|
||||||
const symlinkPath = path.join(binDir, binFile);
|
|
||||||
if (
|
|
||||||
fs.existsSync(symlinkPath) ||
|
|
||||||
fs.lstatSync(symlinkPath, { throwIfNoEntry: false })
|
|
||||||
) {
|
|
||||||
fs.unlinkSync(symlinkPath);
|
|
||||||
}
|
|
||||||
fs.symlinkSync("/usr/local/bin/hugo", symlinkPath);
|
|
||||||
} else {
|
} else {
|
||||||
// All other platforms and macOS pre-0.153.0 (tar.gz) use archive extraction
|
// Defensive: should not happen since unsupported platforms are caught earlier
|
||||||
logger.info("📦 Extracting...");
|
throw new Error(
|
||||||
|
`Unexpected archive type for ${releaseFile}. Expected .zip, .tar.gz, or .pkg.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (archiveType === "zip") {
|
// Cleanup downloaded package
|
||||||
const zip = new AdmZip(downloadPath);
|
fs.unlinkSync(downloadPath);
|
||||||
zip.extractAllTo(binDir, true);
|
|
||||||
|
|
||||||
// Cleanup zip
|
const binPath = path.join(binDir, binFile);
|
||||||
fs.unlinkSync(downloadPath);
|
if (fs.existsSync(binPath)) {
|
||||||
} else if (archiveType === "tar.gz") {
|
fs.chmodSync(binPath, 0o755);
|
||||||
await tar.x({
|
|
||||||
file: downloadPath,
|
|
||||||
cwd: binDir,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Cleanup tar.gz
|
|
||||||
fs.unlinkSync(downloadPath);
|
|
||||||
} else {
|
|
||||||
// Defensive: should not happen since unsupported platforms are caught earlier
|
|
||||||
throw new Error(
|
|
||||||
`Unexpected archive type for ${releaseFile}. Expected .zip, .tar.gz, or .pkg.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const binPath = path.join(binDir, binFile);
|
|
||||||
if (fs.existsSync(binPath)) {
|
|
||||||
fs.chmodSync(binPath, 0o755);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("🎉 Hugo installed successfully!");
|
logger.info("🎉 Hugo installed successfully!");
|
||||||
|
|
||||||
// Check version and return path
|
// Check version and return path
|
||||||
const binPath = path.join(binDir, binFile);
|
|
||||||
logger.info(getBinVersion(binPath));
|
logger.info(getBinVersion(binPath));
|
||||||
return binPath;
|
return binPath;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("⛔ Hugo installation failed. :(");
|
logger.error("Hugo installation failed. :(");
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-4
@@ -120,8 +120,7 @@ export function getBinFilename(): string {
|
|||||||
* 1. HUGO_BIN_PATH environment variable (if set)
|
* 1. HUGO_BIN_PATH environment variable (if set)
|
||||||
* 2. Local bin directory (./bin/hugo or ./bin/hugo.exe)
|
* 2. Local bin directory (./bin/hugo or ./bin/hugo.exe)
|
||||||
*
|
*
|
||||||
* @returns The absolute path to hugo binary.
|
* @returns The absolute path to hugo binary
|
||||||
* On macOS (when using local bin), this is a symlink to "/usr/local/bin/hugo".
|
|
||||||
*/
|
*/
|
||||||
export function getBinPath(): string {
|
export function getBinPath(): string {
|
||||||
const envConfig = getEnvConfig();
|
const envConfig = getEnvConfig();
|
||||||
@@ -286,7 +285,7 @@ export const logger = {
|
|||||||
*/
|
*/
|
||||||
warn: (message: string): void => {
|
warn: (message: string): void => {
|
||||||
if (!getEnvConfig().quiet) {
|
if (!getEnvConfig().quiet) {
|
||||||
console.warn(message);
|
console.warn(`⚠ ${message}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -294,6 +293,6 @@ export const logger = {
|
|||||||
* Log an error message (always shown, even in quiet mode).
|
* Log an error message (always shown, even in quiet mode).
|
||||||
*/
|
*/
|
||||||
error: (message: string): void => {
|
error: (message: string): void => {
|
||||||
console.error(message);
|
console.error(`✖ ${message}`);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { execFileSync } from "node:child_process";
|
import { execFileSync } from "node:child_process";
|
||||||
import { existsSync, lstatSync, readlinkSync, statSync } from "node:fs";
|
import { existsSync, lstatSync, statSync } from "node:fs";
|
||||||
import { beforeAll, describe, expect, it } from "vitest";
|
import { beforeAll, describe, expect, it } from "vitest";
|
||||||
import hugo, { execWithOutput, getHugoBinary } from "../../src/hugo";
|
import hugo, { execWithOutput, getHugoBinary } from "../../src/hugo";
|
||||||
import {
|
import {
|
||||||
@@ -59,13 +59,13 @@ describe("Hugo Installation E2E", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
it.skipIf(process.platform !== "darwin")(
|
it.skipIf(process.platform !== "darwin")(
|
||||||
"should be a symlink to /usr/local/bin/hugo on macOS",
|
"should be a regular file on macOS (not a symlink)",
|
||||||
() => {
|
() => {
|
||||||
const isSymlink = lstatSync(binaryPath).isSymbolicLink();
|
// Since v0.153.0, we extract the .pkg locally using pkgutil
|
||||||
expect(isSymlink).toBe(true);
|
// instead of running `sudo installer`, so the binary is a regular file
|
||||||
|
const stats = lstatSync(binaryPath);
|
||||||
const target = readlinkSync(binaryPath);
|
expect(stats.isSymbolicLink()).toBe(false);
|
||||||
expect(target).toBe("/usr/local/bin/hugo");
|
expect(stats.isFile()).toBe(true);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import crypto from "node:crypto";
|
import crypto from "node:crypto";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import os from "node:os";
|
||||||
|
import path from "node:path";
|
||||||
import { afterEach, assert, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, assert, beforeEach, describe, expect, it } from "vitest";
|
||||||
import { getArchiveType, parseChecksumFile } from "../../src/lib/install";
|
import {
|
||||||
|
extractPkg,
|
||||||
|
getArchiveType,
|
||||||
|
parseChecksumFile,
|
||||||
|
} from "../../src/lib/install";
|
||||||
import { getReleaseFilename } from "../../src/lib/utils";
|
import { getReleaseFilename } from "../../src/lib/utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -165,6 +172,53 @@ f0e9d8c7b6a5432109876543210fedcba0987654321fedcba0987654321fedc hugo_extended_0
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("extractPkg", () => {
|
||||||
|
it.skipIf(process.platform !== "darwin")(
|
||||||
|
"should throw error for non-existent .pkg file",
|
||||||
|
() => {
|
||||||
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "hugo-test-"));
|
||||||
|
try {
|
||||||
|
const nonExistentPkg = path.join(tempDir, "nonexistent.pkg");
|
||||||
|
expect(() => extractPkg(nonExistentPkg, tempDir)).toThrow();
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
it.skipIf(process.platform !== "darwin")(
|
||||||
|
"should throw descriptive error when pkgutil fails on invalid pkg",
|
||||||
|
() => {
|
||||||
|
// Create a temporary directory with a fake .pkg file
|
||||||
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "hugo-test-"));
|
||||||
|
try {
|
||||||
|
// Create a fake .pkg file (just an empty file - pkgutil will fail to expand it)
|
||||||
|
const fakePkg = path.join(tempDir, "fake.pkg");
|
||||||
|
fs.writeFileSync(fakePkg, "not a real pkg");
|
||||||
|
|
||||||
|
expect(() => extractPkg(fakePkg, tempDir)).toThrow();
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
it.skipIf(process.platform === "darwin")(
|
||||||
|
"should not be available on non-macOS platforms (pkgutil is macOS-only)",
|
||||||
|
() => {
|
||||||
|
// On non-macOS platforms, pkgutil doesn't exist, so the function will fail
|
||||||
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "hugo-test-"));
|
||||||
|
try {
|
||||||
|
const fakePkg = path.join(tempDir, "fake.pkg");
|
||||||
|
fs.writeFileSync(fakePkg, "");
|
||||||
|
expect(() => extractPkg(fakePkg, tempDir)).toThrow();
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
describe("getReleaseFilename + getArchiveType integration", () => {
|
describe("getReleaseFilename + getArchiveType integration", () => {
|
||||||
let originalPlatform: NodeJS.Platform;
|
let originalPlatform: NodeJS.Platform;
|
||||||
let originalArch: NodeJS.Architecture;
|
let originalArch: NodeJS.Architecture;
|
||||||
|
|||||||
@@ -440,7 +440,7 @@ describe("utils", () => {
|
|||||||
describe("warn", () => {
|
describe("warn", () => {
|
||||||
it("should log when not quiet", () => {
|
it("should log when not quiet", () => {
|
||||||
logger.warn("warning message");
|
logger.warn("warning message");
|
||||||
expect(console.warn).toHaveBeenCalledWith("warning message");
|
expect(console.warn).toHaveBeenCalledWith("⚠ warning message");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not log when HUGO_SILENT is set", () => {
|
it("should not log when HUGO_SILENT is set", () => {
|
||||||
@@ -453,13 +453,13 @@ describe("utils", () => {
|
|||||||
describe("error", () => {
|
describe("error", () => {
|
||||||
it("should always log errors", () => {
|
it("should always log errors", () => {
|
||||||
logger.error("error message");
|
logger.error("error message");
|
||||||
expect(console.error).toHaveBeenCalledWith("error message");
|
expect(console.error).toHaveBeenCalledWith("✖ error message");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should log errors even when quiet", () => {
|
it("should log errors even when quiet", () => {
|
||||||
process.env.HUGO_QUIET = "1";
|
process.env.HUGO_QUIET = "1";
|
||||||
logger.error("error message");
|
logger.error("error message");
|
||||||
expect(console.error).toHaveBeenCalledWith("error message");
|
expect(console.error).toHaveBeenCalledWith("✖ error message");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ export default defineConfig({
|
|||||||
// Test file patterns
|
// Test file patterns
|
||||||
include: ["tests/**/*.test.ts"],
|
include: ["tests/**/*.test.ts"],
|
||||||
|
|
||||||
|
// Don't show noisy Hugo output unless the test fails
|
||||||
|
silent: "passed-only",
|
||||||
|
|
||||||
// Coverage configuration
|
// Coverage configuration
|
||||||
coverage: {
|
coverage: {
|
||||||
provider: "v8",
|
provider: "v8",
|
||||||
|
|||||||
Reference in New Issue
Block a user