Skip to content

Commit 0346b10

Browse files
committed
fix: replace wget-improved-2 with native fetch, detect system OpenSSL
- Replace wget-improved-2 (broken on Node.js 24) with native fetch() for downloading OpenSSL on Windows. Fixes certificate generation failure on GitHub Actions with Node.js 24. - Try system-installed OpenSSL via 'where openssl' on Windows before falling back to bundled download. CI runners (Windows Server 2025) ship OpenSSL 3.6+. - Accept OpenSSL 4.x in version check regex. OpenSSL 4.0.0 ships on Windows Server 2025 runners and the CLI is compatible for pki operations. - Remove wget-improved-2 and progress from dependencies.
1 parent 829e259 commit 0346b10

2 files changed

Lines changed: 75 additions & 74 deletions

File tree

packages/node-opcua-pki/lib/toolbox/with_openssl/install_prerequisite.ts

Lines changed: 75 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -28,62 +28,21 @@ import fs from "node:fs";
2828
import os from "node:os";
2929
import path from "node:path";
3030
import type { Readable } from "node:stream";
31-
import url from "node:url";
31+
import { pipeline } from "node:stream/promises";
3232

3333
import byline from "byline";
3434
import chalk from "chalk";
35-
import ProgressBar from "progress";
36-
import wget from "wget-improved-2";
3735
import yauzl from "yauzl";
3836

3937
import { warningLog } from "../debug";
4038

4139
const doDebug = process.env.NODEOPCUAPKIDEBUG || false;
4240

43-
declare interface ProxyOptions {
44-
host: string;
45-
port: number;
46-
localAddress?: string;
47-
proxyAuth?: string;
48-
headers?: Record<string, string>;
49-
protocol: string; // "https" | "http"
50-
}
51-
declare interface WgetOptions {
52-
gunzip?: boolean;
53-
proxy?: ProxyOptions;
54-
}
55-
56-
declare interface WgetInterface {
57-
download(url: string, outputFilename: string, options: WgetOptions): NodeJS.EventEmitter;
58-
}
59-
6041
interface ExecuteResult {
6142
exitCode: number;
6243
output: string;
6344
}
6445

65-
function makeOptions(): WgetOptions {
66-
const proxy =
67-
process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy || undefined;
68-
if (proxy) {
69-
const a = new url.URL(proxy);
70-
const auth = a.username ? `${a.username}:${a.password}` : undefined;
71-
72-
const options: WgetOptions = {
73-
proxy: {
74-
port: a.port ? parseInt(a.port, 10) : 80,
75-
protocol: a.protocol.replace(":", ""),
76-
host: a.hostname ?? "",
77-
proxyAuth: auth
78-
}
79-
};
80-
warningLog(chalk.green("- using proxy "), proxy);
81-
warningLog(options);
82-
return options;
83-
}
84-
return {};
85-
}
86-
8746
async function execute(cmd: string, cwd?: string): Promise<ExecuteResult> {
8847
let output = "";
8948

@@ -122,7 +81,7 @@ function quote(str: string): string {
12281
}
12382

12483
function is_expected_openssl_version(strVersion: string): boolean {
125-
return !!strVersion.match(/OpenSSL 1|3/);
84+
return !!strVersion.match(/OpenSSL \d/);
12685
}
12786

12887
async function getopensslExecPath(): Promise<string> {
@@ -230,6 +189,38 @@ async function install_and_check_win32_openssl_version(): Promise<string> {
230189
}
231190
}
232191

192+
/**
193+
* Try to find a system-installed openssl on Windows via `where`.
194+
* Returns the path if found and version is acceptable, otherwise undefined.
195+
*/
196+
async function find_system_openssl_win32(): Promise<string | undefined> {
197+
try {
198+
const result = await execute("where openssl");
199+
if (result.exitCode !== 0) {
200+
return undefined;
201+
}
202+
// `where` may return multiple lines; take the first one
203+
const opensslPath = result.output.split(/\r?\n/)[0].trim();
204+
if (!opensslPath || !fs.existsSync(opensslPath)) {
205+
return undefined;
206+
}
207+
// verify version
208+
const q = quote(opensslPath);
209+
const versionResult = await execute(`${q} version`);
210+
const version = versionResult.output.trim();
211+
if (versionResult.exitCode === 0 && is_expected_openssl_version(version)) {
212+
warningLog(
213+
chalk.green("Using system OpenSSL: ") + chalk.cyan(version) + chalk.green(" at ") + chalk.cyan(opensslPath)
214+
);
215+
return opensslPath;
216+
}
217+
warningLog(chalk.yellow("System OpenSSL found but version not accepted: ") + version);
218+
return undefined;
219+
} catch (_err) {
220+
return undefined;
221+
}
222+
}
223+
233224
/**
234225
* detect whether windows OS is a 64 bits or 32 bits
235226
* http://ss64.com/nt/syntax-64bit.html
@@ -253,10 +244,6 @@ async function install_and_check_win32_openssl_version(): Promise<string> {
253244
}
254245

255246
async function download_openssl(): Promise<{ downloadedFile: string }> {
256-
// const url = (win32or64() === 64 )
257-
// ? "http://indy.fulgan.com/SSL/openssl-1.0.2o-x64_86-win64.zip"
258-
// : "http://indy.fulgan.com/SSL/openssl-1.0.2o-i386-win32.zip"
259-
// ;
260247
const url =
261248
win32or64() === 64
262249
? "https://github.com/node-opcua/node-opcua-pki/releases/download/2.14.2/openssl-1.0.2u-x64_86-win64.zip"
@@ -269,34 +256,42 @@ async function install_and_check_win32_openssl_version(): Promise<string> {
269256
if (fs.existsSync(outputFilename)) {
270257
return { downloadedFile: outputFilename };
271258
}
272-
const options = makeOptions();
273-
const bar = new ProgressBar(chalk.cyan("[:bar]") + chalk.cyan(" :percent ") + chalk.white(":etas"), {
274-
complete: "=",
275-
incomplete: " ",
276-
total: 100,
277-
width: 100
278-
});
279259

280-
return await new Promise((resolve, reject) => {
281-
const download = wget.download(url, outputFilename, options);
282-
download.on("error", (err: Error) => {
283-
warningLog(err);
284-
setImmediate(() => {
285-
reject(err);
286-
});
287-
});
288-
download.on("end", (output: string) => {
289-
// istanbul ignore next
290-
if (doDebug) {
291-
warningLog(output);
260+
const response = await fetch(url, { redirect: "follow" });
261+
if (!response.ok || !response.body) {
262+
throw new Error(`Failed to download OpenSSL from ${url}: ${response.status} ${response.statusText}`);
263+
}
264+
265+
const contentLength = parseInt(response.headers.get("content-length") || "0", 10);
266+
let downloaded = 0;
267+
let lastPercent = -1;
268+
269+
const fileStream = fs.createWriteStream(outputFilename);
270+
271+
// Use pipeline for proper backpressure and cleanup
272+
const body = response.body as unknown as Readable;
273+
body.on("data", (chunk: Buffer) => {
274+
downloaded += chunk.length;
275+
if (contentLength > 0) {
276+
const percent = Math.floor((downloaded / contentLength) * 100);
277+
if (percent !== lastPercent && percent % 10 === 0) {
278+
lastPercent = percent;
279+
warningLog(` download progress: ${percent}%`);
292280
}
293-
// warningLog("done ...");
294-
resolve({ downloadedFile: outputFilename });
295-
});
296-
download.on("progress", (progress: number) => {
297-
bar.update(progress);
298-
});
281+
}
299282
});
283+
284+
await pipeline(body, fileStream);
285+
286+
// Verify the downloaded file exists and has content
287+
const stat = fs.statSync(outputFilename);
288+
if (stat.size === 0) {
289+
fs.unlinkSync(outputFilename);
290+
throw new Error(`Downloaded file is empty: ${outputFilename}`);
291+
}
292+
293+
warningLog(chalk.green("Download complete: ") + `${stat.size} bytes`);
294+
return { downloadedFile: outputFilename };
300295
}
301296

302297
async function unzip_openssl(zipFilename: string) {
@@ -358,6 +353,13 @@ async function install_and_check_win32_openssl_version(): Promise<string> {
358353
});
359354
}
360355

356+
// 1. Try system-installed OpenSSL first (e.g. on CI runners)
357+
const systemOpenssl = await find_system_openssl_win32();
358+
if (systemOpenssl) {
359+
return systemOpenssl;
360+
}
361+
362+
// 2. Check bundled OpenSSL at the expected local path
361363
const opensslFolder = get_openssl_folder_win32();
362364
const opensslExecPath = get_openssl_exec_path_win32();
363365

@@ -372,6 +374,7 @@ async function install_and_check_win32_openssl_version(): Promise<string> {
372374
const { opensslOk, version: _version } = await check_openssl_win32();
373375

374376
if (!opensslOk) {
377+
// 3. Download as last resort
375378
warningLog(chalk.yellow("openssl seems to be missing and need to be installed"));
376379
const { downloadedFile } = await download_openssl();
377380

packages/node-opcua-pki/package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@
4646
"command-line-args": "^6.0.2",
4747
"command-line-usage": "^7.0.4",
4848
"node-opcua-crypto": "5.3.6",
49-
"progress": "^2.0.3",
50-
"wget-improved-2": "^3.3.0",
5149
"yauzl": "^3.3.0"
5250
}
5351
}

0 commit comments

Comments
 (0)