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.
- package/README.md +24 -12
- package/package.json +10 -4
- package/templates/adapters/react/index.html.tpl +12 -0
- package/templates/adapters/react/package.fragment.json +17 -0
- package/templates/adapters/react/src/App.tsx.tpl +120 -0
- package/templates/adapters/react/src/main.tsx.tpl +9 -0
- package/templates/adapters/react/tsconfig.json.tpl +15 -0
- package/templates/adapters/react/vite.config.ts.tpl +11 -0
- package/templates/adapters/svelte/package.fragment.json +14 -0
- package/templates/adapters/vue/index.html.tpl +12 -0
- package/templates/adapters/vue/package.fragment.json +14 -0
- package/templates/adapters/vue/src/App.vue.tpl +117 -0
- package/templates/adapters/vue/src/env.d.ts.tpl +8 -0
- package/templates/adapters/vue/src/main.ts.tpl +7 -0
- package/templates/adapters/vue/tsconfig.json.tpl +20 -0
- package/templates/adapters/vue/vite.config.ts.tpl +11 -0
- package/templates/{svelte-view → base}/package.json.tpl +2 -8
- package/src/cli.js +0 -334
- /package/templates/{svelte-view → adapters/svelte}/index.html.tpl +0 -0
- /package/templates/{svelte-view → adapters/svelte}/src/App.svelte.tpl +0 -0
- /package/templates/{svelte-view → adapters/svelte}/src/main.ts.tpl +0 -0
- /package/templates/{svelte-view → adapters/svelte}/svelte.config.js.tpl +0 -0
- /package/templates/{svelte-view → adapters/svelte}/tsconfig.json.tpl +0 -0
- /package/templates/{svelte-view → adapters/svelte}/vite.config.ts.tpl +0 -0
- /package/templates/{svelte-view → base}/.gitignore.tpl +0 -0
- /package/templates/{svelte-view → base}/README.md.tpl +0 -0
- /package/templates/{svelte-view → base}/icon.svg.tpl +0 -0
- /package/templates/{svelte-view → base}/manifest.json.tpl +0 -0
- /package/templates/{svelte-view → base}/src/lifecycle.ts.tpl +0 -0
- /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
|
-
|
|
31
|
+
Create a React starter instead:
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
```bash
|
|
34
|
+
npx create-onin-plugin my-plugin --framework react
|
|
35
|
+
```
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
-
|
|
39
|
-
|
|
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
|
-
|
|
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`
|
|
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.
|
|
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": "./
|
|
12
|
+
"create-onin-plugin": "./dist/cli.js"
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"README.md",
|
|
16
|
-
"
|
|
16
|
+
"dist",
|
|
17
17
|
"templates"
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
|
-
"
|
|
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,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,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,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
|
+
}
|
|
@@ -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
|
-
"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|