@abapify/p2-cli 0.4.0 → 0.4.1

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/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ export { };
package/dist/cli.mjs ADDED
@@ -0,0 +1,339 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ import { Command } from "commander";
4
+ import { existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
5
+ import { basename, join } from "node:path";
6
+ import { execSync } from "node:child_process";
7
+ //#region \0rolldown/runtime.js
8
+ var __defProp = Object.defineProperty;
9
+ var __exportAll = (all, no_symbols) => {
10
+ let target = {};
11
+ for (var name in all) __defProp(target, name, {
12
+ get: all[name],
13
+ enumerable: true
14
+ });
15
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
16
+ return target;
17
+ };
18
+ //#endregion
19
+ //#region src/lib/utils.ts
20
+ /**
21
+ * Execute shell command and return output
22
+ */
23
+ function execOutput(cmd) {
24
+ return execSync(cmd, {
25
+ encoding: "utf-8",
26
+ maxBuffer: 50 * 1024 * 1024
27
+ });
28
+ }
29
+ /**
30
+ * Find files matching glob pattern using native find/ls
31
+ */
32
+ function findFiles(dir, pattern) {
33
+ if (!existsSync(dir)) return [];
34
+ const files = [];
35
+ const entries = readdirSync(dir, {
36
+ withFileTypes: true,
37
+ recursive: true
38
+ });
39
+ const escaped = pattern.replaceAll(/[.+^${}()|[\]\\/]/g, "\\$&");
40
+ const regex = new RegExp("^" + escaped.replaceAll(/\*/g, ".*").replaceAll(/\?/g, ".") + "$");
41
+ for (const entry of entries) if (entry.isFile() && regex.test(entry.name)) {
42
+ const parentPath = entry.parentPath ?? dir;
43
+ files.push(join(parentPath, entry.name));
44
+ }
45
+ return files;
46
+ }
47
+ /**
48
+ * Ensure directory exists
49
+ */
50
+ function ensureDir(dir) {
51
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
52
+ }
53
+ //#endregion
54
+ //#region src/commands/download.ts
55
+ /**
56
+ * Parse artifacts.xml from P2 repository
57
+ */
58
+ function parseArtifacts(xml) {
59
+ const artifacts = [];
60
+ const regex = /<artifact classifier='([^']+)' id='([^']+)' version='([^']+)'>/g;
61
+ let match;
62
+ while ((match = regex.exec(xml)) !== null) artifacts.push({
63
+ classifier: match[1],
64
+ id: match[2],
65
+ version: match[3]
66
+ });
67
+ return artifacts;
68
+ }
69
+ /**
70
+ * Download P2 repository metadata and optionally plugins
71
+ */
72
+ async function download(repoUrl, options) {
73
+ const { output, filter, extract, extractOutput, extractPatterns } = options;
74
+ console.log(`🔧 P2 Repository Download`);
75
+ console.log(` Repository: ${repoUrl}`);
76
+ console.log(` Output: ${output}`);
77
+ console.log("");
78
+ ensureDir(output);
79
+ const artifactsJar = join(output, "artifacts.jar");
80
+ console.log("📥 Downloading artifacts.jar...");
81
+ execOutput(`wget -q "${repoUrl}/artifacts.jar" -O "${artifactsJar}"`);
82
+ const contentJar = join(output, "content.jar");
83
+ console.log("📥 Downloading content.jar...");
84
+ execOutput(`wget -q "${repoUrl}/content.jar" -O "${contentJar}"`);
85
+ const bundles = parseArtifacts(execOutput(`unzip -p "${artifactsJar}" artifacts.xml`)).filter((a) => a.classifier === "osgi.bundle");
86
+ console.log(`📋 Found ${bundles.length} plugins`);
87
+ let toDownload = bundles;
88
+ if (filter) {
89
+ const patterns = filter.split(",").map((p) => p.trim());
90
+ toDownload = bundles.filter((a) => patterns.some((p) => {
91
+ if (p.includes("*")) {
92
+ const escaped = p.replaceAll(/[.+?^${}()|[\]\\/]/g, "\\$&");
93
+ return new RegExp("^" + escaped.replaceAll(/\*/g, ".*") + "$").test(a.id);
94
+ }
95
+ return a.id.startsWith(p);
96
+ }));
97
+ console.log(`🎯 Filtered to ${toDownload.length} plugins matching: ${filter}`);
98
+ }
99
+ if (toDownload.length === 0) {
100
+ console.log("⚠️ No plugins to download");
101
+ return;
102
+ }
103
+ const pluginsDir = join(output, "plugins");
104
+ ensureDir(pluginsDir);
105
+ let downloaded = 0;
106
+ let skipped = 0;
107
+ for (let i = 0; i < toDownload.length; i++) {
108
+ const artifact = toDownload[i];
109
+ const fileName = `${artifact.id}_${artifact.version}.jar`;
110
+ const targetPath = join(pluginsDir, fileName);
111
+ process.stdout.write(`\r Downloading ${i + 1}/${toDownload.length}: ${artifact.id.slice(0, 50).padEnd(50)}`);
112
+ if (existsSync(targetPath)) {
113
+ skipped++;
114
+ continue;
115
+ }
116
+ const url = `${repoUrl}/plugins/${fileName}`;
117
+ try {
118
+ execOutput(`wget -q "${url}" -O "${targetPath}"`);
119
+ downloaded++;
120
+ } catch {
121
+ console.error(`\n ❌ Failed: ${artifact.id}`);
122
+ }
123
+ }
124
+ console.log("\n");
125
+ console.log("✅ Download complete!");
126
+ console.log(` Downloaded: ${downloaded}`);
127
+ console.log(` Skipped: ${skipped}`);
128
+ console.log(` Location: ${pluginsDir}`);
129
+ if (extract) {
130
+ const { extractJars } = await Promise.resolve().then(() => extract_exports);
131
+ console.log("");
132
+ await extractJars(pluginsDir, {
133
+ output: extractOutput || join(output, "extracted"),
134
+ patterns: extractPatterns
135
+ });
136
+ }
137
+ }
138
+ //#endregion
139
+ //#region src/commands/extract.ts
140
+ var extract_exports = /* @__PURE__ */ __exportAll({ extractJars: () => extractJars });
141
+ /**
142
+ * Extract files from JAR archives - just unzip preserving package structure
143
+ */
144
+ async function extractJars(input, options) {
145
+ const { output, patterns, verbose = false } = options;
146
+ console.log(`🔧 JAR Extraction`);
147
+ console.log(` Input: ${input}`);
148
+ console.log(` Output: ${output}`);
149
+ console.log(` Patterns: ${patterns ? patterns.join(", ") : "all files"}`);
150
+ console.log("");
151
+ const jars = findFiles(input, "*.jar");
152
+ if (jars.length === 0) if (input.endsWith(".jar") && existsSync(input)) jars.push(input);
153
+ else {
154
+ console.log("❌ No JAR files found");
155
+ return;
156
+ }
157
+ console.log(`📦 Found ${jars.length} JAR files`);
158
+ console.log("");
159
+ if (existsSync(output)) rmSync(output, { recursive: true });
160
+ ensureDir(output);
161
+ const patternArgs = patterns ? patterns.map((p) => `"${p}"`).join(" ") : "";
162
+ for (const jar of jars) {
163
+ const jarName = basename(jar);
164
+ if (verbose) console.log(` 📦 ${jarName}`);
165
+ try {
166
+ execOutput(`unzip -o -q "${jar}" ${patternArgs} -d "${output}" 2>/dev/null || true`);
167
+ } catch {}
168
+ }
169
+ console.log("");
170
+ console.log("✅ Extraction complete!");
171
+ console.log(` Output: ${output}`);
172
+ }
173
+ //#endregion
174
+ //#region src/commands/decompile.ts
175
+ const DECOMPILERS = {
176
+ cfr: {
177
+ name: "CFR",
178
+ check: "which cfr-decompiler",
179
+ command: (input, output) => `cfr-decompiler "${input}" --outputdir "${output}"`,
180
+ install: "brew install cfr-decompiler (macOS/Linux) or download from https://www.benf.org/other/cfr/"
181
+ },
182
+ procyon: {
183
+ name: "Procyon",
184
+ check: "which procyon",
185
+ command: (input, output) => `procyon -o "${output}" "${input}"`,
186
+ install: "Download from https://github.com/mstrobel/procyon"
187
+ },
188
+ fernflower: {
189
+ name: "Fernflower",
190
+ check: "test -f fernflower.jar",
191
+ command: (input, output) => `java -jar fernflower.jar "${input}" "${output}"`,
192
+ install: "Find in IntelliJ IDEA: plugins/java-decompiler/lib/java-decompiler.jar"
193
+ }
194
+ };
195
+ /**
196
+ * Check if a decompiler is available
197
+ */
198
+ function isDecompilerAvailable(decompiler) {
199
+ try {
200
+ execSync(DECOMPILERS[decompiler].check, { stdio: "ignore" });
201
+ return true;
202
+ } catch {
203
+ return false;
204
+ }
205
+ }
206
+ /**
207
+ * Find available decompiler
208
+ */
209
+ function findDecompiler() {
210
+ for (const key of Object.keys(DECOMPILERS)) if (isDecompilerAvailable(key)) return key;
211
+ return null;
212
+ }
213
+ /**
214
+ * Decompile Java class files or JAR files
215
+ */
216
+ async function decompile(input, options) {
217
+ const { output, decompiler: requestedDecompiler, filter, verbose = false } = options;
218
+ console.log(`🔧 Java Decompilation`);
219
+ console.log(` Input: ${input}`);
220
+ console.log(` Output: ${output}`);
221
+ console.log("");
222
+ const decompiler = requestedDecompiler || findDecompiler();
223
+ if (!decompiler) {
224
+ console.log("❌ No Java decompiler found!");
225
+ console.log("");
226
+ console.log("Install one of these decompilers:");
227
+ for (const [_key, config] of Object.entries(DECOMPILERS)) console.log(` ${config.name}: ${config.install}`);
228
+ process.exit(1);
229
+ }
230
+ if (requestedDecompiler && !isDecompilerAvailable(requestedDecompiler)) {
231
+ console.log(`❌ Requested decompiler '${requestedDecompiler}' is not available.`);
232
+ console.log(` ${DECOMPILERS[requestedDecompiler].install}`);
233
+ process.exit(1);
234
+ }
235
+ const config = DECOMPILERS[decompiler];
236
+ console.log(`📦 Using decompiler: ${config.name}`);
237
+ console.log("");
238
+ ensureDir(output);
239
+ let jarFiles = findFiles(input, "*.jar");
240
+ if (filter && jarFiles.length > 0) {
241
+ const patterns = filter.split(",").map((p) => p.trim());
242
+ jarFiles = jarFiles.filter((jar) => {
243
+ const jarName = basename(jar);
244
+ return patterns.some((pattern) => {
245
+ const escaped = pattern.replaceAll(/[.+?^${}()|[\]\\/]/g, "\\$&");
246
+ return new RegExp(escaped.replaceAll(/\*/g, ".*")).test(jarName);
247
+ });
248
+ });
249
+ console.log(`🎯 Filtered to ${jarFiles.length} JARs matching: ${filter}`);
250
+ }
251
+ if (jarFiles.length > 0) {
252
+ console.log(`🔍 Found ${jarFiles.length} JAR files`);
253
+ console.log("");
254
+ let success = 0;
255
+ let failed = 0;
256
+ for (let i = 0; i < jarFiles.length; i++) {
257
+ const jar = jarFiles[i];
258
+ const jarName = basename(jar, ".jar");
259
+ process.stdout.write(`\r Decompiling ${i + 1}/${jarFiles.length}: ${jarName.slice(0, 50).padEnd(50)}`);
260
+ try {
261
+ execSync(config.command(jar, output), { stdio: "ignore" });
262
+ success++;
263
+ } catch {
264
+ if (verbose) console.error(`\n ❌ Failed: ${jarName}`);
265
+ failed++;
266
+ }
267
+ }
268
+ console.log("\n");
269
+ console.log("✅ Decompilation complete!");
270
+ console.log(` Success: ${success}`);
271
+ console.log(` Failed: ${failed}`);
272
+ console.log(` Output: ${output}`);
273
+ } else {
274
+ const classFiles = findFiles(input, "*.class");
275
+ if (classFiles.length === 0) {
276
+ console.log("❌ No JAR or class files found");
277
+ return;
278
+ }
279
+ console.log(`🔍 Found ${classFiles.length} class files`);
280
+ console.log("");
281
+ let success = 0;
282
+ let failed = 0;
283
+ for (let i = 0; i < classFiles.length; i++) {
284
+ const classFile = classFiles[i];
285
+ if (verbose) process.stdout.write(`\r Decompiling ${i + 1}/${classFiles.length}: ${basename(classFile).slice(0, 50).padEnd(50)}`);
286
+ try {
287
+ execSync(config.command(classFile, output), { stdio: "ignore" });
288
+ success++;
289
+ } catch {
290
+ failed++;
291
+ }
292
+ }
293
+ console.log("\n");
294
+ console.log("✅ Decompilation complete!");
295
+ console.log(` Success: ${success}`);
296
+ console.log(` Failed: ${failed}`);
297
+ console.log(` Output: ${output}`);
298
+ }
299
+ }
300
+ //#endregion
301
+ //#region src/cli.ts
302
+ /**
303
+ * P2 CLI - Eclipse P2 Repository Tools
304
+ *
305
+ * Commands:
306
+ * p2 download <url> Download plugins from P2 repository
307
+ * p2 extract <input> Extract files from JAR archives
308
+ * p2 decompile <input> Decompile Java class files
309
+ */
310
+ const program = new Command();
311
+ const pkg = createRequire(import.meta.url)("../package.json");
312
+ program.name("p2").description("CLI for Eclipse P2 repositories - download, extract, and decompile plugins").version(pkg.version ?? "0.0.0");
313
+ program.command("download <url>").description("Download plugins from a P2 repository").option("-o, --output <dir>", "Output directory", "./p2-download").option("-f, --filter <patterns>", "Filter plugins by ID pattern (comma-separated)", "").option("-e, --extract", "Also extract files after download").option("--extract-output <dir>", "Output directory for extraction").option("--extract-patterns <patterns>", "File patterns to extract (comma-separated, default: all)").action(async (url, opts) => {
314
+ await download(url, {
315
+ output: opts.output,
316
+ filter: opts.filter || void 0,
317
+ extract: opts.extract,
318
+ extractOutput: opts.extractOutput,
319
+ extractPatterns: opts.extractPatterns?.split(",").map((p) => p.trim())
320
+ });
321
+ });
322
+ program.command("extract <input>").description("Extract files from JAR archives (preserves package structure)").option("-o, --output <dir>", "Output directory", "./extracted").option("-p, --patterns <patterns>", "File patterns to extract (comma-separated, default: all)").option("-v, --verbose", "Verbose output").action(async (input, opts) => {
323
+ await extractJars(input, {
324
+ output: opts.output,
325
+ patterns: opts.patterns ? opts.patterns.split(",").map((p) => p.trim()) : void 0,
326
+ verbose: opts.verbose
327
+ });
328
+ });
329
+ program.command("decompile <input>").description("Decompile Java class files or JAR files").option("-o, --output <dir>", "Output directory", "./decompiled").option("-f, --filter <patterns>", "Filter JARs by name pattern (comma-separated)").option("-d, --decompiler <name>", "Decompiler to use (cfr, procyon, fernflower)").option("-v, --verbose", "Verbose output").action(async (input, opts) => {
330
+ await decompile(input, {
331
+ output: opts.output,
332
+ filter: opts.filter,
333
+ decompiler: opts.decompiler,
334
+ verbose: opts.verbose
335
+ });
336
+ });
337
+ program.parse();
338
+ //#endregion
339
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abapify/p2-cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/abapify/adt-cli.git",