@jchat/addon-sdk 1.0.0-alpha.2 → 1.0.0-alpha.4
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/CHANGELOG.md +13 -0
- package/README.md +24 -0
- package/dist/diagnostics.d.mts +47 -0
- package/dist/diagnostics.mjs +65 -0
- package/dist/index.d.mts +15 -1
- package/dist/index.mjs +20 -1
- package/dist/vite.d.mts +55 -2
- package/dist/vite.mjs +49 -3
- package/package.json +2 -3
- package/src/contracts.d.ts +0 -127
- package/src/contracts.ts +0 -132
- package/src/diagnostics.d.ts +0 -19
- package/src/diagnostics.test.ts +0 -106
- package/src/diagnostics.ts +0 -182
- package/src/index.d.ts +0 -249
- package/src/index.test.ts +0 -35
- package/src/index.ts +0 -299
- package/src/vite.test.ts +0 -62
- package/src/vite.ts +0 -289
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
|
-
}
|
package/src/diagnostics.d.ts
DELETED
|
@@ -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;
|
package/src/diagnostics.test.ts
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test"
|
|
2
|
-
import * as fs from "node:fs"
|
|
3
|
-
import * as os from "node:os"
|
|
4
|
-
import * as path from "node:path"
|
|
5
|
-
import {
|
|
6
|
-
createCompatibleMajorRange,
|
|
7
|
-
diagnoseAddonPackage,
|
|
8
|
-
hasAddonDiagnosticErrors
|
|
9
|
-
} from "./diagnostics"
|
|
10
|
-
|
|
11
|
-
describe("addon package diagnostics", () => {
|
|
12
|
-
test("accepts compatible builds with every declared file", () => {
|
|
13
|
-
const buildDir = fs.mkdtempSync(path.join(os.tmpdir(), "jchat-addon-diagnostics-"))
|
|
14
|
-
fs.writeFileSync(path.join(buildDir, "ui.js"), "export default function Addon() {}")
|
|
15
|
-
fs.writeFileSync(path.join(buildDir, "api.js"), "export default () => {}")
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
const diagnostics = diagnoseAddonPackage({
|
|
19
|
-
buildDir,
|
|
20
|
-
manifest: {
|
|
21
|
-
id: "sample-addon",
|
|
22
|
-
name: "Sample addon",
|
|
23
|
-
version: "1.0.0",
|
|
24
|
-
compatibility: {
|
|
25
|
-
jchat: ">=1.0.0-alpha.0 <2.0.0",
|
|
26
|
-
addonSdk: "^1.0.0"
|
|
27
|
-
},
|
|
28
|
-
frontend: { bundle: "./ui.js", mountPoints: ["chat-toolbar"] },
|
|
29
|
-
server: { bundle: "./api.js" }
|
|
30
|
-
},
|
|
31
|
-
jchatVersion: "1.0.0-alpha.0",
|
|
32
|
-
addonSdkVersion: "1.0.0"
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
expect(hasAddonDiagnosticErrors(diagnostics)).toBe(false)
|
|
36
|
-
expect(diagnostics.find(({ id }) => id === "jchat")?.status).toBe("pass")
|
|
37
|
-
expect(diagnostics.find(({ id }) => id === "server-bundle")?.status).toBe("pass")
|
|
38
|
-
} finally {
|
|
39
|
-
fs.rmSync(buildDir, { recursive: true })
|
|
40
|
-
}
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
test("reports incompatible versions, missing files, and unsafe references", () => {
|
|
44
|
-
const buildDir = fs.mkdtempSync(path.join(os.tmpdir(), "jchat-addon-diagnostics-"))
|
|
45
|
-
try {
|
|
46
|
-
const diagnostics = diagnoseAddonPackage({
|
|
47
|
-
buildDir,
|
|
48
|
-
manifest: {
|
|
49
|
-
id: "sample-addon",
|
|
50
|
-
name: "Sample addon",
|
|
51
|
-
version: "1.0.0",
|
|
52
|
-
compatibility: { jchat: "^2.0.0", addonSdk: "^1.0.0" },
|
|
53
|
-
frontend: { bundle: "../ui.js", mountPoints: [] },
|
|
54
|
-
server: { bundle: "./missing-api.js" }
|
|
55
|
-
},
|
|
56
|
-
jchatVersion: "1.0.0-alpha.0",
|
|
57
|
-
addonSdkVersion: "1.0.0"
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
expect(hasAddonDiagnosticErrors(diagnostics)).toBe(true)
|
|
61
|
-
expect(diagnostics.find(({ id }) => id === "jchat")?.status).toBe("error")
|
|
62
|
-
expect(diagnostics.find(({ id }) => id === "frontend-bundle")?.status).toBe("error")
|
|
63
|
-
expect(diagnostics.find(({ id }) => id === "server-bundle")?.status).toBe("error")
|
|
64
|
-
} finally {
|
|
65
|
-
fs.rmSync(buildDir, { recursive: true })
|
|
66
|
-
}
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
test("creates a prerelease-compatible range for the current major", () => {
|
|
70
|
-
expect(createCompatibleMajorRange("1.0.0-alpha.0")).toBe(">=1.0.0-alpha.0 <2.0.0")
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
test("rejects malformed versions using Bun semver validation", () => {
|
|
74
|
-
expect(() => createCompatibleMajorRange("1.invalid.0")).toThrow(
|
|
75
|
-
"Invalid semantic version: 1.invalid.0"
|
|
76
|
-
)
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
test("measures the distributable archive for production package size", () => {
|
|
80
|
-
const buildDir = fs.mkdtempSync(path.join(os.tmpdir(), "jchat-addon-diagnostics-"))
|
|
81
|
-
fs.writeFileSync(path.join(buildDir, "ui.js"), "unpacked content is intentionally larger")
|
|
82
|
-
fs.writeFileSync(path.join(buildDir, "sample-addon-1.0.0.zip"), "zip")
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
const diagnostics = diagnoseAddonPackage({
|
|
86
|
-
buildDir,
|
|
87
|
-
manifest: {
|
|
88
|
-
id: "sample-addon",
|
|
89
|
-
name: "Sample addon",
|
|
90
|
-
version: "1.0.0",
|
|
91
|
-
compatibility: { jchat: "^1.0.0", addonSdk: "^1.0.0" },
|
|
92
|
-
frontend: { bundle: "./ui.js", mountPoints: ["chat-toolbar"] }
|
|
93
|
-
},
|
|
94
|
-
jchatVersion: "1.0.0",
|
|
95
|
-
addonSdkVersion: "1.0.0",
|
|
96
|
-
productionPackage: true,
|
|
97
|
-
maxBytes: 3
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
expect(diagnostics.find(({ id }) => id === "package-size")?.status).toBe("pass")
|
|
101
|
-
expect(diagnostics.find(({ id }) => id === "production-archive")?.status).toBe("pass")
|
|
102
|
-
} finally {
|
|
103
|
-
fs.rmSync(buildDir, { recursive: true })
|
|
104
|
-
}
|
|
105
|
-
})
|
|
106
|
-
})
|
package/src/diagnostics.ts
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs"
|
|
2
|
-
import * as path from "node:path"
|
|
3
|
-
import type { AddonManifest } from "./contracts"
|
|
4
|
-
|
|
5
|
-
export type AddonDiagnosticStatus = "pass" | "warning" | "error"
|
|
6
|
-
|
|
7
|
-
export interface AddonPackageDiagnostic {
|
|
8
|
-
id: string
|
|
9
|
-
label: string
|
|
10
|
-
status: AddonDiagnosticStatus
|
|
11
|
-
message: string
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface AddonPackageDiagnosticOptions {
|
|
15
|
-
buildDir: string
|
|
16
|
-
manifest: AddonManifest
|
|
17
|
-
jchatVersion: string
|
|
18
|
-
addonSdkVersion: string
|
|
19
|
-
productionPackage?: boolean
|
|
20
|
-
maxBytes?: number
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function createCompatibleMajorRange(version: string): string {
|
|
24
|
-
try {
|
|
25
|
-
Bun.semver.order(version, version)
|
|
26
|
-
} catch {
|
|
27
|
-
throw new Error(`Invalid semantic version: ${version}`)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const majorComponent = /^(?:v|=)?(\d+)\./.exec(version)?.[1]
|
|
31
|
-
const major = majorComponent ? Number(majorComponent) : Number.NaN
|
|
32
|
-
if (!Number.isSafeInteger(major)) throw new Error(`Invalid semantic version: ${version}`)
|
|
33
|
-
|
|
34
|
-
return `>=${version} <${major + 1}.0.0`
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function diagnoseAddonPackage(
|
|
38
|
-
options: AddonPackageDiagnosticOptions
|
|
39
|
-
): AddonPackageDiagnostic[] {
|
|
40
|
-
const { buildDir, manifest } = options
|
|
41
|
-
const diagnostics: AddonPackageDiagnostic[] = []
|
|
42
|
-
diagnostics.push(
|
|
43
|
-
compatibilityDiagnostic(
|
|
44
|
-
"jchat",
|
|
45
|
-
"JChat compatibility",
|
|
46
|
-
options.jchatVersion,
|
|
47
|
-
manifest.compatibility?.jchat
|
|
48
|
-
),
|
|
49
|
-
compatibilityDiagnostic(
|
|
50
|
-
"addon-sdk",
|
|
51
|
-
"Addon SDK compatibility",
|
|
52
|
-
options.addonSdkVersion,
|
|
53
|
-
manifest.compatibility?.addonSdk
|
|
54
|
-
)
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
const references = [
|
|
58
|
-
["frontend-bundle", "Frontend bundle", manifest.frontend?.bundle],
|
|
59
|
-
["server-bundle", "Server bundle", manifest.server?.bundle],
|
|
60
|
-
...(manifest.server?.migrations ?? []).map(
|
|
61
|
-
(file) => [`migration:${file}`, `Migration ${file}`, file] as const
|
|
62
|
-
),
|
|
63
|
-
...(manifest.server?.seeds ?? []).map((file) => [`seed:${file}`, `Seed ${file}`, file] as const)
|
|
64
|
-
] as const
|
|
65
|
-
|
|
66
|
-
for (const [id, label, reference] of references) {
|
|
67
|
-
if (!reference) continue
|
|
68
|
-
const file = resolveBuildFile(buildDir, reference)
|
|
69
|
-
diagnostics.push({
|
|
70
|
-
id,
|
|
71
|
-
label,
|
|
72
|
-
status: file && fs.existsSync(file) && fs.statSync(file).isFile() ? "pass" : "error",
|
|
73
|
-
message:
|
|
74
|
-
file && fs.existsSync(file) && fs.statSync(file).isFile()
|
|
75
|
-
? reference
|
|
76
|
-
: `Missing or unsafe build file: ${reference}`
|
|
77
|
-
})
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const files = listRegularFiles(buildDir)
|
|
81
|
-
const productionArchive = path.join(buildDir, `${manifest.id}-${manifest.version}.zip`)
|
|
82
|
-
const hasProductionArchive =
|
|
83
|
-
fs.existsSync(productionArchive) && fs.statSync(productionArchive).isFile()
|
|
84
|
-
const totalBytes =
|
|
85
|
-
options.productionPackage && hasProductionArchive
|
|
86
|
-
? fs.statSync(productionArchive).size
|
|
87
|
-
: files
|
|
88
|
-
.filter((file) => file !== productionArchive)
|
|
89
|
-
.reduce((total, file) => total + fs.statSync(file).size, 0)
|
|
90
|
-
const maxBytes = options.maxBytes ?? 25 * 1024 * 1024
|
|
91
|
-
diagnostics.push({
|
|
92
|
-
id: "package-size",
|
|
93
|
-
label: "Package size",
|
|
94
|
-
status: totalBytes <= maxBytes ? "pass" : "error",
|
|
95
|
-
message: `${totalBytes} of ${maxBytes} bytes`
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
const sourceMaps = files.filter((file) => file.endsWith(".map"))
|
|
99
|
-
diagnostics.push({
|
|
100
|
-
id: "source-maps",
|
|
101
|
-
label: "Source maps",
|
|
102
|
-
status: options.productionPackage && sourceMaps.length ? "warning" : "pass",
|
|
103
|
-
message: sourceMaps.length ? `${sourceMaps.length} source map(s) present` : "No source maps"
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
const hasDatabaseFiles = Boolean(
|
|
107
|
-
manifest.server?.migrations?.length || manifest.server?.seeds?.length
|
|
108
|
-
)
|
|
109
|
-
diagnostics.push({
|
|
110
|
-
id: "database-signature",
|
|
111
|
-
label: "Database file signature",
|
|
112
|
-
status:
|
|
113
|
-
hasDatabaseFiles && options.productionPackage && !manifest.signature?.value
|
|
114
|
-
? "error"
|
|
115
|
-
: hasDatabaseFiles && !manifest.signature?.value
|
|
116
|
-
? "warning"
|
|
117
|
-
: "pass",
|
|
118
|
-
message:
|
|
119
|
-
hasDatabaseFiles && !manifest.signature?.value
|
|
120
|
-
? options.productionPackage
|
|
121
|
-
? "Production database files require a publisher signature"
|
|
122
|
-
: "Unsigned database files are allowed only in the developer sandbox"
|
|
123
|
-
: hasDatabaseFiles
|
|
124
|
-
? "Publisher signature present"
|
|
125
|
-
: "No database files declared"
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
if (options.productionPackage) {
|
|
129
|
-
diagnostics.push({
|
|
130
|
-
id: "production-archive",
|
|
131
|
-
label: "Production archive",
|
|
132
|
-
status: hasProductionArchive ? "pass" : "error",
|
|
133
|
-
message: hasProductionArchive
|
|
134
|
-
? path.basename(productionArchive)
|
|
135
|
-
: "Production archive is missing"
|
|
136
|
-
})
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return diagnostics
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export function hasAddonDiagnosticErrors(diagnostics: readonly AddonPackageDiagnostic[]): boolean {
|
|
143
|
-
return diagnostics.some(({ status }) => status === "error")
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function compatibilityDiagnostic(
|
|
147
|
-
id: string,
|
|
148
|
-
label: string,
|
|
149
|
-
version: string,
|
|
150
|
-
range: string | undefined
|
|
151
|
-
): AddonPackageDiagnostic {
|
|
152
|
-
const compatible = Boolean(range && Bun.semver.satisfies(version, range))
|
|
153
|
-
return {
|
|
154
|
-
id,
|
|
155
|
-
label,
|
|
156
|
-
status: compatible ? "pass" : "error",
|
|
157
|
-
message: range
|
|
158
|
-
? `${version} ${compatible ? "satisfies" : "does not satisfy"} ${range}`
|
|
159
|
-
: "Compatibility range is missing"
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function resolveBuildFile(buildDir: string, reference: string): string | null {
|
|
164
|
-
if (
|
|
165
|
-
path.isAbsolute(reference) ||
|
|
166
|
-
reference.includes("\\") ||
|
|
167
|
-
reference.split("/").includes("..")
|
|
168
|
-
) {
|
|
169
|
-
return null
|
|
170
|
-
}
|
|
171
|
-
const target = path.resolve(buildDir, ...reference.replace(/^\.\//, "").split("/"))
|
|
172
|
-
const relative = path.relative(path.resolve(buildDir), target)
|
|
173
|
-
return relative && !relative.startsWith("..") && !path.isAbsolute(relative) ? target : null
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function listRegularFiles(root: string): string[] {
|
|
177
|
-
if (!fs.existsSync(root)) return []
|
|
178
|
-
return fs
|
|
179
|
-
.readdirSync(root, { recursive: true, withFileTypes: true })
|
|
180
|
-
.filter((entry) => entry.isFile())
|
|
181
|
-
.map((entry) => path.join(entry.parentPath, entry.name))
|
|
182
|
-
}
|
package/src/index.d.ts
DELETED
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
import type { IconPack } from "@jchat/icon";
|
|
2
|
-
import type { Elysia } from "elysia";
|
|
3
|
-
import type { Kysely } from "kysely";
|
|
4
|
-
import type { AddonManifest, AddonPermission, MountPoint } from "./contracts";
|
|
5
|
-
export { MOUNT_POINTS, type AddonManifest, type AddonPermission, type AddonPermissionDefinition, type ConfigSchemaField, type MountPoint } from "./contracts";
|
|
6
|
-
export type AddonJsonBody = Record<string, unknown> | unknown[] | null;
|
|
7
|
-
/**
|
|
8
|
-
* @jchat/addon-sdk
|
|
9
|
-
*
|
|
10
|
-
* Lightweight SDK for Jchat addon developers. Provides TypeScript types for addon props, manifest
|
|
11
|
-
* schema, and host API access. This package is always EXTERNALISED from addon bundles — the host
|
|
12
|
-
* app provides the runtime implementation.
|
|
13
|
-
*/
|
|
14
|
-
/** Minimal user profile accessible to addon backends. */
|
|
15
|
-
export interface AddonUser {
|
|
16
|
-
/** Unique user identifier */
|
|
17
|
-
id: string;
|
|
18
|
-
/** Display name */
|
|
19
|
-
name: string;
|
|
20
|
-
/** Full display name */
|
|
21
|
-
displayName: string;
|
|
22
|
-
/** Full URL to the user's avatar image */
|
|
23
|
-
avatar: string;
|
|
24
|
-
/** User's gender identifier */
|
|
25
|
-
gender: string;
|
|
26
|
-
}
|
|
27
|
-
export interface AddonRequestUser {
|
|
28
|
-
id?: string;
|
|
29
|
-
permissions?: string[];
|
|
30
|
-
siteId?: string;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Typed, schema-scoped database facade for an addon. The host deliberately omits raw execution,
|
|
34
|
-
* schema switching, dialect/connection access, and lifecycle methods.
|
|
35
|
-
*/
|
|
36
|
-
export type AddonDatabase<TDatabase = Record<string, Record<string, unknown>>> = Pick<Kysely<TDatabase>, "deleteFrom" | "fn" | "insertInto" | "selectFrom" | "transaction" | "updateTable">;
|
|
37
|
-
/**
|
|
38
|
-
* Context provided to an addon's server-side factory. Parameterize it with the addon's table
|
|
39
|
-
* interface: `AddonServerContext<MyAddonDatabase>`.
|
|
40
|
-
*/
|
|
41
|
-
export interface AddonServerContext<TDatabase = Record<string, Record<string, unknown>>> {
|
|
42
|
-
/**
|
|
43
|
-
* Retrieve the encrypted settings for this addon for a specific site. Internal secrets are
|
|
44
|
-
* automatically decrypted using the host's master key.
|
|
45
|
-
*/
|
|
46
|
-
getConfig: (siteId: string) => Promise<Record<string, unknown>>;
|
|
47
|
-
/** Fetch a specific user's public profile */
|
|
48
|
-
getUser: (userId: string) => Promise<AddonUser | null>;
|
|
49
|
-
/** Efficiently fetch multiple user profiles in a single query. Returns a Map keyed by user ID. */
|
|
50
|
-
getUsers: (userIds: string[]) => Promise<Map<string, AddonUser>>;
|
|
51
|
-
/**
|
|
52
|
-
* Check whether the request user holds a given permission. Provides autocomplete for core Jchat
|
|
53
|
-
* permissions while also accepting any custom addon permission string.
|
|
54
|
-
*
|
|
55
|
-
* @param user - The user object from the request context
|
|
56
|
-
* @param permission - The permission to check, e.g., 'message.send' or 'addon.stories.view'
|
|
57
|
-
*/
|
|
58
|
-
hasPermission: (user: AddonRequestUser | null | undefined, permission: AddonPermission) => boolean;
|
|
59
|
-
/**
|
|
60
|
-
* A Kysely database instance scoped entirely to your addon's tables. You cannot access core Jchat
|
|
61
|
-
* tables (Users, Rooms, etc.) through this instance.
|
|
62
|
-
*/
|
|
63
|
-
db: AddonDatabase<TDatabase>;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* The entry point for your addon's server bundle. You should default-export a function matching
|
|
67
|
-
* this signature.
|
|
68
|
-
*
|
|
69
|
-
* @example
|
|
70
|
-
* ;```ts
|
|
71
|
-
* import { type AddonServerFactory } from '@jchat/addon-sdk'
|
|
72
|
-
*
|
|
73
|
-
* const factory: AddonServerFactory = ({ db, hasPermission }, app) => {
|
|
74
|
-
* return app.get('/trending', async () => { ... })
|
|
75
|
-
* }
|
|
76
|
-
* export default factory
|
|
77
|
-
* ```
|
|
78
|
-
*/
|
|
79
|
-
export type AddonServerFactory<TDatabase = Record<string, Record<string, unknown>>> = (ctx: AddonServerContext<TDatabase>, app: Elysia) => Elysia;
|
|
80
|
-
/** Props passed to your addon's root React component by the Jchat host. */
|
|
81
|
-
export interface AddonProps {
|
|
82
|
-
/** The mount point where this addon instance is rendered (e.g., 'chat-sidebar') */
|
|
83
|
-
mountPoint: MountPoint;
|
|
84
|
-
/** Information about the currently logged-in user (if any) */
|
|
85
|
-
user?: {
|
|
86
|
-
id: string;
|
|
87
|
-
name: string;
|
|
88
|
-
displayName?: string;
|
|
89
|
-
avatar?: string;
|
|
90
|
-
gender?: string;
|
|
91
|
-
};
|
|
92
|
-
/** The ID of the room the user is currently viewing (if applicable) */
|
|
93
|
-
roomId?: string;
|
|
94
|
-
/** The ID of the active Jchat site */
|
|
95
|
-
siteId?: string;
|
|
96
|
-
}
|
|
97
|
-
export interface AddonApi {
|
|
98
|
-
get: <TResponse = unknown>(path: string) => Promise<TResponse>;
|
|
99
|
-
post: <TResponse = unknown>(path: string, body?: AddonJsonBody) => Promise<TResponse>;
|
|
100
|
-
put: <TResponse = unknown>(path: string, body?: AddonJsonBody) => Promise<TResponse>;
|
|
101
|
-
delete: <TResponse = unknown>(path: string) => Promise<TResponse>;
|
|
102
|
-
}
|
|
103
|
-
export type AddonEventListener = (data: unknown) => void;
|
|
104
|
-
/** A font option provided by the JChat host. */
|
|
105
|
-
export interface AddonFont {
|
|
106
|
-
/** Display label shown to users (e.g. 'Modern', 'Elegant') */
|
|
107
|
-
label: string;
|
|
108
|
-
/** Font identifier stored in the database (e.g. 'poppins', 'lato') */
|
|
109
|
-
value: string;
|
|
110
|
-
/** CSS font-family string ready for use in inline styles */
|
|
111
|
-
family: string;
|
|
112
|
-
/** Google Fonts name for loading the font via a <link> tag */
|
|
113
|
-
googleName: string;
|
|
114
|
-
/** Relative size multiplier — apply this to base font sizes to match the host's rendering */
|
|
115
|
-
scale: number;
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* The bridge between your addon and the Jchat host application. Use the `useAddonSDK()` hook to
|
|
119
|
-
* access this interface in your components.
|
|
120
|
-
*/
|
|
121
|
-
export interface AddonSDK {
|
|
122
|
-
/**
|
|
123
|
-
* Make authenticated API calls to your addon's backend or core Jchat endpoints. All calls
|
|
124
|
-
* automatically include the current user's sessionId and site validation headers.
|
|
125
|
-
*/
|
|
126
|
-
api: AddonApi;
|
|
127
|
-
/**
|
|
128
|
-
* Display a temporary notification toast in the host UI.
|
|
129
|
-
*
|
|
130
|
-
* @param message - The text content to display
|
|
131
|
-
* @param variant - Visual style: 'default' (blue), 'success' (green), or 'error' (red)
|
|
132
|
-
*/
|
|
133
|
-
toast: (message: string, variant?: "default" | "success" | "error") => void;
|
|
134
|
-
/**
|
|
135
|
-
* Navigate the host application to a different internal route.
|
|
136
|
-
*
|
|
137
|
-
* @param path - The target location, e.g., '/settings' or '/addons/store'
|
|
138
|
-
*/
|
|
139
|
-
navigate: (path: string) => void;
|
|
140
|
-
/**
|
|
141
|
-
* Interaction utilities for the active Slate message editor. Only available if your addon is
|
|
142
|
-
* mounted near the chat input.
|
|
143
|
-
*/
|
|
144
|
-
editor: {
|
|
145
|
-
/**
|
|
146
|
-
* Trigger a specialized editor command defined by the host.
|
|
147
|
-
*
|
|
148
|
-
* @example
|
|
149
|
-
* sdk.editor.dispatchCommand("INSERT_YOUTUBE_COMMAND", "dQw4w9WgXcQ")
|
|
150
|
-
*/
|
|
151
|
-
dispatchCommand: (command: string, payload?: unknown) => void;
|
|
152
|
-
/**
|
|
153
|
-
* Insert a low-level Slate node directly at the user's cursor position.
|
|
154
|
-
*
|
|
155
|
-
* @param node - A valid Slate Descendant or Fragment object
|
|
156
|
-
*/
|
|
157
|
-
insertNode: (node: unknown) => void;
|
|
158
|
-
/**
|
|
159
|
-
* Upload and attach a file to the current message without inserting it inline. Users will see
|
|
160
|
-
* the file in the attachment bar below the editor.
|
|
161
|
-
*/
|
|
162
|
-
addAttachment: (attachment: {
|
|
163
|
-
url: string;
|
|
164
|
-
name: string;
|
|
165
|
-
type: string;
|
|
166
|
-
}) => void;
|
|
167
|
-
/** Force the browser focus back to the editor input */
|
|
168
|
-
focus: () => void;
|
|
169
|
-
};
|
|
170
|
-
/** Event Bus for reacting to real-time application state changes. */
|
|
171
|
-
events: {
|
|
172
|
-
/**
|
|
173
|
-
* Register a persistent listener for a specific event.
|
|
174
|
-
*
|
|
175
|
-
* @param event - Event name, e.g., 'room.message', 'user.typing'
|
|
176
|
-
*/
|
|
177
|
-
on: (event: string, callback: AddonEventListener) => void;
|
|
178
|
-
/** Unregister a previously attached listener */
|
|
179
|
-
off: (event: string, callback: AddonEventListener) => void;
|
|
180
|
-
};
|
|
181
|
-
/**
|
|
182
|
-
* The list of fonts available in this JChat deployment, as configured by the site administrator.
|
|
183
|
-
* Use this to build a font picker or apply a matching font family in your addon UI.
|
|
184
|
-
*/
|
|
185
|
-
supportedFonts?: AddonFont[];
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Seed data for your addon's tables, provided inside the addon's isolated schema.
|
|
189
|
-
*
|
|
190
|
-
* These documents are inserted into the addon's dedicated schema on installation and can be used to
|
|
191
|
-
* provide default values, references, or supporting data. The host guarantees that these documents
|
|
192
|
-
* do not conflict with core schemas or other addons.
|
|
193
|
-
*/
|
|
194
|
-
export interface AddonSeedDocument {
|
|
195
|
-
/** Addon-owned table name. Core prefixes the addon's isolated PostgreSQL schema. */
|
|
196
|
-
table: string;
|
|
197
|
-
/** Behavior when a row conflicts with an existing unique or primary key. */
|
|
198
|
-
conflict?: "error" | "ignore" | "update";
|
|
199
|
-
/** Required for update behavior; identifies the unique key used by ON CONFLICT. */
|
|
200
|
-
conflictColumns?: string[];
|
|
201
|
-
/** JSON-compatible rows inserted using parameterized values. */
|
|
202
|
-
rows: Record<string, unknown>[];
|
|
203
|
-
}
|
|
204
|
-
/** Define a manifest with full validation-friendly typing while preserving literal values. */
|
|
205
|
-
export declare function defineAddonManifest<const TManifest extends AddonManifest>(manifest: TManifest): TManifest;
|
|
206
|
-
/** Define a typed server factory without widening its addon database interface. */
|
|
207
|
-
export declare function defineAddonServer<TDatabase = Record<string, Record<string, unknown>>>(factory: AddonServerFactory<TDatabase>): AddonServerFactory<TDatabase>;
|
|
208
|
-
/**
|
|
209
|
-
* Access the host application's SDK. Provides authenticated API access, toast notifications, and
|
|
210
|
-
* navigation.
|
|
211
|
-
*
|
|
212
|
-
* This function is a shell; the real implementation is provided by the Jchat host environment at
|
|
213
|
-
* runtime.
|
|
214
|
-
*
|
|
215
|
-
* @example
|
|
216
|
-
* ;```tsx
|
|
217
|
-
* import { useAddonSDK } from "@jchat/addon-sdk"
|
|
218
|
-
*
|
|
219
|
-
* export default function MyAddon({ mountPoint }: AddonProps) {
|
|
220
|
-
* const sdk = useAddonSDK()
|
|
221
|
-
*
|
|
222
|
-
* const handleClick = async () => {
|
|
223
|
-
* const data = await sdk.api.get("/trending")
|
|
224
|
-
* sdk.toast("Fetched trending posts!", "success")
|
|
225
|
-
* }
|
|
226
|
-
* }
|
|
227
|
-
* ```
|
|
228
|
-
*/
|
|
229
|
-
export declare function useAddonSDK(): AddonSDK;
|
|
230
|
-
/**
|
|
231
|
-
* Access the host application's active icon pack. Returns the icon set matching the site's
|
|
232
|
-
* configured icon pack preference.
|
|
233
|
-
*
|
|
234
|
-
* This function is a shell; the real implementation is provided by the Jchat host environment at
|
|
235
|
-
* runtime.
|
|
236
|
-
*
|
|
237
|
-
* @example
|
|
238
|
-
* ;```tsx
|
|
239
|
-
* import { useIcons } from "@jchat/addon-sdk"
|
|
240
|
-
*
|
|
241
|
-
* export default function MyAddon() {
|
|
242
|
-
* const { Icons } = useIcons()
|
|
243
|
-
* return <Icons.Send className="size-5" />
|
|
244
|
-
* }
|
|
245
|
-
* ```
|
|
246
|
-
*/
|
|
247
|
-
export declare function useIcons(): {
|
|
248
|
-
Icons: IconPack;
|
|
249
|
-
};
|