feat(sdk): ESM multi-fichiers + storage plugin-owned (#134/#139)
Documente et illustre deux contrats SDK : - Multi-fichiers ESM : le `main` du manifeste peut importer d'autres fichiers du package via specifiers relatifs, servis par IdeA sur `idea-plugin://` (build `tsc` non bundlé). Le packager embarque tout `dist/**/*.js` et vérifie la présence du `main`. Les bare specifiers (`node_modules`) restent hors contrat : à bundler ou vendorer. - Storage plugin-owned : `ctx.storage` est la place canonique de l'état interne du plugin (compteurs, flags, préférences, caches), hors des fichiers projet. Les APIs workspace/config restent pour le contenu project-owned. L'exemple hello-plugin est éclaté en modules (constants, core/layout, core/workspace, core/storage) pour exercer l'import relatif et le storage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
||||
import { dirname, join } from "node:path";
|
||||
|
||||
const pluginRoot = join(process.cwd(), "examples", "hello-plugin");
|
||||
@ -8,9 +8,13 @@ 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 distEntries = await collectFiles(join(pluginRoot, "dist"), "dist");
|
||||
if (!distEntries.some((entry) => entry.archivePath === main)) {
|
||||
throw new Error(`Built plugin dist does not contain manifest main: ${main}`);
|
||||
}
|
||||
const archiveEntries = [
|
||||
{ archivePath: "idea-plugin.json", sourcePath: manifestPath },
|
||||
{ archivePath: main, sourcePath: join(pluginRoot, main) },
|
||||
...distEntries,
|
||||
{ archivePath: "README.md", sourcePath: join(pluginRoot, "README.md") }
|
||||
];
|
||||
const DOS_TIME_MIDNIGHT = 0;
|
||||
@ -43,6 +47,23 @@ for (const entry of archiveEntries) {
|
||||
await writeFile(archivePath, createZip(files));
|
||||
console.log(`created ${archivePath}`);
|
||||
|
||||
async function collectFiles(sourceDir, archiveDir) {
|
||||
const entries = await readdir(sourceDir, { withFileTypes: true });
|
||||
const files = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
const sourcePath = join(sourceDir, entry.name);
|
||||
const archivePath = `${archiveDir}/${entry.name}`;
|
||||
if (entry.isDirectory()) {
|
||||
files.push(...await collectFiles(sourcePath, archivePath));
|
||||
} else if (entry.isFile()) {
|
||||
files.push({ archivePath, sourcePath });
|
||||
}
|
||||
}
|
||||
|
||||
return files.sort((left, right) => left.archivePath.localeCompare(right.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`);
|
||||
|
||||
Reference in New Issue
Block a user