1.0.16 • Published 5 months ago
mint-nft-plugin v1.0.16
API City Plugin Setup Guide
This guide outlines the requirements and best practices for creating a plugin for API City.
Quick Start Example
Here's a minimal working plugin example:
import { BasePlugin, PluginContext } from "api-city-sdk";
class HelloPlugin extends BasePlugin {
constructor() {
super({
name: "Hello Plugin",
description: "A simple hello world plugin",
version: "1.0.0",
author: "Your Name",
permissions: [],
entryPoints: {
main: "hello" // This must match your base command
}
});
}
async initialize(context: PluginContext): Promise<void> {
// Store context for later use
this.context = context;
}
async handleCommand(command: string, args: string[]): Promise<void> {
if (command !== "hello") {
this.emit("message", { content: "Unknown command" });
return;
}
const name = args[0] || "World";
this.emit("message", { content: `Hello, ${name}!` });
}
getCommands(): { command: string; description: string; usage: string }[] {
return [
{
command: "hello", // Must match entryPoints.main
description: "Say hello",
usage: "/hello [name]" // Always prefix with /
}
];
}
}
export = HelloPlugin; // Must use this exact syntax
Core Requirements
1. Plugin Class Structure
import { BasePlugin, PluginContext } from "api-city-sdk";
class YourPlugin extends BasePlugin {
constructor() {
super({
name: "Plugin Name",
description: "Plugin Description",
version: "1.0.0",
author: "Author Name",
permissions: [],
entryPoints: {
main: "commandName"
}
});
}
async initialize(context: PluginContext): Promise<void> {
// Initialize your plugin
}
async handleCommand(command: string, args: string[]): Promise<void> {
// Handle commands
}
getCommands(): { command: string; description: string; usage: string }[] {
return [
{
command: "commandName", // Base command without slash
description: "Command description",
usage: "/commandName <params>" // Usage with slash
}
];
}
}
export = YourPlugin; // Must use CommonJS export
2. Package Configuration
Create a package.json
with:
{
"name": "your-plugin-name",
"version": "1.0.0",
"type": "module",
"main": "dist/index.cjs",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"require": "./dist/index.cjs",
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
}
}
3. Build Configuration
Create a build.js
with:
import * as esbuild from "esbuild";
const sharedConfig = {
entryPoints: ["src/index.ts"],
bundle: true,
minify: true,
target: "es2020",
platform: "node",
sourcemap: true,
treeShaking: true,
external: [
"@solana/web3.js",
"@metaplex-foundation/umi",
"@metaplex-foundation/umi-bundle-defaults",
"@metaplex-foundation/mpl-core",
"api-city-sdk"
]
};
await esbuild.build({
...sharedConfig,
format: "esm",
outfile: "dist/index.js",
});
await esbuild.build({
...sharedConfig,
format: "cjs",
outfile: "dist/index.cjs",
});
Command Handling Best Practices
Command Validation
async handleCommand(command: string, args: string[]): Promise<void> { // Always validate the base command first if (command !== "yourcommand") { this.emit("message", { content: "Unknown command" }); return; } // Handle subcommands with a switch statement const subCommand = args[0] || "help"; switch (subCommand) { case "help": this.emit("message", { content: `Available commands:
- /yourcommand help: Show this help message
/yourcommand action : Do something` }); break;
case "action": // Validate parameters if (args.length < 2) { this.emit("message", { content: "Missing parameters" }); return; } // Handle the action break; default: this.emit("message", { content: "Unknown subcommand" });
} }
User Feedback
// Success messages this.emit("message", { content: "Operation successful!" }); // Error handling try { // Your code } catch (err: any) { console.error("Error:", err); this.emit("message", { content: `Error: ${err.message}` }); }
Project Structure
your-plugin/
├── src/
│ └── index.ts # Main plugin implementation
├── build.js # Build configuration
├── package.json # Package configuration
├── tsconfig.json # TypeScript configuration
└── README.md # Documentation
Testing Your Plugin
Local Testing
// test.js import YourPlugin from './dist/index.js'; const plugin = new YourPlugin(); // Mock the context const mockContext = { wallet: { publicKey: null, signTransaction: async (tx) => tx, connected: false } }; // Test your commands async function test() { await plugin.initialize(mockContext); await plugin.handleCommand('yourcommand', ['help']); } // Mock the emit method YourPlugin.prototype.emit = (event, data) => { console.log(`[${event}] ${data.content}`); }; test().catch(console.error);
Run Tests
npm run build node test.js
Important Guidelines
Export Syntax
- Use
export = PluginClass
(CommonJS style) - Don't mix named exports with module exports
- Don't use
export default
- Use
Command Structure
- Use static strings for commands, not class properties
- Include proper command descriptions and usage strings
- Always prefix usage examples with "/" but not the command itself
Build Requirements
- Build both ESM and CommonJS versions
- Mark all SDK dependencies as external
- Use Node.js platform in build config
Plugin Service Integration
- Implement all required BasePlugin methods
- Properly handle command parsing and execution
- Use the emit method for user feedback
Development Workflow
Setup
npm init npm install api-city-sdk @types/node typescript esbuild --save-dev
Build
npm run build
Publish
npm version patch npm publish
Submit
- Submit your plugin through the API City plugin submission interface
- Ensure all commands are properly defined and documented
- Test your plugin thoroughly before submission
Common Issues
No Commands Found Error
- Check if
getCommands()
returns the correct format - Verify export syntax is CommonJS style
- Ensure build configuration is correct
- Check if
Build Errors
- Make sure all dependencies are properly marked as external
- Verify package.json has correct "type" and "exports" fields
- Check for proper TypeScript configuration
Dependencies
Required peer dependencies:
{
"api-city-sdk": "latest"
}
License
ISC
Browser Compatibility
Your plugin must work in both Node.js and browser environments since it will be loaded via CDN in the frontend. Ensure:
Build Configuration
// ESM build for browsers await esbuild.build({ ...sharedConfig, format: "esm", platform: "browser", // Important for CDN loading outfile: "dist/index.js", });
Package Configuration
{ "browser": "dist/index.js", "exports": { ".": { "browser": "./dist/index.js", "require": "./dist/index.cjs", "import": "./dist/index.js" } } }
Browser-Safe Code
- Don't use Node.js-specific APIs (fs, path, etc.)
- Handle browser-specific cases in your plugin
- Test your plugin in both Node.js and browser environments