@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/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
+ }