create-onin-plugin 1.7.0 → 1.8.0

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.
Files changed (30) hide show
  1. package/README.md +24 -12
  2. package/package.json +10 -4
  3. package/templates/adapters/react/index.html.tpl +12 -0
  4. package/templates/adapters/react/package.fragment.json +17 -0
  5. package/templates/adapters/react/src/App.tsx.tpl +120 -0
  6. package/templates/adapters/react/src/main.tsx.tpl +9 -0
  7. package/templates/adapters/react/tsconfig.json.tpl +15 -0
  8. package/templates/adapters/react/vite.config.ts.tpl +11 -0
  9. package/templates/adapters/svelte/package.fragment.json +14 -0
  10. package/templates/adapters/vue/index.html.tpl +12 -0
  11. package/templates/adapters/vue/package.fragment.json +14 -0
  12. package/templates/adapters/vue/src/App.vue.tpl +117 -0
  13. package/templates/adapters/vue/src/env.d.ts.tpl +8 -0
  14. package/templates/adapters/vue/src/main.ts.tpl +7 -0
  15. package/templates/adapters/vue/tsconfig.json.tpl +20 -0
  16. package/templates/adapters/vue/vite.config.ts.tpl +11 -0
  17. package/templates/{svelte-view → base}/package.json.tpl +2 -8
  18. package/src/cli.js +0 -334
  19. /package/templates/{svelte-view → adapters/svelte}/index.html.tpl +0 -0
  20. /package/templates/{svelte-view → adapters/svelte}/src/App.svelte.tpl +0 -0
  21. /package/templates/{svelte-view → adapters/svelte}/src/main.ts.tpl +0 -0
  22. /package/templates/{svelte-view → adapters/svelte}/svelte.config.js.tpl +0 -0
  23. /package/templates/{svelte-view → adapters/svelte}/tsconfig.json.tpl +0 -0
  24. /package/templates/{svelte-view → adapters/svelte}/vite.config.ts.tpl +0 -0
  25. /package/templates/{svelte-view → base}/.gitignore.tpl +0 -0
  26. /package/templates/{svelte-view → base}/README.md.tpl +0 -0
  27. /package/templates/{svelte-view → base}/icon.svg.tpl +0 -0
  28. /package/templates/{svelte-view → base}/manifest.json.tpl +0 -0
  29. /package/templates/{svelte-view → base}/src/lifecycle.ts.tpl +0 -0
  30. /package/templates/{svelte-view → base}/vite.lifecycle.config.ts.tpl +0 -0
package/README.md CHANGED
@@ -28,24 +28,36 @@ Interactive mode:
28
28
  npx create-onin-plugin
29
29
  ```
30
30
 
31
- ## Current template
31
+ Create a React starter instead:
32
32
 
33
- The first version ships a single `svelte-view` template with:
33
+ ```bash
34
+ npx create-onin-plugin my-plugin --framework react
35
+ ```
34
36
 
35
- - `src/main.ts`
36
- - `src/lifecycle.ts`
37
- - `vite.lifecycle.config.ts`
38
- - `pnpm build`
39
- - `pnpm pack:plugin`
37
+ Create a Vue starter instead:
38
+
39
+ ```bash
40
+ npx create-onin-plugin my-plugin --framework vue
41
+ ```
42
+
43
+ ## Frameworks
44
+
45
+ The CLI currently supports:
46
+
47
+ - `svelte` (default)
48
+ - `react`
49
+ - `vue`
40
50
 
41
51
  ## Generated project
42
52
 
43
- The generated plugin includes:
53
+ Every generated plugin includes:
54
+
55
+ - `src/lifecycle.ts`
56
+ - `manifest.json`
57
+ - `vite.lifecycle.config.ts`
58
+ - `pnpm pack:plugin`
44
59
 
45
- - `src/main.ts` for the UI entry
46
- - `src/lifecycle.ts` for settings, commands, and startup initialization
47
- - `manifest.json` wired to `dist/lifecycle.js`
48
- - `pnpm pack:plugin` to create `plugin.zip`
60
+ Framework-specific starters add their own UI entry files such as `src/main.ts`, `src/main.tsx`, and framework app components.
49
61
 
50
62
  The release zip contains:
51
63
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-onin-plugin",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "private": false,
5
5
  "description": "CLI for scaffolding Onin plugins with lifecycle and release packaging built in.",
6
6
  "license": "GPL-3.0",
@@ -9,15 +9,21 @@
9
9
  },
10
10
  "type": "module",
11
11
  "bin": {
12
- "create-onin-plugin": "./src/cli.js"
12
+ "create-onin-plugin": "./dist/cli.js"
13
13
  },
14
14
  "files": [
15
15
  "README.md",
16
- "src",
16
+ "dist",
17
17
  "templates"
18
18
  ],
19
19
  "scripts": {
20
- "start": "node ./src/cli.js"
20
+ "build": "tsc -p tsconfig.json",
21
+ "start": "pnpm run build && node ./dist/cli.js",
22
+ "test": "pnpm run build && node --test dist/render.test.js"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^24.0.0",
26
+ "typescript": "^5.8.3"
21
27
  },
22
28
  "engines": {
23
29
  "node": ">=18"
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>__PLUGIN_NAME__</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,17 @@
1
+ {
2
+ "scripts": {
3
+ "dev": "vite",
4
+ "build:index": "vite build"
5
+ },
6
+ "dependencies": {
7
+ "react": "^18.3.1",
8
+ "react-dom": "^18.3.1"
9
+ },
10
+ "devDependencies": {
11
+ "@types/react": "^18.3.12",
12
+ "@types/react-dom": "^18.3.1",
13
+ "@vitejs/plugin-react": "^4.3.4",
14
+ "typescript": "^5.5.0",
15
+ "vite": "^7.3.1"
16
+ }
17
+ }
@@ -0,0 +1,120 @@
1
+ type AppProps = {
2
+ pluginName: string;
3
+ pluginId: string;
4
+ };
5
+
6
+ export default function App({ pluginName, pluginId }: AppProps) {
7
+ return (
8
+ <main className="shell">
9
+ <section className="hero">
10
+ <p className="eyebrow">Onin Plugin</p>
11
+ <h1>{pluginName}</h1>
12
+ <p className="lede">
13
+ This starter includes a dedicated lifecycle build, release pack command,
14
+ and a manifest wired for marketplace-safe output.
15
+ </p>
16
+ </section>
17
+
18
+ <section className="card">
19
+ <h2>What is ready</h2>
20
+ <ul>
21
+ <li>
22
+ Vite app build to <code>dist/</code>
23
+ </li>
24
+ <li>
25
+ Standalone <code>lifecycle.js</code> build
26
+ </li>
27
+ <li>
28
+ <code>pnpm pack</code> for release zip creation
29
+ </li>
30
+ <li>Manifest and lifecycle path already aligned</li>
31
+ </ul>
32
+ </section>
33
+
34
+ <section className="card">
35
+ <h2>Plugin ID</h2>
36
+ <code>{pluginId}</code>
37
+ </section>
38
+ <style>{`
39
+ body {
40
+ margin: 0;
41
+ font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
42
+ background:
43
+ radial-gradient(circle at top left, rgba(59, 130, 246, 0.18), transparent 35%),
44
+ linear-gradient(180deg, #f7f7f5 0%, #eceae3 100%);
45
+ color: #161616;
46
+ }
47
+
48
+ code {
49
+ font-family: "IBM Plex Mono", "Cascadia Code", monospace;
50
+ }
51
+
52
+ .shell {
53
+ min-height: 100vh;
54
+ padding: 28px;
55
+ display: grid;
56
+ gap: 18px;
57
+ align-content: start;
58
+ }
59
+
60
+ .hero,
61
+ .card {
62
+ background: rgba(255, 255, 255, 0.78);
63
+ border: 1px solid rgba(22, 22, 22, 0.08);
64
+ border-radius: 24px;
65
+ padding: 24px;
66
+ box-shadow: 0 18px 50px rgba(22, 22, 22, 0.08);
67
+ backdrop-filter: blur(14px);
68
+ }
69
+
70
+ .eyebrow {
71
+ margin: 0 0 8px;
72
+ font-size: 12px;
73
+ letter-spacing: 0.16em;
74
+ text-transform: uppercase;
75
+ color: #5f6368;
76
+ }
77
+
78
+ h1,
79
+ h2,
80
+ p,
81
+ ul {
82
+ margin: 0;
83
+ }
84
+
85
+ h1 {
86
+ font-size: 32px;
87
+ line-height: 1.05;
88
+ }
89
+
90
+ h2 {
91
+ font-size: 18px;
92
+ margin-bottom: 12px;
93
+ }
94
+
95
+ .lede {
96
+ margin-top: 12px;
97
+ max-width: 48ch;
98
+ color: #4b5563;
99
+ line-height: 1.6;
100
+ }
101
+
102
+ ul {
103
+ padding-left: 18px;
104
+ color: #374151;
105
+ line-height: 1.7;
106
+ }
107
+
108
+ @media (max-width: 640px) {
109
+ .shell {
110
+ padding: 16px;
111
+ }
112
+
113
+ h1 {
114
+ font-size: 28px;
115
+ }
116
+ }
117
+ `}</style>
118
+ </main>
119
+ );
120
+ }
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ import ReactDOM from "react-dom/client";
3
+ import App from "./App";
4
+
5
+ ReactDOM.createRoot(document.getElementById("root")!).render(
6
+ <React.StrictMode>
7
+ <App pluginName="__PLUGIN_NAME__" pluginId="__PLUGIN_ID__" />
8
+ </React.StrictMode>,
9
+ );
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "Node",
7
+ "jsx": "react-jsx",
8
+ "strict": true,
9
+ "resolveJsonModule": true,
10
+ "isolatedModules": true,
11
+ "esModuleInterop": true,
12
+ "skipLibCheck": true
13
+ },
14
+ "include": ["src/**/*.ts", "src/**/*.tsx", "vite.config.ts", "vite.lifecycle.config.ts"]
15
+ }
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ base: "./",
7
+ server: {
8
+ port: 5173,
9
+ cors: true,
10
+ },
11
+ });
@@ -0,0 +1,14 @@
1
+ {
2
+ "scripts": {
3
+ "dev": "vite",
4
+ "build:index": "vite build"
5
+ },
6
+ "dependencies": {
7
+ "svelte": "^5.0.0"
8
+ },
9
+ "devDependencies": {
10
+ "@sveltejs/vite-plugin-svelte": "^6.2.4",
11
+ "typescript": "^5.5.0",
12
+ "vite": "^7.3.1"
13
+ }
14
+ }
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>__PLUGIN_NAME__</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.ts"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,14 @@
1
+ {
2
+ "scripts": {
3
+ "dev": "vite",
4
+ "build:index": "vite build"
5
+ },
6
+ "dependencies": {
7
+ "vue": "^3.5.29"
8
+ },
9
+ "devDependencies": {
10
+ "@vitejs/plugin-vue": "^5.2.4",
11
+ "typescript": "^5.5.0",
12
+ "vite": "^7.3.1"
13
+ }
14
+ }
@@ -0,0 +1,117 @@
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ pluginName: string;
4
+ pluginId: string;
5
+ }>();
6
+ </script>
7
+
8
+ <template>
9
+ <main class="shell">
10
+ <section class="hero">
11
+ <p class="eyebrow">Onin Plugin</p>
12
+ <h1>{{ pluginName }}</h1>
13
+ <p class="lede">
14
+ This starter includes a dedicated lifecycle build, release pack command,
15
+ and a manifest wired for marketplace-safe output.
16
+ </p>
17
+ </section>
18
+
19
+ <section class="card">
20
+ <h2>What is ready</h2>
21
+ <ul>
22
+ <li>Vite app build to <code>dist/</code></li>
23
+ <li>Standalone <code>lifecycle.js</code> build</li>
24
+ <li><code>pnpm pack</code> for release zip creation</li>
25
+ <li>Manifest and lifecycle path already aligned</li>
26
+ </ul>
27
+ </section>
28
+
29
+ <section class="card">
30
+ <h2>Plugin ID</h2>
31
+ <code>{{ pluginId }}</code>
32
+ </section>
33
+ </main>
34
+ </template>
35
+
36
+ <style scoped>
37
+ :global(body) {
38
+ margin: 0;
39
+ font-family:
40
+ "IBM Plex Sans", "Segoe UI", sans-serif;
41
+ background:
42
+ radial-gradient(circle at top left, rgba(59, 130, 246, 0.18), transparent 35%),
43
+ linear-gradient(180deg, #f7f7f5 0%, #eceae3 100%);
44
+ color: #161616;
45
+ }
46
+
47
+ code {
48
+ font-family:
49
+ "IBM Plex Mono", "Cascadia Code", monospace;
50
+ }
51
+
52
+ .shell {
53
+ min-height: 100vh;
54
+ padding: 28px;
55
+ display: grid;
56
+ gap: 18px;
57
+ align-content: start;
58
+ }
59
+
60
+ .hero,
61
+ .card {
62
+ background: rgba(255, 255, 255, 0.78);
63
+ border: 1px solid rgba(22, 22, 22, 0.08);
64
+ border-radius: 24px;
65
+ padding: 24px;
66
+ box-shadow: 0 18px 50px rgba(22, 22, 22, 0.08);
67
+ backdrop-filter: blur(14px);
68
+ }
69
+
70
+ .eyebrow {
71
+ margin: 0 0 8px;
72
+ font-size: 12px;
73
+ letter-spacing: 0.16em;
74
+ text-transform: uppercase;
75
+ color: #5f6368;
76
+ }
77
+
78
+ h1,
79
+ h2,
80
+ p,
81
+ ul {
82
+ margin: 0;
83
+ }
84
+
85
+ h1 {
86
+ font-size: 32px;
87
+ line-height: 1.05;
88
+ }
89
+
90
+ h2 {
91
+ font-size: 18px;
92
+ margin-bottom: 12px;
93
+ }
94
+
95
+ .lede {
96
+ margin-top: 12px;
97
+ max-width: 48ch;
98
+ color: #4b5563;
99
+ line-height: 1.6;
100
+ }
101
+
102
+ ul {
103
+ padding-left: 18px;
104
+ color: #374151;
105
+ line-height: 1.7;
106
+ }
107
+
108
+ @media (max-width: 640px) {
109
+ .shell {
110
+ padding: 16px;
111
+ }
112
+
113
+ h1 {
114
+ font-size: 28px;
115
+ }
116
+ }
117
+ </style>
@@ -0,0 +1,8 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ declare module "*.vue" {
4
+ import type { DefineComponent } from "vue";
5
+
6
+ const component: DefineComponent<Record<string, never>, Record<string, never>, unknown>;
7
+ export default component;
8
+ }
@@ -0,0 +1,7 @@
1
+ import { createApp } from "vue";
2
+ import App from "./App.vue";
3
+
4
+ createApp(App, {
5
+ pluginName: "__PLUGIN_NAME__",
6
+ pluginId: "__PLUGIN_ID__",
7
+ }).mount("#app");
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "Node",
7
+ "strict": true,
8
+ "resolveJsonModule": true,
9
+ "isolatedModules": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true
12
+ },
13
+ "include": [
14
+ "src/**/*.ts",
15
+ "src/**/*.d.ts",
16
+ "src/**/*.vue",
17
+ "vite.config.ts",
18
+ "vite.lifecycle.config.ts"
19
+ ]
20
+ }
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from "vite";
2
+ import vue from "@vitejs/plugin-vue";
3
+
4
+ export default defineConfig({
5
+ plugins: [vue()],
6
+ base: "./",
7
+ server: {
8
+ port: 5173,
9
+ cors: true,
10
+ },
11
+ });
@@ -4,20 +4,14 @@
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
7
- "dev": "vite",
8
- "build:index": "vite build",
9
7
  "build:lifecycle": "vite build --config vite.lifecycle.config.ts",
10
8
  "build": "npm run build:index && npm run build:lifecycle",
11
9
  "pack:plugin": "npm run build && bestzip plugin.zip manifest.json icon.svg dist"
12
10
  },
13
11
  "dependencies": {
14
- "onin-sdk": "^1.6.0",
15
- "svelte": "^5.0.0"
12
+ "onin-sdk": "^1.6.0"
16
13
  },
17
14
  "devDependencies": {
18
- "@sveltejs/vite-plugin-svelte": "^6.2.4",
19
- "bestzip": "^2.2.1",
20
- "typescript": "^5.5.0",
21
- "vite": "^7.3.1"
15
+ "bestzip": "^2.2.1"
22
16
  }
23
17
  }
package/src/cli.js DELETED
@@ -1,334 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { stdin as input, stdout as output } from "node:process";
4
- import { createInterface } from "node:readline/promises";
5
- import { fileURLToPath } from "node:url";
6
- import { basename, dirname, join, resolve } from "node:path";
7
- import { copyFile, mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
8
-
9
- const TEMPLATE_NAME = "svelte-view";
10
- const SUPPORTED_TEMPLATES = [TEMPLATE_NAME];
11
- const CLI_DIR = dirname(fileURLToPath(import.meta.url));
12
- const TEMPLATE_DIR = resolve(
13
- CLI_DIR,
14
- "../templates",
15
- TEMPLATE_NAME,
16
- );
17
-
18
- function parseArgs(argv) {
19
- const options = {
20
- targetDir: undefined,
21
- pluginName: undefined,
22
- pluginId: undefined,
23
- withSettings: undefined,
24
- yes: false,
25
- template: TEMPLATE_NAME,
26
- };
27
-
28
- for (let i = 0; i < argv.length; i += 1) {
29
- const arg = argv[i];
30
-
31
- if (arg === "--") {
32
- continue;
33
- }
34
-
35
- if (!arg.startsWith("--") && !options.targetDir) {
36
- options.targetDir = arg;
37
- continue;
38
- }
39
-
40
- if (arg === "--template") {
41
- options.template = argv[i + 1] ?? TEMPLATE_NAME;
42
- i += 1;
43
- continue;
44
- }
45
-
46
- if (arg === "--plugin-name") {
47
- options.pluginName = argv[i + 1];
48
- i += 1;
49
- continue;
50
- }
51
-
52
- if (arg === "--plugin-id") {
53
- options.pluginId = argv[i + 1];
54
- i += 1;
55
- continue;
56
- }
57
-
58
- if (arg === "--yes") {
59
- options.yes = true;
60
- continue;
61
- }
62
-
63
- if (arg === "--with-settings") {
64
- options.withSettings = true;
65
- continue;
66
- }
67
-
68
- if (arg === "--no-with-settings") {
69
- options.withSettings = false;
70
- continue;
71
- }
72
- }
73
-
74
- return options;
75
- }
76
-
77
- function printHelp() {
78
- console.log("create-onin-plugin");
79
- console.log("");
80
- console.log("Usage:");
81
- console.log(" create-onin-plugin [target-dir] [options]");
82
- console.log("");
83
- console.log("Options:");
84
- console.log(" --template <name> Template to use (default: svelte-view)");
85
- console.log(" --plugin-name <name> Plugin display name");
86
- console.log(" --plugin-id <id> Plugin manifest id");
87
- console.log(" --with-settings Include settings schema example");
88
- console.log(" --no-with-settings Skip settings schema example");
89
- console.log(" --yes Use defaults for missing answers");
90
- console.log(" --help Show this help message");
91
- }
92
-
93
- function toTitleCase(value) {
94
- return value
95
- .split(/[-_.\s]+/)
96
- .filter(Boolean)
97
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
98
- .join(" ");
99
- }
100
-
101
- function slugify(value) {
102
- return value
103
- .trim()
104
- .toLowerCase()
105
- .replace(/[^a-z0-9.-]+/g, "-")
106
- .replace(/^-+|-+$/g, "")
107
- .replace(/-{2,}/g, "-");
108
- }
109
-
110
- function isValidPluginId(value) {
111
- return /^[a-z0-9][a-z0-9.-]*[a-z0-9]$/.test(value) && !value.includes("..");
112
- }
113
-
114
- function isValidPackageName(value) {
115
- return /^[a-z0-9][a-z0-9.-]*[a-z0-9]$/.test(value);
116
- }
117
-
118
- async function isDirectoryEmpty(dir) {
119
- const entries = await readdir(dir);
120
- return entries.length === 0;
121
- }
122
-
123
- async function ensureTargetDirectory(targetDir) {
124
- try {
125
- const targetStat = await stat(targetDir);
126
- if (!targetStat.isDirectory()) {
127
- throw new Error(
128
- `Target path exists and is not a directory: ${targetDir}\nChoose a new directory name and try again.`,
129
- );
130
- }
131
-
132
- if (!(await isDirectoryEmpty(targetDir))) {
133
- throw new Error(
134
- `Target directory is not empty: ${targetDir}\nUse an empty directory or choose a new project name.`,
135
- );
136
- }
137
- } catch (error) {
138
- if (error && error.code === "ENOENT") {
139
- await mkdir(targetDir, { recursive: true });
140
- return;
141
- }
142
-
143
- throw error;
144
- }
145
- }
146
-
147
- function renderTemplate(content, context) {
148
- return content
149
- .replaceAll("__PLUGIN_NAME__", context.pluginName)
150
- .replaceAll("__PLUGIN_ID__", context.pluginId)
151
- .replaceAll("__PACKAGE_NAME__", context.packageName)
152
- .replaceAll("__PLUGIN_DESCRIPTION__", context.pluginDescription)
153
- .replaceAll("__SETTINGS_BLOCK__", context.settingsBlock)
154
- .replaceAll("__SETTINGS_IMPORT__", context.settingsImport)
155
- .replaceAll("__SETTINGS_NOTE__", context.settingsNote)
156
- .replaceAll("__KEYWORD__", context.keyword);
157
- }
158
-
159
- async function copyTemplateDir(sourceDir, targetDir, context) {
160
- const entries = await readdir(sourceDir, { withFileTypes: true });
161
-
162
- for (const entry of entries) {
163
- const sourcePath = join(sourceDir, entry.name);
164
- const outputName = entry.name.endsWith(".tpl")
165
- ? entry.name.slice(0, -4)
166
- : entry.name;
167
- const targetPath = join(targetDir, outputName);
168
-
169
- if (entry.isDirectory()) {
170
- await mkdir(targetPath, { recursive: true });
171
- await copyTemplateDir(sourcePath, targetPath, context);
172
- continue;
173
- }
174
-
175
- if (entry.name.endsWith(".tpl")) {
176
- const content = await readFile(sourcePath, "utf8");
177
- await writeFile(targetPath, renderTemplate(content, context), "utf8");
178
- continue;
179
- }
180
-
181
- await copyFile(sourcePath, targetPath);
182
- }
183
- }
184
-
185
- function buildSettingsBlock(withSettings) {
186
- if (!withSettings) {
187
- return " // Add settings.useSettingsSchema(...) here when your plugin needs configurable options.\n";
188
- }
189
-
190
- return ` await settings.useSettingsSchema([
191
- {
192
- key: "accentColor",
193
- label: "Accent Color",
194
- type: "color",
195
- defaultValue: "#111827",
196
- description: "Example plugin setting registered during lifecycle onLoad.",
197
- },
198
- ]);
199
- `;
200
- }
201
-
202
- async function promptForMissingOptions(initialOptions) {
203
- if (initialOptions.yes) {
204
- const targetDir = initialOptions.targetDir || "my-onin-plugin";
205
- const packageName = slugify(basename(targetDir));
206
- const pluginName =
207
- initialOptions.pluginName || toTitleCase(packageName) || "My Onin Plugin";
208
- const pluginId =
209
- initialOptions.pluginId || `com.example.${packageName || "my-onin-plugin"}`;
210
-
211
- return {
212
- targetDir,
213
- pluginName,
214
- pluginId,
215
- withSettings: initialOptions.withSettings ?? true,
216
- };
217
- }
218
-
219
- const rl = createInterface({ input, output });
220
-
221
- try {
222
- const targetDirInput =
223
- initialOptions.targetDir ||
224
- (await rl.question("Project directory name: ")).trim();
225
- const targetDir = targetDirInput || "my-onin-plugin";
226
- const packageName = slugify(basename(targetDir));
227
-
228
- const pluginNameInput =
229
- initialOptions.pluginName ||
230
- (await rl.question(
231
- `Plugin name (${toTitleCase(packageName) || "My Onin Plugin"}): `,
232
- )).trim();
233
- const pluginName = pluginNameInput || toTitleCase(packageName) || "My Onin Plugin";
234
-
235
- const defaultPluginId = `com.example.${packageName || "my-onin-plugin"}`;
236
- const pluginIdInput =
237
- initialOptions.pluginId ||
238
- (await rl.question(`Plugin ID (${defaultPluginId}): `)).trim();
239
- const pluginId = pluginIdInput || defaultPluginId;
240
-
241
- let withSettings = initialOptions.withSettings;
242
- if (withSettings === undefined) {
243
- const answer = (
244
- await rl.question("Include settings schema example? (Y/n): ")
245
- ).trim().toLowerCase();
246
- withSettings = answer !== "n";
247
- }
248
-
249
- return {
250
- targetDir,
251
- pluginName,
252
- pluginId,
253
- withSettings,
254
- };
255
- } finally {
256
- rl.close();
257
- }
258
- }
259
-
260
- function printNextSteps(targetDir) {
261
- console.log("");
262
- console.log("Project created.");
263
- console.log("");
264
- console.log(` cd ${targetDir}`);
265
- console.log(" pnpm install");
266
- console.log(" pnpm dev");
267
- console.log("");
268
- console.log("To build release artifacts:");
269
- console.log(" pnpm build");
270
- console.log(" pnpm pack:plugin");
271
- console.log("");
272
- console.log("To load the plugin in Onin:");
273
- console.log(" Open Settings -> Plugins -> Import Local Plugin");
274
- }
275
-
276
- async function main() {
277
- const options = parseArgs(process.argv.slice(2));
278
-
279
- if (process.argv.includes("--help")) {
280
- printHelp();
281
- return;
282
- }
283
-
284
- if (!SUPPORTED_TEMPLATES.includes(options.template)) {
285
- console.error(
286
- `Unsupported template: ${options.template}\nSupported templates: ${SUPPORTED_TEMPLATES.join(", ")}`,
287
- );
288
- process.exitCode = 1;
289
- return;
290
- }
291
-
292
- const answers = await promptForMissingOptions(options);
293
- const targetDir = resolve(process.cwd(), answers.targetDir);
294
- const packageName = slugify(basename(targetDir));
295
-
296
- if (!isValidPackageName(packageName)) {
297
- console.error(
298
- `Invalid project directory name: ${packageName}\nUse lowercase letters, numbers, dots, or hyphens only.`,
299
- );
300
- process.exitCode = 1;
301
- return;
302
- }
303
-
304
- if (!isValidPluginId(answers.pluginId)) {
305
- console.error(
306
- `Invalid plugin ID: ${answers.pluginId}\nUse lowercase letters, numbers, dots, and hyphens only.`,
307
- );
308
- process.exitCode = 1;
309
- return;
310
- }
311
-
312
- await ensureTargetDirectory(targetDir);
313
-
314
- const context = {
315
- packageName,
316
- pluginName: answers.pluginName,
317
- pluginId: answers.pluginId,
318
- pluginDescription: `${answers.pluginName} plugin for Onin`,
319
- keyword: packageName.split(".").pop() || packageName,
320
- settingsImport: answers.withSettings ? ", settings" : "",
321
- settingsBlock: buildSettingsBlock(answers.withSettings),
322
- settingsNote: answers.withSettings
323
- ? "This template includes a sample settings schema registered from lifecycle.ts."
324
- : "This template omits settings schema. Add it later in src/lifecycle.ts if needed.",
325
- };
326
-
327
- await copyTemplateDir(TEMPLATE_DIR, targetDir, context);
328
- printNextSteps(answers.targetDir);
329
- }
330
-
331
- main().catch((error) => {
332
- console.error(error instanceof Error ? error.message : String(error));
333
- process.exitCode = 1;
334
- });
File without changes
File without changes
File without changes