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:
67
README.md
67
README.md
@ -6,6 +6,7 @@ This first version intentionally stays small:
|
||||
|
||||
- public manifest types for `idea-plugin.json`;
|
||||
- public runtime types for plugin modules exposing `activate(ctx)`;
|
||||
- plugin-owned persistent storage through `ctx.storage`;
|
||||
- a stable `ctx.services` facade for workspace, background task and terminal operations;
|
||||
- public workspace file APIs for reading, writing, listing, stat and path resolution;
|
||||
- a bounded generic project-structure query API;
|
||||
@ -47,7 +48,8 @@ examples/hello-plugin/build/hello-plugin-0.1.0.zip
|
||||
```
|
||||
|
||||
Its ZIP root contains `idea-plugin.json` directly, with no wrapping parent directory. The
|
||||
compiled ESM entrypoint is emitted at `dist/index.js`, matching the manifest `main` field.
|
||||
compiled ESM entrypoint is emitted at `dist/index.js`, matching the manifest `main` field,
|
||||
alongside the other compiled files imported by that entrypoint.
|
||||
|
||||
## Plugin shape
|
||||
|
||||
@ -66,6 +68,25 @@ TypeScript.
|
||||
}
|
||||
```
|
||||
|
||||
The `main` field is the ESM entrypoint loaded by IdeA. It may import other
|
||||
JavaScript files from the same plugin package with relative specifiers:
|
||||
|
||||
```js
|
||||
import { COMMAND_ID } from "./constants.js";
|
||||
import { useWorkspaceSdk } from "./core/workspace.js";
|
||||
```
|
||||
|
||||
Those relative imports are served by IdeA through the `idea-plugin://` protocol,
|
||||
so plugins do not need to be bundled into a single JavaScript file. A plain
|
||||
`tsc` build that emits multiple ESM files under `dist/` is supported, as shown
|
||||
by `examples/hello-plugin`.
|
||||
|
||||
Only package-relative imports are resolved this way. Bare specifiers such as
|
||||
`react`, `lodash` or any dependency expected from `node_modules` are not
|
||||
resolved by the host at runtime. Third-party dependencies must be bundled into
|
||||
the plugin output or vendored as relative files shipped inside the plugin
|
||||
package.
|
||||
|
||||
The entrypoint exports an `activate(ctx)` function:
|
||||
|
||||
```ts
|
||||
@ -129,6 +150,32 @@ user actions, effects or asynchronous callbacks, not unconditionally while
|
||||
rendering. Services are available from `ctx.services` to plugins declaring the
|
||||
`tooling` capability; layout props do not expose private runtime gateways.
|
||||
|
||||
## Plugin-Owned Storage
|
||||
|
||||
Use `ctx.storage` for state owned by the plugin itself: internal counters, flags,
|
||||
preferences, small caches and host-facing settings. This is the canonical place
|
||||
for plugin-owned state because the host can scope and persist it outside the
|
||||
user's project files.
|
||||
|
||||
```ts
|
||||
import type { ActivateContext } from "@idea/plugin-sdk";
|
||||
|
||||
const ACTIVATION_COUNT_KEY = "helloPlugin.activationCount";
|
||||
|
||||
export async function activate(ctx: ActivateContext): Promise<void> {
|
||||
const current = await ctx.storage?.get<number>(ACTIVATION_COUNT_KEY);
|
||||
const next = typeof current === "number" ? current + 1 : 1;
|
||||
await ctx.storage?.set(ACTIVATION_COUNT_KEY, next);
|
||||
|
||||
ctx.logger.info("activation count", { next });
|
||||
}
|
||||
```
|
||||
|
||||
Do not write plugin-internal state into `.ideai/*` or other project files by
|
||||
default. Use workspace files and config-document helpers only when the file is
|
||||
project-owned content or project-owned configuration that the user expects to
|
||||
see, review and version with the project.
|
||||
|
||||
## Runtime Services
|
||||
|
||||
Plugins declaring the `tooling` capability receive `ctx.services`. Plugins
|
||||
@ -155,7 +202,9 @@ export async function activate(ctx: ActivateContext): Promise<void> {
|
||||
Workspace paths are always relative to the project root. Hosts reject absolute
|
||||
paths, `..`, empty path segments and paths outside the sandbox. Text APIs use
|
||||
UTF-8; binary APIs use `Uint8Array`. Missing files reject on reads and resolve
|
||||
to `{ exists: false }` from `stat`.
|
||||
to `{ exists: false }` from `stat`. These APIs are for project-owned files:
|
||||
source files, generated reports or user-visible artifacts. Use `ctx.storage`
|
||||
instead for plugin-owned counters, flags, preferences and caches.
|
||||
|
||||
```ts
|
||||
import type { ActivateContext } from "@idea/plugin-sdk";
|
||||
@ -165,10 +214,10 @@ export async function activate(ctx: ActivateContext): Promise<void> {
|
||||
const project = await workspace?.getCurrentProject();
|
||||
if (!workspace || !project) return;
|
||||
|
||||
await workspace.writeTextFile(".ideai/hello-plugin.txt", "hello\n", project.id);
|
||||
await workspace.writeTextFile("hello-plugin-report.txt", "hello\n", project.id);
|
||||
|
||||
const file = await workspace.readTextFile(".ideai/hello-plugin.txt", project.id);
|
||||
const listing = await workspace.listDirectory(".ideai", project.id);
|
||||
const file = await workspace.readTextFile("hello-plugin-report.txt", project.id);
|
||||
const listing = await workspace.listDirectory(".", project.id);
|
||||
const stat = await workspace.stat(file.path, project.id);
|
||||
|
||||
ctx.logger.info("workspace file", {
|
||||
@ -327,7 +376,9 @@ subscription up to the requested/host-capped capacity, drained oldest-first, and
|
||||
### Structured Config Documents
|
||||
|
||||
Use `ctx.services.config` when a plugin needs to read or update a structured
|
||||
configuration file without reimplementing parsing and serialization.
|
||||
project-owned configuration file without reimplementing parsing and
|
||||
serialization. Do not use project config documents as the default persistence
|
||||
mechanism for plugin-internal state; use `ctx.storage` for that.
|
||||
|
||||
First-lot format support is deliberately narrow:
|
||||
|
||||
@ -343,11 +394,11 @@ import type { ActivateContext } from "@idea/plugin-sdk";
|
||||
|
||||
export async function activate(ctx: ActivateContext): Promise<void> {
|
||||
const config = await ctx.services?.config.readDocument({
|
||||
path: ".ideai/hello-plugin.json"
|
||||
path: "tooling.config.json"
|
||||
});
|
||||
|
||||
await ctx.services?.config.updateDocument({
|
||||
path: ".ideai/hello-plugin.json",
|
||||
path: "tooling.config.json",
|
||||
mode: "mergePatch",
|
||||
value: {
|
||||
enabled: true,
|
||||
|
||||
Reference in New Issue
Block a user