@jchat/addon-sdk 1.0.0-alpha.1 → 1.0.0-alpha.11

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/dist/vite.mjs CHANGED
@@ -1,11 +1,28 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { execFileSync } from "node:child_process";
4
3
  import crypto from "node:crypto";
5
4
  //#region src/vite.ts
6
5
  /**
7
- * Vite plugin to configure Jchat addon manifest generation, bundle backend scripts, copy
8
- * migrations, declarative seed data, and zip production build artifacts.
6
+ * Vite plugin for building JChat sandboxed addon extensions.
7
+ *
8
+ * Configures automatic manifest generation (`manifest.json`), bundles server-side API scripts
9
+ * (`src/api.ts`), embeds scoped Tailwind CSS into the frontend bundle, packages database migrations
10
+ * and seed files, copies documentation (`README.md`), signs database-bearing addon manifests, and
11
+ * zips production build artifacts.
12
+ *
13
+ * @example
14
+ * ;```ts
15
+ * import { jchatAddon } from "@jchat/addon-sdk/vite"
16
+ * import manifest from "./src/manifest"
17
+ * import { defineConfig } from "vite"
18
+ *
19
+ * export default defineConfig({
20
+ * plugins: [jchatAddon({ manifest })]
21
+ * })
22
+ * ```
23
+ *
24
+ * @param options - Configuration options for the JChat addon Vite plugin.
25
+ * @returns A Vite {@link Plugin} instance configured for addon compilation.
9
26
  */
10
27
  function jchatAddon(options) {
11
28
  const { manifest: manifestData } = options;
@@ -15,10 +32,45 @@ function jchatAddon(options) {
15
32
  name: "jchat-addon",
16
33
  apply: "build",
17
34
  config(_, env) {
18
- return { define: { "process.env.NODE_ENV": JSON.stringify(env.mode || "production") } };
35
+ const rootDir = process.cwd();
36
+ const uiEntry = path.resolve(rootDir, "src/ui.tsx");
37
+ const hasUi = fs.existsSync(uiEntry);
38
+ return {
39
+ define: {
40
+ global: "window",
41
+ "process.env.NODE_ENV": JSON.stringify(env.mode || "production")
42
+ },
43
+ build: {
44
+ outDir: "build",
45
+ emptyOutDir: true,
46
+ minify: env.mode === "production",
47
+ ...hasUi ? {
48
+ lib: {
49
+ entry: uiEntry,
50
+ name: manifestData.id,
51
+ formats: ["es"],
52
+ fileName: () => "ui.[hash].js"
53
+ },
54
+ rolldownOptions: {
55
+ external: [
56
+ "react",
57
+ "react-dom",
58
+ "react/jsx-runtime",
59
+ "react/jsx-dev-runtime",
60
+ "jotai",
61
+ "react-hot-toast",
62
+ "@tanstack/react-query",
63
+ "@jchat/addon-sdk",
64
+ "@jchat/addon-sdk/ui"
65
+ ],
66
+ output: { format: "esm" }
67
+ }
68
+ } : {}
69
+ }
70
+ };
19
71
  },
20
72
  configResolved(config) {
21
- developmentBuild = config.mode === "development";
73
+ developmentBuild = options.development ?? (config.mode === "development" || process.env.JCHAT_ADDON_DEV === "true" || process.env.NODE_ENV === "development");
22
74
  if (shouldZip === void 0) shouldZip = config.mode === "production";
23
75
  },
24
76
  async writeBundle() {
@@ -41,9 +93,13 @@ function jchatAddon(options) {
41
93
  }
42
94
  };
43
95
  const serverSrcPath = path.resolve(rootDir, "src/api.ts");
44
- const hasServer = fs.existsSync(serverSrcPath);
96
+ const hasServer = await Bun.file(serverSrcPath).exists();
45
97
  if (hasServer) {
46
- execFileSync("bun", createAddonServerBuildArguments(serverSrcPath, path.resolve(buildDir, "api.js"), developmentBuild), { stdio: "inherit" });
98
+ Bun.spawnSync(["bun", ...createAddonServerBuildArguments(serverSrcPath, path.resolve(buildDir, "api.js"), developmentBuild)], { stdio: [
99
+ "inherit",
100
+ "inherit",
101
+ "inherit"
102
+ ] });
47
103
  manifest = {
48
104
  ...manifest,
49
105
  server: {
@@ -59,12 +115,15 @@ function jchatAddon(options) {
59
115
  });
60
116
  for (const relativePath of databaseFiles) {
61
117
  if (path.isAbsolute(relativePath) || relativePath.includes("\\") || relativePath.split("/").includes("..")) throw new Error(`Unsafe addon database file path: ${relativePath}`);
62
- const source = path.resolve(rootDir, relativePath);
63
- if (!fs.existsSync(source) || !fs.statSync(source).isFile()) throw new Error(`Missing addon database file: ${relativePath}`);
64
118
  const destination = path.resolve(buildDir, relativePath);
65
119
  fs.mkdirSync(path.dirname(destination), { recursive: true });
66
- fs.copyFileSync(source, destination);
67
120
  }
121
+ await Promise.all(databaseFiles.map(async (relativePath) => {
122
+ const source = path.resolve(rootDir, relativePath);
123
+ if (!await Bun.file(source).exists() || !fs.statSync(source).isFile()) throw new Error(`Missing addon database file: ${relativePath}`);
124
+ const destination = path.resolve(buildDir, relativePath);
125
+ return Bun.write(destination, Bun.file(source));
126
+ }));
68
127
  if (manifest.server?.migrations?.length || manifest.server?.seeds?.length) {
69
128
  const signingPrivateKey = requireAddonSigningKey(options.signingPrivateKey ?? process.env.JCHAT_ADDON_SIGNING_PRIVATE_KEY, developmentBuild);
70
129
  if (signingPrivateKey) {
@@ -75,36 +134,57 @@ function jchatAddon(options) {
75
134
  ...manifest.server?.seeds ?? []
76
135
  ];
77
136
  if (hasServer) archivePaths.push("api.js");
78
- manifest = signAddonManifest(manifest, archivePaths.map((file) => ({
137
+ const resolvedFiles = await Promise.all(archivePaths.map(async (file) => ({
79
138
  path: file,
80
- contents: file === "manifest.json" ? Buffer.from("") : fs.readFileSync(path.resolve(buildDir, file))
81
- })), signingPrivateKey);
139
+ contents: file === "manifest.json" ? Buffer.from("") : Buffer.from(await Bun.file(path.resolve(buildDir, file)).arrayBuffer())
140
+ })));
141
+ manifest = signAddonManifest(manifest, resolvedFiles, signingPrivateKey);
82
142
  }
83
143
  }
144
+ const readmeSource = path.resolve(rootDir, "README.md");
145
+ if (await Bun.file(readmeSource).exists()) await Bun.write(path.resolve(buildDir, "README.md"), Bun.file(readmeSource));
146
+ const vendorLicensesContent = await generateAddonVendorLicenses(rootDir, manifest.name);
147
+ await Bun.write(path.resolve(buildDir, "vendor-licenses.txt"), vendorLicensesContent);
84
148
  const manifestJson = JSON.stringify(manifest, null, 2);
85
- fs.writeFileSync(path.resolve(buildDir, "manifest.json"), manifestJson);
149
+ await Bun.write(path.resolve(buildDir, "manifest.json"), manifestJson);
86
150
  if (shouldZip) {
87
151
  const zipName = `${manifest.id}-${pkg.version}.zip`;
88
152
  const filesToZip = ["manifest.json", bundleFile];
89
- if (fs.existsSync(path.resolve(buildDir, "api.js"))) filesToZip.push("api.js");
153
+ if (await Bun.file(path.resolve(buildDir, "api.js")).exists()) filesToZip.push("api.js");
154
+ if (await Bun.file(path.resolve(buildDir, "README.md")).exists()) filesToZip.push("README.md");
155
+ if (await Bun.file(path.resolve(buildDir, "vendor-licenses.txt")).exists()) filesToZip.push("vendor-licenses.txt");
90
156
  const migrationsDest = path.resolve(buildDir, "migrations");
91
157
  if (fs.existsSync(migrationsDest)) filesToZip.push("migrations");
92
158
  const seedsDest = path.resolve(buildDir, "seeds");
93
159
  if (fs.existsSync(seedsDest)) filesToZip.push("seeds");
94
- execFileSync("tar", [
160
+ Bun.spawnSync([
161
+ "tar",
95
162
  "-a",
96
163
  "-cf",
97
164
  zipName,
98
165
  ...filesToZip
99
166
  ], {
100
167
  cwd: buildDir,
101
- stdio: "inherit"
168
+ stdio: [
169
+ "inherit",
170
+ "inherit",
171
+ "inherit"
172
+ ]
102
173
  });
103
174
  console.log(`Build Complete: ${manifest.id} v${pkg.version} → ${zipName}`);
104
175
  }
105
176
  }
106
177
  };
107
178
  }
179
+ /**
180
+ * Generates CLI argument parameters for bundling server-side Elysia API scripts (`src/api.ts`) via
181
+ * Bun.
182
+ *
183
+ * @param entryPath - Absolute file path to the server entry script (`src/api.ts`).
184
+ * @param outputPath - Absolute destination file path for the bundled output (`build/api.js`).
185
+ * @param developmentBuild - Indicates whether to enable external sourcemaps or minification.
186
+ * @returns Array of command line arguments for running `bun build`.
187
+ */
108
188
  function createAddonServerBuildArguments(entryPath, outputPath, developmentBuild) {
109
189
  return [
110
190
  "build",
@@ -112,9 +192,17 @@ function createAddonServerBuildArguments(entryPath, outputPath, developmentBuild
112
192
  "--target=bun",
113
193
  "--format=esm",
114
194
  `--outfile=${outputPath}`,
115
- developmentBuild ? "--sourcemap=external" : "--minify"
195
+ developmentBuild ? "--sourcemap=inline" : "--minify"
116
196
  ];
117
197
  }
198
+ /**
199
+ * Cryptographically signs an addon manifest using RSA-SHA256 over all archive file contents.
200
+ *
201
+ * @param manifest - Unsigned or base addon manifest.
202
+ * @param files - List of archive file paths and raw contents to digest.
203
+ * @param privateKey - RSA private key in PEM format.
204
+ * @returns Updated {@link AddonManifest} containing an RSA signature block.
205
+ */
118
206
  function signAddonManifest(manifest, files, privateKey) {
119
207
  if (!manifest.publisher?.id) throw new Error("Signed addons require manifest.publisher.id");
120
208
  const unsigned = {
@@ -126,7 +214,7 @@ function signAddonManifest(manifest, files, privateKey) {
126
214
  }
127
215
  };
128
216
  const hash = crypto.createHash("sha256");
129
- for (const file of [...files].sort((a, b) => a.path.localeCompare(b.path))) {
217
+ for (const file of [...files].toSorted((a, b) => a.path.localeCompare(b.path))) {
130
218
  hash.update(file.path);
131
219
  hash.update("\0");
132
220
  hash.update(file.path === "manifest.json" ? stableStringify(unsigned) : file.contents);
@@ -142,6 +230,14 @@ function signAddonManifest(manifest, files, privateKey) {
142
230
  }
143
231
  };
144
232
  }
233
+ /**
234
+ * Resolves the required RSA signing key for database-bearing addons.
235
+ *
236
+ * @param signingPrivateKey - Provided signing key or undefined.
237
+ * @param developmentBuild - Whether the current build is targeting development mode.
238
+ * @returns Resolved signing key string or undefined (in dev builds).
239
+ * @throws {@link Error} If building a database-bearing addon in production without a signing key.
240
+ */
145
241
  function requireAddonSigningKey(signingPrivateKey, developmentBuild) {
146
242
  if (signingPrivateKey) return signingPrivateKey;
147
243
  if (developmentBuild) return void 0;
@@ -179,13 +275,67 @@ function embedCssIntoBundle(buildDir, addonId) {
179
275
  fs.unlinkSync(cssPath);
180
276
  } catch {}
181
277
  }
278
+ function sortValue(child) {
279
+ if (Array.isArray(child)) return child.map(sortValue);
280
+ if (child && typeof child === "object") return Object.fromEntries(Object.entries(child).toSorted(([left], [right]) => left.localeCompare(right)).map(([key, nested]) => [key, sortValue(nested)]));
281
+ return child;
282
+ }
182
283
  function stableStringify(value) {
183
- const sortValue = (child) => {
184
- if (Array.isArray(child)) return child.map(sortValue);
185
- if (child && typeof child === "object") return Object.fromEntries(Object.entries(child).sort(([left], [right]) => left.localeCompare(right)).map(([key, nested]) => [key, sortValue(nested)]));
186
- return child;
187
- };
188
284
  return JSON.stringify(sortValue(value));
189
285
  }
286
+ async function generateAddonVendorLicenses(addonDir, addonName) {
287
+ const title = addonName ? `${addonName} Addon` : "JChat Addon";
288
+ try {
289
+ const proc = Bun.spawn([
290
+ "bun",
291
+ "pm",
292
+ "licenses",
293
+ "--json",
294
+ "--prod"
295
+ ], {
296
+ cwd: addonDir,
297
+ stdout: "pipe",
298
+ stderr: "pipe"
299
+ });
300
+ const output = await new Response(proc.stdout).text();
301
+ await proc.exited;
302
+ const data = JSON.parse(output);
303
+ const lines = [
304
+ "================================================================================",
305
+ `${title} - Third-Party Vendor Licenses & Open Source Attribution`,
306
+ "================================================================================",
307
+ "This distribution incorporates open-source libraries and software components.",
308
+ "Their licenses and attributions are listed below.",
309
+ "================================================================================",
310
+ ""
311
+ ];
312
+ let totalPackages = 0;
313
+ const sortedCategories = Object.keys(data).toSorted((a, b) => a.localeCompare(b));
314
+ for (const licenseCategory of sortedCategories) {
315
+ const packages = data[licenseCategory];
316
+ if (!packages || packages.length === 0) continue;
317
+ lines.push(`\n## License: ${licenseCategory} (${packages.length} packages)\n`);
318
+ for (const pkg of packages) {
319
+ totalPackages++;
320
+ lines.push(`- Package: ${pkg.name}@${pkg.versions.join(", ")}`);
321
+ if (pkg.description) lines.push(` Description: ${pkg.description}`);
322
+ if (pkg.author) lines.push(` Author: ${pkg.author}`);
323
+ if (pkg.homepage) lines.push(` Homepage: ${pkg.homepage}`);
324
+ }
325
+ }
326
+ lines.splice(5, 0, `Total Open-Source Dependencies: ${totalPackages}\n`);
327
+ return lines.join("\n") + "\n";
328
+ } catch {
329
+ return [
330
+ "================================================================================",
331
+ `${title} - Third-Party Vendor Licenses & Open Source Attribution`,
332
+ "================================================================================",
333
+ "This distribution incorporates open-source libraries and software components.",
334
+ "See individual package.json files and dependencies for specific license details.",
335
+ "================================================================================",
336
+ ""
337
+ ].join("\n");
338
+ }
339
+ }
190
340
  //#endregion
191
341
  export { createAddonServerBuildArguments, jchatAddon, requireAddonSigningKey, signAddonManifest };
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@jchat/addon-sdk",
3
- "version": "1.0.0-alpha.1",
3
+ "version": "1.0.0-alpha.11",
4
4
  "private": false,
5
+ "description": "JChat Addon SDK for building JChat addons.",
5
6
  "keywords": [
6
7
  "addon",
7
8
  "chat",
@@ -11,15 +12,18 @@
11
12
  "sdk",
12
13
  "vite"
13
14
  ],
14
- "license": "Apache-2.0",
15
+ "license": "SEE LICENSE IN LICENSE",
15
16
  "repository": {
16
17
  "type": "git",
17
18
  "url": "git+https://github.com/mjjabarullah/jchat.git",
18
19
  "directory": "packages/addon-sdk"
19
20
  },
21
+ "bin": {
22
+ "jchat-addon": "./dist/cli.mjs"
23
+ },
20
24
  "files": [
21
25
  "dist",
22
- "src",
26
+ "tsconfig.addon.json",
23
27
  "README.md",
24
28
  "CHANGELOG.md"
25
29
  ],
@@ -31,6 +35,10 @@
31
35
  "types": "./dist/index.d.mts",
32
36
  "import": "./dist/index.mjs"
33
37
  },
38
+ "./cli": {
39
+ "types": "./dist/cli.d.mts",
40
+ "import": "./dist/cli.mjs"
41
+ },
34
42
  "./diagnostics": {
35
43
  "types": "./dist/diagnostics.d.mts",
36
44
  "import": "./dist/diagnostics.mjs"
@@ -38,20 +46,36 @@
38
46
  "./vite": {
39
47
  "types": "./dist/vite.d.mts",
40
48
  "import": "./dist/vite.mjs"
41
- }
49
+ },
50
+ "./ui": {
51
+ "types": "./dist/ui.d.mts",
52
+ "import": "./dist/ui.mjs"
53
+ },
54
+ "./ui/tailwind.css": {
55
+ "style": "./src/tailwind.css",
56
+ "import": "./src/tailwind.css",
57
+ "default": "./src/tailwind.css"
58
+ },
59
+ "./tsconfig.json": "./tsconfig.addon.json"
42
60
  },
43
61
  "publishConfig": {
44
62
  "access": "public"
45
63
  },
46
64
  "scripts": {
47
65
  "build": "tsdown",
48
- "lint": "oxlint . --config=../../oxlint.json",
66
+ "lint": "oxlint .",
49
67
  "format": "oxfmt . --write",
68
+ "test": "bun test",
50
69
  "typecheck": "tsc --noEmit",
51
70
  "prepack": "bun run build"
52
71
  },
72
+ "dependencies": {},
53
73
  "devDependencies": {
54
74
  "@jchat/icon": "workspace:*",
75
+ "@jchat/ui": "workspace:*",
76
+ "@tailwindcss/vite": "catalog:",
77
+ "@tanstack/react-query": "catalog:",
78
+ "@types/bun": "catalog:",
55
79
  "@types/react": "catalog:",
56
80
  "elysia": "catalog:",
57
81
  "kysely": "^catalog:",
@@ -59,6 +83,8 @@
59
83
  "vite": "catalog:"
60
84
  },
61
85
  "peerDependencies": {
86
+ "@tailwindcss/vite": "catalog:",
87
+ "@tanstack/react-query": "catalog:",
62
88
  "elysia": "catalog:",
63
89
  "kysely": "^catalog:",
64
90
  "react": "catalog:",
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "display": "JChat Addon Base",
4
+ "compilerOptions": {
5
+ "target": "ESNext",
6
+ "module": "ESNext",
7
+ "moduleResolution": "bundler",
8
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
9
+ "jsx": "react-jsx",
10
+ "strict": true,
11
+ "esModuleInterop": true,
12
+ "verbatimModuleSyntax": true,
13
+ "resolveJsonModule": true,
14
+ "skipLibCheck": true,
15
+ "isolatedModules": true,
16
+ "noEmit": true,
17
+ "types": ["bun", "vite/client"]
18
+ },
19
+ "exclude": ["build", "dist", "node_modules"]
20
+ }
@@ -1,127 +0,0 @@
1
- export type AddonPermission = string;
2
- /**
3
- * Defines a single configuration field for an addon's settings panel. These are shown to site
4
- * admins in the Jchat dashboard.
5
- */
6
- export interface ConfigSchemaField {
7
- /** Unique key used to store and retrieve the value in `getConfig` */
8
- key: string;
9
- /** Data type for validation and UI rendering */
10
- type: "string" | "number" | "boolean";
11
- /** Human-readable label shown in the settings UI */
12
- label: string;
13
- /** If true, the admin must provide a value before saving */
14
- required?: boolean;
15
- /** Whether the value is sensitive (encrypted at rest and masked in the UI) */
16
- secret?: boolean;
17
- /** Default value pre-filled in the settings form for new installations */
18
- default?: string | number | boolean;
19
- }
20
- /** Definition of a custom permission introduced by an addon. */
21
- export interface AddonPermissionDefinition {
22
- /** Permission identifier (recommended: `addon.<id>.<action>`) */
23
- name: string;
24
- /** Short, human-readable name for the permission */
25
- label: string;
26
- /** Detailed description of what the permission grants (shown in Rank editor) */
27
- description: string;
28
- }
29
- /** Valid locations where an addon can be rendered within the Jchat layout. */
30
- export declare const MOUNT_POINTS: {
31
- /** Top of the main chat column */
32
- readonly CHAT_HEADER: "chat-header";
33
- /** Below the main chat header, above messages */
34
- readonly CHAT_TOOLBAR: "chat-toolbar";
35
- /** The persistent right-side sidebar */
36
- readonly CHAT_SIDEBAR: "chat-sidebar";
37
- /** Top of the left sidebar (room list) */
38
- readonly SIDEBAR_TOP: "sidebar-top";
39
- /** Bottom of the left sidebar */
40
- readonly SIDEBAR_BOTTOM: "sidebar-bottom";
41
- /** Action list shown when clicking a user's avatar */
42
- readonly USER_POPOVER_ACTIONS: "user-popover-actions";
43
- /** Menu shown when right-clicking a message */
44
- readonly MESSAGE_CONTEXT_MENU: "message-context-menu";
45
- /** Floating overlay rendered on top of the message stream */
46
- readonly MESSAGE_FEED_OVERLAY: "message-feed-overlay";
47
- /** Main admin dashboard area */
48
- readonly ADMIN_PANEL: "admin-panel";
49
- /** User or site settings dashboard area */
50
- readonly SETTINGS_PANEL: "settings-panel";
51
- /** The 'plus' (+) menu in the message composer */
52
- readonly CHAT_PLUS_MENU: "chat-plus-menu";
53
- /** Custom tab in the emoji/gif selection panel */
54
- readonly EMOJI_PICKER_GIF_TAB: "emoji-picker-gif-tab";
55
- /** Full-screen or modal dialog specifically for TubeScan */
56
- readonly TUBESCAN_DIALOG: "tubescan-dialog";
57
- };
58
- /** Union type of all valid addon mount points. */
59
- export type MountPoint = (typeof MOUNT_POINTS)[keyof typeof MOUNT_POINTS];
60
- /**
61
- * The `manifest.json` schema required for every Jchat addon. This file describes how the addon is
62
- * identified and mounted.
63
- */
64
- export interface AddonManifest {
65
- /** Unique kebab-case addon identifier (must match the folder name) */
66
- id: string;
67
- /** Human-readable addon name */
68
- name: string;
69
- /** Semver version string (e.g. `1.2.0`) */
70
- version: string;
71
- /** Brief summary of what the addon does */
72
- description?: string;
73
- /** Author name, organization, or GitHub handle */
74
- author?: string;
75
- /** Support or documentation URL */
76
- homepage?: string;
77
- /** SPDX license identifier (e.g. `MIT`) */
78
- license?: string;
79
- /** Host and SDK versions this directly executed plugin was built and tested against */
80
- compatibility: {
81
- /** Supported JChat versions as a semantic-version range, for example `^1.0.0` */
82
- jchat: string;
83
- /** Supported `@jchat/addon-sdk` versions as a semantic-version range */
84
- addonSdk: string;
85
- };
86
- /** Identity of the trusted publisher that signed this package. Required when migrations exist */
87
- publisher?: {
88
- id: string;
89
- };
90
- /** Signature over the canonical archive digest. Required when migrations exist */
91
- signature?: {
92
- algorithm: "rsa-sha256";
93
- digest: string;
94
- value: string;
95
- };
96
- /** Configuration for the React-based frontend bundle */
97
- frontend?: {
98
- /** Relative path to the pre-compiled ESM bundle (created by Vite) */
99
- bundle: string;
100
- /** List of UI locations where this addon should be active */
101
- mountPoints: MountPoint[];
102
- /** Optional path to a specific UI for the settings panel */
103
- settings?: string;
104
- /** Additional shared packages this addon imports from the host */
105
- externals?: string[];
106
- };
107
- /** Configuration for the server-side API bundle */
108
- server?: {
109
- /** Relative path to the pre-compiled server bundle */
110
- bundle?: string;
111
- /** List of SQL files to run (in order) to set up the addon's database tables */
112
- migrations?: string[];
113
- /** List of declarative JSON master-data files to apply once, in order */
114
- seeds?: string[];
115
- };
116
- /**
117
- * The minimum permission required to see or use this addon. If provided, the host gates
118
- * visibility in the UI and access to all server routes.
119
- */
120
- accessPermission?: AddonPermission;
121
- /** Full list of all permissions used by this addon for informational purposes */
122
- permissions?: AddonPermission[];
123
- /** Custom permissions that should be registered with the host application on installation */
124
- permissionDefinitions?: AddonPermissionDefinition[];
125
- /** Settings form structure shown to site admins. Values are stored encrypted per site */
126
- configSchema?: ConfigSchemaField[];
127
- }
package/src/contracts.ts DELETED
@@ -1,132 +0,0 @@
1
- export type AddonPermission = string
2
-
3
- /**
4
- * Defines a single configuration field for an addon's settings panel. These are shown to site
5
- * admins in the Jchat dashboard.
6
- */
7
- export interface ConfigSchemaField {
8
- /** Unique key used to store and retrieve the value in `getConfig` */
9
- key: string
10
- /** Data type for validation and UI rendering */
11
- type: "string" | "number" | "boolean"
12
- /** Human-readable label shown in the settings UI */
13
- label: string
14
- /** If true, the admin must provide a value before saving */
15
- required?: boolean
16
- /** Whether the value is sensitive (encrypted at rest and masked in the UI) */
17
- secret?: boolean
18
- /** Default value pre-filled in the settings form for new installations */
19
- default?: string | number | boolean
20
- }
21
-
22
- /** Definition of a custom permission introduced by an addon. */
23
- export interface AddonPermissionDefinition {
24
- /** Permission identifier (recommended: `addon.<id>.<action>`) */
25
- name: string
26
- /** Short, human-readable name for the permission */
27
- label: string
28
- /** Detailed description of what the permission grants (shown in Rank editor) */
29
- description: string
30
- }
31
-
32
- /** Valid locations where an addon can be rendered within the Jchat layout. */
33
- export const MOUNT_POINTS = {
34
- /** Top of the main chat column */
35
- CHAT_HEADER: "chat-header",
36
- /** Below the main chat header, above messages */
37
- CHAT_TOOLBAR: "chat-toolbar",
38
- /** The persistent right-side sidebar */
39
- CHAT_SIDEBAR: "chat-sidebar",
40
- /** Top of the left sidebar (room list) */
41
- SIDEBAR_TOP: "sidebar-top",
42
- /** Bottom of the left sidebar */
43
- SIDEBAR_BOTTOM: "sidebar-bottom",
44
- /** Action list shown when clicking a user's avatar */
45
- USER_POPOVER_ACTIONS: "user-popover-actions",
46
- /** Menu shown when right-clicking a message */
47
- MESSAGE_CONTEXT_MENU: "message-context-menu",
48
- /** Floating overlay rendered on top of the message stream */
49
- MESSAGE_FEED_OVERLAY: "message-feed-overlay",
50
- /** Main admin dashboard area */
51
- ADMIN_PANEL: "admin-panel",
52
- /** User or site settings dashboard area */
53
- SETTINGS_PANEL: "settings-panel",
54
- /** The 'plus' (+) menu in the message composer */
55
- CHAT_PLUS_MENU: "chat-plus-menu",
56
- /** Custom tab in the emoji/gif selection panel */
57
- EMOJI_PICKER_GIF_TAB: "emoji-picker-gif-tab",
58
- /** Full-screen or modal dialog specifically for TubeScan */
59
- TUBESCAN_DIALOG: "tubescan-dialog"
60
- } as const
61
-
62
- /** Union type of all valid addon mount points. */
63
- export type MountPoint = (typeof MOUNT_POINTS)[keyof typeof MOUNT_POINTS]
64
-
65
- /**
66
- * The `manifest.json` schema required for every Jchat addon. This file describes how the addon is
67
- * identified and mounted.
68
- */
69
- export interface AddonManifest {
70
- /** Unique kebab-case addon identifier (must match the folder name) */
71
- id: string
72
- /** Human-readable addon name */
73
- name: string
74
- /** Semver version string (e.g. `1.2.0`) */
75
- version: string
76
- /** Brief summary of what the addon does */
77
- description?: string
78
- /** Author name, organization, or GitHub handle */
79
- author?: string
80
- /** Support or documentation URL */
81
- homepage?: string
82
- /** SPDX license identifier (e.g. `MIT`) */
83
- license?: string
84
- /** Host and SDK versions this directly executed plugin was built and tested against */
85
- compatibility: {
86
- /** Supported JChat versions as a semantic-version range, for example `^1.0.0` */
87
- jchat: string
88
- /** Supported `@jchat/addon-sdk` versions as a semantic-version range */
89
- addonSdk: string
90
- }
91
- /** Identity of the trusted publisher that signed this package. Required when migrations exist */
92
- publisher?: {
93
- id: string
94
- }
95
- /** Signature over the canonical archive digest. Required when migrations exist */
96
- signature?: {
97
- algorithm: "rsa-sha256"
98
- digest: string
99
- value: string
100
- }
101
- /** Configuration for the React-based frontend bundle */
102
- frontend?: {
103
- /** Relative path to the pre-compiled ESM bundle (created by Vite) */
104
- bundle: string
105
- /** List of UI locations where this addon should be active */
106
- mountPoints: MountPoint[]
107
- /** Optional path to a specific UI for the settings panel */
108
- settings?: string
109
- /** Additional shared packages this addon imports from the host */
110
- externals?: string[]
111
- }
112
- /** Configuration for the server-side API bundle */
113
- server?: {
114
- /** Relative path to the pre-compiled server bundle */
115
- bundle?: string
116
- /** List of SQL files to run (in order) to set up the addon's database tables */
117
- migrations?: string[]
118
- /** List of declarative JSON master-data files to apply once, in order */
119
- seeds?: string[]
120
- }
121
- /**
122
- * The minimum permission required to see or use this addon. If provided, the host gates
123
- * visibility in the UI and access to all server routes.
124
- */
125
- accessPermission?: AddonPermission
126
- /** Full list of all permissions used by this addon for informational purposes */
127
- permissions?: AddonPermission[]
128
- /** Custom permissions that should be registered with the host application on installation */
129
- permissionDefinitions?: AddonPermissionDefinition[]
130
- /** Settings form structure shown to site admins. Values are stored encrypted per site */
131
- configSchema?: ConfigSchemaField[]
132
- }
@@ -1,19 +0,0 @@
1
- import type { AddonManifest } from "./contracts";
2
- export type AddonDiagnosticStatus = "pass" | "warning" | "error";
3
- export interface AddonPackageDiagnostic {
4
- id: string;
5
- label: string;
6
- status: AddonDiagnosticStatus;
7
- message: string;
8
- }
9
- export interface AddonPackageDiagnosticOptions {
10
- buildDir: string;
11
- manifest: AddonManifest;
12
- jchatVersion: string;
13
- addonSdkVersion: string;
14
- productionPackage?: boolean;
15
- maxBytes?: number;
16
- }
17
- export declare function createCompatibleMajorRange(version: string): string;
18
- export declare function diagnoseAddonPackage(options: AddonPackageDiagnosticOptions): AddonPackageDiagnostic[];
19
- export declare function hasAddonDiagnosticErrors(diagnostics: readonly AddonPackageDiagnostic[]): boolean;