Initial commit: IdeaSDK TypeScript plugin SDK
This commit is contained in:
33
examples/hello-plugin/README.md
Normal file
33
examples/hello-plugin/README.md
Normal file
@ -0,0 +1,33 @@
|
||||
# Hello Plugin
|
||||
|
||||
Installable IdeA plugin example rebuilt from the public SDK types.
|
||||
|
||||
It exercises the current plugin primitives end to end:
|
||||
|
||||
- top-level menu: `Hello Plugin`;
|
||||
- menu entry: `hello-plugin`;
|
||||
- command: `hello-plugin`, returning `hello-world`;
|
||||
- layout contribution: `hello-plugin.hello-world`, rendered as `hello-world`.
|
||||
- tooling capability: logs the focused workspace project when `ctx.services` is available.
|
||||
|
||||
```sh
|
||||
npm run typecheck:examples
|
||||
npm run package:hello-plugin
|
||||
```
|
||||
|
||||
The installable archive is emitted at `examples/hello-plugin/build/hello-plugin-0.1.0.zip`.
|
||||
It contains `idea-plugin.json` at the ZIP root and the compiled ESM entrypoint at
|
||||
`dist/index.js`, matching the manifest `main` field.
|
||||
|
||||
## Diagnostics
|
||||
|
||||
During activation the plugin logs:
|
||||
|
||||
- whether the command and layout runtime registries are available;
|
||||
- successful registration of the `hello-plugin` command;
|
||||
- successful registration of the `hello-plugin.hello-world` layout;
|
||||
- availability of the workspace service from the `tooling` runtime capability;
|
||||
- the first layout render, including project/node identifiers.
|
||||
|
||||
These messages are intentionally small and stable so installation, bundle import, activation and
|
||||
layout rendering failures can be separated quickly in IdeA logs/devtools.
|
||||
44
examples/hello-plugin/idea-plugin.json
Normal file
44
examples/hello-plugin/idea-plugin.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"ideaPluginManifestVersion": 1,
|
||||
"id": "com.example.hello-plugin",
|
||||
"displayName": "Hello Plugin",
|
||||
"publisher": "IdeA Examples",
|
||||
"version": "0.1.0",
|
||||
"description": "SDK example plugin for validating command, menu and layout loading.",
|
||||
"main": "dist/index.js",
|
||||
"engines": {
|
||||
"idea": ">=0.1.0"
|
||||
},
|
||||
"trustLevel": "full",
|
||||
"capabilities": [
|
||||
"ui",
|
||||
"tooling"
|
||||
],
|
||||
"contributes": {
|
||||
"menus": [
|
||||
{
|
||||
"id": "hello-plugin.menu",
|
||||
"label": "Hello Plugin",
|
||||
"topLevel": true,
|
||||
"order": 100
|
||||
}
|
||||
],
|
||||
"menuItems": [
|
||||
{
|
||||
"id": "hello-plugin.command.item",
|
||||
"targetMenuId": "hello-plugin.menu",
|
||||
"label": "hello-plugin",
|
||||
"command": "hello-plugin",
|
||||
"order": 10
|
||||
}
|
||||
],
|
||||
"layouts": [
|
||||
{
|
||||
"type": "hello-plugin.hello-world",
|
||||
"label": "hello-world",
|
||||
"component": "hello-world",
|
||||
"order": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
27
examples/hello-plugin/package-lock.json
generated
Normal file
27
examples/hello-plugin/package-lock.json
generated
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "hello-plugin",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "hello-plugin",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@idea/plugin-sdk": "file:../.."
|
||||
}
|
||||
},
|
||||
"../..": {
|
||||
"name": "@idea/plugin-sdk",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"typescript": "^5.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@idea/plugin-sdk": {
|
||||
"resolved": "../..",
|
||||
"link": true
|
||||
}
|
||||
}
|
||||
}
|
||||
14
examples/hello-plugin/package.json
Normal file
14
examples/hello-plugin/package.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "hello-plugin",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@idea/plugin-sdk": "file:../.."
|
||||
}
|
||||
}
|
||||
|
||||
186
examples/hello-plugin/src/index.ts
Normal file
186
examples/hello-plugin/src/index.ts
Normal file
@ -0,0 +1,186 @@
|
||||
import type { ActivateContext, IdeAPluginModule, PluginLayoutProps } from "@idea/plugin-sdk";
|
||||
|
||||
const COMMAND_ID = "hello-plugin";
|
||||
const LAYOUT_TYPE = "hello-plugin.hello-world";
|
||||
|
||||
let hasLoggedFirstLayoutRender = false;
|
||||
|
||||
function HelloWorldLayout(props: PluginLayoutProps): string {
|
||||
if (!hasLoggedFirstLayoutRender) {
|
||||
hasLoggedFirstLayoutRender = true;
|
||||
console.info("[hello-plugin] layout first render", {
|
||||
projectId: props.projectId,
|
||||
nodeId: props.nodeId,
|
||||
layoutType: props.layoutType,
|
||||
hasState: props.state !== undefined
|
||||
});
|
||||
}
|
||||
|
||||
return "hello-world";
|
||||
}
|
||||
|
||||
export function activate(ctx: ActivateContext): void {
|
||||
ctx.logger.info("activating hello-plugin", {
|
||||
pluginId: ctx.pluginId,
|
||||
hasCommands: Boolean(ctx.commands),
|
||||
hasLayouts: Boolean(ctx.layouts)
|
||||
});
|
||||
|
||||
const commandDisposable = ctx.commands?.registerCommand(COMMAND_ID, () => {
|
||||
ctx.logger.info("command executed", { commandId: COMMAND_ID });
|
||||
return "hello-world";
|
||||
});
|
||||
|
||||
if (commandDisposable) {
|
||||
ctx.subscriptions.push(commandDisposable);
|
||||
ctx.logger.info("command registered", { commandId: COMMAND_ID });
|
||||
} else {
|
||||
ctx.logger.warn("command registry unavailable", { commandId: COMMAND_ID });
|
||||
}
|
||||
|
||||
const layoutDisposable = ctx.layouts?.register({
|
||||
type: LAYOUT_TYPE,
|
||||
component: HelloWorldLayout
|
||||
});
|
||||
|
||||
if (layoutDisposable) {
|
||||
ctx.subscriptions.push(layoutDisposable);
|
||||
ctx.logger.info("layout registered", {
|
||||
layoutType: LAYOUT_TYPE,
|
||||
component: "hello-world"
|
||||
});
|
||||
} else {
|
||||
ctx.logger.warn("layout registry unavailable", { layoutType: LAYOUT_TYPE });
|
||||
}
|
||||
|
||||
void useWorkspaceSdk(ctx);
|
||||
}
|
||||
|
||||
async function useWorkspaceSdk(ctx: ActivateContext): Promise<void> {
|
||||
const workspace = ctx.services?.workspace;
|
||||
if (!workspace) return;
|
||||
|
||||
const project = await workspace.getCurrentProject();
|
||||
if (!project) {
|
||||
ctx.logger.info("workspace service available without a focused project");
|
||||
return;
|
||||
}
|
||||
|
||||
const fixturePath = ".ideai/hello-plugin.txt";
|
||||
await workspace.writeTextFile(fixturePath, "hello from @idea/plugin-sdk\n", project.id);
|
||||
const file = await workspace.readTextFile(fixturePath, project.id);
|
||||
const stat = await workspace.stat(fixturePath, project.id);
|
||||
const listing = await workspace.listDirectory(".ideai", project.id);
|
||||
const structure = await workspace.queryStructure({
|
||||
projectId: project.id,
|
||||
maxDepth: 2,
|
||||
maxEntries: 100
|
||||
});
|
||||
|
||||
ctx.logger.info("workspace file round-trip complete", {
|
||||
projectId: project.id,
|
||||
path: file.path,
|
||||
bytes: stat.len,
|
||||
ideaiEntries: listing.entries.length,
|
||||
conventions: structure.conventions.map((convention) => convention.id)
|
||||
});
|
||||
|
||||
const diagnostic = await ctx.services?.tooling.diagnose({
|
||||
projectId: project.id,
|
||||
tools: [
|
||||
{
|
||||
id: "echo",
|
||||
executable: "echo",
|
||||
versionArgs: ["hello-plugin-toolcheck"],
|
||||
required: true
|
||||
}
|
||||
],
|
||||
env: [{ name: "PATH", required: true }],
|
||||
files: [{ path: fixturePath, kind: "file" }]
|
||||
});
|
||||
|
||||
ctx.logger.info("tooling diagnostic complete", {
|
||||
ok: diagnostic?.ok,
|
||||
echoVersion: diagnostic?.tools.find((tool) => tool.id === "echo")?.version,
|
||||
messages: diagnostic?.messages
|
||||
});
|
||||
|
||||
const configPath = ".ideai/hello-plugin.json";
|
||||
await workspace.writeTextFile(
|
||||
configPath,
|
||||
JSON.stringify({ enabled: true, launches: 0 }, null, 2) + "\n",
|
||||
project.id
|
||||
);
|
||||
const configDocument = await ctx.services?.config.readDocument({
|
||||
projectId: project.id,
|
||||
path: configPath
|
||||
});
|
||||
await ctx.services?.config.updateDocument({
|
||||
projectId: project.id,
|
||||
path: configPath,
|
||||
mode: "mergePatch",
|
||||
value: { lastFormat: configDocument?.format ?? "json", launches: 1 }
|
||||
});
|
||||
ctx.logger.info("config document updated", {
|
||||
path: configDocument?.path,
|
||||
format: configDocument?.format
|
||||
});
|
||||
|
||||
const watch = await workspace.watch(".ideai", (event) => {
|
||||
ctx.logger.info("workspace watch event", {
|
||||
path: event.path,
|
||||
kind: event.kind,
|
||||
operation: event.operation
|
||||
});
|
||||
}, project.id);
|
||||
ctx.subscriptions.push(watch);
|
||||
|
||||
const events = await ctx.services?.events.subscribe(
|
||||
{
|
||||
projectId: project.id,
|
||||
eventTypes: ["backgroundTaskChanged"],
|
||||
pollIntervalMs: 2000,
|
||||
onDropped: (count) => ctx.logger.warn("plugin events dropped", { count })
|
||||
},
|
||||
(event) => {
|
||||
if (event.type === "backgroundTaskChanged") {
|
||||
ctx.logger.info("background task changed", {
|
||||
taskId: event.taskId,
|
||||
state: event.state
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
if (events) ctx.subscriptions.push(events);
|
||||
|
||||
const ownerAgentId = await ctx.storage?.get<string>("helloPlugin.ownerAgentId");
|
||||
if (!ownerAgentId) {
|
||||
ctx.logger.info("command task example skipped: no owner agent configured");
|
||||
return;
|
||||
}
|
||||
|
||||
const task = await ctx.services?.tasks.runCommand({
|
||||
projectId: project.id,
|
||||
ownerAgentId,
|
||||
label: "Hello plugin command",
|
||||
command: "echo",
|
||||
args: ["hello from @idea/plugin-sdk"],
|
||||
cwd: ".",
|
||||
recordOnly: true
|
||||
});
|
||||
|
||||
if (task) {
|
||||
const status = await ctx.services?.tasks.getCommandStatus(task.taskId);
|
||||
ctx.logger.info("command task launched", {
|
||||
taskId: task.taskId,
|
||||
state: status?.state ?? task.state,
|
||||
exitCode: status?.exitCode ?? task.exitCode
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const plugin: IdeAPluginModule = {
|
||||
activate
|
||||
};
|
||||
|
||||
export default plugin;
|
||||
19
examples/hello-plugin/tsconfig.build.json
Normal file
19
examples/hello-plugin/tsconfig.build.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"declaration": false,
|
||||
"declarationMap": false,
|
||||
"noEmit": false,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"sourceMap": false,
|
||||
"paths": {
|
||||
"@idea/plugin-sdk": [
|
||||
"../../dist/index.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
]
|
||||
}
|
||||
18
examples/hello-plugin/tsconfig.json
Normal file
18
examples/hello-plugin/tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"declaration": false,
|
||||
"declarationMap": false,
|
||||
"noEmit": true,
|
||||
"rootDir": "../..",
|
||||
"paths": {
|
||||
"@idea/plugin-sdk": [
|
||||
"../../src/index.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"../../src/**/*.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user