@@ -28,62 +28,21 @@ import fs from "node:fs";
2828import os from "node:os" ;
2929import path from "node:path" ;
3030import type { Readable } from "node:stream" ;
31- import url from "node:url " ;
31+ import { pipeline } from "node:stream/promises " ;
3232
3333import byline from "byline" ;
3434import chalk from "chalk" ;
35- import ProgressBar from "progress" ;
36- import wget from "wget-improved-2" ;
3735import yauzl from "yauzl" ;
3836
3937import { warningLog } from "../debug" ;
4038
4139const 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-
6041interface 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-
8746async function execute ( cmd : string , cwd ?: string ) : Promise < ExecuteResult > {
8847 let output = "" ;
8948
@@ -122,7 +81,7 @@ function quote(str: string): string {
12281}
12382
12483function is_expected_openssl_version ( strVersion : string ) : boolean {
125- return ! ! strVersion . match ( / O p e n S S L 1 | 3 / ) ;
84+ return ! ! strVersion . match ( / O p e n S S L \d / ) ;
12685}
12786
12887async 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
0 commit comments