webim-plugin 1.0.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/LICENSE +21 -0
- package/README.md +78 -0
- package/bin/webim-plugin.js +382 -0
- package/package.json +36 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Orhan POLAT
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# webim-plugin
|
|
2
|
+
|
|
3
|
+
CLI for Web-IM builder plugins: scaffold a project, bundle any TypeScript/JSX
|
|
4
|
+
source tree into the single-module standard the dashboard accepts, validate it
|
|
5
|
+
with the same rules the plugin manager enforces, and push it straight to a
|
|
6
|
+
tenant.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install -g webim-plugin # global install
|
|
12
|
+
npx webim-plugin init my-widget # or one-off via npx
|
|
13
|
+
npm create webim-plugin my-widget # or the create flow
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Requires Node 18+.
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
webim-plugin init my-widget # scaffold: webim-plugin.json, src/, README
|
|
22
|
+
cd my-widget
|
|
23
|
+
webim-plugin build # esbuild -> dist/plugin.js + validation
|
|
24
|
+
webim-plugin push # build, sign in, merge into builder_plugins
|
|
25
|
+
webim-plugin dev # watch src/, rebuild + push on every change
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Config — `webim-plugin.json`
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"entry": "src/index.js",
|
|
33
|
+
"out": "dist/plugin.js",
|
|
34
|
+
"api": "https://your-webim/api",
|
|
35
|
+
"tenant": "your-tenant-slug"
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Auth for push/dev
|
|
40
|
+
|
|
41
|
+
Environment variables, never stored in the project:
|
|
42
|
+
|
|
43
|
+
- `WEBIM_TOKEN` — a Bearer access token (required for accounts with 2FA), or
|
|
44
|
+
- `WEBIM_EMAIL` + `WEBIM_PASSWORD` — a normal dashboard sign-in.
|
|
45
|
+
|
|
46
|
+
The account needs the `settings.tenant` edit grant (owner/admin pass).
|
|
47
|
+
|
|
48
|
+
## The plugin standard
|
|
49
|
+
|
|
50
|
+
One ES module, default-exporting:
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
export default {
|
|
54
|
+
id: 'kebab-slug', // stable — saved pages reference it
|
|
55
|
+
name: 'Display Name',
|
|
56
|
+
version: '1.0.0',
|
|
57
|
+
blocks: [{
|
|
58
|
+
id: 'kebab-slug', // stable per plugin
|
|
59
|
+
label: 'Toolbox label',
|
|
60
|
+
props: { /* defaults */ },
|
|
61
|
+
settings: [{ name, label, type }], // text|number|switch|select|color|image
|
|
62
|
+
mount(el, props, ctx) {}, // ctx = { tenant, apiUrl, locale, settings }
|
|
63
|
+
update(el, props, ctx) {}, // optional — omitted = unmount + mount
|
|
64
|
+
unmount(el) {} // optional
|
|
65
|
+
}]
|
|
66
|
+
};
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Source can span as many files as you like — `build` flattens them. Keep DOM
|
|
70
|
+
work inside `mount`/`update`: the built module is imported in Node during
|
|
71
|
+
validation, so module-level `document` access fails the build (on purpose).
|
|
72
|
+
|
|
73
|
+
Pushing merges by plugin id over the tenant's existing catalog — one plugin
|
|
74
|
+
per project, other installed plugins untouched.
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable no-console */
|
|
3
|
+
// webim-plugin — the Web-IM builder plugin CLI.
|
|
4
|
+
//
|
|
5
|
+
// webim-plugin init [dir] scaffold a plugin project
|
|
6
|
+
// webim-plugin build bundle src/ into dist/plugin.js and validate it
|
|
7
|
+
// webim-plugin push build + upload to the tenant's builder_plugins
|
|
8
|
+
// webim-plugin dev watch src/, rebuild and push on every change
|
|
9
|
+
//
|
|
10
|
+
// The build target is the SINGLE-MODULE STANDARD the dashboard's plugin
|
|
11
|
+
// manager accepts (see ui components/builder/plugins/runtime.js): one ES
|
|
12
|
+
// module whose default export is { id, name, version, blocks: [{ id, label,
|
|
13
|
+
// props, settings, mount(el, props, ctx), update?, unmount? }] }. Write
|
|
14
|
+
// TypeScript/JSX across as many files as you like — esbuild flattens them.
|
|
15
|
+
//
|
|
16
|
+
// Auth for push: WEBIM_TOKEN (a Bearer access token) or WEBIM_EMAIL +
|
|
17
|
+
// WEBIM_PASSWORD (a normal dashboard sign-in; accounts with 2FA must use
|
|
18
|
+
// WEBIM_TOKEN). The account needs the settings.tenant edit grant — owner or
|
|
19
|
+
// admin roles pass automatically.
|
|
20
|
+
|
|
21
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
22
|
+
import { existsSync } from 'node:fs';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import { pathToFileURL } from 'node:url';
|
|
25
|
+
|
|
26
|
+
const CONFIG_FILE = 'webim-plugin.json';
|
|
27
|
+
|
|
28
|
+
const HELP = `webim-plugin — Web-IM builder plugin CLI
|
|
29
|
+
|
|
30
|
+
Commands:
|
|
31
|
+
init [dir] scaffold a plugin project (webim-plugin.json, src/, README)
|
|
32
|
+
build bundle the entry into dist/plugin.js and validate it
|
|
33
|
+
push build, then upload to the tenant's builder_plugins setting
|
|
34
|
+
dev watch the source; rebuild and push on every change
|
|
35
|
+
|
|
36
|
+
Config (webim-plugin.json):
|
|
37
|
+
{ "entry": "src/index.js", "out": "dist/plugin.js",
|
|
38
|
+
"api": "http://localhost:4100/api", "tenant": "root" }
|
|
39
|
+
|
|
40
|
+
Auth (push/dev): WEBIM_TOKEN, or WEBIM_EMAIL + WEBIM_PASSWORD.`;
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// scaffolding
|
|
44
|
+
|
|
45
|
+
const TEMPLATE_CONFIG = (name) => `${JSON.stringify(
|
|
46
|
+
{ entry: 'src/index.js', out: 'dist/plugin.js', api: 'http://localhost:4100/api', tenant: 'root' },
|
|
47
|
+
null,
|
|
48
|
+
2
|
|
49
|
+
)}\n`;
|
|
50
|
+
|
|
51
|
+
const TEMPLATE_PLUGIN = (id, name) => `// ${name} — a Web-IM builder plugin.
|
|
52
|
+
//
|
|
53
|
+
// One module is the whole plugin. Split code into as many files as you like
|
|
54
|
+
// ("webim-plugin build" bundles them), use TypeScript or JSX if you prefer —
|
|
55
|
+
// only this default export shape is the contract.
|
|
56
|
+
|
|
57
|
+
export default {
|
|
58
|
+
id: '${id}',
|
|
59
|
+
name: '${name}',
|
|
60
|
+
version: '1.0.0',
|
|
61
|
+
blocks: [
|
|
62
|
+
{
|
|
63
|
+
id: 'card',
|
|
64
|
+
label: '${name}',
|
|
65
|
+
// Default prop values — what a freshly dropped block starts with.
|
|
66
|
+
props: { title: 'Hello from ${name}', color: '#6366f1' },
|
|
67
|
+
// Typed fields the builder's Inspector renders for this block.
|
|
68
|
+
// Types: text | number | switch | select | color | image
|
|
69
|
+
settings: [
|
|
70
|
+
{ name: 'title', label: 'Title', type: 'text' },
|
|
71
|
+
{ name: 'color', label: 'Color', type: 'color' }
|
|
72
|
+
],
|
|
73
|
+
/**
|
|
74
|
+
* Renders the block. \`el\` is yours until unmount; \`ctx\` is
|
|
75
|
+
* { tenant, apiUrl, locale, settings }.
|
|
76
|
+
*/
|
|
77
|
+
mount(el, props, ctx) {
|
|
78
|
+
el.innerHTML = '';
|
|
79
|
+
|
|
80
|
+
const card = document.createElement('div');
|
|
81
|
+
|
|
82
|
+
card.style.cssText = 'padding:18px 22px;border-radius:10px;color:#fff;font-weight:600;';
|
|
83
|
+
card.style.background = props.color || '#6366f1';
|
|
84
|
+
card.textContent = props.title || '${name}';
|
|
85
|
+
el.appendChild(card);
|
|
86
|
+
},
|
|
87
|
+
/** Called when props change in the editor; omit to remount instead. */
|
|
88
|
+
update(el, props, ctx) {
|
|
89
|
+
const card = el.firstChild;
|
|
90
|
+
|
|
91
|
+
if (!card) return this.mount(el, props, ctx);
|
|
92
|
+
card.style.background = props.color || '#6366f1';
|
|
93
|
+
card.textContent = props.title || '${name}';
|
|
94
|
+
},
|
|
95
|
+
unmount(el) {}
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
};
|
|
99
|
+
`;
|
|
100
|
+
|
|
101
|
+
const TEMPLATE_README = (id, name) => `# ${name}
|
|
102
|
+
|
|
103
|
+
A Web-IM builder plugin. Develop:
|
|
104
|
+
|
|
105
|
+
\`\`\`bash
|
|
106
|
+
npm install
|
|
107
|
+
npx webim-plugin build # bundle + validate -> dist/plugin.js
|
|
108
|
+
npx webim-plugin push # upload to the tenant in webim-plugin.json
|
|
109
|
+
npx webim-plugin dev # watch + rebuild + push
|
|
110
|
+
\`\`\`
|
|
111
|
+
|
|
112
|
+
Set credentials for push: \`WEBIM_TOKEN\`, or \`WEBIM_EMAIL\` + \`WEBIM_PASSWORD\`.
|
|
113
|
+
`;
|
|
114
|
+
|
|
115
|
+
const TEMPLATE_PACKAGE = (id) => `${JSON.stringify(
|
|
116
|
+
{
|
|
117
|
+
name: id,
|
|
118
|
+
private: true,
|
|
119
|
+
version: '1.0.0',
|
|
120
|
+
type: 'module',
|
|
121
|
+
scripts: { build: 'webim-plugin build', push: 'webim-plugin push', dev: 'webim-plugin dev' },
|
|
122
|
+
devDependencies: { 'webim-plugin': '^0.1.0' }
|
|
123
|
+
},
|
|
124
|
+
null,
|
|
125
|
+
2
|
|
126
|
+
)}\n`;
|
|
127
|
+
|
|
128
|
+
async function init(dirArg) {
|
|
129
|
+
const dir = path.resolve(dirArg ?? '.');
|
|
130
|
+
const id = path
|
|
131
|
+
.basename(dir)
|
|
132
|
+
.toLowerCase()
|
|
133
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
134
|
+
.replace(/(^-|-$)/g, '') || 'my-plugin';
|
|
135
|
+
const name = id
|
|
136
|
+
.split('-')
|
|
137
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
138
|
+
.join(' ');
|
|
139
|
+
|
|
140
|
+
if (existsSync(path.join(dir, CONFIG_FILE))) {
|
|
141
|
+
throw new Error(`${CONFIG_FILE} already exists in ${dir}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
await mkdir(path.join(dir, 'src'), { recursive: true });
|
|
145
|
+
await writeFile(path.join(dir, CONFIG_FILE), TEMPLATE_CONFIG(name));
|
|
146
|
+
await writeFile(path.join(dir, 'src', 'index.js'), TEMPLATE_PLUGIN(id, name));
|
|
147
|
+
await writeFile(path.join(dir, 'README.md'), TEMPLATE_README(id, name));
|
|
148
|
+
|
|
149
|
+
if (!existsSync(path.join(dir, 'package.json'))) {
|
|
150
|
+
await writeFile(path.join(dir, 'package.json'), TEMPLATE_PACKAGE(id));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
console.log(`Scaffolded "${id}" in ${dir}`);
|
|
154
|
+
console.log('Next: edit webim-plugin.json (api/tenant), then `webim-plugin build`.');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
// build + validate
|
|
159
|
+
|
|
160
|
+
async function loadConfig() {
|
|
161
|
+
const file = path.resolve(CONFIG_FILE);
|
|
162
|
+
|
|
163
|
+
if (!existsSync(file)) throw new Error(`No ${CONFIG_FILE} here — run \`webim-plugin init\` first`);
|
|
164
|
+
|
|
165
|
+
const config = JSON.parse(await readFile(file, 'utf8'));
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
entry: config.entry ?? 'src/index.js',
|
|
169
|
+
out: config.out ?? 'dist/plugin.js',
|
|
170
|
+
api: config.api ?? 'http://localhost:4100/api',
|
|
171
|
+
tenant: config.tenant ?? ''
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function bundle(config) {
|
|
176
|
+
const esbuild = await import('esbuild');
|
|
177
|
+
const result = await esbuild.build({
|
|
178
|
+
entryPoints: [config.entry],
|
|
179
|
+
outfile: config.out,
|
|
180
|
+
bundle: true,
|
|
181
|
+
format: 'esm',
|
|
182
|
+
platform: 'browser',
|
|
183
|
+
target: 'es2020',
|
|
184
|
+
minify: false,
|
|
185
|
+
logLevel: 'silent'
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
if (result.errors.length) throw new Error(result.errors.map((e) => e.text).join('\n'));
|
|
189
|
+
|
|
190
|
+
return config.out;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* The same rules the dashboard's packPlugin enforces (keep in sync with
|
|
195
|
+
* ui components/builder/plugins/runtime.js) — failing here beats failing in
|
|
196
|
+
* the manager after an upload.
|
|
197
|
+
*/
|
|
198
|
+
function validatePlugin(plugin) {
|
|
199
|
+
if (!plugin || typeof plugin !== 'object') throw new Error('The module must default-export the plugin object');
|
|
200
|
+
if (typeof plugin.id !== 'string' || !/^[a-z0-9-]+$/.test(plugin.id)) {
|
|
201
|
+
throw new Error('`id` must be a kebab-case slug');
|
|
202
|
+
}
|
|
203
|
+
if (typeof plugin.name !== 'string' || !plugin.name.trim()) throw new Error('`name` is required');
|
|
204
|
+
if (!Array.isArray(plugin.blocks) || !plugin.blocks.length) throw new Error('`blocks` must be a non-empty array');
|
|
205
|
+
|
|
206
|
+
for (const block of plugin.blocks) {
|
|
207
|
+
if (!block || typeof block !== 'object') throw new Error('Every block must be an object');
|
|
208
|
+
if (typeof block.id !== 'string' || !/^[a-z0-9-]+$/.test(block.id)) {
|
|
209
|
+
throw new Error('Every block needs a kebab-case `id`');
|
|
210
|
+
}
|
|
211
|
+
if (typeof block.label !== 'string' || !block.label.trim()) throw new Error(`Block "${block.id}" needs a label`);
|
|
212
|
+
if (typeof block.mount !== 'function') throw new Error(`Block "${block.id}" needs a mount(el, props, ctx) function`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
id: plugin.id,
|
|
217
|
+
name: plugin.name,
|
|
218
|
+
version: typeof plugin.version === 'string' ? plugin.version : '1.0.0',
|
|
219
|
+
blocks: plugin.blocks.map((block) => ({
|
|
220
|
+
id: block.id,
|
|
221
|
+
label: block.label,
|
|
222
|
+
props: block.props && typeof block.props === 'object' ? block.props : {},
|
|
223
|
+
settings: Array.isArray(block.settings)
|
|
224
|
+
? block.settings.filter((field) => field && typeof field.name === 'string')
|
|
225
|
+
: []
|
|
226
|
+
}))
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function build() {
|
|
231
|
+
const config = await loadConfig();
|
|
232
|
+
const out = await bundle(config);
|
|
233
|
+
// Import the BUILT file: what ships is what gets validated. Module-level
|
|
234
|
+
// DOM access would fail right here — keep DOM work inside mount().
|
|
235
|
+
const mod = await import(`${pathToFileURL(path.resolve(out)).href}?v=${Date.now()}`);
|
|
236
|
+
const manifest = validatePlugin(mod?.default);
|
|
237
|
+
|
|
238
|
+
console.log(`Built ${out}`);
|
|
239
|
+
console.log(
|
|
240
|
+
` ${manifest.name} v${manifest.version} (${manifest.id}) — blocks: ${manifest.blocks
|
|
241
|
+
.map((block) => block.id)
|
|
242
|
+
.join(', ')}`
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
return { config, out, manifest };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
// push
|
|
250
|
+
|
|
251
|
+
async function api(config, pathname, { method = 'GET', token, body } = {}) {
|
|
252
|
+
const response = await fetch(`${config.api}${pathname}`, {
|
|
253
|
+
method,
|
|
254
|
+
headers: {
|
|
255
|
+
'content-type': 'application/json',
|
|
256
|
+
...(config.tenant ? { 'x-tenant': config.tenant } : {}),
|
|
257
|
+
...(token ? { authorization: `Bearer ${token}` } : {})
|
|
258
|
+
},
|
|
259
|
+
body: body === undefined ? undefined : JSON.stringify(body)
|
|
260
|
+
});
|
|
261
|
+
const data = await response.json().catch(() => null);
|
|
262
|
+
|
|
263
|
+
if (!response.ok || data?.success === false) {
|
|
264
|
+
const error = data?.error ?? {};
|
|
265
|
+
|
|
266
|
+
throw new Error(`${method} ${pathname} failed: ${error.code ?? response.status} ${error.message ?? ''}`.trim());
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return data?.payload;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function resolveToken(config) {
|
|
273
|
+
if (process.env.WEBIM_TOKEN) return process.env.WEBIM_TOKEN;
|
|
274
|
+
|
|
275
|
+
const email = process.env.WEBIM_EMAIL;
|
|
276
|
+
const password = process.env.WEBIM_PASSWORD;
|
|
277
|
+
|
|
278
|
+
if (!email || !password) {
|
|
279
|
+
throw new Error('Set WEBIM_TOKEN, or WEBIM_EMAIL and WEBIM_PASSWORD, to push');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const session = await api(config, '/auth/login', { method: 'POST', body: { email, password } });
|
|
283
|
+
const token = session?.session?.access?.token;
|
|
284
|
+
|
|
285
|
+
if (!token) throw new Error('Login succeeded but no access token came back');
|
|
286
|
+
|
|
287
|
+
return token;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function push() {
|
|
291
|
+
const { config, out, manifest } = await build();
|
|
292
|
+
const token = await resolveToken(config);
|
|
293
|
+
const code = await readFile(path.resolve(out), 'utf8');
|
|
294
|
+
|
|
295
|
+
// A personal access token is TENANT-BOUND: authed routes write to the
|
|
296
|
+
// token's tenant and ignore X-Tenant. Ask /me who we really are and refuse
|
|
297
|
+
// a mismatch — a silent cross-tenant push is how catalogs get polluted.
|
|
298
|
+
const me = await api(config, '/me', { token });
|
|
299
|
+
const actualTenant = me?.tenant?.slug;
|
|
300
|
+
|
|
301
|
+
if (config.tenant && actualTenant && (actualTenant !== config.tenant)) {
|
|
302
|
+
throw new Error(
|
|
303
|
+
`This credential acts on tenant "${actualTenant}" but webim-plugin.json says "${config.tenant}" — ` +
|
|
304
|
+
'use a token minted in the right tenant, or fix the config'
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Merge over the tenant's existing catalog — pushing one plugin must not
|
|
309
|
+
// wipe the others.
|
|
310
|
+
const rows = await api(config, '/settings/tenant', { token });
|
|
311
|
+
const current = Array.isArray(rows) ? rows.find((row) => row?.key === 'builder_plugins')?.value : null;
|
|
312
|
+
const plugins = current && typeof current.plugins === 'object' ? { ...current.plugins } : {};
|
|
313
|
+
|
|
314
|
+
plugins[manifest.id] = { code, manifest };
|
|
315
|
+
|
|
316
|
+
await api(config, '/settings/builder_plugins', {
|
|
317
|
+
method: 'PUT',
|
|
318
|
+
token,
|
|
319
|
+
body: { value: { plugins }, is_public: true }
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
console.log(`Pushed ${manifest.id} v${manifest.version} to tenant "${actualTenant ?? config.tenant}" (${config.api})`);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ---------------------------------------------------------------------------
|
|
326
|
+
// dev (watch)
|
|
327
|
+
|
|
328
|
+
async function dev() {
|
|
329
|
+
const config = await loadConfig();
|
|
330
|
+
const esbuild = await import('esbuild');
|
|
331
|
+
const context = await esbuild.context({
|
|
332
|
+
entryPoints: [config.entry],
|
|
333
|
+
outfile: config.out,
|
|
334
|
+
bundle: true,
|
|
335
|
+
format: 'esm',
|
|
336
|
+
platform: 'browser',
|
|
337
|
+
target: 'es2020',
|
|
338
|
+
logLevel: 'silent',
|
|
339
|
+
plugins: [
|
|
340
|
+
{
|
|
341
|
+
name: 'webim-push',
|
|
342
|
+
setup(buildHook) {
|
|
343
|
+
buildHook.onEnd(async (result) => {
|
|
344
|
+
if (result.errors.length) {
|
|
345
|
+
console.error(result.errors.map((e) => e.text).join('\n'));
|
|
346
|
+
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
await push();
|
|
352
|
+
} catch (err) {
|
|
353
|
+
console.error(String(err?.message ?? err));
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
]
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
await context.watch();
|
|
362
|
+
console.log(`Watching ${config.entry} — every change rebuilds and pushes to "${config.tenant}". Ctrl-C to stop.`);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ---------------------------------------------------------------------------
|
|
366
|
+
|
|
367
|
+
const [command, argument] = process.argv.slice(2);
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
if ((command === '--version') || (command === '-v')) {
|
|
371
|
+
const pkg = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'));
|
|
372
|
+
|
|
373
|
+
console.log(pkg.version);
|
|
374
|
+
} else if (command === 'init') await init(argument);
|
|
375
|
+
else if (command === 'build') await build();
|
|
376
|
+
else if (command === 'push') await push();
|
|
377
|
+
else if (command === 'dev') await dev();
|
|
378
|
+
else console.log(HELP);
|
|
379
|
+
} catch (err) {
|
|
380
|
+
console.error(`Error: ${err?.message ?? err}`);
|
|
381
|
+
process.exit(1);
|
|
382
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "webim-plugin",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI for Web-IM builder plugins — scaffold, bundle TS/JSX into the single-module standard, validate, and push to a tenant",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Orhan POLAT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"webim-plugin": "./bin/webim-plugin.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"bin"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/morsaken/webim-plugin.git"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/morsaken/webim-plugin#readme",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/morsaken/webim-plugin/issues"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"webim",
|
|
27
|
+
"plugin",
|
|
28
|
+
"cli",
|
|
29
|
+
"builder",
|
|
30
|
+
"cms",
|
|
31
|
+
"esbuild"
|
|
32
|
+
],
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"esbuild": "^0.25.0"
|
|
35
|
+
}
|
|
36
|
+
}
|