fix(plugin-sdk): aligne le manifeste hello-plugin sur le schéma backend + fixture de test
Le manifeste hello-plugin était généré par un SDK obsolète/désaligné avec le schéma backend actuel (champ ideaPluginManifestVersion manquant, displayName, trustLevel, shape de contributes), ce qui faisait échouer l'installation avec « invalid input: missing field ideaPluginManifestVersion ». - SDK (manifest.ts/js, index.ts) aligné sur le schéma backend courant. - hello-plugin (exemple + fixture) régénéré avec le manifeste valide. - Base de tests fonctionnels plugins : fixture réelle versionnée (reference-minimal) + test d'installation/chargement bout en bout (plugin_install_load.rs), pour couvrir install/load/catalog depuis un dossier fixture réel. Le sous-repo git imbriqué et vide (sdk/IdeaSDK/.git, 0 commit) a été neutralisé pour que les sources du SDK soient suivies normalement dans ce dépôt plutôt que comme gitlink vide. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
124
sdk/IdeaSDK/scripts/package-hello-plugin.mjs
Normal file
124
sdk/IdeaSDK/scripts/package-hello-plugin.mjs
Normal file
@ -0,0 +1,124 @@
|
||||
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { dirname, join } from "node:path";
|
||||
|
||||
const pluginRoot = join(process.cwd(), "examples", "hello-plugin");
|
||||
const manifestPath = join(pluginRoot, "idea-plugin.json");
|
||||
const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
||||
|
||||
const main = requireString(manifest, "main");
|
||||
const version = requireString(manifest, "version");
|
||||
const archivePath = join(pluginRoot, "build", `hello-plugin-${version}.zip`);
|
||||
const archiveEntries = [
|
||||
{ archivePath: "idea-plugin.json", sourcePath: manifestPath },
|
||||
{ archivePath: main, sourcePath: join(pluginRoot, main) },
|
||||
{ archivePath: "README.md", sourcePath: join(pluginRoot, "README.md") }
|
||||
];
|
||||
const DOS_TIME_MIDNIGHT = 0;
|
||||
const DOS_DATE_1980_01_01 = 33;
|
||||
const CRC32_TABLE = Array.from({ length: 256 }, (_, index) => {
|
||||
let value = index;
|
||||
|
||||
for (let bit = 0; bit < 8; bit += 1) {
|
||||
value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
|
||||
}
|
||||
|
||||
return value >>> 0;
|
||||
});
|
||||
|
||||
await rm(join(pluginRoot, "build"), { recursive: true, force: true });
|
||||
await mkdir(dirname(archivePath), { recursive: true });
|
||||
|
||||
const files = [];
|
||||
for (const entry of archiveEntries) {
|
||||
if (entry.archivePath.startsWith("/") || entry.archivePath.includes("..")) {
|
||||
throw new Error(`Refusing unsafe archive path: ${entry.archivePath}`);
|
||||
}
|
||||
|
||||
files.push({
|
||||
archivePath: entry.archivePath,
|
||||
data: await readFile(entry.sourcePath)
|
||||
});
|
||||
}
|
||||
|
||||
await writeFile(archivePath, createZip(files));
|
||||
console.log(`created ${archivePath}`);
|
||||
|
||||
function requireString(record, key) {
|
||||
if (typeof record[key] !== "string" || record[key].trim().length === 0) {
|
||||
throw new Error(`idea-plugin.json field "${key}" must be a non-empty string`);
|
||||
}
|
||||
|
||||
return record[key];
|
||||
}
|
||||
|
||||
function createZip(files) {
|
||||
const localFileHeaders = [];
|
||||
const centralDirectoryHeaders = [];
|
||||
let offset = 0;
|
||||
|
||||
for (const file of files) {
|
||||
const filename = Buffer.from(file.archivePath, "utf8");
|
||||
const checksum = crc32(file.data);
|
||||
const localFileHeader = Buffer.alloc(30);
|
||||
|
||||
localFileHeader.writeUInt32LE(0x04034b50, 0);
|
||||
localFileHeader.writeUInt16LE(20, 4);
|
||||
localFileHeader.writeUInt16LE(0x0800, 6);
|
||||
localFileHeader.writeUInt16LE(0, 8);
|
||||
localFileHeader.writeUInt16LE(DOS_TIME_MIDNIGHT, 10);
|
||||
localFileHeader.writeUInt16LE(DOS_DATE_1980_01_01, 12);
|
||||
localFileHeader.writeUInt32LE(checksum, 14);
|
||||
localFileHeader.writeUInt32LE(file.data.length, 18);
|
||||
localFileHeader.writeUInt32LE(file.data.length, 22);
|
||||
localFileHeader.writeUInt16LE(filename.length, 26);
|
||||
localFileHeader.writeUInt16LE(0, 28);
|
||||
|
||||
localFileHeaders.push(localFileHeader, filename, file.data);
|
||||
|
||||
const centralDirectoryHeader = Buffer.alloc(46);
|
||||
centralDirectoryHeader.writeUInt32LE(0x02014b50, 0);
|
||||
centralDirectoryHeader.writeUInt16LE(20, 4);
|
||||
centralDirectoryHeader.writeUInt16LE(20, 6);
|
||||
centralDirectoryHeader.writeUInt16LE(0x0800, 8);
|
||||
centralDirectoryHeader.writeUInt16LE(0, 10);
|
||||
centralDirectoryHeader.writeUInt16LE(DOS_TIME_MIDNIGHT, 12);
|
||||
centralDirectoryHeader.writeUInt16LE(DOS_DATE_1980_01_01, 14);
|
||||
centralDirectoryHeader.writeUInt32LE(checksum, 16);
|
||||
centralDirectoryHeader.writeUInt32LE(file.data.length, 20);
|
||||
centralDirectoryHeader.writeUInt32LE(file.data.length, 24);
|
||||
centralDirectoryHeader.writeUInt16LE(filename.length, 28);
|
||||
centralDirectoryHeader.writeUInt16LE(0, 30);
|
||||
centralDirectoryHeader.writeUInt16LE(0, 32);
|
||||
centralDirectoryHeader.writeUInt16LE(0, 34);
|
||||
centralDirectoryHeader.writeUInt16LE(0, 36);
|
||||
centralDirectoryHeader.writeUInt32LE(0, 38);
|
||||
centralDirectoryHeader.writeUInt32LE(offset, 42);
|
||||
|
||||
centralDirectoryHeaders.push(centralDirectoryHeader, filename);
|
||||
offset += localFileHeader.length + filename.length + file.data.length;
|
||||
}
|
||||
|
||||
const centralDirectory = Buffer.concat(centralDirectoryHeaders);
|
||||
const endOfCentralDirectory = Buffer.alloc(22);
|
||||
|
||||
endOfCentralDirectory.writeUInt32LE(0x06054b50, 0);
|
||||
endOfCentralDirectory.writeUInt16LE(0, 4);
|
||||
endOfCentralDirectory.writeUInt16LE(0, 6);
|
||||
endOfCentralDirectory.writeUInt16LE(files.length, 8);
|
||||
endOfCentralDirectory.writeUInt16LE(files.length, 10);
|
||||
endOfCentralDirectory.writeUInt32LE(centralDirectory.length, 12);
|
||||
endOfCentralDirectory.writeUInt32LE(offset, 16);
|
||||
endOfCentralDirectory.writeUInt16LE(0, 20);
|
||||
|
||||
return Buffer.concat([...localFileHeaders, centralDirectory, endOfCentralDirectory]);
|
||||
}
|
||||
|
||||
function crc32(data) {
|
||||
let value = 0xffffffff;
|
||||
|
||||
for (const byte of data) {
|
||||
value = (value >>> 8) ^ CRC32_TABLE[(value ^ byte) & 0xff];
|
||||
}
|
||||
|
||||
return (value ^ 0xffffffff) >>> 0;
|
||||
}
|
||||
Reference in New Issue
Block a user