114 lines
3.8 KiB
JavaScript
114 lines
3.8 KiB
JavaScript
#!/usr/bin/env node
|
|
// jetbrains-fetch-worker.mjs — Sub-process worker that fetches a JetBrains
|
|
// plugin URL and extracts the downloaded ZIP into a writable directory.
|
|
// Mirrors `vsix-fetch-worker.mjs` IPC exactly.
|
|
//
|
|
// Contract:
|
|
// stdin: none
|
|
// argv: --url <url> --tmpdir <writable-dir>
|
|
// stdout: single JSON line {ok:true, sha256, size, finalUrl, source, extRoot}
|
|
// on success, or {ok:false, error:"<msg>", code?:"<ZIP_CODE>"} on failure
|
|
// stderr: never (silent — all errors via JSON on stdout)
|
|
// exit: 0 on success, 1 on any failure (caller still parses stdout)
|
|
//
|
|
// Key JetBrains-specific difference from the VSIX worker: plugin archives are
|
|
// NOT nested under `extension/`. The top-level entry is usually the plugin
|
|
// directory itself — identified by the presence of a `lib/` subdir containing
|
|
// at least one `*.jar`. If no such directory is found, fall back to `<tmpdir>`
|
|
// itself and let the parser surface a warning.
|
|
|
|
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { fetchJetBrainsPlugin, detectUrlType } from './vsix-fetch.mjs';
|
|
import { extractToDir, ZipError } from './zip-extract.mjs';
|
|
|
|
function emit(obj) {
|
|
process.stdout.write(JSON.stringify(obj) + '\n');
|
|
}
|
|
|
|
function parseArgs(argv) {
|
|
const out = { url: null, tmpdir: null };
|
|
for (let i = 0; i < argv.length; i++) {
|
|
if (argv[i] === '--url' && i + 1 < argv.length) out.url = argv[++i];
|
|
else if (argv[i] === '--tmpdir' && i + 1 < argv.length) out.tmpdir = argv[++i];
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// Find the top-level plugin dir inside an extracted JetBrains archive.
|
|
// JetBrains plugin zips contain `<plugin-name>/lib/*.jar`. We walk immediate
|
|
// children of <tmpdir>; the first child with a `lib/` subdir containing a
|
|
// `.jar` file is the plugin root. Fallback: return <tmpdir>.
|
|
function findPluginRoot(tmpdir) {
|
|
let entries;
|
|
try { entries = readdirSync(tmpdir); } catch { return tmpdir; }
|
|
for (const name of entries) {
|
|
const candidate = join(tmpdir, name);
|
|
let s;
|
|
try { s = statSync(candidate); } catch { continue; }
|
|
if (!s.isDirectory()) continue;
|
|
const libDir = join(candidate, 'lib');
|
|
let libStat;
|
|
try { libStat = statSync(libDir); } catch { continue; }
|
|
if (!libStat.isDirectory()) continue;
|
|
let libEntries;
|
|
try { libEntries = readdirSync(libDir); } catch { continue; }
|
|
if (libEntries.some((n) => n.toLowerCase().endsWith('.jar'))) {
|
|
return candidate;
|
|
}
|
|
}
|
|
return tmpdir;
|
|
}
|
|
|
|
async function main() {
|
|
const { url, tmpdir: dir } = parseArgs(process.argv.slice(2));
|
|
if (!url || !dir) {
|
|
emit({ ok: false, error: 'missing --url or --tmpdir' });
|
|
process.exit(1);
|
|
}
|
|
const detected = detectUrlType(url);
|
|
if (detected.type !== 'jetbrains') {
|
|
emit({ ok: false, error: `worker expected JetBrains URL, got type=${detected.type}` });
|
|
process.exit(1);
|
|
}
|
|
|
|
let fetched;
|
|
try {
|
|
fetched = await fetchJetBrainsPlugin({
|
|
numericId: detected.numericId,
|
|
xmlId: detected.xmlId,
|
|
version: detected.version,
|
|
});
|
|
} catch (err) {
|
|
emit({ ok: false, error: `fetch failed: ${err.message}` });
|
|
process.exit(1);
|
|
}
|
|
|
|
try {
|
|
await extractToDir(fetched.buffer, dir);
|
|
} catch (err) {
|
|
if (err instanceof ZipError) {
|
|
emit({ ok: false, error: `malformed JetBrains plugin (${err.code}): ${err.message}`, code: err.code });
|
|
} else {
|
|
emit({ ok: false, error: `extract failed: ${err.message}` });
|
|
}
|
|
process.exit(1);
|
|
}
|
|
|
|
const extRoot = existsSync(dir) ? findPluginRoot(dir) : dir;
|
|
|
|
emit({
|
|
ok: true,
|
|
sha256: fetched.sha256,
|
|
size: fetched.size,
|
|
finalUrl: fetched.finalUrl,
|
|
source: fetched.source,
|
|
extRoot,
|
|
});
|
|
process.exit(0);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
emit({ ok: false, error: `worker crashed: ${err.message || String(err)}` });
|
|
process.exit(1);
|
|
});
|