@kodiak-finance/orderly-devkit 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +160 -0
- package/bin/cli.js +112 -0
- package/package.json +37 -0
- package/src/commands/create/module.js +49 -0
- package/src/commands/create/plugin.js +270 -0
- package/src/commands/delete.js +224 -0
- package/src/commands/disable.js +219 -0
- package/src/commands/list.js +196 -0
- package/src/commands/login.js +147 -0
- package/src/commands/logout.js +22 -0
- package/src/commands/mcp/detect.js +128 -0
- package/src/commands/mcp/install.js +122 -0
- package/src/commands/mcp.js +9 -0
- package/src/commands/skills/install.js +211 -0
- package/src/commands/skills.js +10 -0
- package/src/commands/submit.js +457 -0
- package/src/commands/update.js +240 -0
- package/src/commands/view.js +76 -0
- package/src/commands/whoami.js +19 -0
- package/src/internal/auth.js +222 -0
- package/src/internal/constants.js +80 -0
- package/src/internal/login-server.js +114 -0
- package/src/internal/manifest.js +189 -0
- package/src/internal/orderlySdkDocsMcpDetect.js +255 -0
- package/src/internal/templateGenerator.js +294 -0
- package/src/shared.js +136 -0
- package/src/version.ts +13 -0
package/README.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# @orderly.network/devkit
|
|
2
|
+
|
|
3
|
+
CLI toolkit for scaffolding plugins and modules, authenticating with Marketplace, submitting and managing plugins, and installing MCP / agent skill integrations.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- **Node.js** v20.19.0 or newer.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
### From npm
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm add -g @orderly.network/devkit
|
|
15
|
+
# or
|
|
16
|
+
npm install -g @orderly.network/devkit
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Then run:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
orderly-devkit --help
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### One-off without global install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pnpm dlx @orderly.network/devkit --help
|
|
29
|
+
# or
|
|
30
|
+
npx @orderly.network/devkit --help
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Authentication
|
|
34
|
+
|
|
35
|
+
Login uses GitHub OAuth in the browser and a short-lived local callback server.
|
|
36
|
+
|
|
37
|
+
- **Credentials file:** `~/.orderly/auth.json` (created automatically).
|
|
38
|
+
- The CLI opens the marketplace login page and completes OAuth callback automatically.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
orderly-devkit login # open browser, complete GitHub auth
|
|
42
|
+
orderly-devkit login --force # re-authenticate even if already logged in
|
|
43
|
+
orderly-devkit login --port 9877 # if default port is busy (default is 9876)
|
|
44
|
+
orderly-devkit whoami
|
|
45
|
+
orderly-devkit logout
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Commands overview
|
|
49
|
+
|
|
50
|
+
Run `orderly-devkit <command> --help` for options on any command.
|
|
51
|
+
|
|
52
|
+
### `create`
|
|
53
|
+
|
|
54
|
+
Scaffold new artifacts (interactive).
|
|
55
|
+
|
|
56
|
+
| Subcommand | Description |
|
|
57
|
+
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
58
|
+
| `create plugin` | Clone and render the plugin template (`OrderlyNetwork/orderly-plugin-template`), prompt for name/plugin id/interceptor target, and optionally generate `.orderly-manifest.json`. |
|
|
59
|
+
| `create module` | Guided flow for module type (`page`, `component`, `hook`, `utils`, `module`). **File generation is not implemented yet**—it only collects choices and prints a summary. |
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
orderly-devkit create plugin
|
|
63
|
+
orderly-devkit create module
|
|
64
|
+
orderly-devkit create module --name my-module
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Marketplace: `submit`, `list`, `update`, `view`
|
|
68
|
+
|
|
69
|
+
These commands use the Marketplace API. You must be logged in (`orderly-devkit login`).
|
|
70
|
+
|
|
71
|
+
**`submit`** — register a new plugin from a local directory.
|
|
72
|
+
|
|
73
|
+
- Resolves metadata from `package.json` and/or `.orderly-manifest.json`.
|
|
74
|
+
- `repoUrl` should be a GitHub URL (`https://github.com/<owner>/<repo>`); it can be filled from `git remote` when missing.
|
|
75
|
+
- **Tags** must be from the allowed set: `UI`, `Indicator`, `Order Entry`, `Trading`, `Chart`, `Portfolio`, `Analytics`, `Tool`, `Widget` (max 5).
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
orderly-devkit submit
|
|
79
|
+
orderly-devkit submit --path ./my-plugin
|
|
80
|
+
orderly-devkit submit -p ./my-plugin --tags UI,Trading --dry-run
|
|
81
|
+
orderly-devkit submit -p ./my-plugin --storybook-url https://example.com/storybook
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**`list`** — list plugins associated with your account.
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
orderly-devkit list
|
|
88
|
+
orderly-devkit list --json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**`update`** — PATCH plugin metadata for an existing listing (requires `pluginId` in `.orderly-manifest.json`).
|
|
92
|
+
|
|
93
|
+
Updatable fields include: `name`, `description`, `tags`, `coverImages`, `storybookUrl`, `storybookTooltip`, `usagePrompt`.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
orderly-devkit update --path ./my-plugin --dry-run
|
|
97
|
+
orderly-devkit update -p ./my-plugin
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**`view`** — fetch one plugin by ID as JSON.
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
orderly-devkit view <plugin-id>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### `mcp install`
|
|
107
|
+
|
|
108
|
+
Install the **Orderly SDK Docs** MCP server entry for Claude, Codex, Cursor, OpenCode, etc.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
orderly-devkit mcp install
|
|
112
|
+
orderly-devkit mcp install --client cursor --scope project
|
|
113
|
+
orderly-devkit mcp install --client all --scope user
|
|
114
|
+
orderly-devkit mcp install --sdk-docs-version 0.1.0
|
|
115
|
+
orderly-devkit mcp install --dry-run
|
|
116
|
+
orderly-devkit mcp install --force
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
| Option | Description |
|
|
120
|
+
| ---------- | ----------------------------------------------------------------------------------------- |
|
|
121
|
+
| `--client` | `claude`, `codex`, `cursor`, `opencode`, `all`, or comma-separated list (default: `all`). |
|
|
122
|
+
| `--scope` | `user` or `project` (default: `user`). |
|
|
123
|
+
| `--name` | MCP server id in config (default: `orderly-sdk-docs`). |
|
|
124
|
+
|
|
125
|
+
### `skills install`
|
|
126
|
+
|
|
127
|
+
Install Orderly **agent skills** for plugin workflows (create, write, add, submit) with `npx -y skills add …`.
|
|
128
|
+
|
|
129
|
+
Default behavior installs four skills non-interactively: `orderly-plugin-create`, `orderly-plugin-write`, `orderly-plugin-add`, `orderly-plugin-submit`.
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
orderly-devkit skills install
|
|
133
|
+
orderly-devkit skills install --list
|
|
134
|
+
orderly-devkit skills install --dry-run
|
|
135
|
+
orderly-devkit skills install other/repo --skill my-skill -y
|
|
136
|
+
orderly-devkit skills install -- --some-upstream-skills-flag
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
| Option | Description |
|
|
140
|
+
| ----------------- | ----------------------------------------------------------------------- |
|
|
141
|
+
| `[source]` | GitHub `owner/repo`, URL, or local path (default source is built in). |
|
|
142
|
+
| `--list` | List skills in the source without installing. |
|
|
143
|
+
| `--skill` / `-s` | Repeatable; install only named skills (replaces default four when set). |
|
|
144
|
+
| `--all` | Forward `--all` to the skills CLI. |
|
|
145
|
+
| `--global` / `-g` | Global install for the skills CLI. |
|
|
146
|
+
| `--agent` / `-a` | Target agent(s), e.g. `-a cursor`. |
|
|
147
|
+
| `--copy` | Copy files instead of symlinks. |
|
|
148
|
+
| `--yes` / `-y` | With `--list` only: pass `-y` through. |
|
|
149
|
+
| `--` | Everything after `--` is forwarded to the upstream `skills` CLI. |
|
|
150
|
+
|
|
151
|
+
## Troubleshooting
|
|
152
|
+
|
|
153
|
+
- **Marketplace API errors** — check your network connection; if the CLI reports a failed request, try again later or verify you are logged in (`orderly-devkit whoami`).
|
|
154
|
+
- **Login: port in use** — run `orderly-devkit login --port <free-port>`.
|
|
155
|
+
- **Ctrl+C during prompts** — the CLI handles enquirer cancellation and exits with a clear message when possible.
|
|
156
|
+
- **Invalid working directory** — if the shell’s cwd was deleted, the CLI may switch to `HOME` or the package directory and warn you.
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
See the repository root license for this package’s distribution terms.
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const yargs = require("yargs/yargs");
|
|
6
|
+
const { hideBin } = require("yargs/helpers");
|
|
7
|
+
const chalk = require("chalk");
|
|
8
|
+
|
|
9
|
+
// Resolve commands directory relative to this file's location
|
|
10
|
+
const commandsDir = path.resolve(__dirname, "../src/commands");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Detect known enquirer cancel crash on newer Node runtimes.
|
|
14
|
+
* This keeps Ctrl+C behavior user-friendly while preserving real errors.
|
|
15
|
+
* @param {unknown} err
|
|
16
|
+
* @returns {boolean}
|
|
17
|
+
*/
|
|
18
|
+
function isEnquirerReadlineCancelError(err) {
|
|
19
|
+
if (!err || typeof err !== "object") {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const error =
|
|
24
|
+
/** @type {{ code?: string; stack?: string; message?: string }} */ (err);
|
|
25
|
+
const stack = typeof error.stack === "string" ? error.stack : "";
|
|
26
|
+
const message = typeof error.message === "string" ? error.message : "";
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
error.code === "ERR_USE_AFTER_CLOSE" &&
|
|
30
|
+
message.includes("readline") &&
|
|
31
|
+
stack.includes("enquirer/lib/")
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Gracefully handle Ctrl+C prompt cancellation emitted by enquirer.
|
|
37
|
+
* Only swallows the known cancellation crash and rethrows everything else.
|
|
38
|
+
*/
|
|
39
|
+
function setupGracefulPromptInterruptHandling() {
|
|
40
|
+
process.on("uncaughtException", (err) => {
|
|
41
|
+
if (!isEnquirerReadlineCancelError(err)) {
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
console.log();
|
|
46
|
+
console.log(chalk.yellow("Operation cancelled."));
|
|
47
|
+
process.exit(130);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Ensure process cwd is valid before yargs initialization.
|
|
53
|
+
* Yargs reads cwd eagerly; when users run CLI from a deleted directory,
|
|
54
|
+
* Node throws ENOENT before command parsing starts.
|
|
55
|
+
*/
|
|
56
|
+
function ensureValidWorkingDirectory() {
|
|
57
|
+
const cwd = process.cwd();
|
|
58
|
+
try {
|
|
59
|
+
fs.accessSync(cwd);
|
|
60
|
+
return;
|
|
61
|
+
} catch {
|
|
62
|
+
const fallbackDir =
|
|
63
|
+
process.env.HOME ||
|
|
64
|
+
process.env.USERPROFILE ||
|
|
65
|
+
path.resolve(__dirname, "..");
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
fs.accessSync(fallbackDir);
|
|
69
|
+
process.chdir(fallbackDir);
|
|
70
|
+
console.warn(
|
|
71
|
+
chalk.yellow(
|
|
72
|
+
`Current working directory is unavailable. Switched to: ${fallbackDir}`,
|
|
73
|
+
),
|
|
74
|
+
);
|
|
75
|
+
} catch {
|
|
76
|
+
// Fallback also unavailable — let the error surface naturally.
|
|
77
|
+
throw new Error(
|
|
78
|
+
`Current working directory "${cwd}" is inaccessible. ` +
|
|
79
|
+
`Please change to a valid directory and try again.`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
setupGracefulPromptInterruptHandling();
|
|
86
|
+
ensureValidWorkingDirectory();
|
|
87
|
+
|
|
88
|
+
yargs(hideBin(process.argv))
|
|
89
|
+
.scriptName("orderly-devkit")
|
|
90
|
+
.usage(chalk.cyan("Usage:") + " $0 <command> [options]")
|
|
91
|
+
.alias("v", "version")
|
|
92
|
+
.alias("h", "help")
|
|
93
|
+
.showHelpOnFail(true)
|
|
94
|
+
.strictCommands()
|
|
95
|
+
.demandCommand(1, "Please specify a command.")
|
|
96
|
+
.command("create", "Create a new plugin or module", (yargs) => {
|
|
97
|
+
return yargs.commandDir(path.join(commandsDir, "create"));
|
|
98
|
+
})
|
|
99
|
+
.commandDir(commandsDir)
|
|
100
|
+
.recommendCommands()
|
|
101
|
+
.fail((message, error, parser) => {
|
|
102
|
+
// Always show usage/help for missing or invalid command input.
|
|
103
|
+
parser.showHelp();
|
|
104
|
+
if (message) {
|
|
105
|
+
console.error(chalk.red(`\n${message}`));
|
|
106
|
+
}
|
|
107
|
+
if (error && !message) {
|
|
108
|
+
console.error(chalk.red(`\n${error.message}`));
|
|
109
|
+
}
|
|
110
|
+
process.exit(1);
|
|
111
|
+
})
|
|
112
|
+
.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kodiak-finance/orderly-devkit",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "CLI toolkit for scaffolding and building DEXs, plugins, and modules with Orderly",
|
|
5
|
+
"main": "",
|
|
6
|
+
"bin": {
|
|
7
|
+
"kodiak-orderly-devkit": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"src"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=20.19.0"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"chalk": "^4.1.2",
|
|
19
|
+
"degit": "^2.8.4",
|
|
20
|
+
"enquirer": "^2.4.1",
|
|
21
|
+
"fast-glob": "^3.3.3",
|
|
22
|
+
"fs-extra": "^11.3.0",
|
|
23
|
+
"handlebars": "^4.7.9",
|
|
24
|
+
"yargs": "^17.7.2"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"rimraf": "^5.0.0",
|
|
28
|
+
"tsconfig": "0.11.34-alpha.0"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"clean": "rimraf dist",
|
|
35
|
+
"link-global": "pnpm link --global"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const { input, select, heading, info, success } = require("../../shared");
|
|
2
|
+
const { MODULE_TYPES } = require("../../internal/constants");
|
|
3
|
+
|
|
4
|
+
// create module command - default export for yargs commandDir
|
|
5
|
+
module.exports = {
|
|
6
|
+
command: "module",
|
|
7
|
+
describe: "Create a new module",
|
|
8
|
+
builder: (yargs) => {
|
|
9
|
+
return yargs
|
|
10
|
+
.option("template", {
|
|
11
|
+
alias: "t",
|
|
12
|
+
type: "string",
|
|
13
|
+
describe:
|
|
14
|
+
"string; currently a placeholder and is not used by this command (module type is always prompted interactively)",
|
|
15
|
+
demandOption: false,
|
|
16
|
+
})
|
|
17
|
+
.positional("name", {
|
|
18
|
+
type: "string",
|
|
19
|
+
describe:
|
|
20
|
+
"string; module name used only for display in the guided flow (actual generation is implemented later)",
|
|
21
|
+
default: "my-module",
|
|
22
|
+
})
|
|
23
|
+
.example(
|
|
24
|
+
"orderly create module",
|
|
25
|
+
"Run guided module creation (prompt for module type)",
|
|
26
|
+
)
|
|
27
|
+
.example(
|
|
28
|
+
"orderly create module --name my-module",
|
|
29
|
+
"Provide a module name (still prompts for module type)",
|
|
30
|
+
);
|
|
31
|
+
},
|
|
32
|
+
handler: async (argv) => {
|
|
33
|
+
heading("Create a New Module");
|
|
34
|
+
info("This command will guide you through creating a new module.\n");
|
|
35
|
+
|
|
36
|
+
// Module type: use argv or prompt
|
|
37
|
+
const moduleType = await select("Select module type:", MODULE_TYPES, 0);
|
|
38
|
+
|
|
39
|
+
console.log("\n--- Module Creation Details ---");
|
|
40
|
+
console.log("Module name:", argv.name || "my-module");
|
|
41
|
+
console.log("Module type:", moduleType);
|
|
42
|
+
console.log("-----------------------------\n");
|
|
43
|
+
|
|
44
|
+
success("Module creation flow completed!");
|
|
45
|
+
success(`Module: ${argv.name || "my-module"}`);
|
|
46
|
+
success(`Type: ${moduleType}`);
|
|
47
|
+
console.log("\nNote: Actual file generation will be implemented later.");
|
|
48
|
+
},
|
|
49
|
+
};
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
const {
|
|
2
|
+
generateFromTemplate,
|
|
3
|
+
toPascalCase,
|
|
4
|
+
toKebabCase,
|
|
5
|
+
toCamelCase,
|
|
6
|
+
validateName,
|
|
7
|
+
} = require("../../internal/templateGenerator");
|
|
8
|
+
const {
|
|
9
|
+
input,
|
|
10
|
+
select,
|
|
11
|
+
confirm,
|
|
12
|
+
heading,
|
|
13
|
+
info,
|
|
14
|
+
success,
|
|
15
|
+
error,
|
|
16
|
+
warn,
|
|
17
|
+
} = require("../../shared");
|
|
18
|
+
const { INTERCEPTOR_TARGETS } = require("../../internal/constants");
|
|
19
|
+
const { generateManifest } = require("../../internal/manifest");
|
|
20
|
+
const {
|
|
21
|
+
maybePrintOrderlyDevEnvironmentHints,
|
|
22
|
+
} = require("../../internal/orderlySdkDocsMcpDetect");
|
|
23
|
+
const path = require("path");
|
|
24
|
+
|
|
25
|
+
const TEMPLATE_REPO = "OrderlyNetwork/orderly-plugin-template";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Validate npm package name for scoped and unscoped formats.
|
|
29
|
+
* Keep this check intentionally strict to prevent publishing failures.
|
|
30
|
+
* @param {string} name
|
|
31
|
+
* @returns {boolean}
|
|
32
|
+
*/
|
|
33
|
+
function isValidNpmPackageName(name) {
|
|
34
|
+
if (!name || typeof name !== "string") {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
const scoped = /^@[a-z0-9][a-z0-9-._]*\/[a-z0-9][a-z0-9-._]*$/;
|
|
38
|
+
const unscoped = /^[a-z0-9][a-z0-9-._]*$/;
|
|
39
|
+
return scoped.test(name) || unscoped.test(name);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Build a npm-compliant package name before template rendering.
|
|
44
|
+
* Prefer pluginId so users can control the final published package name.
|
|
45
|
+
* @param {string} pluginId
|
|
46
|
+
* @param {string} pluginName
|
|
47
|
+
* @returns {string}
|
|
48
|
+
*/
|
|
49
|
+
function buildNpmName(pluginId, pluginName) {
|
|
50
|
+
const candidate = typeof pluginId === "string" ? pluginId.trim() : "";
|
|
51
|
+
if (isValidNpmPackageName(candidate)) {
|
|
52
|
+
return candidate;
|
|
53
|
+
}
|
|
54
|
+
return toKebabCase(pluginName);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Build template variables from user answers
|
|
58
|
+
function buildVars(pluginName, pluginId, interceptorTarget, npmName) {
|
|
59
|
+
return {
|
|
60
|
+
name: pluginName,
|
|
61
|
+
pluginName,
|
|
62
|
+
pluginId,
|
|
63
|
+
npmName,
|
|
64
|
+
pluginIdCamel: toCamelCase(pluginId),
|
|
65
|
+
interceptorTarget,
|
|
66
|
+
pluginVersion: "1.0.0",
|
|
67
|
+
date: new Date().toISOString().split("T")[0],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Interactive prompts
|
|
72
|
+
async function promptPluginName(existing) {
|
|
73
|
+
let name;
|
|
74
|
+
let validation;
|
|
75
|
+
do {
|
|
76
|
+
name = await input("Enter plugin name:", existing || "");
|
|
77
|
+
name = toPascalCase(name);
|
|
78
|
+
validation = validateName(name);
|
|
79
|
+
if (!validation.valid) {
|
|
80
|
+
console.log(` ${validation.error}`);
|
|
81
|
+
}
|
|
82
|
+
} while (!validation.valid);
|
|
83
|
+
return name;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function promptPluginId(pluginName, existing) {
|
|
87
|
+
const defaultId = toKebabCase(pluginName);
|
|
88
|
+
return await input("Enter plugin ID:", existing || defaultId);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function promptInterceptorTarget() {
|
|
92
|
+
return await select("Select interceptor target:", INTERCEPTOR_TARGETS, 0);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function promptTargetDir(pluginName) {
|
|
96
|
+
return await input("Enter target directory:", `./${pluginName}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Main command
|
|
100
|
+
module.exports = {
|
|
101
|
+
command: "plugin",
|
|
102
|
+
describe: "Create a new plugin from template",
|
|
103
|
+
builder: (yargs) => {
|
|
104
|
+
return yargs
|
|
105
|
+
.option("name", {
|
|
106
|
+
alias: "n",
|
|
107
|
+
type: "string",
|
|
108
|
+
describe:
|
|
109
|
+
"string; plugin name in PascalCase (input is normalized to PascalCase and validated). Required when `--no-interactive` is set",
|
|
110
|
+
demandOption: false,
|
|
111
|
+
})
|
|
112
|
+
.option("id", {
|
|
113
|
+
alias: "i",
|
|
114
|
+
type: "string",
|
|
115
|
+
describe:
|
|
116
|
+
"string; plugin ID in kebab-case. If omitted in `--no-interactive` mode, derived from `--name` via toKebabCase()",
|
|
117
|
+
demandOption: false,
|
|
118
|
+
})
|
|
119
|
+
.option("target", {
|
|
120
|
+
alias: "t",
|
|
121
|
+
type: "string",
|
|
122
|
+
describe:
|
|
123
|
+
"string; output directory. Must be empty or non-existent (template generator throws if non-empty). If omitted in `--no-interactive` mode, defaults to `./<PluginName>`",
|
|
124
|
+
demandOption: false,
|
|
125
|
+
})
|
|
126
|
+
.option("interceptor", {
|
|
127
|
+
type: "string",
|
|
128
|
+
choices: INTERCEPTOR_TARGETS,
|
|
129
|
+
describe:
|
|
130
|
+
"string; interceptor target (must be one of the listed values). Required when `--no-interactive` is set",
|
|
131
|
+
demandOption: false,
|
|
132
|
+
})
|
|
133
|
+
.option("skip-install", {
|
|
134
|
+
type: "boolean",
|
|
135
|
+
describe:
|
|
136
|
+
"boolean; if true, skip `npm install` in the generated plugin folder",
|
|
137
|
+
default: false,
|
|
138
|
+
})
|
|
139
|
+
.option("no-interactive", {
|
|
140
|
+
type: "boolean",
|
|
141
|
+
default: false,
|
|
142
|
+
describe:
|
|
143
|
+
"boolean; if true, do not prompt. Requires `--name` and `--interceptor`. If `--id`/`--target` are omitted, they default from `--name`. Prompts are also skipped when `--name --id --interceptor --target` are all provided",
|
|
144
|
+
})
|
|
145
|
+
.example(
|
|
146
|
+
"orderly create plugin --no-interactive --name MyPlugin --id my-plugin --interceptor Trading.Layout.Desktop --target ./my-plugin --skip-install",
|
|
147
|
+
"Create plugin without prompts (explicit id/target) and skip npm install",
|
|
148
|
+
)
|
|
149
|
+
.example(
|
|
150
|
+
"orderly create plugin",
|
|
151
|
+
"Interactive mode: prompt for name, id, interceptor, and target directory",
|
|
152
|
+
);
|
|
153
|
+
},
|
|
154
|
+
handler: async (argv) => {
|
|
155
|
+
heading("Create a New Plugin");
|
|
156
|
+
|
|
157
|
+
// Determine if all required params are provided (non-interactive mode)
|
|
158
|
+
const hasAllParams =
|
|
159
|
+
argv.name && argv.id && argv.interceptor && argv.target;
|
|
160
|
+
const nonInteractive = argv["no-interactive"] || hasAllParams;
|
|
161
|
+
|
|
162
|
+
if (!nonInteractive) {
|
|
163
|
+
info(
|
|
164
|
+
"This command will download a plugin template and customize it for you.\n",
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Gather inputs (from flags or interactive prompts)
|
|
169
|
+
let pluginName = argv.name ? toPascalCase(argv.name) : null;
|
|
170
|
+
if (!pluginName) {
|
|
171
|
+
if (nonInteractive) {
|
|
172
|
+
error("Missing required option: --name");
|
|
173
|
+
process.exitCode = 1;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
pluginName = await promptPluginName("");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let pluginId = argv.id || null;
|
|
180
|
+
if (!pluginId) {
|
|
181
|
+
if (nonInteractive) {
|
|
182
|
+
pluginId = toKebabCase(pluginName);
|
|
183
|
+
} else {
|
|
184
|
+
pluginId = await promptPluginId(pluginName, "");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let interceptorTarget = argv.interceptor || null;
|
|
189
|
+
if (!interceptorTarget) {
|
|
190
|
+
if (nonInteractive) {
|
|
191
|
+
error("Missing required option: --interceptor");
|
|
192
|
+
process.exitCode = 1;
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
interceptorTarget = await promptInterceptorTarget();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let targetDir = argv.target || null;
|
|
199
|
+
if (!targetDir) {
|
|
200
|
+
if (nonInteractive) {
|
|
201
|
+
targetDir = `./${pluginName}`;
|
|
202
|
+
} else {
|
|
203
|
+
targetDir = await promptTargetDir(pluginName);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const skipInstall = argv["skip-install"];
|
|
208
|
+
|
|
209
|
+
// Summary
|
|
210
|
+
console.log("\n--- Plugin Creation Summary ---");
|
|
211
|
+
console.log(`Plugin Name: ${pluginName}`);
|
|
212
|
+
console.log(`Plugin ID: ${pluginId}`);
|
|
213
|
+
console.log(`Interceptor Target: ${interceptorTarget}`);
|
|
214
|
+
console.log(`Target Directory: ${targetDir}`);
|
|
215
|
+
console.log(`-----------------------------\n`);
|
|
216
|
+
|
|
217
|
+
if (!nonInteractive) {
|
|
218
|
+
const proceed = await confirm("Proceed with creation?");
|
|
219
|
+
if (!proceed) {
|
|
220
|
+
info("Cancelled.");
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Build npm-safe package name up front and pass into template variables.
|
|
226
|
+
const npmName = buildNpmName(pluginId, pluginName);
|
|
227
|
+
if (npmName !== pluginId) {
|
|
228
|
+
warn(
|
|
229
|
+
`Plugin ID "${pluginId}" is not npm-compliant, using "${npmName}" as package name.`,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Build vars and generate
|
|
234
|
+
const vars = buildVars(pluginName, pluginId, interceptorTarget, npmName);
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
await generateFromTemplate({
|
|
238
|
+
repo: TEMPLATE_REPO,
|
|
239
|
+
targetDir: path.resolve(targetDir),
|
|
240
|
+
vars,
|
|
241
|
+
skipInstall,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Generate orderly manifest file
|
|
245
|
+
const resolvedDir = path.resolve(targetDir);
|
|
246
|
+
try {
|
|
247
|
+
generateManifest(resolvedDir, {
|
|
248
|
+
pluginId: pluginId,
|
|
249
|
+
tags: [],
|
|
250
|
+
});
|
|
251
|
+
info(
|
|
252
|
+
`Manifest file generated at ${resolvedDir}/.orderly-manifest.json`,
|
|
253
|
+
);
|
|
254
|
+
} catch (manifestErr) {
|
|
255
|
+
warn(`Could not generate manifest: ${manifestErr.message}`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
success(`\nPlugin created successfully!`);
|
|
259
|
+
info(`Next steps:`);
|
|
260
|
+
info(` cd ${targetDir}`);
|
|
261
|
+
info(` npm run dev # or your setup command`);
|
|
262
|
+
maybePrintOrderlyDevEnvironmentHints(resolvedDir);
|
|
263
|
+
} catch (err) {
|
|
264
|
+
error(`Failed to create plugin: ${err.message}`);
|
|
265
|
+
if (err.stack) {
|
|
266
|
+
console.error(err.stack);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
};
|