@wiremock/npm-jar-wrapper-maker 1.0.0
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/LICENSE +190 -0
- package/README.md +190 -0
- package/bin/cli.js +291 -0
- package/lib/generator.js +711 -0
- package/lib/index.js +36 -0
- package/lib/java-runtime.js +376 -0
- package/package.json +47 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* npm-jar-wrapper-maker
|
|
3
|
+
*
|
|
4
|
+
* Create NPM wrappers for Java JARs with automatic JRE management.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const generator = require('./generator');
|
|
8
|
+
const javaRuntime = require('./java-runtime');
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
// Generator functions
|
|
12
|
+
generateWrapper: generator.generateWrapper,
|
|
13
|
+
generatePackageJson: generator.generatePackageJson,
|
|
14
|
+
generateIndexJs: generator.generateIndexJs,
|
|
15
|
+
generateCliJs: generator.generateCliJs,
|
|
16
|
+
generatePostinstallJs: generator.generatePostinstallJs,
|
|
17
|
+
generateGitignore: generator.generateGitignore,
|
|
18
|
+
generateNpmignore: generator.generateNpmignore,
|
|
19
|
+
generateReadme: generator.generateReadme,
|
|
20
|
+
validateConfig: generator.validateConfig,
|
|
21
|
+
defaults: generator.defaults,
|
|
22
|
+
|
|
23
|
+
// Java runtime utilities (for advanced use cases)
|
|
24
|
+
javaRuntime: {
|
|
25
|
+
findOrDownloadJava: javaRuntime.findOrDownloadJava,
|
|
26
|
+
runJavaCommand: javaRuntime.runJavaCommand,
|
|
27
|
+
mapPlatformToOs: javaRuntime.mapPlatformToOs,
|
|
28
|
+
mapArchToJreArch: javaRuntime.mapArchToJreArch,
|
|
29
|
+
getUserConfigDir: javaRuntime.getUserConfigDir,
|
|
30
|
+
isValidArchivePath: javaRuntime.isValidArchivePath,
|
|
31
|
+
verifyChecksum: javaRuntime.verifyChecksum,
|
|
32
|
+
checkJavaHome: javaRuntime.checkJavaHome,
|
|
33
|
+
checkSystemJava: javaRuntime.checkSystemJava,
|
|
34
|
+
checkManagedJre: javaRuntime.checkManagedJre,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Java Runtime Management
|
|
3
|
+
*
|
|
4
|
+
* Provides functionality for finding, downloading, and managing Java JRE installations.
|
|
5
|
+
* Supports automatic download from Adoptium API with checksum verification.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { spawnSync, execSync } = require('node:child_process');
|
|
9
|
+
const fs = require('node:fs');
|
|
10
|
+
const path = require('node:path');
|
|
11
|
+
const https = require('node:https');
|
|
12
|
+
const crypto = require('node:crypto');
|
|
13
|
+
const os = require('node:os');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Map Node.js platform to JRE OS name
|
|
17
|
+
*/
|
|
18
|
+
function mapPlatformToOs(platform) {
|
|
19
|
+
switch (platform) {
|
|
20
|
+
case 'darwin':
|
|
21
|
+
return 'mac';
|
|
22
|
+
case 'win32':
|
|
23
|
+
return 'windows';
|
|
24
|
+
default:
|
|
25
|
+
return 'linux';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Map Node.js architecture to JRE architecture name
|
|
31
|
+
*/
|
|
32
|
+
function mapArchToJreArch(arch) {
|
|
33
|
+
switch (arch) {
|
|
34
|
+
case 'arm64':
|
|
35
|
+
return 'aarch64';
|
|
36
|
+
case 'x64':
|
|
37
|
+
return 'x64';
|
|
38
|
+
case 'ia32':
|
|
39
|
+
return 'x32';
|
|
40
|
+
default:
|
|
41
|
+
return arch;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get platform-specific user config directory
|
|
47
|
+
*/
|
|
48
|
+
function getUserConfigDir(platform = process.platform) {
|
|
49
|
+
switch (platform) {
|
|
50
|
+
case 'darwin':
|
|
51
|
+
return path.join(os.homedir(), '.config');
|
|
52
|
+
case 'win32':
|
|
53
|
+
return process.env['LOCALAPPDATA'] || path.join(os.homedir(), 'AppData', 'Local');
|
|
54
|
+
default:
|
|
55
|
+
return process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check if a path is valid (no path traversal attacks)
|
|
61
|
+
*/
|
|
62
|
+
function isValidArchivePath(filePath) {
|
|
63
|
+
if (path.isAbsolute(filePath)) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
const segments = filePath.split(path.sep);
|
|
67
|
+
return !segments.some((segment) => segment === '..');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Verify file checksum
|
|
72
|
+
*/
|
|
73
|
+
function verifyChecksum(filePath, expectedChecksum, algorithm = 'sha256') {
|
|
74
|
+
const fileBuffer = fs.readFileSync(filePath);
|
|
75
|
+
const hash = crypto.createHash(algorithm);
|
|
76
|
+
hash.update(fileBuffer);
|
|
77
|
+
const actualChecksum = hash.digest('hex');
|
|
78
|
+
return actualChecksum === expectedChecksum;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Download a file from URL to destination
|
|
83
|
+
*/
|
|
84
|
+
function downloadFile(url, dest) {
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
const file = fs.createWriteStream(dest);
|
|
87
|
+
|
|
88
|
+
const request = (url) => {
|
|
89
|
+
https.get(url, (response) => {
|
|
90
|
+
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
91
|
+
// Handle redirects
|
|
92
|
+
request(response.headers.location);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (response.statusCode !== 200) {
|
|
97
|
+
reject(new Error(`Failed to download: HTTP ${response.statusCode}`));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
response.pipe(file);
|
|
102
|
+
file.on('finish', () => {
|
|
103
|
+
file.close(resolve);
|
|
104
|
+
});
|
|
105
|
+
}).on('error', (err) => {
|
|
106
|
+
fs.unlink(dest, () => {}); // Clean up partial file
|
|
107
|
+
reject(err);
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
request(url);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Fetch JSON from URL
|
|
117
|
+
*/
|
|
118
|
+
function fetchJson(url) {
|
|
119
|
+
return new Promise((resolve, reject) => {
|
|
120
|
+
const request = (url) => {
|
|
121
|
+
https.get(url, (response) => {
|
|
122
|
+
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
123
|
+
request(response.headers.location);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (response.statusCode !== 200) {
|
|
128
|
+
reject(new Error(`Failed to fetch: HTTP ${response.statusCode}`));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let data = '';
|
|
133
|
+
response.on('data', (chunk) => (data += chunk));
|
|
134
|
+
response.on('end', () => {
|
|
135
|
+
try {
|
|
136
|
+
resolve(JSON.parse(data));
|
|
137
|
+
} catch (e) {
|
|
138
|
+
reject(new Error(`Failed to parse JSON: ${e.message}`));
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}).on('error', reject);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
request(url);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Check if Java is available at JAVA_HOME
|
|
150
|
+
*/
|
|
151
|
+
function checkJavaHome(env) {
|
|
152
|
+
const javaHome = env.JAVA_HOME;
|
|
153
|
+
if (!javaHome) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const javaBin = path.join(javaHome, 'bin', 'java');
|
|
158
|
+
try {
|
|
159
|
+
if (fs.existsSync(javaBin) || fs.existsSync(javaBin + '.exe')) {
|
|
160
|
+
return javaHome;
|
|
161
|
+
}
|
|
162
|
+
} catch (e) {
|
|
163
|
+
// Ignore errors
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Check if Java is available on PATH
|
|
170
|
+
*/
|
|
171
|
+
function checkSystemJava() {
|
|
172
|
+
try {
|
|
173
|
+
const result = spawnSync('java', ['-version'], {
|
|
174
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
175
|
+
timeout: 5000,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
if (result.status === 0) {
|
|
179
|
+
// Get the java executable path
|
|
180
|
+
const whichResult = spawnSync(process.platform === 'win32' ? 'where' : 'which', ['java'], {
|
|
181
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
if (whichResult.status === 0) {
|
|
185
|
+
const javaPath = whichResult.stdout.toString().trim().split('\n')[0];
|
|
186
|
+
// Return the JAVA_HOME (2 levels up from bin/java)
|
|
187
|
+
return path.dirname(path.dirname(javaPath));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
} catch (e) {
|
|
191
|
+
// Ignore errors
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Check if JRE exists in managed directory
|
|
198
|
+
*/
|
|
199
|
+
function checkManagedJre(jreDir) {
|
|
200
|
+
if (!fs.existsSync(jreDir)) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Look for java executable in the JRE directory
|
|
205
|
+
const entries = fs.readdirSync(jreDir);
|
|
206
|
+
for (const entry of entries) {
|
|
207
|
+
const entryPath = path.join(jreDir, entry);
|
|
208
|
+
const stat = fs.statSync(entryPath);
|
|
209
|
+
|
|
210
|
+
if (stat.isDirectory()) {
|
|
211
|
+
// Check common JRE structures
|
|
212
|
+
const possiblePaths = [
|
|
213
|
+
path.join(entryPath, 'bin', 'java'),
|
|
214
|
+
path.join(entryPath, 'Contents', 'Home', 'bin', 'java'),
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
for (const javaBin of possiblePaths) {
|
|
218
|
+
if (fs.existsSync(javaBin) || fs.existsSync(javaBin + '.exe')) {
|
|
219
|
+
// Return the directory containing bin/java
|
|
220
|
+
return javaBin.includes('Contents/Home')
|
|
221
|
+
? path.join(entryPath, 'Contents', 'Home')
|
|
222
|
+
: entryPath;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Download JRE from Adoptium
|
|
233
|
+
*/
|
|
234
|
+
async function downloadJre(targetVersion, jreDir, platform, arch, onProgress) {
|
|
235
|
+
const jreOs = mapPlatformToOs(platform);
|
|
236
|
+
const jreArch = mapArchToJreArch(arch);
|
|
237
|
+
|
|
238
|
+
const apiUrl = `https://api.adoptium.net/v3/assets/latest/${targetVersion}/hotspot?architecture=${jreArch}&image_type=jre&os=${jreOs}`;
|
|
239
|
+
|
|
240
|
+
if (onProgress) {
|
|
241
|
+
onProgress({ stage: 'fetching-metadata', message: 'Fetching JRE metadata from Adoptium...' });
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const assets = await fetchJson(apiUrl);
|
|
245
|
+
|
|
246
|
+
if (!assets || assets.length === 0) {
|
|
247
|
+
throw new Error(`No JRE found for Java ${targetVersion} on ${jreOs}/${jreArch}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const asset = assets[0];
|
|
251
|
+
const binary = asset.binary;
|
|
252
|
+
const downloadUrl = binary.package.link;
|
|
253
|
+
const checksum = binary.package.checksum;
|
|
254
|
+
const filename = binary.package.name;
|
|
255
|
+
|
|
256
|
+
// Create JRE directory if it doesn't exist
|
|
257
|
+
fs.mkdirSync(jreDir, { recursive: true });
|
|
258
|
+
|
|
259
|
+
const downloadPath = path.join(jreDir, filename);
|
|
260
|
+
|
|
261
|
+
if (onProgress) {
|
|
262
|
+
onProgress({ stage: 'downloading', message: `Downloading JRE from ${downloadUrl}...` });
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
await downloadFile(downloadUrl, downloadPath);
|
|
266
|
+
|
|
267
|
+
// Verify checksum
|
|
268
|
+
if (onProgress) {
|
|
269
|
+
onProgress({ stage: 'verifying', message: 'Verifying checksum...' });
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (!verifyChecksum(downloadPath, checksum)) {
|
|
273
|
+
fs.unlinkSync(downloadPath);
|
|
274
|
+
throw new Error('Checksum verification failed');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Extract archive
|
|
278
|
+
if (onProgress) {
|
|
279
|
+
onProgress({ stage: 'extracting', message: 'Extracting JRE...' });
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const decompress = require('decompress');
|
|
283
|
+
await decompress(downloadPath, jreDir, {
|
|
284
|
+
filter: (file) => isValidArchivePath(file.path),
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Clean up archive
|
|
288
|
+
fs.unlinkSync(downloadPath);
|
|
289
|
+
|
|
290
|
+
// Find and return the extracted JRE path
|
|
291
|
+
return checkManagedJre(jreDir);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Find or download Java JRE
|
|
296
|
+
*/
|
|
297
|
+
async function findOrDownloadJava(options) {
|
|
298
|
+
const {
|
|
299
|
+
targetVersion = 17,
|
|
300
|
+
jreDir,
|
|
301
|
+
platform = process.platform,
|
|
302
|
+
arch = process.arch,
|
|
303
|
+
env = process.env,
|
|
304
|
+
onProgress,
|
|
305
|
+
} = options;
|
|
306
|
+
|
|
307
|
+
// Check JAVA_HOME first
|
|
308
|
+
let javaHome = checkJavaHome(env);
|
|
309
|
+
if (javaHome) {
|
|
310
|
+
return javaHome;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Check system Java
|
|
314
|
+
javaHome = checkSystemJava();
|
|
315
|
+
if (javaHome) {
|
|
316
|
+
return javaHome;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Check managed JRE
|
|
320
|
+
javaHome = checkManagedJre(jreDir);
|
|
321
|
+
if (javaHome) {
|
|
322
|
+
return javaHome;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Download JRE
|
|
326
|
+
return downloadJre(targetVersion, jreDir, platform, arch, onProgress);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Run Java command with the JAR
|
|
331
|
+
*/
|
|
332
|
+
function runJavaCommand(javaHome, jarPath, args, options = {}) {
|
|
333
|
+
const {
|
|
334
|
+
javaOpts = [],
|
|
335
|
+
env = process.env,
|
|
336
|
+
spawnOptions = { stdio: 'inherit' },
|
|
337
|
+
} = options;
|
|
338
|
+
|
|
339
|
+
const javaBin = path.join(javaHome, 'bin', 'java');
|
|
340
|
+
|
|
341
|
+
// Build Java arguments
|
|
342
|
+
const javaArgs = [
|
|
343
|
+
'-Dsun.misc.URLClassPath.disableJarChecking=true',
|
|
344
|
+
...javaOpts,
|
|
345
|
+
'-jar',
|
|
346
|
+
jarPath,
|
|
347
|
+
...args,
|
|
348
|
+
];
|
|
349
|
+
|
|
350
|
+
// Add JAVA_OPTS if set
|
|
351
|
+
if (env.JAVA_OPTS) {
|
|
352
|
+
const extraOpts = env.JAVA_OPTS.split(' ').filter(Boolean);
|
|
353
|
+
javaArgs.unshift(...extraOpts);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return spawnSync(javaBin, javaArgs, {
|
|
357
|
+
...spawnOptions,
|
|
358
|
+
env,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
module.exports = {
|
|
363
|
+
mapPlatformToOs,
|
|
364
|
+
mapArchToJreArch,
|
|
365
|
+
getUserConfigDir,
|
|
366
|
+
isValidArchivePath,
|
|
367
|
+
verifyChecksum,
|
|
368
|
+
downloadFile,
|
|
369
|
+
fetchJson,
|
|
370
|
+
checkJavaHome,
|
|
371
|
+
checkSystemJava,
|
|
372
|
+
checkManagedJre,
|
|
373
|
+
downloadJre,
|
|
374
|
+
findOrDownloadJava,
|
|
375
|
+
runJavaCommand,
|
|
376
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wiremock/npm-jar-wrapper-maker",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Create NPM wrappers for Java JARs with automatic JRE management",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"npm-jar-wrapper-maker": "bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"lib/",
|
|
11
|
+
"bin/",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "node --test test/*.test.js",
|
|
16
|
+
"lint": "prettier --check .",
|
|
17
|
+
"format": "prettier --write ."
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"npm",
|
|
21
|
+
"jar",
|
|
22
|
+
"java",
|
|
23
|
+
"wrapper",
|
|
24
|
+
"cli",
|
|
25
|
+
"jre",
|
|
26
|
+
"wiremock"
|
|
27
|
+
],
|
|
28
|
+
"author": "WireMock Inc.",
|
|
29
|
+
"license": "Apache-2.0",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"decompress": "^4.2.1"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"prettier": "^3.2.4"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18.0.0"
|
|
38
|
+
},
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/wiremock/npm-jar-wrapper-maker.git"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://github.com/wiremock/npm-jar-wrapper-maker#readme",
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/wiremock/npm-jar-wrapper-maker/issues"
|
|
46
|
+
}
|
|
47
|
+
}
|