@abapify/p2-cli 0.0.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/README.md +168 -1
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +339 -0
- package/package.json +36 -2
package/README.md
CHANGED
|
@@ -1,3 +1,170 @@
|
|
|
1
1
|
# @abapify/p2-cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CLI for Eclipse P2 repositories - download, extract, and decompile plugins.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# npm
|
|
9
|
+
npm install -g @abapify/p2-cli
|
|
10
|
+
|
|
11
|
+
# or run without installing globally
|
|
12
|
+
npx @abapify/p2-cli --help
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Development
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# From monorepo root
|
|
19
|
+
bun install
|
|
20
|
+
npx nx build p2-cli
|
|
21
|
+
|
|
22
|
+
# Or run directly with tsx
|
|
23
|
+
npx tsx tools/p2-cli/src/cli.ts
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Publishing Bootstrap (maintainers)
|
|
27
|
+
|
|
28
|
+
`p2-cli` is wired into workspace release publishing via `nx release`.
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Validate npm publish/trusted-publisher readiness
|
|
32
|
+
bunx nx run p2-cli:npm-trust-check
|
|
33
|
+
|
|
34
|
+
# One-time bootstrap for new npm package/trusted publishing
|
|
35
|
+
bunx nx run p2-cli:npm-trust-check --prepare
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Then publish from CI using the repository release workflow (which triggers `.github/workflows/publish.yml`).
|
|
39
|
+
|
|
40
|
+
## Commands
|
|
41
|
+
|
|
42
|
+
### `p2 download <url>`
|
|
43
|
+
|
|
44
|
+
Download plugins from a P2 repository.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Download ADT plugins from SAP tools
|
|
48
|
+
p2 download https://tools.hana.ondemand.com/latest -o ./sdk
|
|
49
|
+
|
|
50
|
+
# Filter to specific plugins
|
|
51
|
+
p2 download https://tools.hana.ondemand.com/latest -o ./sdk -f "com.sap.adt.*"
|
|
52
|
+
|
|
53
|
+
# Download and extract in one step
|
|
54
|
+
p2 download https://tools.hana.ondemand.com/latest -o ./sdk --extract
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Options:**
|
|
58
|
+
|
|
59
|
+
- `-o, --output <dir>` - Output directory (default: `./p2-download`)
|
|
60
|
+
- `-f, --filter <patterns>` - Filter plugins by ID pattern (comma-separated)
|
|
61
|
+
- `-e, --extract` - Also extract files after download
|
|
62
|
+
- `--extract-output <dir>` - Output directory for extraction
|
|
63
|
+
- `--extract-patterns <patterns>` - File patterns to extract (default: `*.xsd,*.ecore,*.genmodel,*.xml`)
|
|
64
|
+
|
|
65
|
+
### `p2 extract <input>`
|
|
66
|
+
|
|
67
|
+
Extract files from JAR archives. Works as a general-purpose JAR extractor.
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Extract schemas from downloaded plugins
|
|
71
|
+
p2 extract ./sdk/plugins -o ./extracted
|
|
72
|
+
|
|
73
|
+
# Extract specific file types
|
|
74
|
+
p2 extract ./sdk/plugins -o ./extracted -p "*.xsd,*.xml"
|
|
75
|
+
|
|
76
|
+
# Extract everything (no organization)
|
|
77
|
+
p2 extract ./sdk/plugins -o ./extracted -p "*" --no-organize
|
|
78
|
+
|
|
79
|
+
# Single JAR file
|
|
80
|
+
p2 extract ./my-plugin.jar -o ./extracted
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Options:**
|
|
84
|
+
|
|
85
|
+
- `-o, --output <dir>` - Output directory (default: `./extracted`)
|
|
86
|
+
- `-p, --patterns <patterns>` - File patterns to extract (default: `*.xsd,*.ecore,*.genmodel,*.xml`)
|
|
87
|
+
- `--no-organize` - Do not organize files by type (flat output)
|
|
88
|
+
- `-v, --verbose` - Verbose output
|
|
89
|
+
|
|
90
|
+
**Organized output structure:**
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
extracted/
|
|
94
|
+
├── schemas/
|
|
95
|
+
│ ├── xsd/ # XML Schema definitions
|
|
96
|
+
│ ├── ecore/ # Eclipse EMF models
|
|
97
|
+
│ └── genmodel/ # Generator models
|
|
98
|
+
├── templates/ # Code templates
|
|
99
|
+
├── xml/ # Other XML files
|
|
100
|
+
├── classes/ # Java class files (by JAR)
|
|
101
|
+
└── sources/ # Java source files (by JAR)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### `p2 decompile <input>`
|
|
105
|
+
|
|
106
|
+
Decompile Java class files to readable source code.
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Decompile extracted classes
|
|
110
|
+
p2 decompile ./extracted/classes -o ./decompiled
|
|
111
|
+
|
|
112
|
+
# Use specific decompiler
|
|
113
|
+
p2 decompile ./extracted/classes -o ./decompiled -d cfr
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Options:**
|
|
117
|
+
|
|
118
|
+
- `-o, --output <dir>` - Output directory (default: `./decompiled`)
|
|
119
|
+
- `-d, --decompiler <name>` - Decompiler to use (`cfr`, `procyon`, `fernflower`)
|
|
120
|
+
- `-v, --verbose` - Verbose output
|
|
121
|
+
|
|
122
|
+
**Supported decompilers:**
|
|
123
|
+
|
|
124
|
+
- **CFR** - `brew install cfr-decompiler` (macOS/Linux)
|
|
125
|
+
- **Procyon** - Download from GitHub
|
|
126
|
+
- **Fernflower** - Bundled with IntelliJ IDEA
|
|
127
|
+
|
|
128
|
+
## Examples
|
|
129
|
+
|
|
130
|
+
### Download SAP ADT SDK
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
# Full workflow: download, extract, and organize
|
|
134
|
+
p2 download https://tools.hana.ondemand.com/latest \
|
|
135
|
+
-o ./adt-sdk \
|
|
136
|
+
-f "com.sap.adt.*,com.sap.conn.jco.*" \
|
|
137
|
+
--extract \
|
|
138
|
+
--extract-output ./adt-sdk/extracted
|
|
139
|
+
|
|
140
|
+
# Result:
|
|
141
|
+
# ./adt-sdk/
|
|
142
|
+
# ├── artifacts.jar
|
|
143
|
+
# ├── content.jar
|
|
144
|
+
# ├── plugins/
|
|
145
|
+
# │ ├── com.sap.adt.tools.core_3.54.1.jar
|
|
146
|
+
# │ └── ...
|
|
147
|
+
# └── extracted/
|
|
148
|
+
# ├── schemas/xsd/
|
|
149
|
+
# ├── schemas/ecore/
|
|
150
|
+
# └── ...
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Extract from existing Eclipse installation
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
# macOS
|
|
157
|
+
p2 extract /Applications/Eclipse.app/Contents/Eclipse/plugins \
|
|
158
|
+
-o ./eclipse-schemas \
|
|
159
|
+
-p "*.xsd"
|
|
160
|
+
|
|
161
|
+
# Linux
|
|
162
|
+
p2 extract ~/.eclipse/*/plugins -o ./eclipse-schemas
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Requirements
|
|
166
|
+
|
|
167
|
+
- Node.js 18+
|
|
168
|
+
- `wget` command (for downloads)
|
|
169
|
+
- `unzip` command (for extraction)
|
|
170
|
+
- Java decompiler (optional, for decompilation)
|
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,5 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abapify/p2-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/abapify/adt-cli.git",
|
|
7
|
+
"directory": "tools/p2-cli"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/abapify/adt-cli/tree/main/tools/p2-cli#readme",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/abapify/adt-cli/issues"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"description": "CLI for Eclipse P2 repositories - download, extract, and decompile plugins",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"type": "module",
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18"
|
|
21
|
+
},
|
|
22
|
+
"bin": {
|
|
23
|
+
"p2": "./dist/cli.mjs"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md"
|
|
28
|
+
],
|
|
29
|
+
"keywords": [
|
|
30
|
+
"sap",
|
|
31
|
+
"eclipse",
|
|
32
|
+
"p2",
|
|
33
|
+
"cli",
|
|
34
|
+
"decompiler"
|
|
35
|
+
],
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"commander": "*"
|
|
38
|
+
}
|
|
5
39
|
}
|