@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/generator.js
ADDED
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NPM Wrapper Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates NPM wrapper projects for Java JARs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('node:fs');
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Default configuration
|
|
12
|
+
*/
|
|
13
|
+
const defaults = {
|
|
14
|
+
targetJavaVersion: 17,
|
|
15
|
+
javaOpts: [],
|
|
16
|
+
enableNativeAccess: false,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Generate package.json for the wrapper
|
|
21
|
+
*/
|
|
22
|
+
function generatePackageJson(config) {
|
|
23
|
+
const {
|
|
24
|
+
packageName,
|
|
25
|
+
version = '1.0.0',
|
|
26
|
+
description = '',
|
|
27
|
+
author = '',
|
|
28
|
+
license = 'Apache-2.0',
|
|
29
|
+
cliName,
|
|
30
|
+
jarFileName,
|
|
31
|
+
repository = {},
|
|
32
|
+
keywords = [],
|
|
33
|
+
homepage = '',
|
|
34
|
+
bugs = {},
|
|
35
|
+
enablePostinstall = false,
|
|
36
|
+
postinstallCommand = '',
|
|
37
|
+
} = config;
|
|
38
|
+
|
|
39
|
+
const pkg = {
|
|
40
|
+
name: packageName,
|
|
41
|
+
version,
|
|
42
|
+
description,
|
|
43
|
+
main: 'index.js',
|
|
44
|
+
bin: {},
|
|
45
|
+
files: ['index.js', 'cli.js', jarFileName],
|
|
46
|
+
scripts: {
|
|
47
|
+
test: 'echo "No tests specified"',
|
|
48
|
+
},
|
|
49
|
+
keywords: ['java', 'cli', ...keywords],
|
|
50
|
+
author,
|
|
51
|
+
license,
|
|
52
|
+
dependencies: {
|
|
53
|
+
decompress: '^4.2.1',
|
|
54
|
+
},
|
|
55
|
+
engines: {
|
|
56
|
+
node: '>=18.0.0',
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Set bin entry
|
|
61
|
+
pkg.bin[cliName] = 'index.js';
|
|
62
|
+
|
|
63
|
+
// Add postinstall if enabled
|
|
64
|
+
if (enablePostinstall) {
|
|
65
|
+
pkg.scripts.postinstall = 'node ./postinstall.js';
|
|
66
|
+
pkg.files.push('postinstall.js');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Add optional fields if provided
|
|
70
|
+
if (Object.keys(repository).length > 0) {
|
|
71
|
+
pkg.repository = repository;
|
|
72
|
+
}
|
|
73
|
+
if (homepage) {
|
|
74
|
+
pkg.homepage = homepage;
|
|
75
|
+
}
|
|
76
|
+
if (Object.keys(bugs).length > 0) {
|
|
77
|
+
pkg.bugs = bugs;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return JSON.stringify(pkg, null, 2);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Generate the CLI entry point (index.js)
|
|
85
|
+
*/
|
|
86
|
+
function generateIndexJs(config) {
|
|
87
|
+
const {
|
|
88
|
+
cliName,
|
|
89
|
+
jarFileName,
|
|
90
|
+
targetJavaVersion = defaults.targetJavaVersion,
|
|
91
|
+
javaOpts = defaults.javaOpts,
|
|
92
|
+
enableNativeAccess = defaults.enableNativeAccess,
|
|
93
|
+
configDirName,
|
|
94
|
+
} = config;
|
|
95
|
+
|
|
96
|
+
// Use the CLI name as config dir name if not specified
|
|
97
|
+
const configDir = configDirName || cliName;
|
|
98
|
+
|
|
99
|
+
return `#!/usr/bin/env node
|
|
100
|
+
|
|
101
|
+
const path = require('node:path');
|
|
102
|
+
const os = require('node:os');
|
|
103
|
+
const { runCli } = require('./cli');
|
|
104
|
+
|
|
105
|
+
const targetJavaVersion = ${targetJavaVersion};
|
|
106
|
+
const jarPath = path.join(__dirname, '${jarFileName}');
|
|
107
|
+
const jreDir = path.join(userConfigDir(), '${configDir}', 'jre');
|
|
108
|
+
|
|
109
|
+
const config = {
|
|
110
|
+
env: process.env,
|
|
111
|
+
targetJavaVersion,
|
|
112
|
+
jreDir,
|
|
113
|
+
jarPath,
|
|
114
|
+
cmdArgs: process.argv.slice(2),
|
|
115
|
+
platform: process.platform,
|
|
116
|
+
arch: process.arch,
|
|
117
|
+
spawnOptions: { stdio: 'inherit' },
|
|
118
|
+
javaOpts: ${JSON.stringify(javaOpts)},
|
|
119
|
+
enableNativeAccess: ${enableNativeAccess},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
runCli(config).then((ret) => {
|
|
123
|
+
if (ret.error) {
|
|
124
|
+
console.error(\`Failed to run: \${ret.error.message}\`);
|
|
125
|
+
}
|
|
126
|
+
process.exit(ret.status ?? 1);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
function userConfigDir() {
|
|
130
|
+
switch (process.platform) {
|
|
131
|
+
case 'darwin':
|
|
132
|
+
return path.join(os.homedir(), '.config');
|
|
133
|
+
case 'win32':
|
|
134
|
+
return process.env['LOCALAPPDATA'] || path.join(os.homedir(), 'AppData', 'Local');
|
|
135
|
+
default:
|
|
136
|
+
return process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Generate the CLI runtime (cli.js)
|
|
144
|
+
*/
|
|
145
|
+
function generateCliJs() {
|
|
146
|
+
return `/**
|
|
147
|
+
* CLI Runtime
|
|
148
|
+
*
|
|
149
|
+
* Manages Java JRE discovery/download and JAR execution.
|
|
150
|
+
*/
|
|
151
|
+
|
|
152
|
+
const { spawnSync } = require('node:child_process');
|
|
153
|
+
const fs = require('node:fs');
|
|
154
|
+
const path = require('node:path');
|
|
155
|
+
const https = require('node:https');
|
|
156
|
+
const crypto = require('node:crypto');
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Map Node.js platform to JRE OS name
|
|
160
|
+
*/
|
|
161
|
+
function mapPlatformToOs(platform) {
|
|
162
|
+
switch (platform) {
|
|
163
|
+
case 'darwin':
|
|
164
|
+
return 'mac';
|
|
165
|
+
case 'win32':
|
|
166
|
+
return 'windows';
|
|
167
|
+
default:
|
|
168
|
+
return 'linux';
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Map Node.js architecture to JRE architecture name
|
|
174
|
+
*/
|
|
175
|
+
function mapArchToJreArch(arch) {
|
|
176
|
+
switch (arch) {
|
|
177
|
+
case 'arm64':
|
|
178
|
+
return 'aarch64';
|
|
179
|
+
case 'x64':
|
|
180
|
+
return 'x64';
|
|
181
|
+
case 'ia32':
|
|
182
|
+
return 'x32';
|
|
183
|
+
default:
|
|
184
|
+
return arch;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Check if a path is valid (no path traversal attacks)
|
|
190
|
+
*/
|
|
191
|
+
function isValidArchivePath(filePath) {
|
|
192
|
+
if (path.isAbsolute(filePath)) {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
const segments = filePath.split(path.sep);
|
|
196
|
+
return !segments.some((segment) => segment === '..');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Verify file checksum
|
|
201
|
+
*/
|
|
202
|
+
function verifyChecksum(filePath, expectedChecksum, algorithm = 'sha256') {
|
|
203
|
+
const fileBuffer = fs.readFileSync(filePath);
|
|
204
|
+
const hash = crypto.createHash(algorithm);
|
|
205
|
+
hash.update(fileBuffer);
|
|
206
|
+
const actualChecksum = hash.digest('hex');
|
|
207
|
+
return actualChecksum === expectedChecksum;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Download a file from URL to destination
|
|
212
|
+
*/
|
|
213
|
+
function downloadFile(url, dest) {
|
|
214
|
+
return new Promise((resolve, reject) => {
|
|
215
|
+
const file = fs.createWriteStream(dest);
|
|
216
|
+
|
|
217
|
+
const request = (url) => {
|
|
218
|
+
https.get(url, (response) => {
|
|
219
|
+
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
220
|
+
request(response.headers.location);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (response.statusCode !== 200) {
|
|
225
|
+
reject(new Error(\`Failed to download: HTTP \${response.statusCode}\`));
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
response.pipe(file);
|
|
230
|
+
file.on('finish', () => {
|
|
231
|
+
file.close(resolve);
|
|
232
|
+
});
|
|
233
|
+
}).on('error', (err) => {
|
|
234
|
+
fs.unlink(dest, () => {});
|
|
235
|
+
reject(err);
|
|
236
|
+
});
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
request(url);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Fetch JSON from URL
|
|
245
|
+
*/
|
|
246
|
+
function fetchJson(url) {
|
|
247
|
+
return new Promise((resolve, reject) => {
|
|
248
|
+
const request = (url) => {
|
|
249
|
+
https.get(url, (response) => {
|
|
250
|
+
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
251
|
+
request(response.headers.location);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (response.statusCode !== 200) {
|
|
256
|
+
reject(new Error(\`Failed to fetch: HTTP \${response.statusCode}\`));
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
let data = '';
|
|
261
|
+
response.on('data', (chunk) => (data += chunk));
|
|
262
|
+
response.on('end', () => {
|
|
263
|
+
try {
|
|
264
|
+
resolve(JSON.parse(data));
|
|
265
|
+
} catch (e) {
|
|
266
|
+
reject(new Error(\`Failed to parse JSON: \${e.message}\`));
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}).on('error', reject);
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
request(url);
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Check if Java is available at JAVA_HOME
|
|
278
|
+
*/
|
|
279
|
+
function checkJavaHome(env) {
|
|
280
|
+
const javaHome = env.JAVA_HOME;
|
|
281
|
+
if (!javaHome) {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const javaBin = path.join(javaHome, 'bin', 'java');
|
|
286
|
+
try {
|
|
287
|
+
if (fs.existsSync(javaBin) || fs.existsSync(javaBin + '.exe')) {
|
|
288
|
+
return javaHome;
|
|
289
|
+
}
|
|
290
|
+
} catch (e) {
|
|
291
|
+
// Ignore errors
|
|
292
|
+
}
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Check if Java is available on PATH
|
|
298
|
+
*/
|
|
299
|
+
function checkSystemJava() {
|
|
300
|
+
try {
|
|
301
|
+
const result = spawnSync('java', ['-version'], {
|
|
302
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
303
|
+
timeout: 5000,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
if (result.status === 0) {
|
|
307
|
+
const whichResult = spawnSync(process.platform === 'win32' ? 'where' : 'which', ['java'], {
|
|
308
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
if (whichResult.status === 0) {
|
|
312
|
+
const javaPath = whichResult.stdout.toString().trim().split('\\n')[0];
|
|
313
|
+
return path.dirname(path.dirname(javaPath));
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
} catch (e) {
|
|
317
|
+
// Ignore errors
|
|
318
|
+
}
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Check if JRE exists in managed directory
|
|
324
|
+
*/
|
|
325
|
+
function checkManagedJre(jreDir) {
|
|
326
|
+
if (!fs.existsSync(jreDir)) {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const entries = fs.readdirSync(jreDir);
|
|
331
|
+
for (const entry of entries) {
|
|
332
|
+
const entryPath = path.join(jreDir, entry);
|
|
333
|
+
const stat = fs.statSync(entryPath);
|
|
334
|
+
|
|
335
|
+
if (stat.isDirectory()) {
|
|
336
|
+
const possiblePaths = [
|
|
337
|
+
path.join(entryPath, 'bin', 'java'),
|
|
338
|
+
path.join(entryPath, 'Contents', 'Home', 'bin', 'java'),
|
|
339
|
+
];
|
|
340
|
+
|
|
341
|
+
for (const javaBin of possiblePaths) {
|
|
342
|
+
if (fs.existsSync(javaBin) || fs.existsSync(javaBin + '.exe')) {
|
|
343
|
+
return javaBin.includes('Contents/Home')
|
|
344
|
+
? path.join(entryPath, 'Contents', 'Home')
|
|
345
|
+
: entryPath;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Download JRE from Adoptium
|
|
356
|
+
*/
|
|
357
|
+
async function downloadJre(targetVersion, jreDir, platform, arch) {
|
|
358
|
+
const jreOs = mapPlatformToOs(platform);
|
|
359
|
+
const jreArch = mapArchToJreArch(arch);
|
|
360
|
+
|
|
361
|
+
const apiUrl = \`https://api.adoptium.net/v3/assets/latest/\${targetVersion}/hotspot?architecture=\${jreArch}&image_type=jre&os=\${jreOs}\`;
|
|
362
|
+
|
|
363
|
+
console.log('Fetching JRE metadata from Adoptium...');
|
|
364
|
+
const assets = await fetchJson(apiUrl);
|
|
365
|
+
|
|
366
|
+
if (!assets || assets.length === 0) {
|
|
367
|
+
throw new Error(\`No JRE found for Java \${targetVersion} on \${jreOs}/\${jreArch}\`);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const asset = assets[0];
|
|
371
|
+
const binary = asset.binary;
|
|
372
|
+
const downloadUrl = binary.package.link;
|
|
373
|
+
const checksum = binary.package.checksum;
|
|
374
|
+
const filename = binary.package.name;
|
|
375
|
+
|
|
376
|
+
fs.mkdirSync(jreDir, { recursive: true });
|
|
377
|
+
|
|
378
|
+
const downloadPath = path.join(jreDir, filename);
|
|
379
|
+
|
|
380
|
+
console.log(\`Downloading JRE from Adoptium (\${binary.package.size / 1024 / 1024 | 0} MB)...\`);
|
|
381
|
+
await downloadFile(downloadUrl, downloadPath);
|
|
382
|
+
|
|
383
|
+
console.log('Verifying checksum...');
|
|
384
|
+
if (!verifyChecksum(downloadPath, checksum)) {
|
|
385
|
+
fs.unlinkSync(downloadPath);
|
|
386
|
+
throw new Error('Checksum verification failed');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
console.log('Extracting JRE...');
|
|
390
|
+
const decompress = require('decompress');
|
|
391
|
+
await decompress(downloadPath, jreDir, {
|
|
392
|
+
filter: (file) => isValidArchivePath(file.path),
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
fs.unlinkSync(downloadPath);
|
|
396
|
+
console.log('JRE installation complete.');
|
|
397
|
+
|
|
398
|
+
return checkManagedJre(jreDir);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Find or download Java JRE
|
|
403
|
+
*/
|
|
404
|
+
async function findOrDownloadJava(targetVersion, jreDir, platform, arch, env) {
|
|
405
|
+
let javaHome = checkJavaHome(env);
|
|
406
|
+
if (javaHome) {
|
|
407
|
+
return javaHome;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
javaHome = checkSystemJava();
|
|
411
|
+
if (javaHome) {
|
|
412
|
+
return javaHome;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
javaHome = checkManagedJre(jreDir);
|
|
416
|
+
if (javaHome) {
|
|
417
|
+
return javaHome;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return downloadJre(targetVersion, jreDir, platform, arch);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Run the CLI
|
|
425
|
+
*/
|
|
426
|
+
async function runCli(config) {
|
|
427
|
+
const {
|
|
428
|
+
env,
|
|
429
|
+
targetJavaVersion,
|
|
430
|
+
jreDir,
|
|
431
|
+
jarPath,
|
|
432
|
+
cmdArgs,
|
|
433
|
+
platform,
|
|
434
|
+
arch,
|
|
435
|
+
spawnOptions,
|
|
436
|
+
javaOpts = [],
|
|
437
|
+
enableNativeAccess = false,
|
|
438
|
+
} = config;
|
|
439
|
+
|
|
440
|
+
try {
|
|
441
|
+
const javaHome = await findOrDownloadJava(targetJavaVersion, jreDir, platform, arch, env);
|
|
442
|
+
|
|
443
|
+
if (!javaHome) {
|
|
444
|
+
return { error: new Error('Failed to find or download Java'), status: 1 };
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const javaBin = path.join(javaHome, 'bin', 'java');
|
|
448
|
+
|
|
449
|
+
const javaArgs = [
|
|
450
|
+
'-Dsun.misc.URLClassPath.disableJarChecking=true',
|
|
451
|
+
...javaOpts,
|
|
452
|
+
];
|
|
453
|
+
|
|
454
|
+
if (enableNativeAccess) {
|
|
455
|
+
javaArgs.push('--enable-native-access=ALL-UNNAMED');
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (env.JAVA_OPTS) {
|
|
459
|
+
const extraOpts = env.JAVA_OPTS.split(' ').filter(Boolean);
|
|
460
|
+
javaArgs.push(...extraOpts);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
javaArgs.push('-jar', jarPath, ...cmdArgs);
|
|
464
|
+
|
|
465
|
+
const result = spawnSync(javaBin, javaArgs, {
|
|
466
|
+
...spawnOptions,
|
|
467
|
+
env,
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
return { status: result.status, error: result.error };
|
|
471
|
+
} catch (error) {
|
|
472
|
+
return { error, status: 1 };
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
module.exports = {
|
|
477
|
+
runCli,
|
|
478
|
+
findOrDownloadJava,
|
|
479
|
+
mapPlatformToOs,
|
|
480
|
+
mapArchToJreArch,
|
|
481
|
+
isValidArchivePath,
|
|
482
|
+
};
|
|
483
|
+
`;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Generate postinstall.js
|
|
488
|
+
*/
|
|
489
|
+
function generatePostinstallJs(config) {
|
|
490
|
+
const { postinstallCommand = '', cliName } = config;
|
|
491
|
+
|
|
492
|
+
if (!postinstallCommand) {
|
|
493
|
+
return `/**
|
|
494
|
+
* Post-install hook
|
|
495
|
+
*
|
|
496
|
+
* Runs after npm install.
|
|
497
|
+
*/
|
|
498
|
+
|
|
499
|
+
// Add custom post-install logic here
|
|
500
|
+
console.log('${cliName} installed successfully.');
|
|
501
|
+
`;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return `/**
|
|
505
|
+
* Post-install hook
|
|
506
|
+
*
|
|
507
|
+
* Runs after npm install.
|
|
508
|
+
*/
|
|
509
|
+
|
|
510
|
+
const { execSync } = require('node:child_process');
|
|
511
|
+
const path = require('node:path');
|
|
512
|
+
|
|
513
|
+
try {
|
|
514
|
+
// Check if installed globally
|
|
515
|
+
const globalPrefix = execSync('npm config get prefix', { encoding: 'utf-8' }).trim();
|
|
516
|
+
const isGlobal = __dirname.startsWith(globalPrefix);
|
|
517
|
+
|
|
518
|
+
if (isGlobal) {
|
|
519
|
+
${postinstallCommand}
|
|
520
|
+
}
|
|
521
|
+
} catch (e) {
|
|
522
|
+
// Ignore errors during postinstall
|
|
523
|
+
}
|
|
524
|
+
`;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Generate .gitignore
|
|
529
|
+
*/
|
|
530
|
+
function generateGitignore(config) {
|
|
531
|
+
const { jarFileName } = config;
|
|
532
|
+
|
|
533
|
+
return `# Dependencies
|
|
534
|
+
node_modules/
|
|
535
|
+
|
|
536
|
+
# Build artifacts
|
|
537
|
+
${jarFileName}
|
|
538
|
+
|
|
539
|
+
# IDE
|
|
540
|
+
.idea/
|
|
541
|
+
.vscode/
|
|
542
|
+
*.iml
|
|
543
|
+
|
|
544
|
+
# OS
|
|
545
|
+
.DS_Store
|
|
546
|
+
Thumbs.db
|
|
547
|
+
|
|
548
|
+
# Logs
|
|
549
|
+
*.log
|
|
550
|
+
npm-debug.log*
|
|
551
|
+
|
|
552
|
+
# Test coverage
|
|
553
|
+
coverage/
|
|
554
|
+
`;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Generate .npmignore
|
|
559
|
+
*/
|
|
560
|
+
function generateNpmignore() {
|
|
561
|
+
return `# Development files
|
|
562
|
+
.gitignore
|
|
563
|
+
.prettierrc
|
|
564
|
+
.editorconfig
|
|
565
|
+
|
|
566
|
+
# Test files
|
|
567
|
+
test/
|
|
568
|
+
*.test.js
|
|
569
|
+
|
|
570
|
+
# IDE
|
|
571
|
+
.idea/
|
|
572
|
+
.vscode/
|
|
573
|
+
*.iml
|
|
574
|
+
|
|
575
|
+
# CI/CD
|
|
576
|
+
.github/
|
|
577
|
+
.gitlab-ci.yml
|
|
578
|
+
Jenkinsfile
|
|
579
|
+
|
|
580
|
+
# Documentation source
|
|
581
|
+
docs/
|
|
582
|
+
`;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Generate README.md
|
|
587
|
+
*/
|
|
588
|
+
function generateReadme(config) {
|
|
589
|
+
const {
|
|
590
|
+
packageName,
|
|
591
|
+
description = '',
|
|
592
|
+
cliName,
|
|
593
|
+
jarFileName,
|
|
594
|
+
targetJavaVersion = defaults.targetJavaVersion,
|
|
595
|
+
} = config;
|
|
596
|
+
|
|
597
|
+
return `# ${packageName}
|
|
598
|
+
|
|
599
|
+
${description}
|
|
600
|
+
|
|
601
|
+
## Installation
|
|
602
|
+
|
|
603
|
+
\`\`\`bash
|
|
604
|
+
npm install -g ${packageName}
|
|
605
|
+
\`\`\`
|
|
606
|
+
|
|
607
|
+
## Usage
|
|
608
|
+
|
|
609
|
+
\`\`\`bash
|
|
610
|
+
${cliName} [options]
|
|
611
|
+
\`\`\`
|
|
612
|
+
|
|
613
|
+
## Requirements
|
|
614
|
+
|
|
615
|
+
- Node.js >= 18.0.0
|
|
616
|
+
- Java ${targetJavaVersion}+ (will be downloaded automatically if not found)
|
|
617
|
+
|
|
618
|
+
## How it works
|
|
619
|
+
|
|
620
|
+
This package wraps a Java JAR file (\`${jarFileName}\`) and provides a convenient CLI interface.
|
|
621
|
+
When you run the CLI, it will:
|
|
622
|
+
|
|
623
|
+
1. Check for Java in \`JAVA_HOME\`
|
|
624
|
+
2. Check for Java on your system PATH
|
|
625
|
+
3. If no Java is found, automatically download and cache a JRE from Adoptium
|
|
626
|
+
|
|
627
|
+
## License
|
|
628
|
+
|
|
629
|
+
Apache-2.0
|
|
630
|
+
`;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Generate the wrapper project
|
|
635
|
+
*/
|
|
636
|
+
function generateWrapper(config, outputDir) {
|
|
637
|
+
const files = {
|
|
638
|
+
'package.json': generatePackageJson(config),
|
|
639
|
+
'index.js': generateIndexJs(config),
|
|
640
|
+
'cli.js': generateCliJs(),
|
|
641
|
+
'.gitignore': generateGitignore(config),
|
|
642
|
+
'.npmignore': generateNpmignore(),
|
|
643
|
+
'README.md': generateReadme(config),
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
if (config.enablePostinstall) {
|
|
647
|
+
files['postinstall.js'] = generatePostinstallJs(config);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Create output directory if it doesn't exist
|
|
651
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
652
|
+
|
|
653
|
+
// Write all files
|
|
654
|
+
const writtenFiles = [];
|
|
655
|
+
for (const [filename, content] of Object.entries(files)) {
|
|
656
|
+
const filePath = path.join(outputDir, filename);
|
|
657
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
658
|
+
writtenFiles.push(filePath);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return {
|
|
662
|
+
outputDir,
|
|
663
|
+
files: writtenFiles,
|
|
664
|
+
jarPlaceholder: path.join(outputDir, config.jarFileName),
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Validate configuration
|
|
670
|
+
*/
|
|
671
|
+
function validateConfig(config) {
|
|
672
|
+
const errors = [];
|
|
673
|
+
|
|
674
|
+
if (!config.packageName) {
|
|
675
|
+
errors.push('packageName is required');
|
|
676
|
+
} else if (!/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(config.packageName)) {
|
|
677
|
+
errors.push('packageName must be a valid npm package name');
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (!config.cliName) {
|
|
681
|
+
errors.push('cliName is required');
|
|
682
|
+
} else if (!/^[a-z0-9-]+$/.test(config.cliName)) {
|
|
683
|
+
errors.push('cliName must contain only lowercase letters, numbers, and hyphens');
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
if (!config.jarFileName) {
|
|
687
|
+
errors.push('jarFileName is required');
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (config.targetJavaVersion && (typeof config.targetJavaVersion !== 'number' || config.targetJavaVersion < 8)) {
|
|
691
|
+
errors.push('targetJavaVersion must be a number >= 8');
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
return {
|
|
695
|
+
valid: errors.length === 0,
|
|
696
|
+
errors,
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
module.exports = {
|
|
701
|
+
generateWrapper,
|
|
702
|
+
generatePackageJson,
|
|
703
|
+
generateIndexJs,
|
|
704
|
+
generateCliJs,
|
|
705
|
+
generatePostinstallJs,
|
|
706
|
+
generateGitignore,
|
|
707
|
+
generateNpmignore,
|
|
708
|
+
generateReadme,
|
|
709
|
+
validateConfig,
|
|
710
|
+
defaults,
|
|
711
|
+
};
|