hugo-extended 0.154.1 → 0.154.3-sudoless.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +220 -65
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +28 -0
- package/dist/generated/flags.json +1143 -0
- package/dist/generated/types.d.mts +383 -0
- package/dist/generated/types.mjs +1 -0
- package/dist/generated/types.ts +490 -0
- package/dist/hugo.d.mts +240 -0
- package/dist/hugo.mjs +246 -0
- package/dist/lib/args.d.mts +30 -0
- package/dist/lib/args.mjs +126 -0
- package/dist/lib/env.d.mts +62 -0
- package/dist/lib/env.mjs +138 -0
- package/dist/lib/install.d.mts +66 -0
- package/dist/lib/install.mjs +190 -0
- package/dist/lib/utils.d.mts +123 -0
- package/dist/lib/utils.mjs +191 -0
- package/package.json +53 -26
- package/postinstall.js +57 -3
- package/index.d.ts +0 -7
- package/index.js +0 -22
- package/lib/cli.js +0 -15
- package/lib/install.js +0 -144
- package/lib/utils.js +0 -109
- /package/{LICENSE.md → LICENSE} +0 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
//#region src/lib/env.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Centralized environment variable handling for hugo-extended.
|
|
4
|
+
*
|
|
5
|
+
* All environment variables are prefixed with `HUGO_` and provide ways to
|
|
6
|
+
* customize the installation and runtime behavior of the package.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Typed environment configuration object.
|
|
12
|
+
* Provides a clean API for accessing all Hugo environment variables.
|
|
13
|
+
*/
|
|
14
|
+
interface HugoEnvConfig {
|
|
15
|
+
/** Override the Hugo version to install (ignores package.json) */
|
|
16
|
+
overrideVersion: string | undefined;
|
|
17
|
+
/** Force vanilla Hugo instead of Extended edition */
|
|
18
|
+
forceStandard: boolean;
|
|
19
|
+
/** Skip the postinstall binary download */
|
|
20
|
+
skipInstall: boolean;
|
|
21
|
+
/** Path to a pre-existing Hugo binary */
|
|
22
|
+
binPath: string | undefined;
|
|
23
|
+
/** Custom base URL for Hugo release downloads */
|
|
24
|
+
downloadBaseUrl: string | undefined;
|
|
25
|
+
/** Skip SHA-256 checksum verification */
|
|
26
|
+
skipChecksum: boolean;
|
|
27
|
+
/** Suppress installation progress output */
|
|
28
|
+
quiet: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Reads and parses all Hugo environment variables.
|
|
32
|
+
*
|
|
33
|
+
* This function reads from `process.env` each time it's called,
|
|
34
|
+
* so it will pick up any runtime changes to environment variables.
|
|
35
|
+
*
|
|
36
|
+
* @returns Parsed environment configuration
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* import { getEnvConfig } from './lib/env';
|
|
41
|
+
*
|
|
42
|
+
* const config = getEnvConfig();
|
|
43
|
+
* if (config.skipInstall) {
|
|
44
|
+
* console.log('Skipping installation');
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
declare function getEnvConfig(): HugoEnvConfig;
|
|
49
|
+
/**
|
|
50
|
+
* Metadata about all supported environment variables.
|
|
51
|
+
* Useful for documentation generation or help output.
|
|
52
|
+
*/
|
|
53
|
+
declare const ENV_VAR_DOCS: {
|
|
54
|
+
key: string;
|
|
55
|
+
name: string;
|
|
56
|
+
aliases: string[] | never[];
|
|
57
|
+
description: string;
|
|
58
|
+
type: string;
|
|
59
|
+
default: false | undefined;
|
|
60
|
+
}[];
|
|
61
|
+
//#endregion
|
|
62
|
+
export { ENV_VAR_DOCS, HugoEnvConfig, getEnvConfig };
|
package/dist/lib/env.mjs
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
//#region src/lib/env.ts
|
|
2
|
+
/**
|
|
3
|
+
* Parses a boolean environment variable.
|
|
4
|
+
* Truthy values: "1", "true", "yes", "on" (case-insensitive)
|
|
5
|
+
* Falsy values: "0", "false", "no", "off", undefined, empty string
|
|
6
|
+
*/
|
|
7
|
+
function parseBoolean(value) {
|
|
8
|
+
if (!value) return false;
|
|
9
|
+
const normalized = value.toLowerCase().trim();
|
|
10
|
+
return [
|
|
11
|
+
"1",
|
|
12
|
+
"true",
|
|
13
|
+
"yes",
|
|
14
|
+
"on"
|
|
15
|
+
].includes(normalized);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Parses a string environment variable (returns undefined if empty).
|
|
19
|
+
*/
|
|
20
|
+
function parseString(value) {
|
|
21
|
+
if (!value || value.trim() === "") return void 0;
|
|
22
|
+
return value.trim();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Parses a version string, stripping any leading "v" prefix.
|
|
26
|
+
*/
|
|
27
|
+
function parseVersion(value) {
|
|
28
|
+
const str = parseString(value);
|
|
29
|
+
if (!str) return void 0;
|
|
30
|
+
return str.startsWith("v") ? str.slice(1) : str;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Gets the first defined value from a list of environment variable names.
|
|
34
|
+
*/
|
|
35
|
+
function getFirstDefined(names) {
|
|
36
|
+
for (const name of names) {
|
|
37
|
+
const value = process.env[name];
|
|
38
|
+
if (value !== void 0 && value !== "") return value;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* All supported environment variable configurations.
|
|
43
|
+
*/
|
|
44
|
+
const ENV_VARS = {
|
|
45
|
+
overrideVersion: {
|
|
46
|
+
name: "HUGO_OVERRIDE_VERSION",
|
|
47
|
+
aliases: [],
|
|
48
|
+
description: "Override the Hugo version to install",
|
|
49
|
+
parse: parseVersion,
|
|
50
|
+
defaultValue: void 0
|
|
51
|
+
},
|
|
52
|
+
forceStandard: {
|
|
53
|
+
name: "HUGO_NO_EXTENDED",
|
|
54
|
+
aliases: ["HUGO_FORCE_STANDARD"],
|
|
55
|
+
description: "Force vanilla Hugo instead of Extended edition",
|
|
56
|
+
parse: parseBoolean,
|
|
57
|
+
defaultValue: false
|
|
58
|
+
},
|
|
59
|
+
skipInstall: {
|
|
60
|
+
name: "HUGO_SKIP_DOWNLOAD",
|
|
61
|
+
aliases: [],
|
|
62
|
+
description: "Skip the postinstall binary download",
|
|
63
|
+
parse: parseBoolean,
|
|
64
|
+
defaultValue: false
|
|
65
|
+
},
|
|
66
|
+
binPath: {
|
|
67
|
+
name: "HUGO_BIN_PATH",
|
|
68
|
+
aliases: [],
|
|
69
|
+
description: "Path to a pre-existing Hugo binary",
|
|
70
|
+
parse: parseString,
|
|
71
|
+
defaultValue: void 0
|
|
72
|
+
},
|
|
73
|
+
downloadBaseUrl: {
|
|
74
|
+
name: "HUGO_MIRROR_BASE_URL",
|
|
75
|
+
aliases: [],
|
|
76
|
+
description: "Custom base URL for Hugo release downloads",
|
|
77
|
+
parse: parseString,
|
|
78
|
+
defaultValue: void 0
|
|
79
|
+
},
|
|
80
|
+
skipChecksum: {
|
|
81
|
+
name: "HUGO_SKIP_CHECKSUM",
|
|
82
|
+
aliases: ["HUGO_SKIP_VERIFY"],
|
|
83
|
+
description: "Skip SHA-256 checksum verification",
|
|
84
|
+
parse: parseBoolean,
|
|
85
|
+
defaultValue: false
|
|
86
|
+
},
|
|
87
|
+
quiet: {
|
|
88
|
+
name: "HUGO_QUIET",
|
|
89
|
+
aliases: ["HUGO_SILENT"],
|
|
90
|
+
description: "Suppress installation progress output",
|
|
91
|
+
parse: parseBoolean,
|
|
92
|
+
defaultValue: false
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Reads and parses all Hugo environment variables.
|
|
97
|
+
*
|
|
98
|
+
* This function reads from `process.env` each time it's called,
|
|
99
|
+
* so it will pick up any runtime changes to environment variables.
|
|
100
|
+
*
|
|
101
|
+
* @returns Parsed environment configuration
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* import { getEnvConfig } from './lib/env';
|
|
106
|
+
*
|
|
107
|
+
* const config = getEnvConfig();
|
|
108
|
+
* if (config.skipInstall) {
|
|
109
|
+
* console.log('Skipping installation');
|
|
110
|
+
* }
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
function getEnvConfig() {
|
|
114
|
+
return {
|
|
115
|
+
overrideVersion: ENV_VARS.overrideVersion.parse(getFirstDefined([ENV_VARS.overrideVersion.name, ...ENV_VARS.overrideVersion.aliases ?? []])),
|
|
116
|
+
forceStandard: ENV_VARS.forceStandard.parse(getFirstDefined([ENV_VARS.forceStandard.name, ...ENV_VARS.forceStandard.aliases ?? []])),
|
|
117
|
+
skipInstall: ENV_VARS.skipInstall.parse(getFirstDefined([ENV_VARS.skipInstall.name, ...ENV_VARS.skipInstall.aliases ?? []])),
|
|
118
|
+
binPath: ENV_VARS.binPath.parse(getFirstDefined([ENV_VARS.binPath.name, ...ENV_VARS.binPath.aliases ?? []])),
|
|
119
|
+
downloadBaseUrl: ENV_VARS.downloadBaseUrl.parse(getFirstDefined([ENV_VARS.downloadBaseUrl.name, ...ENV_VARS.downloadBaseUrl.aliases ?? []])),
|
|
120
|
+
skipChecksum: ENV_VARS.skipChecksum.parse(getFirstDefined([ENV_VARS.skipChecksum.name, ...ENV_VARS.skipChecksum.aliases ?? []])),
|
|
121
|
+
quiet: ENV_VARS.quiet.parse(getFirstDefined([ENV_VARS.quiet.name, ...ENV_VARS.quiet.aliases ?? []]))
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Metadata about all supported environment variables.
|
|
126
|
+
* Useful for documentation generation or help output.
|
|
127
|
+
*/
|
|
128
|
+
const ENV_VAR_DOCS = Object.entries(ENV_VARS).map(([key, config]) => ({
|
|
129
|
+
key,
|
|
130
|
+
name: config.name,
|
|
131
|
+
aliases: config.aliases ?? [],
|
|
132
|
+
description: config.description,
|
|
133
|
+
type: config.defaultValue === void 0 ? "string" : typeof config.defaultValue === "boolean" ? "boolean" : "string",
|
|
134
|
+
default: config.defaultValue
|
|
135
|
+
}));
|
|
136
|
+
|
|
137
|
+
//#endregion
|
|
138
|
+
export { ENV_VAR_DOCS, getEnvConfig };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
//#region src/lib/install.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Archive types supported by the installer.
|
|
4
|
+
*/
|
|
5
|
+
type ArchiveType = "zip" | "tar.gz" | "pkg" | null;
|
|
6
|
+
/**
|
|
7
|
+
* Detects the archive type from a filename based on its extension.
|
|
8
|
+
*
|
|
9
|
+
* @param filename - The filename to check
|
|
10
|
+
* @returns The detected archive type, or null if unknown
|
|
11
|
+
*/
|
|
12
|
+
declare function getArchiveType(filename: string): ArchiveType;
|
|
13
|
+
/**
|
|
14
|
+
* Parses a checksums file content into a lookup map.
|
|
15
|
+
*
|
|
16
|
+
* The checksums file format is: "sha256hash filename" (hash followed by whitespace and filename).
|
|
17
|
+
* This is the standard format used by Hugo releases.
|
|
18
|
+
*
|
|
19
|
+
* @param content - The raw content of the checksums file
|
|
20
|
+
* @returns A Map of filename to SHA-256 hash
|
|
21
|
+
*/
|
|
22
|
+
declare function parseChecksumFile(content: string): Map<string, string>;
|
|
23
|
+
/**
|
|
24
|
+
* Extracts a Hugo binary from a macOS .pkg file without requiring sudo.
|
|
25
|
+
*
|
|
26
|
+
* Uses `pkgutil --expand-full` to expand the package, then locates and copies
|
|
27
|
+
* the Hugo binary from the payload to the destination directory.
|
|
28
|
+
*
|
|
29
|
+
* The Hugo .pkg structure after expansion contains:
|
|
30
|
+
* - A "Payload" directory containing the hugo binary directly
|
|
31
|
+
* - Or a component package directory with Payload inside
|
|
32
|
+
*
|
|
33
|
+
* @param pkgPath - The path to the .pkg file to extract
|
|
34
|
+
* @param destDir - The directory where the hugo binary should be placed
|
|
35
|
+
* @throws {Error} If extraction fails, Payload is not found, or hugo binary is missing
|
|
36
|
+
* @see https://github.com/jmooring/hvm/commit/16eb55ae4965b5d2e414061085490a90fe7ea73e
|
|
37
|
+
*/
|
|
38
|
+
declare function extractPkg(pkgPath: string, destDir: string): void;
|
|
39
|
+
/**
|
|
40
|
+
* Downloads, verifies, and installs Hugo (Extended when available) for the current platform.
|
|
41
|
+
*
|
|
42
|
+
* This function handles the complete installation process:
|
|
43
|
+
* - Determines the correct Hugo release file for the current platform and architecture
|
|
44
|
+
* - Downloads the release file and checksums from GitHub (or custom mirror)
|
|
45
|
+
* - Verifies the integrity of the downloaded file using SHA-256 checksums (unless HUGO_SKIP_CHECKSUM is set)
|
|
46
|
+
* - Extracts the binary (platform-specific):
|
|
47
|
+
* - macOS v0.153.0+: Extracts from .pkg using pkgutil (no sudo required)
|
|
48
|
+
* - macOS pre-v0.153.0: Extracts from .tar.gz archive
|
|
49
|
+
* - Windows: Extracts from .zip archive
|
|
50
|
+
* - Linux/BSD: Extracts from .tar.gz archive
|
|
51
|
+
* - Sets appropriate file permissions on Unix-like systems
|
|
52
|
+
* - Displays the installed Hugo version
|
|
53
|
+
*
|
|
54
|
+
* Environment variables that affect installation:
|
|
55
|
+
* - HUGO_OVERRIDE_VERSION: Install a different Hugo version
|
|
56
|
+
* - HUGO_NO_EXTENDED: Force vanilla Hugo instead of Extended
|
|
57
|
+
* - HUGO_MIRROR_BASE_URL: Custom download mirror
|
|
58
|
+
* - HUGO_SKIP_CHECKSUM: Skip SHA-256 verification
|
|
59
|
+
* - HUGO_QUIET: Suppress progress output
|
|
60
|
+
*
|
|
61
|
+
* @throws {Error} If the platform is unsupported, download fails, checksum doesn't match, or installation fails
|
|
62
|
+
* @returns A promise that resolves with the absolute path to the installed Hugo binary
|
|
63
|
+
*/
|
|
64
|
+
declare function install(): Promise<string>;
|
|
65
|
+
//#endregion
|
|
66
|
+
export { ArchiveType, install as default, extractPkg, getArchiveType, parseChecksumFile };
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { getEnvConfig } from "./env.mjs";
|
|
2
|
+
import { getBinFilename, getBinVersion, getChecksumFilename, getPkgVersion, getReleaseFilename, getReleaseUrl, isExtended, logger } from "./utils.mjs";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import crypto from "node:crypto";
|
|
8
|
+
import os from "node:os";
|
|
9
|
+
import { Readable } from "node:stream";
|
|
10
|
+
import { pipeline } from "node:stream/promises";
|
|
11
|
+
import AdmZip from "adm-zip";
|
|
12
|
+
import * as tar from "tar";
|
|
13
|
+
|
|
14
|
+
//#region src/lib/install.ts
|
|
15
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
/**
|
|
17
|
+
* Detects the archive type from a filename based on its extension.
|
|
18
|
+
*
|
|
19
|
+
* @param filename - The filename to check
|
|
20
|
+
* @returns The detected archive type, or null if unknown
|
|
21
|
+
*/
|
|
22
|
+
function getArchiveType(filename) {
|
|
23
|
+
if (filename.endsWith(".zip")) return "zip";
|
|
24
|
+
if (filename.endsWith(".tar.gz")) return "tar.gz";
|
|
25
|
+
if (filename.endsWith(".pkg")) return "pkg";
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Parses a checksums file content into a lookup map.
|
|
30
|
+
*
|
|
31
|
+
* The checksums file format is: "sha256hash filename" (hash followed by whitespace and filename).
|
|
32
|
+
* This is the standard format used by Hugo releases.
|
|
33
|
+
*
|
|
34
|
+
* @param content - The raw content of the checksums file
|
|
35
|
+
* @returns A Map of filename to SHA-256 hash
|
|
36
|
+
*/
|
|
37
|
+
function parseChecksumFile(content) {
|
|
38
|
+
const checksums = /* @__PURE__ */ new Map();
|
|
39
|
+
for (const line of content.split("\n")) {
|
|
40
|
+
const trimmed = line.trim();
|
|
41
|
+
if (!trimmed) continue;
|
|
42
|
+
const tokens = trimmed.split(/\s+/);
|
|
43
|
+
if (tokens.length >= 2) {
|
|
44
|
+
const hash = tokens[0];
|
|
45
|
+
const filename = tokens[tokens.length - 1];
|
|
46
|
+
checksums.set(filename, hash);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return checksums;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Downloads a file from a URL to a local destination path.
|
|
53
|
+
*
|
|
54
|
+
* @param url - The URL to download the file from
|
|
55
|
+
* @param dest - The local file path where the downloaded file will be saved
|
|
56
|
+
* @throws {Error} If the download fails or the response is invalid
|
|
57
|
+
* @returns A promise that resolves when the download is complete
|
|
58
|
+
*/
|
|
59
|
+
async function downloadFile(url, dest) {
|
|
60
|
+
const response = await fetch(url);
|
|
61
|
+
if (!response.ok) throw new Error(`Failed to download ${url}: ${response.statusText}`);
|
|
62
|
+
if (!response.body) throw new Error(`No response body from ${url}`);
|
|
63
|
+
await pipeline(Readable.fromWeb(response.body), fs.createWriteStream(dest));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Extracts a Hugo binary from a macOS .pkg file without requiring sudo.
|
|
67
|
+
*
|
|
68
|
+
* Uses `pkgutil --expand-full` to expand the package, then locates and copies
|
|
69
|
+
* the Hugo binary from the payload to the destination directory.
|
|
70
|
+
*
|
|
71
|
+
* The Hugo .pkg structure after expansion contains:
|
|
72
|
+
* - A "Payload" directory containing the hugo binary directly
|
|
73
|
+
* - Or a component package directory with Payload inside
|
|
74
|
+
*
|
|
75
|
+
* @param pkgPath - The path to the .pkg file to extract
|
|
76
|
+
* @param destDir - The directory where the hugo binary should be placed
|
|
77
|
+
* @throws {Error} If extraction fails, Payload is not found, or hugo binary is missing
|
|
78
|
+
* @see https://github.com/jmooring/hvm/commit/16eb55ae4965b5d2e414061085490a90fe7ea73e
|
|
79
|
+
*/
|
|
80
|
+
function extractPkg(pkgPath, destDir) {
|
|
81
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "hugo-pkg-"));
|
|
82
|
+
try {
|
|
83
|
+
const expansionDir = path.join(tempDir, "expanded");
|
|
84
|
+
execSync(`pkgutil --expand-full "${pkgPath}" "${expansionDir}"`, { stdio: "pipe" });
|
|
85
|
+
const hugoPayload = path.join(expansionDir, "Payload", "hugo");
|
|
86
|
+
if (!fs.existsSync(hugoPayload)) throw new Error("Could not find hugo binary in expanded .pkg. Expected path: */Payload/hugo");
|
|
87
|
+
const destPath = path.join(destDir, getBinFilename());
|
|
88
|
+
fs.copyFileSync(hugoPayload, destPath);
|
|
89
|
+
fs.chmodSync(destPath, 493);
|
|
90
|
+
} finally {
|
|
91
|
+
fs.rmSync(tempDir, {
|
|
92
|
+
recursive: true,
|
|
93
|
+
force: true
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Verifies that a downloaded file matches its expected SHA-256 checksum.
|
|
99
|
+
*
|
|
100
|
+
* Downloads the checksums file from GitHub, extracts the expected checksum for the
|
|
101
|
+
* specified filename, computes the actual checksum of the local file, and compares them.
|
|
102
|
+
*
|
|
103
|
+
* @param filePath - The local path to the file to verify
|
|
104
|
+
* @param checksumUrl - The URL to the checksums file (usually checksums.txt from the release)
|
|
105
|
+
* @param filename - The name of the file to find in the checksums file
|
|
106
|
+
* @throws {Error} If checksums don't match, the checksums file can't be downloaded, or the filename isn't found
|
|
107
|
+
* @returns A promise that resolves when verification is successful
|
|
108
|
+
*/
|
|
109
|
+
async function verifyChecksum(filePath, checksumUrl, filename) {
|
|
110
|
+
const response = await fetch(checksumUrl);
|
|
111
|
+
if (!response.ok) throw new Error(`Failed to download checksums: ${response.statusText}`);
|
|
112
|
+
const expectedChecksum = parseChecksumFile(await response.text()).get(filename);
|
|
113
|
+
if (!expectedChecksum) throw new Error(`Checksum for ${filename} not found in checksums file.`);
|
|
114
|
+
const fileBuffer = fs.readFileSync(filePath);
|
|
115
|
+
const hash = crypto.createHash("sha256");
|
|
116
|
+
hash.update(fileBuffer);
|
|
117
|
+
const actualChecksum = hash.digest("hex");
|
|
118
|
+
if (actualChecksum !== expectedChecksum) throw new Error(`Checksum mismatch! Expected ${expectedChecksum}, got ${actualChecksum}`);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Downloads, verifies, and installs Hugo (Extended when available) for the current platform.
|
|
122
|
+
*
|
|
123
|
+
* This function handles the complete installation process:
|
|
124
|
+
* - Determines the correct Hugo release file for the current platform and architecture
|
|
125
|
+
* - Downloads the release file and checksums from GitHub (or custom mirror)
|
|
126
|
+
* - Verifies the integrity of the downloaded file using SHA-256 checksums (unless HUGO_SKIP_CHECKSUM is set)
|
|
127
|
+
* - Extracts the binary (platform-specific):
|
|
128
|
+
* - macOS v0.153.0+: Extracts from .pkg using pkgutil (no sudo required)
|
|
129
|
+
* - macOS pre-v0.153.0: Extracts from .tar.gz archive
|
|
130
|
+
* - Windows: Extracts from .zip archive
|
|
131
|
+
* - Linux/BSD: Extracts from .tar.gz archive
|
|
132
|
+
* - Sets appropriate file permissions on Unix-like systems
|
|
133
|
+
* - Displays the installed Hugo version
|
|
134
|
+
*
|
|
135
|
+
* Environment variables that affect installation:
|
|
136
|
+
* - HUGO_OVERRIDE_VERSION: Install a different Hugo version
|
|
137
|
+
* - HUGO_NO_EXTENDED: Force vanilla Hugo instead of Extended
|
|
138
|
+
* - HUGO_MIRROR_BASE_URL: Custom download mirror
|
|
139
|
+
* - HUGO_SKIP_CHECKSUM: Skip SHA-256 verification
|
|
140
|
+
* - HUGO_QUIET: Suppress progress output
|
|
141
|
+
*
|
|
142
|
+
* @throws {Error} If the platform is unsupported, download fails, checksum doesn't match, or installation fails
|
|
143
|
+
* @returns A promise that resolves with the absolute path to the installed Hugo binary
|
|
144
|
+
*/
|
|
145
|
+
async function install() {
|
|
146
|
+
const envConfig = getEnvConfig();
|
|
147
|
+
try {
|
|
148
|
+
const version = getPkgVersion();
|
|
149
|
+
const releaseFile = getReleaseFilename(version);
|
|
150
|
+
const checksumFile = getChecksumFilename(version);
|
|
151
|
+
const binFile = getBinFilename();
|
|
152
|
+
if (!releaseFile) throw new Error(`Are you sure this platform is supported? See: https://github.com/gohugoio/hugo/releases/tag/v${version}`);
|
|
153
|
+
if (!isExtended(releaseFile)) if (envConfig.forceStandard) logger.info("Installing vanilla Hugo (HUGO_NO_EXTENDED is set).");
|
|
154
|
+
else logger.warn("Hugo Extended isn't supported on this platform, downloading vanilla Hugo instead.");
|
|
155
|
+
const binDir = path.join(__dirname, "..", "..", "bin");
|
|
156
|
+
if (!fs.existsSync(binDir)) fs.mkdirSync(binDir, { recursive: true });
|
|
157
|
+
const releaseUrl = getReleaseUrl(version, releaseFile);
|
|
158
|
+
const checksumUrl = getReleaseUrl(version, checksumFile);
|
|
159
|
+
const downloadPath = path.join(binDir, releaseFile);
|
|
160
|
+
logger.info(`☁️ Downloading ${releaseFile}...`);
|
|
161
|
+
await downloadFile(releaseUrl, downloadPath);
|
|
162
|
+
if (envConfig.skipChecksum) logger.warn("Skipping checksum verification (HUGO_SKIP_CHECKSUM is set).");
|
|
163
|
+
else {
|
|
164
|
+
logger.info("🕵️ Verifying checksum...");
|
|
165
|
+
await verifyChecksum(downloadPath, checksumUrl, releaseFile);
|
|
166
|
+
}
|
|
167
|
+
logger.info("📦 Extracting...");
|
|
168
|
+
const archiveType = getArchiveType(releaseFile);
|
|
169
|
+
if (archiveType === "pkg") extractPkg(downloadPath, binDir);
|
|
170
|
+
else if (archiveType === "zip") new AdmZip(downloadPath).extractAllTo(binDir, true);
|
|
171
|
+
else if (archiveType === "tar.gz") await tar.x({
|
|
172
|
+
file: downloadPath,
|
|
173
|
+
cwd: binDir
|
|
174
|
+
});
|
|
175
|
+
else throw new Error(`Unexpected archive type for ${releaseFile}. Expected .zip, .tar.gz, or .pkg.`);
|
|
176
|
+
fs.unlinkSync(downloadPath);
|
|
177
|
+
const binPath = path.join(binDir, binFile);
|
|
178
|
+
if (fs.existsSync(binPath)) fs.chmodSync(binPath, 493);
|
|
179
|
+
logger.info("🎉 Hugo installed successfully!");
|
|
180
|
+
logger.info(getBinVersion(binPath));
|
|
181
|
+
return binPath;
|
|
182
|
+
} catch (error) {
|
|
183
|
+
logger.error("Hugo installation failed. :(");
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
var install_default = install;
|
|
188
|
+
|
|
189
|
+
//#endregion
|
|
190
|
+
export { install_default as default, extractPkg, getArchiveType, parseChecksumFile };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
//#region src/lib/utils.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Compares two semver version strings.
|
|
4
|
+
*
|
|
5
|
+
* @param a - First version string (e.g., "0.153.0")
|
|
6
|
+
* @param b - Second version string (e.g., "0.152.1")
|
|
7
|
+
* @returns -1 if a < b, 0 if a === b, 1 if a > b
|
|
8
|
+
*/
|
|
9
|
+
declare function compareVersions(a: string, b: string): -1 | 0 | 1;
|
|
10
|
+
/**
|
|
11
|
+
* Checks if a version uses .pkg installers for macOS.
|
|
12
|
+
* Hugo v0.153.0+ uses .pkg, earlier versions use .tar.gz.
|
|
13
|
+
*
|
|
14
|
+
* @param version - The Hugo version to check
|
|
15
|
+
* @returns true if the version uses .pkg installers on macOS
|
|
16
|
+
*/
|
|
17
|
+
declare function usesMacOSPkg(version: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Gets the Hugo version to install.
|
|
20
|
+
*
|
|
21
|
+
* Resolution order:
|
|
22
|
+
* 1. HUGO_OVERRIDE_VERSION environment variable (if set)
|
|
23
|
+
* 2. `hugoVersion` field in package.json (for emergency overrides)
|
|
24
|
+
* 3. `version` field in package.json (should match Hugo release)
|
|
25
|
+
*
|
|
26
|
+
* @throws {Error} If package.json cannot be found and no override is set
|
|
27
|
+
* @returns The version string (e.g., "0.88.1")
|
|
28
|
+
*/
|
|
29
|
+
declare function getPkgVersion(): string;
|
|
30
|
+
/**
|
|
31
|
+
* Generates the full URL to a Hugo release file.
|
|
32
|
+
*
|
|
33
|
+
* By default, downloads from GitHub releases. Can be overridden with
|
|
34
|
+
* HUGO_MIRROR_BASE_URL for mirrors or air-gapped environments.
|
|
35
|
+
*
|
|
36
|
+
* @param version - The Hugo version number (e.g., "0.88.1")
|
|
37
|
+
* @param filename - The release filename (e.g., "hugo_extended_0.88.1_darwin-universal.pkg")
|
|
38
|
+
* @returns The complete download URL for the release file
|
|
39
|
+
*/
|
|
40
|
+
declare function getReleaseUrl(version: string, filename: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Gets the Hugo binary filename for the current platform.
|
|
43
|
+
*
|
|
44
|
+
* @returns "hugo.exe" on Windows, "hugo" on all other platforms
|
|
45
|
+
*/
|
|
46
|
+
declare function getBinFilename(): string;
|
|
47
|
+
/**
|
|
48
|
+
* Gets the absolute path to the Hugo binary.
|
|
49
|
+
*
|
|
50
|
+
* Resolution order:
|
|
51
|
+
* 1. HUGO_BIN_PATH environment variable (if set)
|
|
52
|
+
* 2. Local bin directory (./bin/hugo or ./bin/hugo.exe)
|
|
53
|
+
*
|
|
54
|
+
* @returns The absolute path to hugo binary
|
|
55
|
+
*/
|
|
56
|
+
declare function getBinPath(): string;
|
|
57
|
+
/**
|
|
58
|
+
* Executes the Hugo binary and returns its version string.
|
|
59
|
+
*
|
|
60
|
+
* @param bin - The absolute path to the Hugo binary
|
|
61
|
+
* @returns The version output string (e.g., "hugo v0.88.1-5BC54738+extended darwin/arm64 BuildDate=...")
|
|
62
|
+
* @throws {Error} If the binary cannot be executed
|
|
63
|
+
*/
|
|
64
|
+
declare function getBinVersion(bin: string): string;
|
|
65
|
+
/**
|
|
66
|
+
* Checks if the Hugo binary exists at the specified path.
|
|
67
|
+
*
|
|
68
|
+
* @param bin - The absolute path to check for the Hugo binary
|
|
69
|
+
* @returns `true` if the file exists, `false` if it doesn't
|
|
70
|
+
* @throws {Error} If an unexpected error occurs (other than ENOENT)
|
|
71
|
+
*/
|
|
72
|
+
declare function doesBinExist(bin: string): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Determines the correct Hugo release filename for the current platform and architecture.
|
|
75
|
+
*
|
|
76
|
+
* Hugo Extended is available for:
|
|
77
|
+
* - macOS: x64 and ARM64 (universal binaries as of v0.102.0)
|
|
78
|
+
* - Linux: x64 and ARM64
|
|
79
|
+
* - Windows: x64 only
|
|
80
|
+
*
|
|
81
|
+
* Other platform/architecture combinations fall back to vanilla Hugo where available.
|
|
82
|
+
* Set HUGO_NO_EXTENDED=1 to force vanilla Hugo even on platforms that support Extended.
|
|
83
|
+
*
|
|
84
|
+
* Note: macOS uses .pkg installers starting from v0.153.0. Earlier versions use .tar.gz.
|
|
85
|
+
*
|
|
86
|
+
* @param version - The Hugo version number (e.g., "0.88.1")
|
|
87
|
+
* @returns The release filename if supported (e.g., "hugo_extended_0.88.1_darwin-universal.pkg"),
|
|
88
|
+
* or `null` if the platform/architecture combination is not supported
|
|
89
|
+
*/
|
|
90
|
+
declare function getReleaseFilename(version: string): string | null;
|
|
91
|
+
/**
|
|
92
|
+
* Generates the checksums filename for a given Hugo version.
|
|
93
|
+
*
|
|
94
|
+
* @param version - The Hugo version number (e.g., "0.88.1")
|
|
95
|
+
* @returns The checksums filename (e.g., "hugo_0.88.1_checksums.txt")
|
|
96
|
+
*/
|
|
97
|
+
declare function getChecksumFilename(version: string): string;
|
|
98
|
+
/**
|
|
99
|
+
* Determines if a release filename corresponds to Hugo Extended or vanilla Hugo.
|
|
100
|
+
*
|
|
101
|
+
* @param releaseFile - The release filename to check (e.g., "hugo_extended_0.88.1_darwin-universal.pkg")
|
|
102
|
+
* @returns `true` if the release is Hugo Extended, `false` if it's vanilla Hugo
|
|
103
|
+
*/
|
|
104
|
+
declare function isExtended(releaseFile: string): boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Logger utility that respects the HUGO_QUIET setting.
|
|
107
|
+
*/
|
|
108
|
+
declare const logger: {
|
|
109
|
+
/**
|
|
110
|
+
* Log an info message (respects HUGO_QUIET).
|
|
111
|
+
*/
|
|
112
|
+
info: (message: string) => void;
|
|
113
|
+
/**
|
|
114
|
+
* Log a warning message (respects HUGO_QUIET).
|
|
115
|
+
*/
|
|
116
|
+
warn: (message: string) => void;
|
|
117
|
+
/**
|
|
118
|
+
* Log an error message (always shown, even in quiet mode).
|
|
119
|
+
*/
|
|
120
|
+
error: (message: string) => void;
|
|
121
|
+
};
|
|
122
|
+
//#endregion
|
|
123
|
+
export { compareVersions, doesBinExist, getBinFilename, getBinPath, getBinVersion, getChecksumFilename, getPkgVersion, getReleaseFilename, getReleaseUrl, isExtended, logger, usesMacOSPkg };
|