@jchat/create-addon 1.0.0-alpha.3 → 1.0.0-alpha.6
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 +17 -0
- package/README.md +2 -2
- package/dist/cli.mjs +3 -3
- package/dist/generator.mjs +18 -16
- package/package.json +6 -9
- package/src/cli.test.ts +0 -30
- package/src/cli.ts +0 -55
- package/src/generator.ts +0 -428
- package/src/index.ts +0 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @jchat/create-addon
|
|
2
2
|
|
|
3
|
+
## 1.0.0-alpha.6
|
|
4
|
+
|
|
5
|
+
## 1.0.0-alpha.5
|
|
6
|
+
|
|
7
|
+
### Patch Changes
|
|
8
|
+
|
|
9
|
+
- 9755c28: Export curated UI components, types, and Tailwind styles from `@jchat/addon-sdk/ui` (`@jchat/addon-sdk/ui/tailwind.css`) for sandbox and third-party addon developers.
|
|
10
|
+
- 8b8297f: Rename package from `create-jchat-addon` to `@jchat/create-addon` and update binary script to `create-addon`.
|
|
11
|
+
- 5bbb2b6: Inherit jchatVersion and addonSdkVersion directly from @jchat/create-addon package version when generating standalone devkit.
|
|
12
|
+
|
|
13
|
+
## 1.0.0-alpha.4
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 8b8297f: Rename package from `create-jchat-addon` to `@jchat/create-addon` and update binary script to `create-addon`.
|
|
18
|
+
- 5bbb2b6: Inherit jchatVersion and addonSdkVersion directly from @jchat/create-addon package version when generating standalone devkit.
|
|
19
|
+
|
|
3
20
|
## 1.0.0-alpha.3
|
|
4
21
|
|
|
5
22
|
## 1.0.0-alpha.2
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ bunx @jchat/create-addon <addon-id>
|
|
|
14
14
|
|
|
15
15
|
- `<addon-id>`: (Required) The unique ID for your addon, typically hyphenated (e.g., `weather-addon`).
|
|
16
16
|
- `--target=<directory>`: (Optional) The directory to generate the workspace in. Defaults to `../<addon-id>` relative to the current working directory.
|
|
17
|
-
- `--image=<image>`: (Optional) A specific Docker image to use for the sandbox environment. Defaults to `ghcr.io/mjjabarullah/
|
|
17
|
+
- `--image=<image>`: (Optional) A specific Docker / Podman container image to use for the sandbox environment. Defaults to `ghcr.io/mjjabarullah/jchat-sandbox`.
|
|
18
18
|
|
|
19
19
|
### Example
|
|
20
20
|
|
|
@@ -26,7 +26,7 @@ cd my-addon
|
|
|
26
26
|
## Requirements
|
|
27
27
|
|
|
28
28
|
- Bun 1.3+
|
|
29
|
-
- Docker
|
|
29
|
+
- Docker or Podman
|
|
30
30
|
|
|
31
31
|
## Licensing
|
|
32
32
|
|
package/dist/cli.mjs
CHANGED
|
@@ -18,9 +18,9 @@ async function runCreator(arguments_) {
|
|
|
18
18
|
const options = parseCreatorArguments(arguments_);
|
|
19
19
|
writeStandaloneDevkit({
|
|
20
20
|
...options,
|
|
21
|
-
jchatVersion: manifest.
|
|
22
|
-
addonSdkVersion: manifest.
|
|
23
|
-
sandboxImage: options.sandboxImage ?? `${manifest.jchat.
|
|
21
|
+
jchatVersion: manifest.version,
|
|
22
|
+
addonSdkVersion: manifest.version,
|
|
23
|
+
sandboxImage: options.sandboxImage ?? `${manifest.jchat?.sandboxImage ?? "ghcr.io/mjjabarullah/jchat-sandbox"}:${manifest.version.replaceAll("+", "-")}`
|
|
24
24
|
});
|
|
25
25
|
return path.resolve(options.targetDir);
|
|
26
26
|
}
|
package/dist/generator.mjs
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
//#region ../addon-sdk/src/diagnostics.ts
|
|
4
|
+
/**
|
|
5
|
+
* Constructs a semantic version compatibility range covering all minor/patch versions of the
|
|
6
|
+
* current major version.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ;```ts
|
|
10
|
+
* createCompatibleMajorRange("1.4.2") // returns ">=1.4.2 <2.0.0"
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* @param version - Valid semver string (e.g. `"1.2.0"`).
|
|
14
|
+
* @returns Semver range string matching the current major series (e.g. `">=1.2.0 <2.0.0"`).
|
|
15
|
+
* @throws {@link Error} If the input is not a valid semantic version string.
|
|
16
|
+
*/
|
|
4
17
|
function createCompatibleMajorRange(version) {
|
|
5
18
|
try {
|
|
6
19
|
Bun.semver.order(version, version);
|
|
@@ -15,7 +28,7 @@ function createCompatibleMajorRange(version) {
|
|
|
15
28
|
//#endregion
|
|
16
29
|
//#region src/generator.ts
|
|
17
30
|
const ADDON_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
18
|
-
const DEFAULT_SANDBOX_IMAGE = "ghcr.io/mjjabarullah/
|
|
31
|
+
const DEFAULT_SANDBOX_IMAGE = "ghcr.io/mjjabarullah/jchat-sandbox";
|
|
19
32
|
function createStandaloneDevkitFiles(options) {
|
|
20
33
|
if (!ADDON_ID_PATTERN.test(options.addonId)) throw new Error("Addon id must contain lowercase letters, numbers, and hyphens");
|
|
21
34
|
const image = options.sandboxImage ?? `${DEFAULT_SANDBOX_IMAGE}:${options.jchatVersion.replace(/\+/g, "-")}`;
|
|
@@ -98,7 +111,7 @@ function createComposeFile() {
|
|
|
98
111
|
ports:
|
|
99
112
|
- "127.0.0.1:\${JCHAT_SANDBOX_PORT:-4317}:4317"
|
|
100
113
|
volumes:
|
|
101
|
-
- ./addon:/app/addons
|
|
114
|
+
- ./addon:/app/addons/\${JCHAT_SANDBOX_ADDON_ID:?Set addon id}:ro
|
|
102
115
|
- sandbox_uploads:/app/uploads
|
|
103
116
|
depends_on:
|
|
104
117
|
db:
|
|
@@ -248,7 +261,8 @@ export default defineConfig({
|
|
|
248
261
|
"react-dom",
|
|
249
262
|
"react/jsx-runtime",
|
|
250
263
|
"@tanstack/react-query",
|
|
251
|
-
"@jchat/addon-sdk"
|
|
264
|
+
"@jchat/addon-sdk",
|
|
265
|
+
"@jchat/addon-sdk/ui"
|
|
252
266
|
],
|
|
253
267
|
output: {
|
|
254
268
|
format: "esm"
|
|
@@ -287,19 +301,7 @@ export default function Addon({ mountPoint }: AddonProps) {
|
|
|
287
301
|
`;
|
|
288
302
|
}
|
|
289
303
|
function createTailwindSource() {
|
|
290
|
-
return `@import "
|
|
291
|
-
|
|
292
|
-
@theme inline {
|
|
293
|
-
--color-background: var(--background);
|
|
294
|
-
--color-foreground: var(--foreground);
|
|
295
|
-
--color-card: var(--card);
|
|
296
|
-
--color-card-foreground: var(--card-foreground);
|
|
297
|
-
--color-border: var(--border);
|
|
298
|
-
--color-primary: var(--primary);
|
|
299
|
-
--color-primary-foreground: var(--primary-foreground);
|
|
300
|
-
--color-muted-foreground: var(--muted-foreground);
|
|
301
|
-
}
|
|
302
|
-
`;
|
|
304
|
+
return `@import "@jchat/addon-sdk/ui/tailwind.css";`;
|
|
303
305
|
}
|
|
304
306
|
function createLifecycleScript() {
|
|
305
307
|
return `import * as fs from "node:fs"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jchat/create-addon",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"keywords": [
|
|
6
6
|
"addon",
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"dist",
|
|
25
|
-
"src",
|
|
26
25
|
"README.md",
|
|
27
26
|
"CHANGELOG.md"
|
|
28
27
|
],
|
|
@@ -40,23 +39,21 @@
|
|
|
40
39
|
},
|
|
41
40
|
"scripts": {
|
|
42
41
|
"build": "tsdown",
|
|
43
|
-
"lint": "oxlint .
|
|
42
|
+
"lint": "oxlint .",
|
|
44
43
|
"format": "oxfmt . --write",
|
|
45
44
|
"typecheck": "tsc --noEmit",
|
|
46
45
|
"prepack": "bun run build"
|
|
47
46
|
},
|
|
48
47
|
"devDependencies": {
|
|
49
|
-
"@jchat/addon-sdk": "
|
|
50
|
-
"@types/node": "
|
|
51
|
-
"bun-types": "
|
|
48
|
+
"@jchat/addon-sdk": "workspace:*",
|
|
49
|
+
"@types/node": "catalog:",
|
|
50
|
+
"bun-types": "catalog:",
|
|
52
51
|
"tsdown": "^0.22.14"
|
|
53
52
|
},
|
|
54
53
|
"engines": {
|
|
55
54
|
"bun": ">=1.3.11"
|
|
56
55
|
},
|
|
57
56
|
"jchat": {
|
|
58
|
-
"
|
|
59
|
-
"addonSdkVersion": "0.0.1",
|
|
60
|
-
"sandboxImage": "ghcr.io/mjjabarullah/addon-developer-sandbox"
|
|
57
|
+
"sandboxImage": "ghcr.io/mjjabarullah/jchat-sandbox"
|
|
61
58
|
}
|
|
62
59
|
}
|
package/src/cli.test.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test"
|
|
2
|
-
import { parseCreatorArguments } from "./cli"
|
|
3
|
-
|
|
4
|
-
describe("create-addon CLI", () => {
|
|
5
|
-
test("uses the addon id as the default target", () => {
|
|
6
|
-
expect(parseCreatorArguments(["weather-addon"])).toEqual({
|
|
7
|
-
addonId: "weather-addon",
|
|
8
|
-
targetDir: "weather-addon",
|
|
9
|
-
sandboxImage: undefined
|
|
10
|
-
})
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
test("accepts target and image overrides", () => {
|
|
14
|
-
expect(
|
|
15
|
-
parseCreatorArguments([
|
|
16
|
-
"weather-addon",
|
|
17
|
-
"--target=../weather-kit",
|
|
18
|
-
"--image=registry.example/sandbox:test"
|
|
19
|
-
])
|
|
20
|
-
).toEqual({
|
|
21
|
-
addonId: "weather-addon",
|
|
22
|
-
targetDir: "../weather-kit",
|
|
23
|
-
sandboxImage: "registry.example/sandbox:test"
|
|
24
|
-
})
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
test("requires an addon id", () => {
|
|
28
|
-
expect(() => parseCreatorArguments([])).toThrow("Usage: bunx @jchat/create-addon")
|
|
29
|
-
})
|
|
30
|
-
})
|
package/src/cli.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
import * as path from "node:path"
|
|
4
|
-
import { writeStandaloneDevkit } from "./generator"
|
|
5
|
-
|
|
6
|
-
interface CreatorPackageManifest {
|
|
7
|
-
version: string
|
|
8
|
-
jchat: {
|
|
9
|
-
jchatVersion: string
|
|
10
|
-
addonSdkVersion: string
|
|
11
|
-
sandboxImage: string
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface CreatorArguments {
|
|
16
|
-
addonId: string
|
|
17
|
-
targetDir: string
|
|
18
|
-
sandboxImage?: string
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function parseCreatorArguments(arguments_: readonly string[]): CreatorArguments {
|
|
22
|
-
const positional = arguments_.filter((argument) => !argument.startsWith("--"))
|
|
23
|
-
const addonId = positional[0]
|
|
24
|
-
if (addonId === undefined || addonId.startsWith("-")) {
|
|
25
|
-
throw new Error("Usage: bunx @jchat/create-addon <addon-id> [--target=<directory>] [--image=<image>]")
|
|
26
|
-
}
|
|
27
|
-
const targetArgument = arguments_.find((argument) => argument.startsWith("--target="))
|
|
28
|
-
const imageArgument = arguments_.find((argument) => argument.startsWith("--image="))
|
|
29
|
-
return {
|
|
30
|
-
addonId,
|
|
31
|
-
targetDir: targetArgument?.slice("--target=".length) || addonId,
|
|
32
|
-
sandboxImage: imageArgument?.slice("--image=".length) || undefined
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export async function runCreator(arguments_: readonly string[]): Promise<string> {
|
|
37
|
-
const manifest = (await Bun.file(
|
|
38
|
-
new URL("../package.json", import.meta.url)
|
|
39
|
-
).json()) as CreatorPackageManifest
|
|
40
|
-
const options = parseCreatorArguments(arguments_)
|
|
41
|
-
writeStandaloneDevkit({
|
|
42
|
-
...options,
|
|
43
|
-
jchatVersion: manifest.jchat.jchatVersion,
|
|
44
|
-
addonSdkVersion: manifest.jchat.addonSdkVersion,
|
|
45
|
-
sandboxImage:
|
|
46
|
-
options.sandboxImage ??
|
|
47
|
-
`${manifest.jchat.sandboxImage}:${manifest.jchat.jchatVersion.replaceAll("+", "-")}`
|
|
48
|
-
})
|
|
49
|
-
return path.resolve(options.targetDir)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (import.meta.main) {
|
|
53
|
-
const target = await runCreator(process.argv.slice(2))
|
|
54
|
-
console.log(`Created standalone addon development kit at ${target}`)
|
|
55
|
-
}
|
package/src/generator.ts
DELETED
|
@@ -1,428 +0,0 @@
|
|
|
1
|
-
import type { AddonManifest } from "@jchat/addon-sdk"
|
|
2
|
-
import { createCompatibleMajorRange } from "@jchat/addon-sdk/diagnostics"
|
|
3
|
-
import * as fs from "node:fs"
|
|
4
|
-
import * as path from "node:path"
|
|
5
|
-
|
|
6
|
-
export interface StandaloneDevkitOptions {
|
|
7
|
-
addonId: string
|
|
8
|
-
targetDir: string
|
|
9
|
-
jchatVersion: string
|
|
10
|
-
addonSdkVersion: string
|
|
11
|
-
sandboxImage?: string
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const ADDON_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
|
|
15
|
-
const DEFAULT_SANDBOX_IMAGE = "ghcr.io/mjjabarullah/addon-developer-sandbox"
|
|
16
|
-
|
|
17
|
-
export function createStandaloneDevkitFiles(
|
|
18
|
-
options: StandaloneDevkitOptions
|
|
19
|
-
): Readonly<Record<string, string>> {
|
|
20
|
-
if (!ADDON_ID_PATTERN.test(options.addonId)) {
|
|
21
|
-
throw new Error("Addon id must contain lowercase letters, numbers, and hyphens")
|
|
22
|
-
}
|
|
23
|
-
const image =
|
|
24
|
-
options.sandboxImage ?? `${DEFAULT_SANDBOX_IMAGE}:${options.jchatVersion.replace(/\+/g, "-")}`
|
|
25
|
-
const manifest: AddonManifest = {
|
|
26
|
-
id: options.addonId,
|
|
27
|
-
name: toDisplayName(options.addonId),
|
|
28
|
-
version: "1.0.0",
|
|
29
|
-
description: `${toDisplayName(options.addonId)} addon for JChat`,
|
|
30
|
-
author: "Developer",
|
|
31
|
-
compatibility: {
|
|
32
|
-
jchat: createCompatibleMajorRange(options.jchatVersion),
|
|
33
|
-
addonSdk: createCompatibleMajorRange(options.addonSdkVersion)
|
|
34
|
-
},
|
|
35
|
-
frontend: {
|
|
36
|
-
bundle: "./ui.js",
|
|
37
|
-
mountPoints: ["chat-toolbar"]
|
|
38
|
-
},
|
|
39
|
-
permissions: []
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return Object.freeze({
|
|
43
|
-
".env.example": createEnvironmentFile(options.addonId, image),
|
|
44
|
-
".gitignore": ["node_modules/", "addon/node_modules/", "addon/build/", ".env", ""].join("\n"),
|
|
45
|
-
"README.md": createReadme(options.addonId),
|
|
46
|
-
"docker-compose.yml": createComposeFile(),
|
|
47
|
-
"package.json": `${JSON.stringify(createRootPackage(options.addonId), null, 2)}\n`,
|
|
48
|
-
"scripts/devkit.ts": createLifecycleScript(),
|
|
49
|
-
"addon/package.json": `${JSON.stringify(
|
|
50
|
-
createAddonPackage(options.addonId, options.addonSdkVersion),
|
|
51
|
-
null,
|
|
52
|
-
2
|
|
53
|
-
)}\n`,
|
|
54
|
-
"addon/tsconfig.json": `${JSON.stringify(createTsConfig(), null, 2)}\n`,
|
|
55
|
-
"addon/vite.config.ts": createViteConfig(),
|
|
56
|
-
"addon/src/manifest.ts": createManifestSource(manifest),
|
|
57
|
-
"addon/src/tailwind.css": createTailwindSource(),
|
|
58
|
-
"addon/src/ui.tsx": createUiSource(manifest.name)
|
|
59
|
-
})
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function writeStandaloneDevkit(options: StandaloneDevkitOptions): void {
|
|
63
|
-
const targetDir = path.resolve(options.targetDir)
|
|
64
|
-
if (fs.existsSync(targetDir)) {
|
|
65
|
-
throw new Error(`Development kit target already exists: ${targetDir}`)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
for (const [relativePath, contents] of Object.entries(createStandaloneDevkitFiles(options))) {
|
|
69
|
-
const target = path.join(targetDir, ...relativePath.split("/"))
|
|
70
|
-
fs.mkdirSync(path.dirname(target), { recursive: true })
|
|
71
|
-
fs.writeFileSync(target, contents)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function createEnvironmentFile(addonId: string, image: string): string {
|
|
76
|
-
return [
|
|
77
|
-
`JCHAT_SANDBOX_IMAGE=${image}`,
|
|
78
|
-
`JCHAT_SANDBOX_ADDON_ID=${addonId}`,
|
|
79
|
-
"JCHAT_SANDBOX_PORT=4317",
|
|
80
|
-
"JCHAT_SANDBOX_HTTP_MOCK_PORT=4318",
|
|
81
|
-
"JCHAT_SANDBOX_HTTP_MOCK_OFFLINE=false",
|
|
82
|
-
"JCHAT_SANDBOX_ADDON_HTTP_MOCK_URL=http://http-mock:4318",
|
|
83
|
-
""
|
|
84
|
-
].join("\n")
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function createComposeFile(): string {
|
|
88
|
-
return `services:
|
|
89
|
-
sandbox:
|
|
90
|
-
image: \${JCHAT_SANDBOX_IMAGE:?Copy .env.example to .env}
|
|
91
|
-
restart: unless-stopped
|
|
92
|
-
environment:
|
|
93
|
-
NODE_ENV: development
|
|
94
|
-
JCHAT_PORT: "4317"
|
|
95
|
-
JCHAT_COOKIE_SECRET: addon-sandbox-cookie-secret
|
|
96
|
-
JCHAT_DATABASE_URL: postgresql://jchat:jchat@db:5432/jchat
|
|
97
|
-
JCHAT_REDIS_DATABASE_URL: redis://redis:6379
|
|
98
|
-
JCHAT_STORAGE_TYPE: LOCAL
|
|
99
|
-
JCHAT_BOT_NAME: Sandbox Bot
|
|
100
|
-
JCHAT_BOT_EMAIL: bot@sandbox.local
|
|
101
|
-
JCHAT_DOMAIN: localhost
|
|
102
|
-
JCHAT_SITE_URL: http://localhost:\${JCHAT_SANDBOX_PORT:-4317}
|
|
103
|
-
JCHAT_ROUTING_MODE: path
|
|
104
|
-
JCHAT_ADDON_ENCRYPTION_KEY: addon-sandbox-encryption-key
|
|
105
|
-
JCHAT_ADDON_HTTP_MOCK_URL: \${JCHAT_SANDBOX_ADDON_HTTP_MOCK_URL-http://http-mock:4318}
|
|
106
|
-
ports:
|
|
107
|
-
- "127.0.0.1:\${JCHAT_SANDBOX_PORT:-4317}:4317"
|
|
108
|
-
volumes:
|
|
109
|
-
- ./addon:/app/addons-dev/\${JCHAT_SANDBOX_ADDON_ID:?Set addon id}:ro
|
|
110
|
-
- sandbox_uploads:/app/uploads
|
|
111
|
-
depends_on:
|
|
112
|
-
db:
|
|
113
|
-
condition: service_healthy
|
|
114
|
-
redis:
|
|
115
|
-
condition: service_healthy
|
|
116
|
-
http-mock:
|
|
117
|
-
condition: service_healthy
|
|
118
|
-
|
|
119
|
-
http-mock:
|
|
120
|
-
image: \${JCHAT_SANDBOX_IMAGE:?Copy .env.example to .env}
|
|
121
|
-
entrypoint: ["/app/addon-http-mock"]
|
|
122
|
-
environment:
|
|
123
|
-
JCHAT_SANDBOX_HTTP_MOCK_PORT: "4318"
|
|
124
|
-
JCHAT_SANDBOX_HTTP_MOCK_OFFLINE: \${JCHAT_SANDBOX_HTTP_MOCK_OFFLINE:-false}
|
|
125
|
-
ports:
|
|
126
|
-
- "127.0.0.1:\${JCHAT_SANDBOX_HTTP_MOCK_PORT:-4318}:4318"
|
|
127
|
-
healthcheck:
|
|
128
|
-
test: ["CMD", "/app/addon-http-mock", "--healthcheck"]
|
|
129
|
-
interval: 2s
|
|
130
|
-
timeout: 3s
|
|
131
|
-
retries: 30
|
|
132
|
-
|
|
133
|
-
db:
|
|
134
|
-
image: postgres:18-alpine
|
|
135
|
-
environment:
|
|
136
|
-
POSTGRES_USER: jchat
|
|
137
|
-
POSTGRES_PASSWORD: jchat
|
|
138
|
-
POSTGRES_DB: jchat
|
|
139
|
-
volumes:
|
|
140
|
-
- sandbox_postgres:/var/lib/postgresql/data
|
|
141
|
-
healthcheck:
|
|
142
|
-
test: ["CMD-SHELL", "pg_isready -U jchat"]
|
|
143
|
-
interval: 2s
|
|
144
|
-
timeout: 3s
|
|
145
|
-
retries: 30
|
|
146
|
-
|
|
147
|
-
redis:
|
|
148
|
-
image: redis:8.0.6-alpine3.21
|
|
149
|
-
volumes:
|
|
150
|
-
- sandbox_redis:/data
|
|
151
|
-
healthcheck:
|
|
152
|
-
test: ["CMD", "redis-cli", "ping"]
|
|
153
|
-
interval: 2s
|
|
154
|
-
timeout: 3s
|
|
155
|
-
retries: 30
|
|
156
|
-
|
|
157
|
-
volumes:
|
|
158
|
-
sandbox_postgres:
|
|
159
|
-
sandbox_redis:
|
|
160
|
-
sandbox_uploads:
|
|
161
|
-
`
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function createRootPackage(addonId: string) {
|
|
165
|
-
return {
|
|
166
|
-
name: `${addonId}-development-kit`,
|
|
167
|
-
private: true,
|
|
168
|
-
type: "module",
|
|
169
|
-
workspaces: ["addon"],
|
|
170
|
-
scripts: {
|
|
171
|
-
sandbox: "bun run ./scripts/devkit.ts run",
|
|
172
|
-
"sandbox:test": "bun run ./scripts/devkit.ts test",
|
|
173
|
-
"sandbox:reset": "bun run ./scripts/devkit.ts reset",
|
|
174
|
-
"sandbox:package": "bun run ./scripts/devkit.ts package",
|
|
175
|
-
"sandbox:cleanup": "bun run ./scripts/devkit.ts cleanup"
|
|
176
|
-
},
|
|
177
|
-
devDependencies: {
|
|
178
|
-
"bun-types": "^1.3.14"
|
|
179
|
-
},
|
|
180
|
-
engines: {
|
|
181
|
-
bun: ">=1.3.11"
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function createAddonPackage(addonId: string, addonSdkVersion: string) {
|
|
187
|
-
return {
|
|
188
|
-
name: `@jchat/${addonId}`,
|
|
189
|
-
version: "1.0.0",
|
|
190
|
-
private: true,
|
|
191
|
-
type: "module",
|
|
192
|
-
scripts: {
|
|
193
|
-
dev: "vite build --watch --mode=development",
|
|
194
|
-
build: "vite build --mode=production",
|
|
195
|
-
test: "bun test",
|
|
196
|
-
typecheck: "tsc --noEmit"
|
|
197
|
-
},
|
|
198
|
-
dependencies: {
|
|
199
|
-
"@jchat/addon-sdk": `^${addonSdkVersion}`,
|
|
200
|
-
"@tanstack/react-query": "^5.101.4",
|
|
201
|
-
react: "^19.2.8",
|
|
202
|
-
"react-dom": "^19.2.8"
|
|
203
|
-
},
|
|
204
|
-
devDependencies: {
|
|
205
|
-
"@tailwindcss/vite": "^4.3.3",
|
|
206
|
-
"@types/node": "^26.1.1",
|
|
207
|
-
"@types/react": "^19.2.17",
|
|
208
|
-
"@types/react-dom": "^19.2.3",
|
|
209
|
-
"bun-types": "^1.3.14",
|
|
210
|
-
tailwindcss: "^4.3.3",
|
|
211
|
-
typescript: "^7.0.2",
|
|
212
|
-
vite: "^8.1.5"
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function createTsConfig() {
|
|
218
|
-
return {
|
|
219
|
-
compilerOptions: {
|
|
220
|
-
strict: true,
|
|
221
|
-
target: "ESNext",
|
|
222
|
-
module: "ESNext",
|
|
223
|
-
moduleResolution: "bundler",
|
|
224
|
-
jsx: "react-jsx",
|
|
225
|
-
noEmit: true,
|
|
226
|
-
types: ["bun-types", "node"]
|
|
227
|
-
},
|
|
228
|
-
include: ["src/**/*.ts", "src/**/*.tsx", "vite.config.ts"]
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function createManifestSource(manifest: AddonManifest): string {
|
|
233
|
-
return `import type { AddonManifest } from "@jchat/addon-sdk"
|
|
234
|
-
|
|
235
|
-
const manifest: AddonManifest = ${JSON.stringify(manifest, null, 2)}
|
|
236
|
-
|
|
237
|
-
export default manifest
|
|
238
|
-
`
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function createViteConfig(): string {
|
|
242
|
-
return `import { jchatAddon } from "@jchat/addon-sdk/vite"
|
|
243
|
-
import tailwindcss from "@tailwindcss/vite"
|
|
244
|
-
import path from "node:path"
|
|
245
|
-
import { defineConfig } from "vite"
|
|
246
|
-
import manifest from "./src/manifest"
|
|
247
|
-
|
|
248
|
-
export default defineConfig({
|
|
249
|
-
plugins: [tailwindcss(), jchatAddon({ manifest })],
|
|
250
|
-
build: {
|
|
251
|
-
outDir: "build",
|
|
252
|
-
emptyOutDir: true,
|
|
253
|
-
lib: {
|
|
254
|
-
entry: path.resolve(import.meta.dirname, "src/ui.tsx"),
|
|
255
|
-
formats: ["es"],
|
|
256
|
-
fileName: () => "ui.[hash].js"
|
|
257
|
-
},
|
|
258
|
-
rolldownOptions: {
|
|
259
|
-
external: [
|
|
260
|
-
"react",
|
|
261
|
-
"react-dom",
|
|
262
|
-
"react/jsx-runtime",
|
|
263
|
-
"@tanstack/react-query",
|
|
264
|
-
"@jchat/addon-sdk"
|
|
265
|
-
],
|
|
266
|
-
output: {
|
|
267
|
-
format: "esm"
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
})
|
|
272
|
-
`
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function createUiSource(name: string): string {
|
|
276
|
-
return `import type { AddonProps } from "@jchat/addon-sdk"
|
|
277
|
-
import { useAddonSDK, useIcons } from "@jchat/addon-sdk"
|
|
278
|
-
import "./tailwind.css"
|
|
279
|
-
|
|
280
|
-
export default function Addon({ mountPoint }: AddonProps) {
|
|
281
|
-
const sdk = useAddonSDK()
|
|
282
|
-
const { Icons } = useIcons()
|
|
283
|
-
|
|
284
|
-
return (
|
|
285
|
-
<div className="rounded border border-border bg-card p-2 text-card-foreground">
|
|
286
|
-
<div className="mb-2 flex items-center gap-2">
|
|
287
|
-
<Icons.Plus aria-hidden="true" className="size-4 text-primary" />
|
|
288
|
-
<strong>${name}</strong>
|
|
289
|
-
</div>
|
|
290
|
-
<p className="mb-2 text-sm text-muted-foreground">Mounted at {mountPoint}</p>
|
|
291
|
-
<button
|
|
292
|
-
type="button"
|
|
293
|
-
className="rounded bg-primary px-3 py-2 text-primary-foreground"
|
|
294
|
-
onClick={() => sdk.toast("Hello from ${name}")}
|
|
295
|
-
>
|
|
296
|
-
Test addon
|
|
297
|
-
</button>
|
|
298
|
-
</div>
|
|
299
|
-
)
|
|
300
|
-
}
|
|
301
|
-
`
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function createTailwindSource(): string {
|
|
305
|
-
return `@import "tailwindcss";
|
|
306
|
-
|
|
307
|
-
@theme inline {
|
|
308
|
-
--color-background: var(--background);
|
|
309
|
-
--color-foreground: var(--foreground);
|
|
310
|
-
--color-card: var(--card);
|
|
311
|
-
--color-card-foreground: var(--card-foreground);
|
|
312
|
-
--color-border: var(--border);
|
|
313
|
-
--color-primary: var(--primary);
|
|
314
|
-
--color-primary-foreground: var(--primary-foreground);
|
|
315
|
-
--color-muted-foreground: var(--muted-foreground);
|
|
316
|
-
}
|
|
317
|
-
`
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function createLifecycleScript(): string {
|
|
321
|
-
return `import * as fs from "node:fs"
|
|
322
|
-
import * as path from "node:path"
|
|
323
|
-
|
|
324
|
-
type Command = "run" | "test" | "reset" | "package" | "cleanup"
|
|
325
|
-
|
|
326
|
-
const command = (process.argv[2] ?? "run") as Command
|
|
327
|
-
if (!["run", "test", "reset", "package", "cleanup"].includes(command)) {
|
|
328
|
-
throw new Error(\`Unknown development-kit command: \${command}\`)
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const root = path.resolve(import.meta.dir, "..")
|
|
332
|
-
const environment = {
|
|
333
|
-
...process.env,
|
|
334
|
-
...readEnvironment(path.join(root, ".env"))
|
|
335
|
-
}
|
|
336
|
-
const addonId = environment.JCHAT_SANDBOX_ADDON_ID
|
|
337
|
-
if (!addonId) throw new Error("JCHAT_SANDBOX_ADDON_ID is missing from .env")
|
|
338
|
-
const compose = [
|
|
339
|
-
"docker",
|
|
340
|
-
"compose",
|
|
341
|
-
"--project-name",
|
|
342
|
-
\`jchat-addon-devkit-\${addonId}\`,
|
|
343
|
-
"--file",
|
|
344
|
-
path.join(root, "docker-compose.yml")
|
|
345
|
-
]
|
|
346
|
-
|
|
347
|
-
if (command === "test") {
|
|
348
|
-
await run(["bun", "test"], path.join(root, "addon"), environment)
|
|
349
|
-
} else if (command === "package") {
|
|
350
|
-
await run(["bun", "run", "build"], path.join(root, "addon"), environment)
|
|
351
|
-
} else if (command === "cleanup") {
|
|
352
|
-
await run([...compose, "down", "--remove-orphans", "--volumes"], root, environment)
|
|
353
|
-
} else {
|
|
354
|
-
if (command === "reset") {
|
|
355
|
-
await run([...compose, "down", "--remove-orphans", "--volumes"], root, environment)
|
|
356
|
-
}
|
|
357
|
-
await run(["bun", "run", "build"], path.join(root, "addon"), environment)
|
|
358
|
-
const watcher = Bun.spawn(["bun", "run", "dev"], {
|
|
359
|
-
cwd: path.join(root, "addon"),
|
|
360
|
-
env: environment,
|
|
361
|
-
stdout: "inherit",
|
|
362
|
-
stderr: "inherit"
|
|
363
|
-
})
|
|
364
|
-
try {
|
|
365
|
-
await run([...compose, "up"], root, environment)
|
|
366
|
-
} finally {
|
|
367
|
-
watcher.kill()
|
|
368
|
-
await watcher.exited
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
async function run(command: string[], cwd: string, env: Record<string, string | undefined>) {
|
|
373
|
-
const child = Bun.spawn(command, { cwd, env, stdin: "inherit", stdout: "inherit", stderr: "inherit" })
|
|
374
|
-
const exitCode = await child.exited
|
|
375
|
-
if (exitCode !== 0) throw new Error(\`\${command.join(" ")} exited with code \${exitCode}\`)
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
function readEnvironment(file: string): Record<string, string> {
|
|
379
|
-
if (!fs.existsSync(file)) throw new Error("Copy .env.example to .env before running the kit")
|
|
380
|
-
return Object.fromEntries(
|
|
381
|
-
fs.readFileSync(file, "utf8")
|
|
382
|
-
.split(/\\r?\\n/)
|
|
383
|
-
.filter((line) => line && !line.startsWith("#"))
|
|
384
|
-
.map((line) => {
|
|
385
|
-
const separator = line.indexOf("=")
|
|
386
|
-
return [line.slice(0, separator), line.slice(separator + 1)]
|
|
387
|
-
})
|
|
388
|
-
)
|
|
389
|
-
}
|
|
390
|
-
`
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
function createReadme(addonId: string): string {
|
|
394
|
-
return `# ${toDisplayName(addonId)} Development Kit
|
|
395
|
-
|
|
396
|
-
This workspace develops \`${addonId}\` against the license-free JChat developer sandbox without
|
|
397
|
-
including JChat Core source.
|
|
398
|
-
|
|
399
|
-
## Start
|
|
400
|
-
|
|
401
|
-
\`\`\`powershell
|
|
402
|
-
Copy-Item .env.example .env
|
|
403
|
-
bun install
|
|
404
|
-
bun sandbox
|
|
405
|
-
\`\`\`
|
|
406
|
-
|
|
407
|
-
Open \`http://127.0.0.1:4317\`. The HTTP fixture recorder is available at
|
|
408
|
-
\`http://127.0.0.1:4318/requests\`.
|
|
409
|
-
|
|
410
|
-
## Commands
|
|
411
|
-
|
|
412
|
-
- \`bun sandbox\`
|
|
413
|
-
- \`bun sandbox:test\`
|
|
414
|
-
- \`bun sandbox:reset\`
|
|
415
|
-
- \`bun sandbox:package\`
|
|
416
|
-
- \`bun sandbox:cleanup\`
|
|
417
|
-
|
|
418
|
-
The referenced JChat packages and sandbox image must be published before this standalone kit can
|
|
419
|
-
install or start outside the JChat repository.
|
|
420
|
-
`
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
function toDisplayName(addonId: string): string {
|
|
424
|
-
return addonId
|
|
425
|
-
.split("-")
|
|
426
|
-
.map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`)
|
|
427
|
-
.join(" ")
|
|
428
|
-
}
|