@checkstack/ai-backend 0.1.1 → 0.1.3
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 +29 -0
- package/package.json +7 -7
- package/src/generated/docs-index.ts +7 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# @checkstack/ai-backend
|
|
2
2
|
|
|
3
|
+
## 0.1.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 00b9367: Refresh the bundled docs search index (`ai.searchDocs` / `ai.getDoc`) for the
|
|
8
|
+
updated plugin-authoring documentation: one-off `bunx` examples now pin
|
|
9
|
+
`@latest`, committed `pack` scripts use the installed `checkstack-scripts` bin,
|
|
10
|
+
and a new "Keep the tooling current" section documents Bun's scaffolder cache
|
|
11
|
+
behaviour (latest re-resolved per run within the ~5 min registry-manifest
|
|
12
|
+
window; tarballs content-addressed by version). Cutting this release also
|
|
13
|
+
rebuilds the Docker image, so the bundled in-app docs served at `/checkstack/*`
|
|
14
|
+
pick up the changes.
|
|
15
|
+
- @checkstack/ai-common@0.1.2
|
|
16
|
+
- @checkstack/backend-api@0.21.3
|
|
17
|
+
- @checkstack/common@0.14.1
|
|
18
|
+
- @checkstack/integration-backend@0.4.3
|
|
19
|
+
- @checkstack/sdk@0.98.1
|
|
20
|
+
|
|
21
|
+
## 0.1.2
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- Updated dependencies [1fee9da]
|
|
26
|
+
- @checkstack/common@0.14.1
|
|
27
|
+
- @checkstack/ai-common@0.1.2
|
|
28
|
+
- @checkstack/backend-api@0.21.2
|
|
29
|
+
- @checkstack/integration-backend@0.4.2
|
|
30
|
+
- @checkstack/sdk@0.96.1
|
|
31
|
+
|
|
3
32
|
## 0.1.1
|
|
4
33
|
|
|
5
34
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/ai-backend",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"license": "Elastic-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -16,12 +16,12 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@ai-sdk/openai-compatible": "^2.0.48",
|
|
19
|
-
"@checkstack/ai-common": "0.1.
|
|
20
|
-
"@checkstack/backend-api": "0.21.
|
|
21
|
-
"@checkstack/common": "0.
|
|
19
|
+
"@checkstack/ai-common": "0.1.2",
|
|
20
|
+
"@checkstack/backend-api": "0.21.3",
|
|
21
|
+
"@checkstack/common": "0.14.1",
|
|
22
22
|
"@checkstack/drizzle-helper": "0.0.5",
|
|
23
|
-
"@checkstack/integration-backend": "0.4.
|
|
24
|
-
"@checkstack/sdk": "0.
|
|
23
|
+
"@checkstack/integration-backend": "0.4.3",
|
|
24
|
+
"@checkstack/sdk": "0.98.1",
|
|
25
25
|
"@orpc/client": "^1.14.4",
|
|
26
26
|
"@orpc/contract": "^1.14.4",
|
|
27
27
|
"@orpc/server": "^1.14.4",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"zod": "^4.2.1"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@checkstack/scripts": "0.
|
|
34
|
+
"@checkstack/scripts": "0.6.0",
|
|
35
35
|
"@checkstack/tsconfig": "0.0.7",
|
|
36
36
|
"@types/node": "^20.0.0",
|
|
37
37
|
"@types/pg": "^8.20.0",
|
|
@@ -277,7 +277,7 @@ export const DOCS_INDEX: readonly DocsIndexEntry[] = [
|
|
|
277
277
|
"Security model",
|
|
278
278
|
"See also"
|
|
279
279
|
],
|
|
280
|
-
"content": "This guide is for **plugin authors** — anyone publishing a Checkstack plugin\nthat operators install via the runtime Plugin Manager UI. It covers the\nrequired `package.json` shape, how to pack with the\n`@checkstack/scripts plugin-pack` CLI, single-package vs bundle mode, and\nrelease workflow patterns for npm, GitHub, and direct tarball delivery.\n\nIf you only want to *consume* a plugin as a platform operator, see\n[Install a plugin](/checkstack/user-guide/guides/install-a-plugin/) for\nthe Plugin Manager UI walkthrough.\n\n> **Looking for the dev loop?** This doc covers the *distribution\n> mechanics* (packing, bundles, channels). For local development — add\n> `@checkstack/dev-server` as a devDependency, wire `\"dev\": \"checkstack-dev\"`\n> into your `package.json` scripts, and run `bun run dev` — see\n> [Developing Plugins in Isolation](/checkstack/developer-guide/getting-started/plugin-development/).\n\n> **Distinction:** monorepo-internal plugins (the ones living in `core/` and\n> `plugins/` of this repo) are loaded automatically at boot via filesystem\n> discovery. Everything below is about plugins that ship **independently**\n> via npm / GitHub release / tarball upload — the mechanism the runtime\n> Plugin Manager uses.\n\n## Anatomy of an installable plugin\n\nEvery installable plugin is just an npm package whose `package.json` declares\na `checkstack` block. The platform's install pipeline validates this block\n(plus a few standard fields) on every install.\n\n### Required `package.json` fields\n\n| Field | Source | Notes |\n|---------------------------|-----------------------|---------------------------------------------------------------------------------------------|\n| `name` | standard | Must be the npm package name. Scoped names (`@org/foo`) are fine. |\n| `version` | standard | A valid semver string. Used for compatibility checks. |\n| `description` | standard | One-line summary shown in the install confirmation modal. |\n| `author` | standard | String (`\"Jane <jane@example.com>\"`) or object (`{ name, email?, url? }`). |\n| `license` | standard | Any SPDX identifier (or `SEE LICENSE IN ...`). |\n| `checkstack.type` | Checkstack | One of `\"backend\"`, `\"frontend\"`, `\"common\"`. |\n| `checkstack.pluginId` | Checkstack | Stable runtime id (e.g. `\"healthcheck-http\"`). Must match `pluginId` in `plugin-metadata.ts`.|\n\n### Optional fields\n\n| Field | Notes |\n|------------------------------------|----------------------------------------------------------------------------------------------------|\n| `homepage` | Linked from the install confirmation modal. |\n| `repository` | Standard `repository` form. Surfaced in the admin UI for the operator's reference. |\n| `checkstack.bundle` | Array of sibling package names that install/uninstall atomically with this one. Set on the **primary** package only. |\n| `checkstack.usageInstructions` | Markdown string shown in the install confirmation modal. Use it to describe required env vars, config, integrations, etc. |\n| `checkstack.allowInstallScripts` | Default `false`. When `true`, the platform runs `bun install` *without* `--ignore-scripts`. Surfaces in the security warning. Use sparingly — it's the loudest dial-up of trust requirements during install. |\n\nA minimal valid package.json:\n\n```json\n{\n \"name\": \"@my-org/widget-backend\",\n \"version\": \"1.2.3\",\n \"description\": \"Widget tracker for Checkstack\",\n \"author\": \"ACME Corp\",\n \"license\": \"MIT\",\n \"checkstack\": {\n \"type\": \"backend\",\n \"pluginId\": \"widget\"\n },\n \"scripts\": {\n \"pack\": \"bunx @checkstack/scripts plugin-pack\"\n }\n}\n```\n\nThe `pack` script is the single supported entrypoint. Do not call\n`bun pm pack` directly — `plugin-pack` validates metadata before packing,\ncatches issues at build time instead of install time.\n\n## The `plugin-pack` CLI\n\nInstall once per machine via npx-style runner:\n\n```bash\nbunx @checkstack/scripts plugin-pack --help\n```\n\n```\nUsage: checkstack-scripts plugin-pack [options]\n\nOptions:\n --bundle Pack the primary plus every sibling declared in\n package.json#checkstack.bundle into a single outer\n tarball with a bundle.json manifest.\n --out-dir <dir> Output directory (default: ./dist)\n --validate-only Only validate metadata; do not pack.\n --cwd <dir> Run as if invoked from <dir> (default: process.cwd())\n --help, -h Show this message.\n```\n\n### What it does (in order)\n\n1. Reads `<cwd>/package.json` and validates it against\n `installPackageMetadataSchema` — the same Zod schema the runtime install\n pipeline uses. Failures are reported with the specific field path so you\n know exactly what to fix.\n2. Runs `bun run typecheck` and `bun run lint` if those scripts exist.\n Mirrors what CI does — catches type/lint regressions before packing.\n3. Resolves any `workspace:*` dependency ranges to concrete versions read\n from sibling `package.json`s. If you publish from a workspace, **never**\n ship `workspace:*` to npm — `plugin-pack` rewrites them at pack time and\n restores your source on disk afterward, so your dev tree is unchanged.\n4. Calls `bun pm pack --destination <out-dir>` to produce the tarball.\n5. (Bundle mode only) Wraps the per-package tarballs in an outer\n `<name>-<version>-bundle.tgz` with a `bundle.json` manifest.\n\n### Two output modes\n\n**Per-package mode** (default — what you publish to npm):\n\n```bash\ncd packages/widget-backend\nbun run pack\n# → dist/my-org-widget-backend-1.2.3.tgz\n```\n\n**Bundle mode** (what you attach to a GitHub release or upload via the\nPlugin Manager UI):\n\n```bash\ncd packages/widget-backend # the *primary* — the one with checkstack.bundle\nbun run pack -- --bundle\n# → dist/my-org-widget-backend-1.2.3-bundle.tgz\n```\n\nBundle tarballs are **never** published to npm. npm always gets the\nper-package `.tgz`s individually.\n\n## Multi-package plugins (bundles)\n\nA \"plugin\" the operator installs may consist of several npm packages — the\nclassic example is a backend (`-backend`), a frontend (`-frontend`), and a\nshared types package (`-common`). The platform installs and uninstalls\nall of them atomically.\n\n### Declaring a bundle\n\nPick one package as the **primary** (typically `-backend`) and add a\n`checkstack.bundle` array listing the sibling package names:\n\n```json\n// my-org-widget-backend/package.json\n{\n \"name\": \"@my-org/widget-backend\",\n \"version\": \"1.2.3\",\n \"checkstack\": {\n \"type\": \"backend\",\n \"pluginId\": \"widget\",\n \"bundle\": [\n \"@my-org/widget-common\",\n \"@my-org/widget-frontend\"\n ]\n }\n}\n```\n\nSiblings should NOT carry `checkstack.bundle` — only the primary does.\nSiblings can still ship the same `pluginId` if they're part of the same\nlogical plugin.\n\n### Versioning rule\n\nAll siblings in a bundle **must share the same `version`** at pack time.\nThe compatibility checker resolves bundle-internal `@checkstack/*`\ndependencies against the bundle's package set first, falling back to the\nplatform's loaded versions only when the dep isn't part of the bundle.\nMismatched sibling versions will fail the install with a clear message.\n\nUse changesets or a release tool that bumps siblings in lockstep.\n\n### Installing bundles via npm\n\nFor npm distribution, **publish each sibling separately** as a normal npm\npackage. The platform installs the primary by name; on `previewInstall`,\nthe runtime resolves each sibling from `checkstack.bundle` against the same\nregistry and pins to the primary's exact version. The Plugin Manager UI\nshows the full list before the operator confirms.\n\n```bash\n# In CI, publish each sibling\ncd packages/widget-common && bun publish --access public\ncd packages/widget-backend && bun publish --access public\ncd packages/widget-frontend && bun publish --access public\n```\n\n### Distributing bundles via GitHub release or tarball upload\n\nFor GitHub or direct upload, use **bundle mode** to produce a single outer\ntarball that contains every sibling and a manifest:\n\n```bash\nbun run pack -- --bundle\n```\n\nThe result has this layout:\n\n```\nmy-org-widget-backend-1.2.3-bundle.tgz\n├── bundle.json # manifest\n└── packages/\n ├── my-org-widget-common-1.2.3.tgz\n ├── my-org-widget-backend-1.2.3.tgz\n └── my-org-widget-frontend-1.2.3.tgz\n```\n\n`bundle.json`:\n\n```json\n{\n \"bundleVersion\": 1,\n \"primary\": \"@my-org/widget-backend\",\n \"packages\": [\n { \"name\": \"@my-org/widget-backend\", \"version\": \"1.2.3\",\n \"tarball\": \"packages/my-org-widget-backend-1.2.3.tgz\" },\n { \"name\": \"@my-org/widget-common\", \"version\": \"1.2.3\",\n \"tarball\": \"packages/my-org-widget-common-1.2.3.tgz\" },\n { \"name\": \"@my-org/widget-frontend\", \"version\": \"1.2.3\",\n \"tarball\": \"packages/my-org-widget-frontend-1.2.3.tgz\" }\n ]\n}\n```\n\nAttach this single tarball to your GitHub release; the platform unpacks\nall siblings on install.\n\n## Compatibility (no manual declaration needed)\n\nYou do **not** declare a \"compatible Checkstack version\" anywhere. The\nplatform reads the semver ranges in your plugin's `dependencies` block and\nchecks each `@checkstack/*` entry with `semver.satisfies` against the\nloaded core packages.\n\n```jsonc\n{\n \"dependencies\": {\n \"@checkstack/backend-api\": \"^0.20.0\", // → must match what's loaded\n \"@checkstack/widget-common\": \"^0.1.0\", // → resolved from bundle if present\n \"lodash\": \"^4.0.0\" // → ignored (not @checkstack/*)\n }\n}\n```\n\nIf the platform has `@checkstack/backend-api@2.0.0` loaded but your plugin\ndeclared `^1.0.0`, install fails with a `BAD_REQUEST` and the message\n`\"Plugin '...' requires @checkstack/backend-api@^1.0.0 but this platform\nhas 2.0.0.\"`\n\n`workspace:*` ranges are explicitly rejected by the runtime — they're a\npack-time-only construct. The `plugin-pack` CLI resolves them\nautomatically.\n\n## Distribution channels\n\n| Channel | Best for | Pack mode |\n|----------------------|---------------------------------------------|----------------|\n| npm (public) | Community plugins; broad discoverability | per-package |\n| npm (private) | Org-internal plugins behind an npm registry | per-package |\n| GitHub release | Plugins not published to npm; signed artifacts | `--bundle` |\n| GitHub Enterprise | Air-gapped / private GHE deployments | `--bundle` |\n| Tarball upload (UI) | Local dev; one-off testing | per-package or `--bundle` |\n\n### npm\n\nPublish per-package as you would any npm library. The platform's npm\ninstaller hits the registry's metadata endpoint\n(`<registry>/<package>/<version>`), downloads the `dist.tarball` URL, and\nstores the bytes in Postgres. Configurable per-source via\n`PluginSource.registry` so deployments behind a private registry (Verdaccio,\nJFrog, GitHub Packages, …) work without env-var twiddling.\n\n### GitHub releases\n\nConvention: each release tag has **exactly one** `.tgz` asset (the bundle\ntarball produced by `plugin-pack --bundle`). The platform fetches via the\nGitHub API, downloads the asset, validates, and stores.\n\nFor repos with multiple `.tgz` assets, the install source carries an\noptional `assetName` field — the Plugin Manager UI exposes this.\n\n### GitHub Enterprise\n\nThree additional fields on the install source:\n\n| Source field | Meaning |\n|----------------|--------------------------------------------------------------------------------|\n| `apiBaseUrl` | Your GHE API root, e.g. `https://github.example.com/api/v3`. Defaults to public github.com when omitted. |\n| `tokenEnvVar` | Name of the env var on the platform that holds the PAT. Defaults to `GITHUB_TOKEN`. |\n\nMultiple GitHub instances in the same deployment? Set different\n`tokenEnvVar` values per source — e.g. `GITHUB_TOKEN_PUBLIC`,\n`GITHUB_TOKEN_GHE`.\n\n### Tarball upload (Plugin Manager UI)\n\nOperators can drag-and-drop a `.tgz` directly. The platform:\n1. Stores the bytes in `plugin_artifacts` and returns an `artifactId`.\n2. Treats it the same as a GitHub-release source — peek the package.json or\n `bundle.json`, validate, install.\n\nUseful for local development where you don't want to push to a registry.\nThe 50MB tarball cap applies to all sources.\n\n## Recommended release workflow (GitHub Actions)\n\nA copy-paste starting point lives at\n[`plugin-release.yml`](/checkstack/examples/plugin-release.yml). The\nshort version:\n\n```yaml\nname: Release Plugin\non:\n push:\n tags: [\"v*.*.*\"]\npermissions:\n contents: write # to upload release assets\n id-token: write # for npm provenance\njobs:\n release:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: oven-sh/setup-bun@v2\n with: { bun-version: latest }\n - run: bun install --frozen-lockfile\n - run: bunx @checkstack/scripts plugin-pack --bundle\n - name: Attach bundle to release\n uses: softprops/action-gh-release@v2\n with:\n files: dist/*-bundle.tgz\n # Optional: publish per-package to npm in a matrix\n # - run: cd packages/widget-backend && bun publish --access public\n # env:\n # NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}\n```\n\nFor per-package npm publish, run `bun publish --access public` in each\nsibling directory. Don't run `bun publish` on the bundle tarball — npm\nwon't accept it.\n\n## CI-friendly metadata validation\n\nLint your `package.json` against the install-time schema in your own CI,\nwithout pack:\n\n```bash\nbunx @checkstack/scripts plugin-pack --validate-only\n```\n\nReturns non-zero on any schema violation with a per-field error list. We\nrun this in our own publish script before each `bun publish` —\n[`scripts/publish-packages.ts`](https://github.com/enyineer/checkstack/blob/main/scripts/publish-packages.ts)\nis a working reference if you want to mirror the pattern.\n\n## Security model\n\nPlugins run **in-process with full platform access** — same Bun event\nloop, same database connection, same secrets. The platform's defenses are:\n\n1. **Strong typed-confirmation modal** — operator types the exact plugin\n name to install. No \"click OK\" muscle-memory bypass.\n2. **`bun install --ignore-scripts`** — postinstall scripts in the plugin\n and its transitive deps don't execute. Opt out per-plugin via\n `checkstack.allowInstallScripts: true`; this surfaces in the security\n warning so the operator sees it.\n3. **Source disclosure** — the install modal shows source type\n (npm/github/tarball), the package name + version, the author, license,\n homepage, and any compatibility issues before commit.\n4. **Bundle atomicity** — partial installs aren't possible; if any sibling\n fails validation, none commit.\n\nThe platform does **not** sandbox plugin code (process isolation, V8\ncontexts, etc. — see the design doc for why). Operators should only install\nplugins from trusted authors. Plugin authors should make their distribution\nchannels easy to audit (signed tags, reproducible builds, public source).\n\n## See also\n\n- [Developing Plugins in Isolation](/checkstack/developer-guide/getting-started/plugin-development/) —\n running Checkstack locally to develop your plugin, iteration patterns\n- [Plugin Architecture Overview](/checkstack/developer-guide/architecture/plugin-system/) — how plugins fit\n into Checkstack at runtime\n- [Backend Plugin Development](/checkstack/developer-guide/backend/plugins/) — writing the\n `-backend` package\n- [Frontend Plugin Development](/checkstack/developer-guide/frontend/plugins/) — writing the\n `-frontend` package\n- [`plugin-release.yml`](/checkstack/examples/plugin-release.yml) —\n GitHub Actions release workflow template",
|
|
280
|
+
"content": "This guide is for **plugin authors** — anyone publishing a Checkstack plugin\nthat operators install via the runtime Plugin Manager UI. It covers the\nrequired `package.json` shape, how to pack with the\n`@checkstack/scripts plugin-pack` CLI, single-package vs bundle mode, and\nrelease workflow patterns for npm, GitHub, and direct tarball delivery.\n\nIf you only want to *consume* a plugin as a platform operator, see\n[Install a plugin](/checkstack/user-guide/guides/install-a-plugin/) for\nthe Plugin Manager UI walkthrough.\n\n> **Looking for the dev loop?** This doc covers the *distribution\n> mechanics* (packing, bundles, channels). For local development — add\n> `@checkstack/dev-server` as a devDependency, wire `\"dev\": \"checkstack-dev\"`\n> into your `package.json` scripts, and run `bun run dev` — see\n> [Developing Plugins in Isolation](/checkstack/developer-guide/getting-started/plugin-development/).\n\n> **Distinction:** monorepo-internal plugins (the ones living in `core/` and\n> `plugins/` of this repo) are loaded automatically at boot via filesystem\n> discovery. Everything below is about plugins that ship **independently**\n> via npm / GitHub release / tarball upload — the mechanism the runtime\n> Plugin Manager uses.\n\n## Anatomy of an installable plugin\n\nEvery installable plugin is just an npm package whose `package.json` declares\na `checkstack` block. The platform's install pipeline validates this block\n(plus a few standard fields) on every install.\n\n### Required `package.json` fields\n\n| Field | Source | Notes |\n|---------------------------|-----------------------|---------------------------------------------------------------------------------------------|\n| `name` | standard | Must be the npm package name. Scoped names (`@org/foo`) are fine. |\n| `version` | standard | A valid semver string. Used for compatibility checks. |\n| `description` | standard | One-line summary shown in the install confirmation modal. |\n| `author` | standard | String (`\"Jane <jane@example.com>\"`) or object (`{ name, email?, url? }`). |\n| `license` | standard | Any SPDX identifier (or `SEE LICENSE IN ...`). |\n| `checkstack.type` | Checkstack | One of `\"backend\"`, `\"frontend\"`, `\"common\"`. |\n| `checkstack.pluginId` | Checkstack | Stable runtime id (e.g. `\"healthcheck-http\"`). Must match `pluginId` in `plugin-metadata.ts`.|\n\n### Optional fields\n\n| Field | Notes |\n|------------------------------------|----------------------------------------------------------------------------------------------------|\n| `homepage` | Linked from the install confirmation modal. |\n| `repository` | Standard `repository` form. Surfaced in the admin UI for the operator's reference. |\n| `checkstack.bundle` | Array of sibling package names that install/uninstall atomically with this one. Set on the **primary** package only. |\n| `checkstack.usageInstructions` | Markdown string shown in the install confirmation modal. Use it to describe required env vars, config, integrations, etc. |\n| `checkstack.allowInstallScripts` | Default `false`. When `true`, the platform runs `bun install` *without* `--ignore-scripts`. Surfaces in the security warning. Use sparingly — it's the loudest dial-up of trust requirements during install. |\n\nA minimal valid package.json:\n\n```json\n{\n \"name\": \"@my-org/widget-backend\",\n \"version\": \"1.2.3\",\n \"description\": \"Widget tracker for Checkstack\",\n \"author\": \"ACME Corp\",\n \"license\": \"MIT\",\n \"checkstack\": {\n \"type\": \"backend\",\n \"pluginId\": \"widget\"\n },\n \"devDependencies\": {\n \"@checkstack/scripts\": \"^0.4.0\"\n },\n \"scripts\": {\n \"pack\": \"checkstack-scripts plugin-pack\"\n }\n}\n```\n\nThe `pack` script is the single supported entrypoint. It calls the\n`checkstack-scripts` bin from the `@checkstack/scripts` devDependency (resolved\nfrom `node_modules/.bin`), so a committed script always runs the pinned version\n- not a cache-resolved \"latest\". Do not call `bun pm pack` directly -\n`plugin-pack` validates metadata before packing, catching issues at build time\ninstead of install time.\n\n## The `plugin-pack` CLI\n\nFor a one-off run without installing, use `bunx` with an explicit `@latest` so\nBun does not execute a stale cached copy:\n\n```bash\nbunx @checkstack/scripts@latest plugin-pack --help\n```\n\n```\nUsage: checkstack-scripts plugin-pack [options]\n\nOptions:\n --bundle Pack the primary plus every sibling declared in\n package.json#checkstack.bundle into a single outer\n tarball with a bundle.json manifest.\n --out-dir <dir> Output directory (default: ./dist)\n --validate-only Only validate metadata; do not pack.\n --cwd <dir> Run as if invoked from <dir> (default: process.cwd())\n --help, -h Show this message.\n```\n\n### What it does (in order)\n\n1. Reads `<cwd>/package.json` and validates it against\n `installPackageMetadataSchema` — the same Zod schema the runtime install\n pipeline uses. Failures are reported with the specific field path so you\n know exactly what to fix.\n2. Runs `bun run typecheck` and `bun run lint` if those scripts exist.\n Mirrors what CI does — catches type/lint regressions before packing.\n3. Resolves any `workspace:*` dependency ranges to concrete versions read\n from sibling `package.json`s. If you publish from a workspace, **never**\n ship `workspace:*` to npm — `plugin-pack` rewrites them at pack time and\n restores your source on disk afterward, so your dev tree is unchanged.\n4. Calls `bun pm pack --destination <out-dir>` to produce the tarball.\n5. (Bundle mode only) Wraps the per-package tarballs in an outer\n `<name>-<version>-bundle.tgz` with a `bundle.json` manifest.\n\n### Two output modes\n\n**Per-package mode** (default — what you publish to npm):\n\n```bash\ncd packages/widget-backend\nbun run pack\n# → dist/my-org-widget-backend-1.2.3.tgz\n```\n\n**Bundle mode** (what you attach to a GitHub release or upload via the\nPlugin Manager UI):\n\n```bash\ncd packages/widget-backend # the *primary* — the one with checkstack.bundle\nbun run pack -- --bundle\n# → dist/my-org-widget-backend-1.2.3-bundle.tgz\n```\n\nBundle tarballs are **never** published to npm. npm always gets the\nper-package `.tgz`s individually.\n\n## Multi-package plugins (bundles)\n\nA \"plugin\" the operator installs may consist of several npm packages — the\nclassic example is a backend (`-backend`), a frontend (`-frontend`), and a\nshared types package (`-common`). The platform installs and uninstalls\nall of them atomically.\n\n### Declaring a bundle\n\nPick one package as the **primary** (typically `-backend`) and add a\n`checkstack.bundle` array listing the sibling package names:\n\n```json\n// my-org-widget-backend/package.json\n{\n \"name\": \"@my-org/widget-backend\",\n \"version\": \"1.2.3\",\n \"checkstack\": {\n \"type\": \"backend\",\n \"pluginId\": \"widget\",\n \"bundle\": [\n \"@my-org/widget-common\",\n \"@my-org/widget-frontend\"\n ]\n }\n}\n```\n\nSiblings should NOT carry `checkstack.bundle` — only the primary does.\nSiblings can still ship the same `pluginId` if they're part of the same\nlogical plugin.\n\n### Versioning rule\n\nAll siblings in a bundle **must share the same `version`** at pack time.\nThe compatibility checker resolves bundle-internal `@checkstack/*`\ndependencies against the bundle's package set first, falling back to the\nplatform's loaded versions only when the dep isn't part of the bundle.\nMismatched sibling versions will fail the install with a clear message.\n\nUse changesets or a release tool that bumps siblings in lockstep.\n\n### Installing bundles via npm\n\nFor npm distribution, **publish each sibling separately** as a normal npm\npackage. The platform installs the primary by name; on `previewInstall`,\nthe runtime resolves each sibling from `checkstack.bundle` against the same\nregistry and pins to the primary's exact version. The Plugin Manager UI\nshows the full list before the operator confirms.\n\n```bash\n# In CI, publish each sibling\ncd packages/widget-common && bun publish --access public\ncd packages/widget-backend && bun publish --access public\ncd packages/widget-frontend && bun publish --access public\n```\n\n### Distributing bundles via GitHub release or tarball upload\n\nFor GitHub or direct upload, use **bundle mode** to produce a single outer\ntarball that contains every sibling and a manifest:\n\n```bash\nbun run pack -- --bundle\n```\n\nThe result has this layout:\n\n```\nmy-org-widget-backend-1.2.3-bundle.tgz\n├── bundle.json # manifest\n└── packages/\n ├── my-org-widget-common-1.2.3.tgz\n ├── my-org-widget-backend-1.2.3.tgz\n └── my-org-widget-frontend-1.2.3.tgz\n```\n\n`bundle.json`:\n\n```json\n{\n \"bundleVersion\": 1,\n \"primary\": \"@my-org/widget-backend\",\n \"packages\": [\n { \"name\": \"@my-org/widget-backend\", \"version\": \"1.2.3\",\n \"tarball\": \"packages/my-org-widget-backend-1.2.3.tgz\" },\n { \"name\": \"@my-org/widget-common\", \"version\": \"1.2.3\",\n \"tarball\": \"packages/my-org-widget-common-1.2.3.tgz\" },\n { \"name\": \"@my-org/widget-frontend\", \"version\": \"1.2.3\",\n \"tarball\": \"packages/my-org-widget-frontend-1.2.3.tgz\" }\n ]\n}\n```\n\nAttach this single tarball to your GitHub release; the platform unpacks\nall siblings on install.\n\n## Compatibility (no manual declaration needed)\n\nYou do **not** declare a \"compatible Checkstack version\" anywhere. The\nplatform reads the semver ranges in your plugin's `dependencies` block and\nchecks each `@checkstack/*` entry with `semver.satisfies` against the\nloaded core packages.\n\n```jsonc\n{\n \"dependencies\": {\n \"@checkstack/backend-api\": \"^0.20.0\", // → must match what's loaded\n \"@checkstack/widget-common\": \"^0.1.0\", // → resolved from bundle if present\n \"lodash\": \"^4.0.0\" // → ignored (not @checkstack/*)\n }\n}\n```\n\nIf the platform has `@checkstack/backend-api@2.0.0` loaded but your plugin\ndeclared `^1.0.0`, install fails with a `BAD_REQUEST` and the message\n`\"Plugin '...' requires @checkstack/backend-api@^1.0.0 but this platform\nhas 2.0.0.\"`\n\n`workspace:*` ranges are explicitly rejected by the runtime — they're a\npack-time-only construct. The `plugin-pack` CLI resolves them\nautomatically.\n\n## Distribution channels\n\n| Channel | Best for | Pack mode |\n|----------------------|---------------------------------------------|----------------|\n| npm (public) | Community plugins; broad discoverability | per-package |\n| npm (private) | Org-internal plugins behind an npm registry | per-package |\n| GitHub release | Plugins not published to npm; signed artifacts | `--bundle` |\n| GitHub Enterprise | Air-gapped / private GHE deployments | `--bundle` |\n| Tarball upload (UI) | Local dev; one-off testing | per-package or `--bundle` |\n\n### npm\n\nPublish per-package as you would any npm library. The platform's npm\ninstaller hits the registry's metadata endpoint\n(`<registry>/<package>/<version>`), downloads the `dist.tarball` URL, and\nstores the bytes in Postgres. Configurable per-source via\n`PluginSource.registry` so deployments behind a private registry (Verdaccio,\nJFrog, GitHub Packages, …) work without env-var twiddling.\n\n### GitHub releases\n\nConvention: each release tag has **exactly one** `.tgz` asset (the bundle\ntarball produced by `plugin-pack --bundle`). The platform fetches via the\nGitHub API, downloads the asset, validates, and stores.\n\nFor repos with multiple `.tgz` assets, the install source carries an\noptional `assetName` field — the Plugin Manager UI exposes this.\n\n### GitHub Enterprise\n\nThree additional fields on the install source:\n\n| Source field | Meaning |\n|----------------|--------------------------------------------------------------------------------|\n| `apiBaseUrl` | Your GHE API root, e.g. `https://github.example.com/api/v3`. Defaults to public github.com when omitted. |\n| `tokenEnvVar` | Name of the env var on the platform that holds the PAT. Defaults to `GITHUB_TOKEN`. |\n\nMultiple GitHub instances in the same deployment? Set different\n`tokenEnvVar` values per source — e.g. `GITHUB_TOKEN_PUBLIC`,\n`GITHUB_TOKEN_GHE`.\n\n### Tarball upload (Plugin Manager UI)\n\nOperators can drag-and-drop a `.tgz` directly. The platform:\n1. Stores the bytes in `plugin_artifacts` and returns an `artifactId`.\n2. Treats it the same as a GitHub-release source — peek the package.json or\n `bundle.json`, validate, install.\n\nUseful for local development where you don't want to push to a registry.\nThe 50MB tarball cap applies to all sources.\n\n## Recommended release workflow (GitHub Actions)\n\nA copy-paste starting point lives at\n[`plugin-release.yml`](/checkstack/examples/plugin-release.yml). The\nshort version:\n\n```yaml\nname: Release Plugin\non:\n push:\n tags: [\"v*.*.*\"]\npermissions:\n contents: write # to upload release assets\n id-token: write # for npm provenance\njobs:\n release:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: oven-sh/setup-bun@v2\n with: { bun-version: latest }\n - run: bun install --frozen-lockfile\n - run: bunx @checkstack/scripts@latest plugin-pack --bundle\n - name: Attach bundle to release\n uses: softprops/action-gh-release@v2\n with:\n files: dist/*-bundle.tgz\n # Optional: publish per-package to npm in a matrix\n # - run: cd packages/widget-backend && bun publish --access public\n # env:\n # NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}\n```\n\nFor per-package npm publish, run `bun publish --access public` in each\nsibling directory. Don't run `bun publish` on the bundle tarball — npm\nwon't accept it.\n\n## CI-friendly metadata validation\n\nLint your `package.json` against the install-time schema in your own CI,\nwithout pack:\n\n```bash\nbunx @checkstack/scripts@latest plugin-pack --validate-only\n```\n\nReturns non-zero on any schema violation with a per-field error list. We\nrun this in our own publish script before each `bun publish` —\n[`scripts/publish-packages.ts`](https://github.com/enyineer/checkstack/blob/main/scripts/publish-packages.ts)\nis a working reference if you want to mirror the pattern.\n\n## Security model\n\nPlugins run **in-process with full platform access** — same Bun event\nloop, same database connection, same secrets. The platform's defenses are:\n\n1. **Strong typed-confirmation modal** — operator types the exact plugin\n name to install. No \"click OK\" muscle-memory bypass.\n2. **`bun install --ignore-scripts`** — postinstall scripts in the plugin\n and its transitive deps don't execute. Opt out per-plugin via\n `checkstack.allowInstallScripts: true`; this surfaces in the security\n warning so the operator sees it.\n3. **Source disclosure** — the install modal shows source type\n (npm/github/tarball), the package name + version, the author, license,\n homepage, and any compatibility issues before commit.\n4. **Bundle atomicity** — partial installs aren't possible; if any sibling\n fails validation, none commit.\n\nThe platform does **not** sandbox plugin code (process isolation, V8\ncontexts, etc. — see the design doc for why). Operators should only install\nplugins from trusted authors. Plugin authors should make their distribution\nchannels easy to audit (signed tags, reproducible builds, public source).\n\n## See also\n\n- [Developing Plugins in Isolation](/checkstack/developer-guide/getting-started/plugin-development/) —\n running Checkstack locally to develop your plugin, iteration patterns\n- [Plugin Architecture Overview](/checkstack/developer-guide/architecture/plugin-system/) — how plugins fit\n into Checkstack at runtime\n- [Backend Plugin Development](/checkstack/developer-guide/backend/plugins/) — writing the\n `-backend` package\n- [Frontend Plugin Development](/checkstack/developer-guide/frontend/plugins/) — writing the\n `-frontend` package\n- [`plugin-release.yml`](/checkstack/examples/plugin-release.yml) —\n GitHub Actions release workflow template",
|
|
281
281
|
"truncated": false
|
|
282
282
|
},
|
|
283
283
|
{
|
|
@@ -317,7 +317,7 @@ export const DOCS_INDEX: readonly DocsIndexEntry[] = [
|
|
|
317
317
|
"Hybrid",
|
|
318
318
|
"Next Steps"
|
|
319
319
|
],
|
|
320
|
-
"content": "## Introduction\n\nCheckstack is built on a **pluggable architecture** that enables extensibility, modularity, and flexible deployment options. Everything beyond the core framework is implemented as a plugin, allowing the system to scale from monolithic deployments to distributed microservices.\n\n## Core Principles\n\n### 1. Runtime Registration\n\nPlugins **MUST** be registerable at runtime. This design enables:\n- Loading plugins from remote sources without code changes\n- Hot-swapping plugins during development\n- Dynamic feature enablement based on deployment needs\n\nThe platform supports four install sources, all going through a discriminated\n`PluginSource` union and a per-source `PluginInstaller`:\n\n| Source | Use case |\n|------------|-------------------------------------------------|\n| `npm` | Public or private npm registry (configurable) |\n| `tarball` | Uploaded `.tgz` (filesystem analogue) |\n| `github` | GitHub release asset (`.tgz` packed by our CLI) |\n| `catalog` | Curated marketplace (stub — coming soon) |\n\nPlugin tarballs (single package or `--bundle`-mode multi-package) are\npersisted in `plugin_artifacts` (Postgres `bytea`). A freshly spun replica\nrecovers every runtime-installed plugin from this table at boot — no\nre-fetch from the original source is needed for replicas to come up.\n\nFor plugin authors: see [Plugin Distribution & Packing](/checkstack/developer-guide/architecture/plugin-distribution/)\nfor the developer-facing guide on packing, bundles, npm/GitHub/tarball\ndistribution, and the `bunx @checkstack/scripts plugin-pack` CLI.\n\n### 2. Inversion of Control (IoC)\n\nPlugins register themselves with the core application through well-defined interfaces:\n- **Backend plugins** register via `BackendPluginRegistry`\n- **Frontend plugins** register via `FrontendPlugin` interface\n\nThe core calls plugin registration functions, not the other way around.\n\n### 3. Secure Service-to-Service Communication\n\nAll plugin-to-plugin communication happens via:\n- **HTTPS** for transport security\n- **Signed JWTs** for authentication\n- **Configured secrets** for token signing\n\nThis ensures security even in distributed deployments.\n\n### 4. Modular Project Structure\n\nEach plugin is a standalone npm package that can:\n- Run independently\n- Be deployed as part of a monolith\n- Be deployed as a separate microservice\n- Share code through common packages\n\n## Project Structure\n\n```\ncheckstack/\n├── core/\n│ ├── backend/ # Core backend framework\n│ ├── frontend/ # Core frontend framework\n│ ├── backend-api/ # Backend plugin API\n│ ├── frontend-api/ # Frontend plugin API\n│ ├── common/ # Shared core types\n│ ├── ui/ # Shared UI components\n│ │\n│ ├── auth-*/ # Authentication (essential)\n│ ├── catalog-*/ # Entity management (essential)\n│ ├── notification-*/ # Notifications (essential)\n│ ├── healthcheck-*/ # Health monitoring (essential)\n│ ├── satellite-*/ # Remote satellite agents (essential)\n│ ├── queue-*/ # Queue abstraction (essential)\n│ └── theme-*/ # UI theming (essential)\n│\n└── plugins/ # Replaceable providers only\n ├── auth-github-backend/ # GitHub OAuth provider\n ├── auth-credential-backend/ # Username/password auth\n ├── auth-ldap-backend/ # LDAP auth provider\n ├── queue-bullmq-*/ # BullMQ implementation\n ├── queue-memory-*/ # In-memory implementation\n └── healthcheck-http-backend/ # HTTP health strategy\n```\n\n> **Note:** See [Packages vs Plugins Architecture](/checkstack/developer-guide/architecture/packages-vs-plugins/) for decision criteria on when to create a package vs a plugin.\n\n## Package Types\n\nCheckstack uses a strict package type system to maintain clean architecture:\n\n| Package Type | Suffix/Pattern | Purpose | Can Depend On |\n|--------------|---------------|---------|---------------|\n| **Backend** | `-backend` | REST APIs, business logic, database | Backend packages, common packages |\n| **Frontend** | `-frontend` | UI components, pages, routing | Frontend packages, common packages |\n| **Common** | `-common` | Shared types, access rules, constants | Common packages only |\n| **Node** | `-node` | Backend-only shared code | Backend packages, common packages |\n| **React** | `-react` | Frontend-only shared components | Frontend packages, common packages |\n\n### Dependency Rules\n\nThese rules are **automatically enforced** by the dependency linter:\n\n- ✅ **Common** → Common only\n- ✅ **Frontend** → Frontend or Common\n- ✅ **Backend** → Backend or Common\n- ❌ **Common** → Backend or Frontend (FORBIDDEN)\n- ❌ **Frontend** → Backend (FORBIDDEN)\n\nSee [dependency-linter.md](/checkstack/developer-guide/tooling/dependency-linter/) for details.\n\n## Plugin Lifecycle\n\n### Backend Plugin Lifecycle\n\nBackend plugins use a **two-phase initialization** to ensure cross-plugin communication works correctly:\n\n```mermaid\ngraph TD\n A[Plugin Discovery] --> B[Load Plugin Module]\n B --> C[Create Plugin Schema]\n C --> D[Run Migrations]\n D --> E[Call register function]\n E --> F[Register Access Rules]\n E --> G[Register Services]\n E --> H[Register Extension Points]\n E --> I[Register Init Function]\n \n subgraph \"Phase 2: Init\"\n I --> J[Resolve Dependencies]\n J --> K[Call init - Register routers]\n end\n \n subgraph \"Phase 3: After Plugins Ready\"\n K --> L[All Plugins Initialized]\n L --> M[Call afterPluginsReady]\n M --> N[Cross-plugin RPC + Hooks]\n end\n \n N --> O[Plugin Active]\n```\n\n> **Key Point:** The `init` function registers routers and services. The `afterPluginsReady` callback runs after ALL plugins have initialized, making it safe to:\n> - Call other plugins via RPC\n> - Subscribe to hooks (`onHook`)\n> - Emit hooks (`emitHook`)\n\n### Frontend Plugin Lifecycle\n\n```mermaid\ngraph TD\n A[Plugin Discovery] --> B[Load Plugin Module]\n B --> C[Register APIs]\n C --> D[Register Routes]\n D --> E[Register Nav Items]\n E --> F[Register Extensions]\n F --> G[Plugin Active]\n```\n\n## Database Isolation\n\nEach backend plugin gets its own **isolated PostgreSQL schema**:\n\n```\nDatabase: checkstack\n├── Schema: public (core only)\n├── Schema: plugin_catalog-backend\n├── Schema: plugin_auth-backend\n└── Schema: plugin_healthcheck-backend\n```\n\n### Benefits\n\n- **Namespace isolation**: No table name conflicts\n- **Independent migrations**: Each plugin manages its own schema\n- **Security**: Plugins can't access each other's data directly\n- **Scalability**: Easy to split into separate databases later\n\nSee [Drizzle Schema Isolation](/checkstack/developer-guide/backend/drizzle-schema/) for implementation details.\n\n## Extension Points\n\nExtension points enable plugins to provide implementations for core functionality:\n\n### Backend Extension Points\n\n- **HealthCheckStrategy**: Implement custom health check methods\n- **ExporterStrategy**: Export metrics and data in various formats\n- **NotificationStrategy**: Send notifications via different channels\n- **AuthenticationStrategy**: Integrate authentication providers\n\n### Frontend Extension Points\n\n- **Slots**: Inject UI components into predefined locations\n- **Routes**: Add new pages to the application\n- **APIs**: Provide client-side services\n\nSee [Extension Points](/checkstack/developer-guide/frontend/extension-points/) for detailed documentation.\n\n## Configuration Management\n\nPlugins use **versioned configurations** to support backward compatibility:\n\n```typescript\ninterface VersionedConfig<T> {\n version: number;\n pluginId: string;\n data: T;\n migratedAt?: Date;\n originalVersion?: number;\n}\n```\n\nThis enables:\n- Schema evolution without breaking existing configs\n- Automatic migration of old configurations\n- Rollback support\n\nSee [versioned-configs.md](/checkstack/developer-guide/backend/versioned-configs/) for details.\n\n## Communication Patterns\n\n### Frontend ↔ Backend\n\n```mermaid\nsequenceDiagram\n participant F as Frontend Plugin\n participant FA as Fetch API\n participant R as Router\n participant B as Backend Plugin\n \n F->>FA: Request with credentials\n FA->>R: HTTPS + JWT\n R->>R: Validate JWT\n R->>R: Check access\n R->>B: Route to plugin\n B->>R: Response\n R->>FA: JSON response\n FA->>F: Typed data\n```\n\n### Backend ↔ Backend\n\n```mermaid\nsequenceDiagram\n participant P1 as Plugin A\n participant S as Service Registry\n participant P2 as Plugin B\n \n P1->>S: Get service reference\n S->>P1: Service instance\n P1->>P2: Call via HTTPS + JWT\n P2->>P2: Validate service token\n P2->>P1: Response\n```\n\n### WebSocket (Plugin-Registered)\n\nPlugins can register custom WebSocket endpoints via the **WebSocket Route Registry**. All routes are automatically namespaced by plugin ID to prevent collisions:\n\n```typescript\n// In satellite-backend's afterPluginsReady:\nwsRegistry.register(\"/\", wsHandler);\n// → Available at /api/ws/satellite\n\n// Plugins can register sub-paths too:\nwsRegistry.register(\"/events\", eventsHandler);\n// → Available at /api/ws/{pluginId}/events\n```\n\nThe registry uses the same **scoped factory pattern** as RPC and health check registries — plugins never provide their ID manually.\n\n> **Note:** The signal/realtime WebSocket (`/api/signals/ws`) uses Bun's native pub/sub and is handled separately from the registry.\n\n## Access System\n\nAccess rules are defined in common packages and registered by backend plugins:\n\n```typescript\n// In catalog-common\nexport const access = {\n entityRead: {\n id: \"entity.read\",\n description: \"Read Systems and Groups\",\n },\n} satisfies Record<string, AccessRule>;\n\n// In catalog-backend\nenv.registerAccessRules(accessRuleList);\n\n// In catalog-frontend\nconst canRead = accessApi.useAccess(access.entityRead.id);\n```\n\nThe core automatically prefixes access rules with the plugin ID: `catalog.entity.read`\n\n## Technology Stack\n\n### Backend\n- **Runtime**: Bun\n- **Framework**: Hono (HTTP routing)\n- **Database**: PostgreSQL + Drizzle ORM\n- **Validation**: Zod\n- **Testing**: Bun test runner\n\n### Frontend\n- **Framework**: React\n- **Routing**: React Router DOM\n- **UI**: ShadCN + Tailwind CSS\n- **Build**: Vite\n- **Testing**: Playwright (E2E)\n\n## Deployment Options\n\n### Monolith (Default)\n\nAll plugins run in a single process:\n```bash\nbun run dev\n```\n\n### Microservices\n\nEach plugin can run independently:\n```bash\n# Terminal 1\nbun run dev:backend --plugins=catalog-backend\n\n# Terminal 2\nbun run dev:backend --plugins=auth-backend\n\n# Terminal 3\nbun run dev:frontend\n```\n\n### Hybrid\n\nMix and match based on scaling needs:\n- Core + frequently-used plugins in monolith\n- Resource-intensive plugins as separate services\n- Geographic distribution for compliance\n\n## Next Steps\n\n- [Packages vs Plugins Architecture](/checkstack/developer-guide/architecture/packages-vs-plugins/)\n- [Plugin Distribution & Packing](/checkstack/developer-guide/architecture/plugin-distribution/)\n- [Backend Plugin Development](/checkstack/developer-guide/backend/plugins/)\n- [Frontend Plugin Development](/checkstack/developer-guide/frontend/plugins/)\n- [Common Plugin Guidelines](/checkstack/developer-guide/common/plugins/)\n- [Extension Points](/checkstack/developer-guide/frontend/extension-points/)\n- [Versioned Configurations](/checkstack/developer-guide/backend/versioned-configs/)\n- [Health and Readiness Probes](/checkstack/user-guide/reference/health-probes/)\n- [Contributing Guide](/checkstack/developer-guide/getting-started/contributing/)",
|
|
320
|
+
"content": "## Introduction\n\nCheckstack is built on a **pluggable architecture** that enables extensibility, modularity, and flexible deployment options. Everything beyond the core framework is implemented as a plugin, allowing the system to scale from monolithic deployments to distributed microservices.\n\n## Core Principles\n\n### 1. Runtime Registration\n\nPlugins **MUST** be registerable at runtime. This design enables:\n- Loading plugins from remote sources without code changes\n- Hot-swapping plugins during development\n- Dynamic feature enablement based on deployment needs\n\nThe platform supports four install sources, all going through a discriminated\n`PluginSource` union and a per-source `PluginInstaller`:\n\n| Source | Use case |\n|------------|-------------------------------------------------|\n| `npm` | Public or private npm registry (configurable) |\n| `tarball` | Uploaded `.tgz` (filesystem analogue) |\n| `github` | GitHub release asset (`.tgz` packed by our CLI) |\n| `catalog` | Curated marketplace (stub — coming soon) |\n\nPlugin tarballs (single package or `--bundle`-mode multi-package) are\npersisted in `plugin_artifacts` (Postgres `bytea`). A freshly spun replica\nrecovers every runtime-installed plugin from this table at boot — no\nre-fetch from the original source is needed for replicas to come up.\n\nFor plugin authors: see [Plugin Distribution & Packing](/checkstack/developer-guide/architecture/plugin-distribution/)\nfor the developer-facing guide on packing, bundles, npm/GitHub/tarball\ndistribution, and the `bunx @checkstack/scripts@latest plugin-pack` CLI.\n\n### 2. Inversion of Control (IoC)\n\nPlugins register themselves with the core application through well-defined interfaces:\n- **Backend plugins** register via `BackendPluginRegistry`\n- **Frontend plugins** register via `FrontendPlugin` interface\n\nThe core calls plugin registration functions, not the other way around.\n\n### 3. Secure Service-to-Service Communication\n\nAll plugin-to-plugin communication happens via:\n- **HTTPS** for transport security\n- **Signed JWTs** for authentication\n- **Configured secrets** for token signing\n\nThis ensures security even in distributed deployments.\n\n### 4. Modular Project Structure\n\nEach plugin is a standalone npm package that can:\n- Run independently\n- Be deployed as part of a monolith\n- Be deployed as a separate microservice\n- Share code through common packages\n\n## Project Structure\n\n```\ncheckstack/\n├── core/\n│ ├── backend/ # Core backend framework\n│ ├── frontend/ # Core frontend framework\n│ ├── backend-api/ # Backend plugin API\n│ ├── frontend-api/ # Frontend plugin API\n│ ├── common/ # Shared core types\n│ ├── ui/ # Shared UI components\n│ │\n│ ├── auth-*/ # Authentication (essential)\n│ ├── catalog-*/ # Entity management (essential)\n│ ├── notification-*/ # Notifications (essential)\n│ ├── healthcheck-*/ # Health monitoring (essential)\n│ ├── satellite-*/ # Remote satellite agents (essential)\n│ ├── queue-*/ # Queue abstraction (essential)\n│ └── theme-*/ # UI theming (essential)\n│\n└── plugins/ # Replaceable providers only\n ├── auth-github-backend/ # GitHub OAuth provider\n ├── auth-credential-backend/ # Username/password auth\n ├── auth-ldap-backend/ # LDAP auth provider\n ├── queue-bullmq-*/ # BullMQ implementation\n ├── queue-memory-*/ # In-memory implementation\n └── healthcheck-http-backend/ # HTTP health strategy\n```\n\n> **Note:** See [Packages vs Plugins Architecture](/checkstack/developer-guide/architecture/packages-vs-plugins/) for decision criteria on when to create a package vs a plugin.\n\n## Package Types\n\nCheckstack uses a strict package type system to maintain clean architecture:\n\n| Package Type | Suffix/Pattern | Purpose | Can Depend On |\n|--------------|---------------|---------|---------------|\n| **Backend** | `-backend` | REST APIs, business logic, database | Backend packages, common packages |\n| **Frontend** | `-frontend` | UI components, pages, routing | Frontend packages, common packages |\n| **Common** | `-common` | Shared types, access rules, constants | Common packages only |\n| **Node** | `-node` | Backend-only shared code | Backend packages, common packages |\n| **React** | `-react` | Frontend-only shared components | Frontend packages, common packages |\n\n### Dependency Rules\n\nThese rules are **automatically enforced** by the dependency linter:\n\n- ✅ **Common** → Common only\n- ✅ **Frontend** → Frontend or Common\n- ✅ **Backend** → Backend or Common\n- ❌ **Common** → Backend or Frontend (FORBIDDEN)\n- ❌ **Frontend** → Backend (FORBIDDEN)\n\nSee [dependency-linter.md](/checkstack/developer-guide/tooling/dependency-linter/) for details.\n\n## Plugin Lifecycle\n\n### Backend Plugin Lifecycle\n\nBackend plugins use a **two-phase initialization** to ensure cross-plugin communication works correctly:\n\n```mermaid\ngraph TD\n A[Plugin Discovery] --> B[Load Plugin Module]\n B --> C[Create Plugin Schema]\n C --> D[Run Migrations]\n D --> E[Call register function]\n E --> F[Register Access Rules]\n E --> G[Register Services]\n E --> H[Register Extension Points]\n E --> I[Register Init Function]\n \n subgraph \"Phase 2: Init\"\n I --> J[Resolve Dependencies]\n J --> K[Call init - Register routers]\n end\n \n subgraph \"Phase 3: After Plugins Ready\"\n K --> L[All Plugins Initialized]\n L --> M[Call afterPluginsReady]\n M --> N[Cross-plugin RPC + Hooks]\n end\n \n N --> O[Plugin Active]\n```\n\n> **Key Point:** The `init` function registers routers and services. The `afterPluginsReady` callback runs after ALL plugins have initialized, making it safe to:\n> - Call other plugins via RPC\n> - Subscribe to hooks (`onHook`)\n> - Emit hooks (`emitHook`)\n\n### Frontend Plugin Lifecycle\n\n```mermaid\ngraph TD\n A[Plugin Discovery] --> B[Load Plugin Module]\n B --> C[Register APIs]\n C --> D[Register Routes]\n D --> E[Register Nav Items]\n E --> F[Register Extensions]\n F --> G[Plugin Active]\n```\n\n## Database Isolation\n\nEach backend plugin gets its own **isolated PostgreSQL schema**:\n\n```\nDatabase: checkstack\n├── Schema: public (core only)\n├── Schema: plugin_catalog-backend\n├── Schema: plugin_auth-backend\n└── Schema: plugin_healthcheck-backend\n```\n\n### Benefits\n\n- **Namespace isolation**: No table name conflicts\n- **Independent migrations**: Each plugin manages its own schema\n- **Security**: Plugins can't access each other's data directly\n- **Scalability**: Easy to split into separate databases later\n\nSee [Drizzle Schema Isolation](/checkstack/developer-guide/backend/drizzle-schema/) for implementation details.\n\n## Extension Points\n\nExtension points enable plugins to provide implementations for core functionality:\n\n### Backend Extension Points\n\n- **HealthCheckStrategy**: Implement custom health check methods\n- **ExporterStrategy**: Export metrics and data in various formats\n- **NotificationStrategy**: Send notifications via different channels\n- **AuthenticationStrategy**: Integrate authentication providers\n\n### Frontend Extension Points\n\n- **Slots**: Inject UI components into predefined locations\n- **Routes**: Add new pages to the application\n- **APIs**: Provide client-side services\n\nSee [Extension Points](/checkstack/developer-guide/frontend/extension-points/) for detailed documentation.\n\n## Configuration Management\n\nPlugins use **versioned configurations** to support backward compatibility:\n\n```typescript\ninterface VersionedConfig<T> {\n version: number;\n pluginId: string;\n data: T;\n migratedAt?: Date;\n originalVersion?: number;\n}\n```\n\nThis enables:\n- Schema evolution without breaking existing configs\n- Automatic migration of old configurations\n- Rollback support\n\nSee [versioned-configs.md](/checkstack/developer-guide/backend/versioned-configs/) for details.\n\n## Communication Patterns\n\n### Frontend ↔ Backend\n\n```mermaid\nsequenceDiagram\n participant F as Frontend Plugin\n participant FA as Fetch API\n participant R as Router\n participant B as Backend Plugin\n \n F->>FA: Request with credentials\n FA->>R: HTTPS + JWT\n R->>R: Validate JWT\n R->>R: Check access\n R->>B: Route to plugin\n B->>R: Response\n R->>FA: JSON response\n FA->>F: Typed data\n```\n\n### Backend ↔ Backend\n\n```mermaid\nsequenceDiagram\n participant P1 as Plugin A\n participant S as Service Registry\n participant P2 as Plugin B\n \n P1->>S: Get service reference\n S->>P1: Service instance\n P1->>P2: Call via HTTPS + JWT\n P2->>P2: Validate service token\n P2->>P1: Response\n```\n\n### WebSocket (Plugin-Registered)\n\nPlugins can register custom WebSocket endpoints via the **WebSocket Route Registry**. All routes are automatically namespaced by plugin ID to prevent collisions:\n\n```typescript\n// In satellite-backend's afterPluginsReady:\nwsRegistry.register(\"/\", wsHandler);\n// → Available at /api/ws/satellite\n\n// Plugins can register sub-paths too:\nwsRegistry.register(\"/events\", eventsHandler);\n// → Available at /api/ws/{pluginId}/events\n```\n\nThe registry uses the same **scoped factory pattern** as RPC and health check registries — plugins never provide their ID manually.\n\n> **Note:** The signal/realtime WebSocket (`/api/signals/ws`) uses Bun's native pub/sub and is handled separately from the registry.\n\n## Access System\n\nAccess rules are defined in common packages and registered by backend plugins:\n\n```typescript\n// In catalog-common\nexport const access = {\n entityRead: {\n id: \"entity.read\",\n description: \"Read Systems and Groups\",\n },\n} satisfies Record<string, AccessRule>;\n\n// In catalog-backend\nenv.registerAccessRules(accessRuleList);\n\n// In catalog-frontend\nconst canRead = accessApi.useAccess(access.entityRead.id);\n```\n\nThe core automatically prefixes access rules with the plugin ID: `catalog.entity.read`\n\n## Technology Stack\n\n### Backend\n- **Runtime**: Bun\n- **Framework**: Hono (HTTP routing)\n- **Database**: PostgreSQL + Drizzle ORM\n- **Validation**: Zod\n- **Testing**: Bun test runner\n\n### Frontend\n- **Framework**: React\n- **Routing**: React Router DOM\n- **UI**: ShadCN + Tailwind CSS\n- **Build**: Vite\n- **Testing**: Playwright (E2E)\n\n## Deployment Options\n\n### Monolith (Default)\n\nAll plugins run in a single process:\n```bash\nbun run dev\n```\n\n### Microservices\n\nEach plugin can run independently:\n```bash\n# Terminal 1\nbun run dev:backend --plugins=catalog-backend\n\n# Terminal 2\nbun run dev:backend --plugins=auth-backend\n\n# Terminal 3\nbun run dev:frontend\n```\n\n### Hybrid\n\nMix and match based on scaling needs:\n- Core + frequently-used plugins in monolith\n- Resource-intensive plugins as separate services\n- Geographic distribution for compliance\n\n## Next Steps\n\n- [Packages vs Plugins Architecture](/checkstack/developer-guide/architecture/packages-vs-plugins/)\n- [Plugin Distribution & Packing](/checkstack/developer-guide/architecture/plugin-distribution/)\n- [Backend Plugin Development](/checkstack/developer-guide/backend/plugins/)\n- [Frontend Plugin Development](/checkstack/developer-guide/frontend/plugins/)\n- [Common Plugin Guidelines](/checkstack/developer-guide/common/plugins/)\n- [Extension Points](/checkstack/developer-guide/frontend/extension-points/)\n- [Versioned Configurations](/checkstack/developer-guide/backend/versioned-configs/)\n- [Health and Readiness Probes](/checkstack/user-guide/reference/health-probes/)\n- [Contributing Guide](/checkstack/developer-guide/getting-started/contributing/)",
|
|
321
321
|
"truncated": false
|
|
322
322
|
},
|
|
323
323
|
{
|
|
@@ -1802,6 +1802,7 @@ export const DOCS_INDEX: readonly DocsIndexEntry[] = [
|
|
|
1802
1802
|
"headings": [
|
|
1803
1803
|
"Prerequisites",
|
|
1804
1804
|
"Bootstrap a new plugin repo",
|
|
1805
|
+
"Keep the tooling current",
|
|
1805
1806
|
"Core plugin dependencies are co-loaded",
|
|
1806
1807
|
"Frontend plugins",
|
|
1807
1808
|
"What `bun run dev` does",
|
|
@@ -1814,7 +1815,7 @@ export const DOCS_INDEX: readonly DocsIndexEntry[] = [
|
|
|
1814
1815
|
"Fallback: workspace fork",
|
|
1815
1816
|
"See also"
|
|
1816
1817
|
],
|
|
1817
|
-
"content": "Develop a Checkstack plugin from its own repo. No monorepo checkout. No\nupload loop. No Docker bind-mount tricks.\n\n```bash\nbunx @checkstack/dev-server\n```\n\n`@checkstack/dev-server` is the published npm package that ships the\ndev server; it exposes a `checkstack-dev` binary so once you've added\nit as a devDependency, your `package.json` can wire `\"dev\":\n\"checkstack-dev\"` and you run `bun run dev` from then on (see the\nbootstrap section below). The `bunx @checkstack/dev-server` form is for\na one-shot try before any install.\n\nThe command boots the same backend code path Checkstack uses\nin production, with two well-defined dev overrides:\n\n- **Filesystem plugin discovery is skipped.** Only your plugin loads —\n nothing else from a `core/` or `plugins/` directory.\n- **Auth is synthetic.** Every access rule the platform registers is\n auto-granted to a `dev-user` identity. No login flow.\n\nYour plugin's `register()` runs against a real `PluginManager`, real\n`coreServices.*`, real oRPC routing, real Drizzle migrations. The boot\ncode is the *exact* same module that ships in the production Docker image\n— there is no parallel \"dev backend\" stack to drift from.\n\nWhen you save a file under `./src`, the backend restarts. Bun cold-starts\nin well under a second for a single plugin, so the loop stays tight.\n\n## Prerequisites\n\n1. **Bun installed locally** (`curl -fsSL https://bun.sh/install | bash`).\n2. **A running Postgres** reachable at `localhost:5432`. The dev server\n doesn't ship one — it expects one. The smallest setup:\n\n ```bash\n docker run --name checkstack-dev-pg -d -p 5432:5432 \\\n -e POSTGRES_USER=checkstack \\\n -e POSTGRES_PASSWORD=checkstack \\\n -e POSTGRES_DB=checkstack \\\n postgres:16-alpine\n ```\n\n To point at a different Postgres, pass `--db-url` or set\n `DATABASE_URL`.\n\n3. **A valid plugin `package.json`.** The dev server validates the same\n `installPackageMetadataSchema` the runtime install pipeline uses, so\n missing or malformed fields fail fast before anything boots. See\n [Plugin Distribution & Packing](/checkstack/developer-guide/architecture/plugin-distribution/#anatomy-of-an-installable-plugin)\n for required fields.\n\n## Bootstrap a new plugin repo\n\nUse `create-checkstack-plugin` to scaffold a complete standalone workspace\n(a `common` contract package, a `backend` implementing it, and a `frontend`\nconsuming it) in one command:\n\n```bash\nbunx create-checkstack-plugin widget\n# or with bun create:\nbun create checkstack-plugin widget\n```\n\nYou will be prompted for an npm scope (e.g. `acme` for `@acme/widget-*`).\nTo accept defaults without prompts, pass `--yes`:\n\n```bash\nbunx create-checkstack-plugin widget --scope acme --yes\n```\n\nThe scaffolder resolves the concrete published `@checkstack/*` versions from\nthe registry at scaffold time (each package independently - they are 0.x and\nnot lockstepped) and writes them as caret ranges in the generated\n`package.json` files. It then runs `git init` in the new directory.\n\nThe result is a Bun workspace ready to boot:\n\n```\nwidget/\n package.json # private root: workspaces [\"packages/*\"], forwarding scripts\n tsconfig.json\n eslint.config.js\n .gitignore\n README.md\n packages/\n widget-common/ # shared contract, Zod schemas, access rules\n widget-backend/ # Drizzle schema, oRPC router, example CRUD procedures\n widget-frontend/ # React page consuming the typed client\n```\n\nThen:\n\n```bash\ncd widget\nbun install\nbun run dev\n# backend: http://localhost:3000\n# frontend: http://localhost:5173\n```\n\nThe backend serves the example `getItems` / `createItem` / ... procedures\nimmediately - a `drizzle/0000_init` migration runs automatically on boot to\ncreate the `items` table. No Redis, no queue, no extra config.\n\nTest the API with curl (auth is synthetic in dev mode):\n\n```bash\ncurl -X POST http://localhost:3000/api/widget/getItems \\\n -H 'content-type: application/json' \\\n -d '{\"json\": {}}'\n# → {\"json\": []}\n```\n\nOpen `http://localhost:5173` to see the frontend list page.\n\n> [!NOTE]\n> **Frontend HMR works from a published install.** The Vite dev server\n> resolves `@checkstack/frontend` (which ships as a dependency of\n> `@checkstack/dev-server`) and the Vite React plugin from the dev server's\n> own install location, so a plugin scaffolded and `bun install`ed from the\n> registry gets HMR without depending on `@checkstack/frontend` directly. A\n> `-frontend` sibling that lives in your workspace (the standalone scaffold\n> layout) is picked up by scanning sibling package directories, so it does\n> not need to be an installed dependency either. The one prerequisite is the\n> obvious one: run `bun install` so your plugin's dev dependencies (including\n> `@checkstack/dev-server`) are present before `bun run dev`.\n\n> [!TIP]\n> **Tailwind styling works in dev from a published install.** Your own\n> custom Tailwind utility classes are compiled into the dev CSS, not just\n> the built-in `@checkstack/ui` components. `@checkstack/frontend` ships the\n> Tailwind toolchain (`tailwindcss`, `autoprefixer`, `tailwindcss-animate`)\n> as runtime dependencies and exports a shared theme preset at\n> `@checkstack/frontend/tailwind-preset`. The dev server applies that preset\n> and injects your plugin's own source globs (`<plugin>/src/**`) into\n> Tailwind's `content`, so classes you write in your `-frontend` components\n> render live with HMR. If you want to reuse the platform theme in your own\n> Tailwind config, add the preset:\n>\n> ```js\n> // tailwind.config.js\n> import checkstackPreset from \"@checkstack/frontend/tailwind-preset\";\n>\n> export default {\n> presets: [checkstackPreset],\n> content: [\"./src/**/*.{ts,tsx}\"],\n> };\n> ```\n\nOpen the URL. The Plugin Manager UI shows your plugin loaded; any\nprocedures it exposes are reachable at `/api/<pluginId>/*`.\n\n## Core plugin dependencies are co-loaded\n\nReal plugins almost always depend on platform plugins —\n`@checkstack/healthcheck-backend` for a health check strategy,\n`@checkstack/notification-backend` for a notification strategy,\n`@checkstack/catalog-backend` for a custom catalog kind, etc. The dev\ncommand walks your plugin's `package.json#dependencies` (recursively)\nand loads every `@checkstack/*-backend` package it finds alongside the\nplugin under dev. Without this, your plugin's `init()` would hit\nunregistered services and the boot would deadlock.\n\nTwo cases the resolver handles automatically:\n\n- **Transitive backend deps.** If your plugin depends on\n `@checkstack/notification-discord-backend`, which itself depends on\n `@checkstack/notification-backend`, both load.\n- **Auto-included dev providers.** When no queue or cache provider is\n in your dep graph (the common case for non-`queue-*` /\n non-`cache-*` plugins), the dev command auto-includes\n `@checkstack/queue-memory-backend` and\n `@checkstack/cache-memory-backend` so the platform's queue and cache\n services have a registered strategy. They're zero-config and fine\n for dev. Operators wire BullMQ / Redis / etc. in production.\n\nYou'll see a line like the following in the boot log:\n\n```\n📦 Co-loading 3 core plugin deps:\n @checkstack/healthcheck-backend, @checkstack/queue-memory-backend, @checkstack/cache-memory-backend\n```\n\nFrontend (`-frontend`) and tooling-type packages are not co-loaded as\nbackend plugins — they're resolved through their own paths (the Vite\ndev server for frontend, transitive type imports for common).\n\n## Frontend plugins\n\nWhen `package.json#checkstack.type === \"frontend\"` (or your `-backend`\ndeclares a `-frontend` sibling in `checkstack.bundle`), the dev command\nalso spawns a Vite dev server with HMR on\n[http://localhost:5173](http://localhost:5173). The Vite server proxies\n`/api` and `/assets/plugins` to the backend on :3000, so the SPA can\ntalk to the plugin you just registered.\n\nBehind the scenes, Vite serves `core/frontend`'s `dev-main.tsx` shell —\nthe same `App.tsx`, `loadPlugins()`, `ThemeProvider`, etc. that ship in\nproduction. Your plugin module is mounted via the\n`virtual:checkstack-dev-plugin` alias resolved at config time. Saving a\ncomponent in your plugin triggers React Fast Refresh in the browser —\nno full reload.\n\nFor pure backend plugins, the Vite server is skipped; only port 3000\nruns.\n\n## What `bun run dev` does\n\n```mermaid\nsequenceDiagram\n participant Dev as Plugin author\n participant DevServer as @checkstack/dev-server\n participant Backend as @checkstack/backend\n participant Watcher as fs.watch on ./src\n\n Dev->>DevServer: bun run dev (checkstack-dev)\n DevServer->>DevServer: validate package.json\n DevServer->>DevServer: resolve @checkstack/backend\n DevServer->>Backend: spawn `bun run <backend-entry>`<br/>env: CHECKSTACK_DEV_PLUGIN_PATH=cwd<br/>env: CHECKSTACK_DEV_AUTH=true\n Backend->>Backend: skipDiscovery=true; load plugin manually\n Backend->>Backend: register dev auth (auto-grants every rule)\n Backend->>Dev: HTTP 200 on http://localhost:3000\n Watcher-->>DevServer: file change in ./src\n DevServer->>Backend: SIGTERM\n DevServer->>Backend: respawn\n```\n\nTwo env vars do the work. Both are inert in production — `core/backend`\nrefuses `CHECKSTACK_DEV_AUTH=true` when `NODE_ENV=production` and ignores\n`CHECKSTACK_DEV_PLUGIN_PATH` if unset.\n\n## Command-line flags\n\n```\nbunx @checkstack/dev-server --help\n```\n\n(After installing `@checkstack/dev-server` as a devDependency, the\nbinary is on the local `node_modules/.bin` path, so `bun run dev --\n--help` or `checkstack-dev --help` both work too.)\n\n| Flag | Default | Notes |\n|------------------------|--------------------------------------------------------------------------|----------------------------------------------------|\n| `--cwd <dir>` | `process.cwd()` | Plugin directory. |\n| `--port <num>` | `3000` (or `$PORT`) | Backend HTTP port. |\n| `--frontend-port <num>`| `5173` (or `$FRONTEND_PORT`) | Vite dev port. Only used when the plugin (or a bundle sibling) is a `-frontend`. |\n| `--db-url <url>` | `$DATABASE_URL` or `postgresql://checkstack:checkstack@localhost:5432/checkstack` | Postgres URL for core + plugin migrations. |\n| `--no-watch` | watching enabled | Disable auto-restart on file changes. |\n\n## Hitting your plugin\n\nAuth is bypassed, so any browser tab or curl invocation against\n`http://localhost:3000/api/<pluginId>/...` authorizes as the dev user\nwith full access. To test from curl:\n\n```bash\ncurl -X POST http://localhost:3000/api/widget/listWidgets \\\n -H 'content-type: application/json' \\\n -d '{\"json\": {}}'\n```\n\noRPC's `RPCHandler` accepts JSON envelopes; the\n[`@orpc/client`](https://orpc.unnoq.com/) packages produce them\nautomatically if you wire a typed client.\n\n## Logs\n\nThe dev server pipes the backend's `stdout` / `stderr` to your terminal\nvia `stdio: \"inherit\"`. You see exactly what production logs would show\n— Winston-formatted lines including request/response logs, plugin\nlifecycle events, and any RPC error stack traces.\n\n## Database state\n\nMigrations run against the live Postgres on every boot. The `plugins`\ntable tracks your plugin (the dev server also passes through the install\nevent recorder), so you can hit Plugin Manager → Events to see\nregister/init traces.\n\nTo wipe state and start fresh, drop and recreate the database:\n\n```bash\ndocker exec -it checkstack-dev-pg \\\n psql -U checkstack -c \"DROP DATABASE checkstack;\"\ndocker exec -it checkstack-dev-pg \\\n psql -U checkstack -d postgres -c \"CREATE DATABASE checkstack;\"\n```\n\n## Validation against production\n\nBefore tagging a release, validate that the runtime install path —\nmetadata schema, compatibility check, install scripts handling — is\nhappy with what you've built:\n\n```bash\nbunx @checkstack/scripts plugin-pack --validate-only\n```\n\nFor a final smoke test, pack and install via the Plugin Manager UI of a\nreal Checkstack deployment (or the same dev server's UI). The dev server\nloads your plugin via `manualPlugins`; the install path loads it from a\ntarball. They exercise the same `register()` / `init()` hooks but not\nthe same install code path, so the pack-and-install run is a useful\nfinal check.\n\n## Troubleshooting\n\n**`Could not locate @checkstack/backend`**\n\nMake sure `@checkstack/dev-server` is in your devDependencies, and that\nthe platform package matching your plugin's type is too — `@checkstack/backend`\nfor a backend plugin, `@checkstack/frontend` for a frontend plugin (or\nboth for a multi-package plugin that ships frontend + backend together).\nThe dev server resolves them from your plugin's own `node_modules` (so\nthe version your plugin pins is what runs). Run `bun install` again.\n\n**Port 3000 in use**\n\nPass `--port 4000` or set `PORT=4000` in your environment.\n\n**Postgres connection refused**\n\nThe dev server expects Postgres on `localhost:5432`. Either start the\nDocker container above or pass `--db-url` pointing at a reachable\ninstance.\n\n**`Plugin package.json failed install-time validation`**\n\nAdd the missing field. The error lists the exact path\n(`checkstack.pluginId`, `description`, etc.). The validator is the same\nZod schema the runtime install uses — see the\n[required fields table](/checkstack/developer-guide/architecture/plugin-distribution/#required-packagejson-fields).\n\n**Restart loop on every save with no actual change**\n\nEditor temp files (Vim swap files, IDE autosave artifacts) can trigger\nspurious events. The dev server already filters dotfiles and `~`-suffixed\nfiles. If your editor uses a different pattern, file an issue with the\nfilename so we can extend the filter.\n\n## Fallback: workspace fork\n\nFor deep core debugging — stepping through `core/backend` while a plugin\nruns — checking out the upstream Checkstack repo and dropping your\nplugin into `plugins/` still works as it always did:\n\n```bash\ngit clone https://github.com/enyineer/checkstack\ncd checkstack\ngit -C plugins/ clone <your-plugin-repo>\nbun install\nbun run typecheck:references:generate\nbun run dev\n```\n\nUse this when the dev server isn't enough — almost always when you're\ncontributing a core change *alongside* a plugin change.\n\n## See also\n\n- [Plugin Distribution & Packing](/checkstack/developer-guide/architecture/plugin-distribution/) —\n how to ship your plugin once it's working\n- [Backend Plugin Development](/checkstack/developer-guide/backend/plugins/) — writing the\n plugin's code itself\n- [Frontend Plugin Development](/checkstack/developer-guide/frontend/plugins/)\n- [Common Plugin Guidelines](/checkstack/developer-guide/common/plugins/)",
|
|
1818
|
+
"content": "Develop a Checkstack plugin from its own repo. No monorepo checkout. No\nupload loop. No Docker bind-mount tricks.\n\n```bash\nbunx @checkstack/dev-server@latest\n```\n\n`@checkstack/dev-server` is the published npm package that ships the\ndev server; it exposes a `checkstack-dev` binary so once you've added\nit as a devDependency, your `package.json` can wire `\"dev\":\n\"checkstack-dev\"` and you run `bun run dev` from then on (see the\nbootstrap section below). The `bunx @checkstack/dev-server@latest` form\nis for a one-shot try before any install - pin `@latest` so Bun does not\nrun a stale cached copy (see [Keep the tooling current](#keep-the-tooling-current)).\n\nThe command boots the same backend code path Checkstack uses\nin production, with two well-defined dev overrides:\n\n- **Filesystem plugin discovery is skipped.** Only your plugin loads —\n nothing else from a `core/` or `plugins/` directory.\n- **Auth is synthetic.** Every access rule the platform registers is\n auto-granted to a `dev-user` identity. No login flow.\n\nYour plugin's `register()` runs against a real `PluginManager`, real\n`coreServices.*`, real oRPC routing, real Drizzle migrations. The boot\ncode is the *exact* same module that ships in the production Docker image\n— there is no parallel \"dev backend\" stack to drift from.\n\nWhen you save a file under `./src`, the backend restarts. Bun cold-starts\nin well under a second for a single plugin, so the loop stays tight.\n\n## Prerequisites\n\n1. **Bun installed locally** (`curl -fsSL https://bun.sh/install | bash`).\n2. **A running Postgres** reachable at `localhost:5432`. The dev server\n doesn't ship one — it expects one. The smallest setup:\n\n ```bash\n docker run --name checkstack-dev-pg -d -p 5432:5432 \\\n -e POSTGRES_USER=checkstack \\\n -e POSTGRES_PASSWORD=checkstack \\\n -e POSTGRES_DB=checkstack \\\n postgres:16-alpine\n ```\n\n To point at a different Postgres, pass `--db-url` or set\n `DATABASE_URL`.\n\n3. **A valid plugin `package.json`.** The dev server validates the same\n `installPackageMetadataSchema` the runtime install pipeline uses, so\n missing or malformed fields fail fast before anything boots. See\n [Plugin Distribution & Packing](/checkstack/developer-guide/architecture/plugin-distribution/#anatomy-of-an-installable-plugin)\n for required fields.\n\n## Bootstrap a new plugin repo\n\nUse `create-checkstack-plugin` to scaffold a complete standalone workspace\n(a `common` contract package, a `backend` implementing it, and a `frontend`\nconsuming it) in one command:\n\n```bash\nbunx create-checkstack-plugin@latest widget\n# or with bun create:\nbun create checkstack-plugin@latest widget\n```\n\nAlways pin `@latest`. Bun caches the package per `name@version` and may serve a\nstale copy otherwise - see [Keep the tooling current](#keep-the-tooling-current).\n\nYou will be prompted for an npm scope (e.g. `acme` for `@acme/widget-*`).\nTo accept defaults without prompts, pass `--yes`:\n\n```bash\nbunx create-checkstack-plugin@latest widget --scope acme --yes\n```\n\nThe scaffolder resolves the concrete published `@checkstack/*` versions from\nthe registry at scaffold time (each package independently - they are 0.x and\nnot lockstepped) and writes them as caret ranges in the generated\n`package.json` files. It then runs `git init` in the new directory.\n\n## Keep the tooling current\n\n`create-checkstack-plugin`, `@checkstack/dev-server`, and `@checkstack/scripts`\nare published to npm and run through Bun's cache. Understanding how that cache\nbehaves saves you from a confusing \"I published a fix but the old behaviour is\nstill running\" loop.\n\n- **Resolving the latest version.** A bare `bunx create-checkstack-plugin`\n resolves the `latest` dist-tag from npm on each run - it is *not* pinned to\n the first version you happened to cache. But Bun's view of \"what is latest\" is\n driven by a cached registry manifest, and Bun deliberately ignores the\n `Age` header, so it \"may be about 5 minutes out of date to receive the latest\n package version metadata from npm\". Right after a publish, expect up to a\n ~5-minute window before a fresh resolve sees it.\n- **Package contents are cached by version.** Downloaded tarballs live at\n `~/.bun/install/cache/<name>@<version>` and are content-addressed by version\n with no time-based expiry. A new version is a new cache key (so you get it\n automatically), but **re-publishing the *same* version with different\n contents is never re-fetched** - always bump the version.\n- **Pin `@latest` in example/one-off commands.** Writing\n `bunx create-checkstack-plugin@latest` (rather than the bare name) makes the\n intent explicit and forces resolution of the `latest` dist-tag.\n\nTo force a refresh immediately - e.g. you just published and do not want to wait\nout the manifest window, or you need to bust a same-version tarball:\n\n```bash\nbun pm cache rm # clear Bun's global install cache, then re-run\n# or, more surgically, target a single package:\nrm -rf ~/.bun/install/cache/create-checkstack-plugin@*\n```\n\n> [!NOTE]\n> A scaffolded plugin's own `package.json` scripts call the **installed binaries**\n> (`\"pack\": \"checkstack-scripts plugin-pack\"`, `\"dev\": \"checkstack-dev\"`), not\n> `bunx`. Those resolve from `node_modules/.bin` against the pinned\n> `@checkstack/scripts` / `@checkstack/dev-server` devDependencies, so a committed\n> script always runs the version your lockfile installed - never a cache-resolved\n> \"latest\". Use `@latest` only for the one-shot `bunx` commands above.\n\nThe result is a Bun workspace ready to boot:\n\n```\nwidget/\n package.json # private root: workspaces [\"packages/*\"], forwarding scripts\n tsconfig.json\n eslint.config.js\n .gitignore\n README.md\n packages/\n widget-common/ # shared contract, Zod schemas, access rules\n widget-backend/ # Drizzle schema, oRPC router, example CRUD procedures\n widget-frontend/ # React page consuming the typed client\n```\n\nThen:\n\n```bash\ncd widget\nbun install\nbun run dev\n# backend: http://localhost:3000\n# frontend: http://localhost:5173\n```\n\nThe backend serves the example `getItems` / `createItem` / ... procedures\nimmediately - a `drizzle/0000_init` migration runs automatically on boot to\ncreate the `items` table. No Redis, no queue, no extra config.\n\nTest the API with curl (auth is synthetic in dev mode):\n\n```bash\ncurl -X POST http://localhost:3000/api/widget/getItems \\\n -H 'content-type: application/json' \\\n -d '{\"json\": {}}'\n# → {\"json\": []}\n```\n\nOpen `http://localhost:5173` to see the frontend list page.\n\n> [!NOTE]\n> **Frontend HMR works from a published install.** The Vite dev server\n> resolves `@checkstack/frontend` (which ships as a dependency of\n> `@checkstack/dev-server`) and the Vite React plugin from the dev server's\n> own install location, so a plugin scaffolded and `bun install`ed from the\n> registry gets HMR without depending on `@checkstack/frontend` directly. A\n> `-frontend` sibling that lives in your workspace (the standalone scaffold\n> layout) is picked up by scanning sibling package directories, so it does\n> not need to be an installed dependency either. The one prerequisite is the\n> obvious one: run `bun install` so your plugin's dev dependencies (including\n> `@checkstack/dev-server`) are present before `bun run dev`.\n\n> [!TIP]\n> **Tailwind styling works in dev from a published install.** Your own\n> custom Tailwind utility classes are compiled into the dev CSS, not just\n> the built-in `@checkstack/ui` components. `@checkstack/frontend` ships the\n> Tailwind toolchain (`tailwindcss`, `autoprefixer`, `tailwindcss-animate`)\n> as runtime dependencies and exports a shared theme preset at\n> `@checkstack/frontend/tailwind-preset`. The dev server applies that preset\n> and injects your plugin's own source globs (`<plugin>/src/**`) into\n> Tailwind's `content`, so classes you write in your `-frontend` components\n> render live with HMR. If you want to reuse the platform theme in your own\n> Tailwind config, add the preset:\n>\n> ```js\n> // tailwind.config.js\n> import checkstackPreset from \"@checkstack/frontend/tailwind-preset\";\n>\n> export default {\n> presets: [checkstackPreset],\n> content: [\"./src/**/*.{ts,tsx}\"],\n> };\n> ```\n\nOpen the URL. The Plugin Manager UI shows your plugin loaded; any\nprocedures it exposes are reachable at `/api/<pluginId>/*`.\n\n## Core plugin dependencies are co-loaded\n\nReal plugins almost always depend on platform plugins —\n`@checkstack/healthcheck-backend` for a health check strategy,\n`@checkstack/notification-backend` for a notification strategy,\n`@checkstack/catalog-backend` for a custom catalog kind, etc. The dev\ncommand walks your plugin's `package.json#dependencies` (recursively)\nand loads every `@checkstack/*-backend` package it finds alongside the\nplugin under dev. Without this, your plugin's `init()` would hit\nunregistered services and the boot would deadlock.\n\nTwo cases the resolver handles automatically:\n\n- **Transitive backend deps.** If your plugin depends on\n `@checkstack/notification-discord-backend`, which itself depends on\n `@checkstack/notification-backend`, both load.\n- **Auto-included dev providers.** When no queue or cache provider is\n in your dep graph (the common case for non-`queue-*` /\n non-`cache-*` plugins), the dev command auto-includes\n `@checkstack/queue-memory-backend` and\n `@checkstack/cache-memory-backend` so the platform's queue and cache\n services have a registered strategy. They're zero-config and fine\n for dev. Operators wire BullMQ / Redis / etc. in production.\n\nYou'll see a line like the following in the boot log:\n\n```\n📦 Co-loading 3 core plugin deps:\n @checkstack/healthcheck-backend, @checkstack/queue-memory-backend, @checkstack/cache-memory-backend\n```\n\nFrontend (`-frontend`) and tooling-type packages are not co-loaded as\nbackend plugins — they're resolved through their own paths (the Vite\ndev server for frontend, transitive type imports for common).\n\n## Frontend plugins\n\nWhen `package.json#checkstack.type === \"frontend\"` (or your `-backend`\ndeclares a `-frontend` sibling in `checkstack.bundle`), the dev command\nalso spawns a Vite dev server with HMR on\n[http://localhost:5173](http://localhost:5173). The Vite server proxies\n`/api` and `/assets/plugins` to the backend on :3000, so the SPA can\ntalk to the plugin you just registered.\n\nBehind the scenes, Vite serves `core/frontend`'s `dev-main.tsx` shell —\nthe same `App.tsx`, `loadPlugins()`, `ThemeProvider`, etc. that ship in\nproduction. Your plugin module is mounted via the\n`virtual:checkstack-dev-plugin` alias resolved at config time. Saving a\ncomponent in your plugin triggers React Fast Refresh in the browser —\nno full reload.\n\nFor pure backend plugins, the Vite server is skipped; only port 3000\nruns.\n\n> [!NOTE]\n> `@checkstack/ui`'s Monaco `CodeEditor` works in standalone `bun run dev`.\n> Because `@checkstack/ui` is a pre-bundled npm dependency in a standalone\n> install, Vite can't process the Monaco language workers it imports via\n> `?worker&url` during dependency pre-bundling. The dev server therefore\n> pre-builds those workers once (cached under\n> `node_modules/.cache/checkstack-dev-monaco`) and serves them, so the editor\n> renders the same as in the monorepo. The first `bun run dev` after installing\n> or upgrading the editor packages takes a little longer while the workers\n> build; subsequent runs reuse the cache.\n\n## What `bun run dev` does\n\n```mermaid\nsequenceDiagram\n participant Dev as Plugin author\n participant DevServer as @checkstack/dev-server\n participant Backend as @checkstack/backend\n participant Watcher as fs.watch on ./src\n\n Dev->>DevServer: bun run dev (checkstack-dev)\n DevServer->>DevServer: validate package.json\n DevServer->>DevServer: resolve @checkstack/backend\n DevServer->>Backend: spawn `bun run <backend-entry>`<br/>env: CHECKSTACK_DEV_PLUGIN_PATH=cwd<br/>env: CHECKSTACK_DEV_AUTH=true\n Backend->>Backend: skipDiscovery=true; load plugin manually\n Backend->>Backend: register dev auth (auto-grants every rule)\n Backend->>Dev: HTTP 200 on http://localhost:3000\n Watcher-->>DevServer: file change in ./src\n DevServer->>Backend: SIGTERM\n DevServer->>Backend: respawn\n```\n\nTwo env vars do the work. Both are inert in production — `core/backend`\nrefuses `CHECKSTACK_DEV_AUTH=true` when `NODE_ENV=production` and ignores\n`CHECKSTACK_DEV_PLUGIN_PATH` if unset.\n\n## Command-line flags\n\n```\nbunx @checkstack/dev-server@latest --help\n```\n\n(After installing `@checkstack/dev-server` as a devDependency, the\nbinary is on the local `node_modules/.bin` path, so `bun run dev --\n--help` or `checkstack-dev --help` both work too.)\n\n| Flag | Default | Notes |\n|------------------------|--------------------------------------------------------------------------|----------------------------------------------------|\n| `--cwd <dir>` | `process.cwd()` | Plugin directory. |\n| `--port <num>` | `3000` (or `$PORT`) | Backend HTTP port. |\n| `--frontend-port <num>`| `5173` (or `$FRONTEND_PORT`) | Vite dev port. Only used when the plugin (or a bundle sibling) is a `-frontend`. |\n| `--db-url <url>` | `$DATABASE_URL` or `postgresql://checkstack:checkstack@localhost:5432/checkstack` | Postgres URL for core + plugin migrations. |\n| `--no-watch` | watching enabled | Disable auto-restart on file changes. |\n\n## Hitting your plugin\n\nAuth is bypassed, so any browser tab or curl invocation against\n`http://localhost:3000/api/<pluginId>/...` authorizes as the dev user\nwith full access. To test from curl:\n\n```bash\ncurl -X POST http://localhost:3000/api/widget/listWidgets \\\n -H 'content-type: application/json' \\\n -d '{\"json\": {}}'\n```\n\noRPC's `RPCHandler` accepts JSON envelopes; the\n[`@orpc/client`](https://orpc.unnoq.com/) packages produce them\nautomatically if you wire a typed client.\n\n## Logs\n\nThe dev server pipes the backend's `stdout` / `stderr` to your terminal\nvia `stdio: \"inherit\"`. You see exactly what production logs would show\n— Winston-formatted lines including request/response logs, plugin\nlifecycle events, and any RPC error stack traces.\n\n## Database state\n\nMigrations run against the live Postgres on every boot. The `plugins`\ntable tracks your plugin (the dev server also passes through the install\nevent recorder), so you can hit Plugin Manager → Events to see\nregister/init traces.\n\nTo wipe state and start fresh, drop and recreate the database:\n\n```bash\ndocker exec -it checkstack-dev-pg \\\n psql -U checkstack -c \"DROP DATABASE checkstack;\"\ndocker exec -it checkstack-dev-pg \\\n psql -U checkstack -d postgres -c \"CREATE DATABASE checkstack;\"\n```\n\n## Validation against production\n\nBefore tagging a release, validate that the runtime install path —\nmetadata schema, compatibility check, install scripts handling — is\nhappy with what you've built:\n\n```bash\nbunx @checkstack/scripts@latest plugin-pack --validate-only\n```\n\nFor a final smoke test, pack and install via the Plugin Manager UI of a\nreal Checkstack deployment (or the same dev server's UI). The dev server\nloads your plugin via `manualPlugins`; the install path loads it from a\ntarball. They exercise the same `register()` / `init()` hooks but not\nthe same install code path, so the pack-and-install run is a useful\nfinal check.\n\n## Troubleshooting\n\n**`Could not locate @checkstack/backend`**\n\nMake sure `@checkstack/dev-server` is in your devDependencies, and that\nthe platform package matching your plugin's type is too — `@checkstack/backend`\nfor a backend plugin, `@checkstack/frontend` for a frontend plugin (or\nboth for a multi-package plugin that ships frontend + backend together).\nThe dev server resolves them from your plugin's own `node_modules` (so\nthe version your plugin pins is what runs). Run `bun install` again.\n\n**Port 3000 in use**\n\nPass `--port 4000` or set `PORT=4000` in your environment.\n\n**Postgres connection refused**\n\nThe dev server expects Postgres on `localhost:5432`. Either start the\nDocker container above or pass `--db-url` pointing at a reachable\ninstance.\n\n**`Plugin package.json failed install-time validation`**\n\nAdd the missing field. The error lists the exact path\n(`checkstack.pluginId`, `description`, etc.). The validator is the same\nZod schema the runtime install uses — see the\n[required fields table](/checkstack/developer-guide/architecture/plugin-distribution/#required-packagejson-fields).\n\n**Restart loop on every save with no actual change**\n\nEditor temp files (Vim swap files, IDE autosave artifacts) can trigger\nspurious events. The dev server already filters dotfiles and `~`-suffixed\nfiles. If your editor uses a different pattern, file an issue with the\nfilename so we can extend the filter.\n\n## Fallback: workspace fork\n\nFor deep core debugging — stepping through `core/backend` while a plugin\nruns — checking out the upstream Checkstack repo and dropping your\nplugin into `plugins/` still works as it always did:\n\n```bash\ngit clone https://github.com/enyineer/checkstack\ncd checkstack\ngit -C plugins/ clone <your-plugin-repo>\nbun install\nbun run typecheck:references:generate\nbun run dev\n```\n\nUse this when the dev server isn't enough — almost always when you're\ncontributing a core change *alongside* a plugin change.\n\n## See also\n\n- [Plugin Distribution & Packing](/checkstack/developer-guide/architecture/plugin-distribution/) —\n how to ship your plugin once it's working\n- [Backend Plugin Development](/checkstack/developer-guide/backend/plugins/) — writing the\n plugin's code itself\n- [Frontend Plugin Development](/checkstack/developer-guide/frontend/plugins/)\n- [Common Plugin Guidelines](/checkstack/developer-guide/common/plugins/)",
|
|
1818
1819
|
"truncated": false
|
|
1819
1820
|
},
|
|
1820
1821
|
{
|
|
@@ -2729,7 +2730,7 @@ export const DOCS_INDEX: readonly DocsIndexEntry[] = [
|
|
|
2729
2730
|
"Example `.env`",
|
|
2730
2731
|
"Where to go next"
|
|
2731
2732
|
],
|
|
2732
|
-
"content": "Checkstack is configured almost entirely through environment variables. The platform reads them once at process start (or on the first request that touches them) and there is no runtime reload. To change a value, restart the container.\n\nThis page is the authoritative list. If you are just looking for the minimum required to boot, see [Run Checkstack with Docker](/checkstack/user-guide/installation/docker/) and come back here when you need detail.\n\n> [!NOTE]\n> All variables are read with `process.env`. They must be set on the **backend** container. Frontend assets are served by the backend, so there are no separate frontend env vars to set at deploy time.\n\n## Core\n\n| Variable | Required | Default | What it does |\n|----------|----------|---------|--------------|\n| `BASE_URL` | yes | none | The exact public URL operators type into the browser to reach Checkstack (e.g. `http://192.168.1.10:3000`, `https://status.example.com`). Used for CORS, OAuth redirects, SAML reply URLs, OpenAPI server URL, notification links, and the runtime config endpoint at `/api/config`. Must match the address you actually use; a mismatch silently breaks SSO redirects and shows a blank UI. |\n| `NODE_ENV` | no | `development` | Set to `production` for JSON-formatted logs and to disable the dev log files under `.dev/logs/`. Also blocks `CHECKSTACK_DEV_AUTH=true` (it throws on boot when set in production). |\n| `CHECKSTACK_FRONTEND_DIST` | no (set by Docker image) | unset | Absolute path to the built frontend `dist/` directory. The official Docker image sets it to `/app/core/frontend/dist`. If you run the backend without static assets, leave this unset and serve the frontend separately. |\n| `INTERNAL_URL` | no | falls back to `BASE_URL` or `http://localhost:3000` | URL used for backend-to-backend RPC inside the cluster. Set this to a cluster-internal address (e.g. a Kubernetes service name like `http://checkstack-svc:3000`) when running multiple replicas so internal traffic skips the external load balancer. |\n\n## Database\n\n| Variable | Required | Default | What it does |\n|----------|----------|---------|--------------|\n| `DATABASE_URL` | yes | none | PostgreSQL connection string used by every backend package (`postgresql://user:pass@host:5432/db`). All plugin schemas live in this one database; per-plugin tables are isolated via PostgreSQL schemas (`plugin_<id>`). No extensions are required. Postgres 16 is the version shipped in the reference compose file. |\n\n> [!IMPORTANT]\n> Checkstack does not support SQLite. The platform relies on Postgres-specific features (JSONB, schema namespacing, `LISTEN`/`NOTIFY` style queueing via plugins). Any reachable Postgres 14+ instance works; managed services like Neon or Supabase are fine.\n\n## Authentication\n\n| Variable | Required | Default | What it does |\n|----------|----------|---------|--------------|\n| `BETTER_AUTH_SECRET` | yes | none | Signs session cookies and OAuth state. Must be at least 32 characters. Used by `core/auth-backend` and the SAML plugin. Treat this like a JWT signing key: rotating it logs every user out. |\n| `PUBLIC_URL` | no (SAML only) | falls back to `BASE_URL` | Optional override the SAML plugin uses to build identity-provider callback URLs. Only set this if the public URL exposed to your IdP differs from `BASE_URL` (rare). |\n\nFor the strategy-by-strategy walkthrough (credential, GitHub OAuth, SAML, LDAP, group mapping) see [Authentication strategies](/checkstack/user-guide/reference/authentication-strategies/).\n\n## Encryption\n\n| Variable | Required | Default | What it does |\n|----------|----------|---------|--------------|\n| `ENCRYPTION_MASTER_KEY` | yes | none | 64-character hex string (32 bytes) used as the AES-256-GCM key for every secret stored in the database: OAuth client secrets, integration API tokens, satellite tokens, etc. Without it, the platform refuses to boot any feature that stores a secret. See [Secret encryption](/checkstack/user-guide/reference/secret-encryption/) for generation and rotation details. |\n\n## Logging\n\n| Variable | Required | Default | What it does |\n|----------|----------|---------|--------------|\n| `LOG_LEVEL` | no | `info` | Winston log level. One of `error`, `warn`, `info`, `debug`. Setting `debug` is verbose but useful when diagnosing plugin install or queue lag issues. |\n| `DEBUG` | no | unset | Only honoured by the satellite agent. Any non-empty value enables `[satellite:debug]` lines on stdout. |\n\n## Plugin development\n\nThese variables only apply when you are developing a plugin locally; never set them in production.\n\n| Variable | Required | Default | What it does |\n|----------|----------|---------|--------------|\n| `CHECKSTACK_DEV_PLUGIN_PATH` | no | unset | Absolute path to a plugin directory whose default export is a `BackendPlugin`. Setting this skips filesystem discovery and loads only that plugin plus core services. Used by `bunx @checkstack/
|
|
2733
|
+
"content": "Checkstack is configured almost entirely through environment variables. The platform reads them once at process start (or on the first request that touches them) and there is no runtime reload. To change a value, restart the container.\n\nThis page is the authoritative list. If you are just looking for the minimum required to boot, see [Run Checkstack with Docker](/checkstack/user-guide/installation/docker/) and come back here when you need detail.\n\n> [!NOTE]\n> All variables are read with `process.env`. They must be set on the **backend** container. Frontend assets are served by the backend, so there are no separate frontend env vars to set at deploy time.\n\n## Core\n\n| Variable | Required | Default | What it does |\n|----------|----------|---------|--------------|\n| `BASE_URL` | yes | none | The exact public URL operators type into the browser to reach Checkstack (e.g. `http://192.168.1.10:3000`, `https://status.example.com`). Used for CORS, OAuth redirects, SAML reply URLs, OpenAPI server URL, notification links, and the runtime config endpoint at `/api/config`. Must match the address you actually use; a mismatch silently breaks SSO redirects and shows a blank UI. |\n| `NODE_ENV` | no | `development` | Set to `production` for JSON-formatted logs and to disable the dev log files under `.dev/logs/`. Also blocks `CHECKSTACK_DEV_AUTH=true` (it throws on boot when set in production). |\n| `CHECKSTACK_FRONTEND_DIST` | no (set by Docker image) | unset | Absolute path to the built frontend `dist/` directory. The official Docker image sets it to `/app/core/frontend/dist`. If you run the backend without static assets, leave this unset and serve the frontend separately. |\n| `INTERNAL_URL` | no | falls back to `BASE_URL` or `http://localhost:3000` | URL used for backend-to-backend RPC inside the cluster. Set this to a cluster-internal address (e.g. a Kubernetes service name like `http://checkstack-svc:3000`) when running multiple replicas so internal traffic skips the external load balancer. |\n\n## Database\n\n| Variable | Required | Default | What it does |\n|----------|----------|---------|--------------|\n| `DATABASE_URL` | yes | none | PostgreSQL connection string used by every backend package (`postgresql://user:pass@host:5432/db`). All plugin schemas live in this one database; per-plugin tables are isolated via PostgreSQL schemas (`plugin_<id>`). No extensions are required. Postgres 16 is the version shipped in the reference compose file. |\n\n> [!IMPORTANT]\n> Checkstack does not support SQLite. The platform relies on Postgres-specific features (JSONB, schema namespacing, `LISTEN`/`NOTIFY` style queueing via plugins). Any reachable Postgres 14+ instance works; managed services like Neon or Supabase are fine.\n\n## Authentication\n\n| Variable | Required | Default | What it does |\n|----------|----------|---------|--------------|\n| `BETTER_AUTH_SECRET` | yes | none | Signs session cookies and OAuth state. Must be at least 32 characters. Used by `core/auth-backend` and the SAML plugin. Treat this like a JWT signing key: rotating it logs every user out. |\n| `PUBLIC_URL` | no (SAML only) | falls back to `BASE_URL` | Optional override the SAML plugin uses to build identity-provider callback URLs. Only set this if the public URL exposed to your IdP differs from `BASE_URL` (rare). |\n\nFor the strategy-by-strategy walkthrough (credential, GitHub OAuth, SAML, LDAP, group mapping) see [Authentication strategies](/checkstack/user-guide/reference/authentication-strategies/).\n\n## Encryption\n\n| Variable | Required | Default | What it does |\n|----------|----------|---------|--------------|\n| `ENCRYPTION_MASTER_KEY` | yes | none | 64-character hex string (32 bytes) used as the AES-256-GCM key for every secret stored in the database: OAuth client secrets, integration API tokens, satellite tokens, etc. Without it, the platform refuses to boot any feature that stores a secret. See [Secret encryption](/checkstack/user-guide/reference/secret-encryption/) for generation and rotation details. |\n\n## Logging\n\n| Variable | Required | Default | What it does |\n|----------|----------|---------|--------------|\n| `LOG_LEVEL` | no | `info` | Winston log level. One of `error`, `warn`, `info`, `debug`. Setting `debug` is verbose but useful when diagnosing plugin install or queue lag issues. |\n| `DEBUG` | no | unset | Only honoured by the satellite agent. Any non-empty value enables `[satellite:debug]` lines on stdout. |\n\n## Plugin development\n\nThese variables only apply when you are developing a plugin locally; never set them in production.\n\n| Variable | Required | Default | What it does |\n|----------|----------|---------|--------------|\n| `CHECKSTACK_DEV_PLUGIN_PATH` | no | unset | Absolute path to a plugin directory whose default export is a `BackendPlugin`. Setting this skips filesystem discovery and loads only that plugin plus core services. Used by the dev server (`bunx @checkstack/dev-server@latest`, or the `checkstack-dev` bin). |\n| `CHECKSTACK_DEV_EXTRA_PLUGIN_PATHS` | no | unset | JSON array of additional plugin module paths to co-load alongside the one under `CHECKSTACK_DEV_PLUGIN_PATH`. The dev script sets this automatically based on `package.json` dependencies. |\n| `CHECKSTACK_DEV_AUTH` | no | `false` | When `true`, registers a synthetic auth service that auto-grants every access rule. Strictly refused when `NODE_ENV=production` (the process throws at boot). Useful for testing plugins without going through a login flow. |\n\n> [!WARNING]\n> `CHECKSTACK_DEV_AUTH=true` disables every access guard in the platform. Never set it on an exposed instance.\n\n## Satellite agent\n\nThe satellite is a separate process. These variables apply to the satellite container only, not to the core backend.\n\n| Variable | Required | Default | What it does |\n|----------|----------|---------|--------------|\n| `CHECKSTACK_CORE_URL` | yes | none | URL of the Checkstack core the satellite connects to. Reachable from wherever the satellite runs. |\n| `CHECKSTACK_SATELLITE_CLIENT_ID` | yes | none | Stable id you assign in the Satellite registration UI. Identifies this satellite to the core. |\n| `CHECKSTACK_SATELLITE_TOKEN` | yes | none | Bearer token issued by the core when the satellite was registered. Treat as a credential. |\n| `DEBUG` | no | unset | Enables verbose satellite logs. |\n\n## Docker Compose helpers\n\nThe reference `docker-compose.yml` adds a few Postgres-side variables for convenience. They are read by the Postgres image, not by Checkstack itself.\n\n| Variable | Default in compose | What it does |\n|----------|--------------------|--------------|\n| `POSTGRES_USER` | `checkstack` | DB user the Postgres container creates on first boot. Plugged into `DATABASE_URL`. |\n| `POSTGRES_PASSWORD` | `checkstack` | Password for the above user. Change this for any non-throwaway install. |\n| `POSTGRES_DB` | `checkstack` | Database name created on first boot. |\n\n## Example `.env`\n\nA complete `.env` for a single-host production install looks like this:\n\n```env\n# Core\nBASE_URL=https://status.example.com\nNODE_ENV=production\nLOG_LEVEL=info\n\n# Database\nPOSTGRES_USER=checkstack\nPOSTGRES_PASSWORD=replace-me\nPOSTGRES_DB=checkstack\n\n# Auth\nBETTER_AUTH_SECRET=replace-with-32-plus-char-random-string\n\n# Encryption\nENCRYPTION_MASTER_KEY=replace-with-64-hex-chars\n```\n\nFor multi-replica deployments, add:\n\n```env\nINTERNAL_URL=http://checkstack-svc:3000\n```\n\n## Where to go next\n\n- [Run Checkstack with Docker](/checkstack/user-guide/installation/docker/) for the install flow that feeds these variables.\n- [Secret encryption](/checkstack/user-guide/reference/secret-encryption/) for generating and rotating `ENCRYPTION_MASTER_KEY`.\n- [Authentication strategies](/checkstack/user-guide/reference/authentication-strategies/) for the strategy-side configuration you set from the UI on top of the env vars above.\n- [Installation troubleshooting](/checkstack/user-guide/troubleshooting/installation/) when boot fails or environment values look wrong.",
|
|
2733
2734
|
"truncated": false
|
|
2734
2735
|
},
|
|
2735
2736
|
{
|
|
@@ -3011,10 +3012,10 @@ export const DOCS_INDEX: readonly DocsIndexEntry[] = [
|
|
|
3011
3012
|
"\"Plugin loaded on one replica but not another\"",
|
|
3012
3013
|
"Where to go next"
|
|
3013
3014
|
],
|
|
3014
|
-
"content": "This page covers the failure modes you hit while installing, upgrading, or removing plugins through the Plugin Manager. For the install walkthrough see [Install a plugin](/checkstack/user-guide/guides/install-a-plugin/).\n\n## Install fails\n\n### Symptom: \"Tarball exceeds maximum size\"\n\nThe plugin tarball is larger than the platform's 50 MB ceiling.\n\n**How to verify** - the error message includes the actual size.\n\n**Fix** - this limit is hardcoded; there is no env var or UI knob. Ask the plugin author to slim the package (typically by adding a tighter `files` field to `package.json` so dev artifacts aren't shipped). If you control the plugin, repack with `bunx @checkstack/scripts plugin-pack` after pruning.\n\n### Symptom: \"Failed to peek tarball\"\n\nThe uploaded `.tgz` is corrupted or missing the required `package.json`/`checkstack` metadata.\n\n**Fix**:\n\n1. Re-download or re-pack the artifact.\n2. Confirm the package was packed with `bunx @checkstack/scripts plugin-pack` (not `bun pm pack` directly). The pack CLI validates the `checkstack` metadata block before producing the tarball.\n\n### Symptom: npm install fails with `404 Not Found`\n\nThe npm registry doesn't have the package at the requested version.\n\n**Fix**:\n\n1. Double-check the package name and version spelled in the install dialog.\n2. If the package is private, configure the npm registry credentials in **Settings -> Infrastructure**. The npm installer honours the registry config the platform-wide.\n3. Confirm the version actually exists with `npm view @org/pkg versions`.\n\n### Symptom: GitHub install can't reach the release\n\nThe GitHub installer fetches a release asset by URL. Failures here are almost always network or auth.\n\n**Fix**:\n\n1. Check the Checkstack container can reach `https://github.com` and `https://objects.githubusercontent.com` outbound.\n2. For private repos, the release asset must be accessible without auth, or you must configure a GitHub token in the integration settings.\n3. Verify the release URL points to a `.tgz` asset, not a source archive.\n\n### Symptom: install warns about install scripts\n\nThe plugin's `package.json` sets `checkstack.allowInstallScripts: true`. The platform refuses to silently run lifecycle scripts (`preinstall`, `postinstall`, ...) - it shows a warning and asks for explicit confirmation.\n\n**Fix** - read the plugin's docs/source to confirm what the script does. If you trust it, click Install to proceed. If you don't, don't install. This warning is loud on purpose; install scripts run with the same permissions as the Checkstack process.\n\n### Symptom: \"Plugin signature could not be verified\"\n\nReserved for a future signed-plugin flow. Current v1.0 BETA installs do not require signatures, so if you see this you're on a build that has signature enforcement turned on - either install from a trusted source that signs releases or temporarily relax the policy in Infrastructure settings.\n\n## Version compatibility\n\nCheckstack v1.0 is **BETA**. The platform follows semver and treats every release as potentially containing breaking changes until v1.0 stable.\n\n| Situation | What to do |\n|-----------|------------|\n| Plugin built against a newer core API | Update the core first; plugins generally tolerate newer cores within the same minor. |\n| Plugin built against a much older core | Update the plugin. Plugins built for pre-v1.0 cores have no compatibility guarantee. |\n| Bundle install with mixed versions | The pack CLI emits a single bundle manifest with one primary version; if a sibling is mismatched, repack the bundle. |\n\n> [!IMPORTANT]\n> Per the changesets rule, the platform is currently in BETA and only bumps minor versions even for breaking changes (with a BREAKING CHANGES note in the changeset). Treat every minor bump as a potential plugin-compatibility checkpoint and read the changelog before upgrading.\n\n## Upgrade behaviour\n\n### When a new plugin version is published, does Checkstack pick it up?\n\nNo. The platform never upgrades plugins on its own. You upgrade explicitly through the Plugin Manager.\n\n### Does restarting the container upgrade plugins?\n\nOn restart, the platform re-loads every `is_uninstallable=true` row in the `plugins` table from the `plugin_artifacts` store. It does **not** re-fetch from npm/GitHub; the tarball in the database is the source of truth. So a restart preserves the exact version you installed, even if upstream has moved on.\n\n### What about core plugins (the built-in ones)?\n\nCore plugins ship inside the Checkstack image. Pulling a newer image upgrades them as a set. There is no per-core-plugin version pinning.\n\n## Uninstall behaviour\n\nWhen you uninstall a runtime plugin from the Plugin Manager, three pieces of state can be touched. The UI exposes each as an explicit toggle on the uninstall preview screen:\n\n| State | What it is | What you choose |\n|-------|------------|-----------------|\n| Plugin row + tarball | The `plugins` and `plugin_artifacts` rows for the plugin. Always removed on uninstall. | No choice. |\n| Plugin Postgres schema | The per-plugin schema (`plugin_<id>`) holding the plugin's tables. Drops everything the plugin ever stored. | \"Delete schema\" toggle. **Defaults to off**. |\n| Plugin configs | Per-plugin configuration rows in `plugin_configs`. | \"Delete configs\" toggle. **Defaults to off**. |\n\nThe defaults are conservative on purpose: an uninstall is reversible (re-install the same plugin and the configs/data are still there) as long as you don't tick those toggles.\n\n> [!CAUTION]\n> Ticking \"Delete schema\" drops the plugin's data tables with `DROP SCHEMA ... CASCADE`. There is no undo. Take a Postgres backup first if you might want the data back.\n\n### Cascading uninstalls\n\nIf other installed plugins declare a `@checkstack/<id>` dependency on the plugin you're removing, the preview screen shows them as **Dependents**. You then choose:\n\n- Uninstall without cascade -> the platform refuses (would leave dangling deps).\n- Uninstall with cascade -> the platform walks the dependents transitively and uninstalls them too, in dependency order. Each dependent honours the same \"delete schema\" / \"delete configs\" toggles you picked.\n\n### Core plugins cannot be uninstalled\n\nThe Uninstall action is hidden for any plugin where `isUninstallable=false`. Those are the built-in core plugins; removing them would brick the platform. Use the official Docker image to swap the core itself.\n\n## \"Plugin loaded on one replica but not another\"\n\nIn a multi-replica deployment, every install/uninstall is broadcast to every replica. If a single replica didn't pick the install up, check the **Plugin Manager -> Plugin events** page - each lifecycle event is recorded per replica, so you can see exactly which one failed and why.\n\nMost-common causes:\n\n- The failing replica was restarting at the moment of the broadcast and missed it. Hitting the restart endpoint on the failing replica forces a re-bootstrap from `plugin_artifacts` (which contains the install tarball regardless of the original source).\n- The failing replica's `INTERNAL_URL` is wrong, so the broadcast couldn't be acked back to the originator.\n\n## Where to go next\n\n- [Install a plugin](/checkstack/user-guide/guides/install-a-plugin/) - the install walkthrough.\n- [Plugin distribution](/checkstack/developer-guide/architecture/plugin-distribution/) - the developer-facing distribution rules.\n- [Plugin system architecture](/checkstack/developer-guide/architecture/plugin-system/) - lifecycle and multi-instance coordination details.",
|
|
3015
|
+
"content": "This page covers the failure modes you hit while installing, upgrading, or removing plugins through the Plugin Manager. For the install walkthrough see [Install a plugin](/checkstack/user-guide/guides/install-a-plugin/).\n\n## Install fails\n\n### Symptom: \"Tarball exceeds maximum size\"\n\nThe plugin tarball is larger than the platform's 50 MB ceiling.\n\n**How to verify** - the error message includes the actual size.\n\n**Fix** - this limit is hardcoded; there is no env var or UI knob. Ask the plugin author to slim the package (typically by adding a tighter `files` field to `package.json` so dev artifacts aren't shipped). If you control the plugin, repack with `bunx @checkstack/scripts@latest plugin-pack` after pruning.\n\n### Symptom: \"Failed to peek tarball\"\n\nThe uploaded `.tgz` is corrupted or missing the required `package.json`/`checkstack` metadata.\n\n**Fix**:\n\n1. Re-download or re-pack the artifact.\n2. Confirm the package was packed with `bunx @checkstack/scripts@latest plugin-pack` (not `bun pm pack` directly). The pack CLI validates the `checkstack` metadata block before producing the tarball.\n\n### Symptom: npm install fails with `404 Not Found`\n\nThe npm registry doesn't have the package at the requested version.\n\n**Fix**:\n\n1. Double-check the package name and version spelled in the install dialog.\n2. If the package is private, configure the npm registry credentials in **Settings -> Infrastructure**. The npm installer honours the registry config the platform-wide.\n3. Confirm the version actually exists with `npm view @org/pkg versions`.\n\n### Symptom: GitHub install can't reach the release\n\nThe GitHub installer fetches a release asset by URL. Failures here are almost always network or auth.\n\n**Fix**:\n\n1. Check the Checkstack container can reach `https://github.com` and `https://objects.githubusercontent.com` outbound.\n2. For private repos, the release asset must be accessible without auth, or you must configure a GitHub token in the integration settings.\n3. Verify the release URL points to a `.tgz` asset, not a source archive.\n\n### Symptom: install warns about install scripts\n\nThe plugin's `package.json` sets `checkstack.allowInstallScripts: true`. The platform refuses to silently run lifecycle scripts (`preinstall`, `postinstall`, ...) - it shows a warning and asks for explicit confirmation.\n\n**Fix** - read the plugin's docs/source to confirm what the script does. If you trust it, click Install to proceed. If you don't, don't install. This warning is loud on purpose; install scripts run with the same permissions as the Checkstack process.\n\n### Symptom: \"Plugin signature could not be verified\"\n\nReserved for a future signed-plugin flow. Current v1.0 BETA installs do not require signatures, so if you see this you're on a build that has signature enforcement turned on - either install from a trusted source that signs releases or temporarily relax the policy in Infrastructure settings.\n\n## Version compatibility\n\nCheckstack v1.0 is **BETA**. The platform follows semver and treats every release as potentially containing breaking changes until v1.0 stable.\n\n| Situation | What to do |\n|-----------|------------|\n| Plugin built against a newer core API | Update the core first; plugins generally tolerate newer cores within the same minor. |\n| Plugin built against a much older core | Update the plugin. Plugins built for pre-v1.0 cores have no compatibility guarantee. |\n| Bundle install with mixed versions | The pack CLI emits a single bundle manifest with one primary version; if a sibling is mismatched, repack the bundle. |\n\n> [!IMPORTANT]\n> Per the changesets rule, the platform is currently in BETA and only bumps minor versions even for breaking changes (with a BREAKING CHANGES note in the changeset). Treat every minor bump as a potential plugin-compatibility checkpoint and read the changelog before upgrading.\n\n## Upgrade behaviour\n\n### When a new plugin version is published, does Checkstack pick it up?\n\nNo. The platform never upgrades plugins on its own. You upgrade explicitly through the Plugin Manager.\n\n### Does restarting the container upgrade plugins?\n\nOn restart, the platform re-loads every `is_uninstallable=true` row in the `plugins` table from the `plugin_artifacts` store. It does **not** re-fetch from npm/GitHub; the tarball in the database is the source of truth. So a restart preserves the exact version you installed, even if upstream has moved on.\n\n### What about core plugins (the built-in ones)?\n\nCore plugins ship inside the Checkstack image. Pulling a newer image upgrades them as a set. There is no per-core-plugin version pinning.\n\n## Uninstall behaviour\n\nWhen you uninstall a runtime plugin from the Plugin Manager, three pieces of state can be touched. The UI exposes each as an explicit toggle on the uninstall preview screen:\n\n| State | What it is | What you choose |\n|-------|------------|-----------------|\n| Plugin row + tarball | The `plugins` and `plugin_artifacts` rows for the plugin. Always removed on uninstall. | No choice. |\n| Plugin Postgres schema | The per-plugin schema (`plugin_<id>`) holding the plugin's tables. Drops everything the plugin ever stored. | \"Delete schema\" toggle. **Defaults to off**. |\n| Plugin configs | Per-plugin configuration rows in `plugin_configs`. | \"Delete configs\" toggle. **Defaults to off**. |\n\nThe defaults are conservative on purpose: an uninstall is reversible (re-install the same plugin and the configs/data are still there) as long as you don't tick those toggles.\n\n> [!CAUTION]\n> Ticking \"Delete schema\" drops the plugin's data tables with `DROP SCHEMA ... CASCADE`. There is no undo. Take a Postgres backup first if you might want the data back.\n\n### Cascading uninstalls\n\nIf other installed plugins declare a `@checkstack/<id>` dependency on the plugin you're removing, the preview screen shows them as **Dependents**. You then choose:\n\n- Uninstall without cascade -> the platform refuses (would leave dangling deps).\n- Uninstall with cascade -> the platform walks the dependents transitively and uninstalls them too, in dependency order. Each dependent honours the same \"delete schema\" / \"delete configs\" toggles you picked.\n\n### Core plugins cannot be uninstalled\n\nThe Uninstall action is hidden for any plugin where `isUninstallable=false`. Those are the built-in core plugins; removing them would brick the platform. Use the official Docker image to swap the core itself.\n\n## \"Plugin loaded on one replica but not another\"\n\nIn a multi-replica deployment, every install/uninstall is broadcast to every replica. If a single replica didn't pick the install up, check the **Plugin Manager -> Plugin events** page - each lifecycle event is recorded per replica, so you can see exactly which one failed and why.\n\nMost-common causes:\n\n- The failing replica was restarting at the moment of the broadcast and missed it. Hitting the restart endpoint on the failing replica forces a re-bootstrap from `plugin_artifacts` (which contains the install tarball regardless of the original source).\n- The failing replica's `INTERNAL_URL` is wrong, so the broadcast couldn't be acked back to the originator.\n\n## Where to go next\n\n- [Install a plugin](/checkstack/user-guide/guides/install-a-plugin/) - the install walkthrough.\n- [Plugin distribution](/checkstack/developer-guide/architecture/plugin-distribution/) - the developer-facing distribution rules.\n- [Plugin system architecture](/checkstack/developer-guide/architecture/plugin-system/) - lifecycle and multi-instance coordination details.",
|
|
3015
3016
|
"truncated": false
|
|
3016
3017
|
}
|
|
3017
3018
|
];
|
|
3018
3019
|
|
|
3019
3020
|
/** A content hash of the source tree, so a CI check can detect drift. */
|
|
3020
|
-
export const DOCS_INDEX_HASH = "
|
|
3021
|
+
export const DOCS_INDEX_HASH = "1ceed8a6cbc326a222af17a8edc94219488f1a4b835deb360acf954ffcf30a25";
|