Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
92daf01
feat: add plugin stability system with import-path enforcement
MarioCadenas Apr 28, 2026
7ee3d2f
fix(plugin): harden promote command against path traversal & sync fai…
MarioCadenas Apr 27, 2026
8cba0d0
docs: regenerate plugin-manifest schema & typedoc for stability field
MarioCadenas Apr 27, 2026
9eb2bc6
refactor(plugin): collapse stability tiers to beta/stable
MarioCadenas Apr 27, 2026
ed3601f
fix(template): import beta plugins from /beta subpath
MarioCadenas Apr 28, 2026
4b1295d
fix(template): consolidate beta plugin imports into a single line
MarioCadenas Apr 28, 2026
5a03918
feat(appkit): codegen stable/beta export barrels from plugin manifests
MarioCadenas Apr 28, 2026
df8791c
fix(plugin): close drift gaps surfaced by review
MarioCadenas Apr 28, 2026
e049aae
chore: reconcile generated artifacts after rebase onto main
MarioCadenas Apr 28, 2026
054ef34
fix(ci): build shared package before sync:template freshness check
MarioCadenas Apr 28, 2026
1e2c07c
fix(ci): build shared dist directly via tsdown, not full build:package
MarioCadenas Apr 28, 2026
8c17bd7
feat(docs): auto-inject beta banner on plugin docs page from manifest
MarioCadenas Apr 29, 2026
283820b
fix(build): run plugin doc-banners generator as part of pnpm build
MarioCadenas Apr 29, 2026
56d219e
fix(plugin): scope promote import rewrite to the targeted plugin
MarioCadenas Apr 29, 2026
5e94a10
refactor(plugin): rename "stable" tier to "ga"
MarioCadenas Apr 30, 2026
2713a70
fix(plugin): propagate stability from node_modules sync; harden doc-b…
MarioCadenas Apr 30, 2026
c160462
docs(plugins): document sync stability paths and doc-banner name checks
MarioCadenas Apr 30, 2026
f352b6e
Merge branch 'main' into mario/plugin-stability-tiers
MarioCadenas Apr 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 39 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,52 @@ jobs:
- name: Check generated types are up to date
run: |
pnpm run generate:types
if ! git diff --exit-code packages/shared/src/schemas/plugin-manifest.generated.ts packages/appkit/src/registry/types.generated.ts; then
echo "❌ Error: Generated types are out of sync with plugin-manifest.schema.json."
if ! git diff --exit-code \
packages/shared/src/schemas/plugin-manifest.generated.ts \
packages/appkit/src/registry/types.generated.ts \
packages/appkit/src/plugins/ga-exports.generated.ts \
packages/appkit/src/plugins/beta-exports.generated.ts \
docs/docs/plugins/analytics.md \
docs/docs/plugins/files.md \
docs/docs/plugins/genie.md \
docs/docs/plugins/jobs.md \
docs/docs/plugins/lakebase.md \
docs/docs/plugins/model-serving.md \
docs/docs/plugins/server.md \
docs/docs/plugins/vector-search.md; then
echo "❌ Error: Generated files are out of sync with their source manifests/schemas."
echo ""
echo "To fix this:"
echo " 1. Run: pnpm run generate:types"
echo " 2. Review and commit the changes"
echo ""
exit 1
fi
- name: Build shared package dist (for sync:template CLI)
# `pnpm run sync:template` runs through `packages/shared/bin/appkit.js`
# which imports the built `packages/shared/dist/cli/index.js`. The
# lint job doesn't otherwise build, so build just shared's dist
# before invoking sync. We invoke tsdown directly rather than
# `pnpm --filter shared build:package` because the latter also
# re-runs `generate-schema-types.ts`, which writes the raw (unformatted)
# version of `plugin-manifest.generated.ts` and trips the biome
# check that runs immediately after. ~1s.
run: pnpm --filter shared exec tsdown --config tsdown.config.ts
- name: Check synced template manifest is up to date
run: |
pnpm run sync:template
if ! git diff --exit-code template/appkit.plugins.json; then
echo "❌ Error: template/appkit.plugins.json is out of sync with packages/appkit/src/plugins/<name>/manifest.json."
echo ""
echo "This usually happens when a plugin manifest's stability, resources, or display fields"
echo "are edited without running the sync step that regenerates the template manifest."
echo ""
echo "To fix this:"
echo " 1. Run: pnpm run sync:template"
echo " 2. Review and commit the changes"
echo ""
exit 1
fi
- name: Run Biome Check
run: pnpm run check
- name: Run Types Check
Expand Down
16 changes: 16 additions & 0 deletions docs/docs/api/appkit/Interface.PluginManifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,22 @@ Resources that must be available for the plugin to function

***

### stability?

```ts
optional stability: "beta" | "ga";
```

Plugin stability level. Beta plugins may have breaking API changes between minor releases but are on a path to GA. GA (general availability) plugins follow semver strictly.

#### Inherited from

```ts
Omit.stability
```

***

### version?

```ts
Expand Down
145 changes: 145 additions & 0 deletions docs/docs/plugins/stability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
---
sidebar_position: 2
---

# Plugin Stability Tiers

AppKit plugins have a two-tier stability system that communicates API maturity and breaking-change expectations.

## Tiers

| Tier | Import Path | Contract |
|------|------------|---------|
| **Beta** | `@databricks/appkit/beta` | API may change between minor releases. On a path to GA. |
| **GA** | `@databricks/appkit` | Generally available. Production ready. Follows semver strictly. |

The import path is the primary stability signal. Importing from `/beta` is explicit consent to potential breaking changes.

## Promotion Path

Promotion is one-way. Plugins can enter at any tier.

```
beta ──→ ga
```

## Usage

### Importing Plugins by Tier

```typescript
// GA plugins
import { server, analytics } from "@databricks/appkit";

// Beta plugins
import { someBetaPlugin } from "@databricks/appkit/beta";
```

### UI Components

`@databricks/appkit-ui` mirrors the same pattern:

```typescript
import { SomeComponent } from "@databricks/appkit-ui/react/beta";
import { someUtil } from "@databricks/appkit-ui/js/beta";
```

## CLI Commands

### Listing Plugins with Stability

```bash
npx appkit plugin list
```

The output includes a STABILITY column showing each plugin's tier.

### Creating a Plugin with Stability

```bash
npx appkit plugin create
```

The interactive flow prompts for a stability level (defaults to GA).

### Promoting a Plugin

```bash
# Promote from beta to GA
npx appkit plugin promote my-plugin --to ga

# Preview changes without modifying files
npx appkit plugin promote my-plugin --to ga --dry-run
```

The promote command:
- Updates the plugin's `manifest.json` stability field
- Rewrites import paths across your project's `.ts`/`.tsx` files
- Runs `plugin sync` to update `appkit.plugins.json`

**Options:**
- `--dry-run` -- Show what would change without writing
- `--skip-imports` -- Only update the manifest
- `--skip-sync` -- Don't auto-run sync
- `--allow-installed` -- Allow promoting a plugin that lives only under `node_modules` (advanced)

## Manifest Field

The `stability` field in `manifest.json` is optional. When absent, the plugin is considered GA.

```json
{
"name": "my-plugin",
"displayName": "My Plugin",
"description": "An in-development feature",
"stability": "beta",
"resources": { "required": [], "optional": [] }
}
```

Valid values: `"beta"`, `"ga"`.

## Template Manifest (appkit.plugins.json)

When `plugin sync` discovers non-GA plugins, it includes their stability in the output. That is true for **every** discovery path: plugins resolved from your server file, from `--plugins-dir` / local plugin trees, and from known packages under `node_modules` (for example `@databricks/appkit`). The tier in each plugin’s `manifest.json` is always reflected in the synced template manifest when it is not GA.

```json
{
"version": "1.1",
"plugins": {
"my-plugin": {
"name": "my-plugin",
"stability": "beta",
"package": "@databricks/appkit"
}
}
}
```

Only GA plugins can be marked `requiredByTemplate`. Non-GA plugins always remain optional during init.

## For Third-Party Plugin Authors

The import path (`/beta`) only applies to first-party plugins shipped inside `@databricks/appkit`. Third-party plugins declare stability via the `stability` field in their `manifest.json`. CLI tooling (`plugin list`, `plugin sync`) surfaces this information to users.

## For First-Party Plugin Authors (AppKit Monorepo)

Inside the AppKit monorepo, each plugin's `manifest.json` `stability` field is the **single source of truth** for which subpath ships the plugin. Two build-time generators read every `packages/appkit/src/plugins/<name>/manifest.json`:

- `tools/generate-plugin-entries.ts` writes the runtime export barrels:
- `packages/appkit/src/plugins/ga-exports.generated.ts` — re-exports of GA plugins, included by `src/index.ts` (the `@databricks/appkit` entry).
- `packages/appkit/src/plugins/beta-exports.generated.ts` — re-exports of beta plugins, included by `src/beta.ts` (the `@databricks/appkit/beta` entry).
- `tools/generate-plugin-doc-banners.ts` injects (or removes) a `:::warning Beta plugin` admonition at the top of each plugin's docs page (`docs/docs/plugins/<name>.md`) so a plugin's documented stability follows its manifest. The script only writes under `docs/docs/plugins/`: each manifest `name` must match the plugin schema pattern (`^[a-z][a-z0-9-]*$`), and resolved doc paths are checked so a malformed `name` cannot escape that directory.

All generated artifacts are committed and verified by CI; an out-of-date file fails the `Check generated types are up to date` step.

The `appkit plugin promote` command detects monorepo context (presence of `tools/generate-plugin-entries.ts`) and re-runs the generator after updating the manifest, so the runtime exports, the synced `appkit.plugins.json`, and the manifest can never drift apart.

To move a built-in plugin between tiers manually:

```bash
# Edit packages/appkit/src/plugins/<name>/manifest.json
# Set "stability": "beta" (or remove the field for GA)
pnpm run generate:types # regenerates schema/registry types, export barrels, and doc banners
pnpm sync:template # regenerates template/appkit.plugins.json
```
6 changes: 6 additions & 0 deletions docs/static/schemas/plugin-manifest.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@
"type": "boolean",
"default": false,
"description": "When true, this plugin is excluded from the template plugins manifest (appkit.plugins.json) during sync."
},
"stability": {
"type": "string",
"enum": ["beta", "ga"],
"default": "ga",
"description": "Plugin stability level. Beta plugins may have breaking API changes between minor releases but are on a path to GA. GA (general availability) plugins follow semver strictly."
}
},
"additionalProperties": false,
Expand Down
8 changes: 7 additions & 1 deletion docs/static/schemas/template-plugins.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"version": {
"type": "string",
"const": "1.0",
"enum": ["1.0", "1.1"],
"description": "Schema version for the template plugins manifest"
},
"plugins": {
Expand Down Expand Up @@ -69,6 +69,12 @@
"type": "string",
"description": "Message displayed to the user after project initialization. Use this to inform about manual setup steps (e.g. environment variables, resource provisioning)."
},
"stability": {
"type": "string",
"enum": ["beta", "ga"],
"default": "ga",
"description": "Plugin stability level. Beta is heading to GA; APIs may change between minor releases. GA (general availability) follows semver."
},
"resources": {
"type": "object",
"required": ["required", "optional"],
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
"version": "0.0.2",
"packageManager": "pnpm@10.21.0",
"scripts": {
"build": "pnpm -r --filter=!docs build:package && pnpm sync:template",
"build": "pnpm -r --filter=!docs build:package && pnpm sync:template && pnpm exec tsx tools/generate-plugin-doc-banners.ts",
"sync:template": "node packages/shared/bin/appkit.js plugin sync --write --silent --plugins-dir packages/appkit/src/plugins --output template/appkit.plugins.json --require-plugins server",
"build:watch": "pnpm -r --filter=!dev-playground --filter=!docs build:watch",
"check:fix": "biome check --write .",
"check": "biome check .",
"generate:types": "tsx tools/generate-schema-types.ts && tsx tools/generate-registry-types.ts",
"generate:types": "tsx tools/generate-schema-types.ts && tsx tools/generate-registry-types.ts && tsx tools/generate-plugin-entries.ts && tsx tools/generate-plugin-doc-banners.ts",
"generate:plugin-entries": "tsx tools/generate-plugin-entries.ts",
"generate:plugin-doc-banners": "tsx tools/generate-plugin-doc-banners.ts",
"generate:app-templates": "tsx tools/generate-app-templates.ts",
"check:licenses": "tsx tools/check-licenses.ts",
"build:notice": "tsx tools/build-notice.ts > NOTICE.md",
Expand Down
10 changes: 10 additions & 0 deletions packages/appkit-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,18 @@
"development": "./src/js/index.ts",
"default": "./dist/js/index.js"
},
"./js/beta": {
"development": "./src/js/beta.ts",
"default": "./dist/js/beta.js"
},
"./react": {
"development": "./src/react/index.ts",
"default": "./dist/react/index.js"
},
"./react/beta": {
"development": "./src/react/beta.ts",
"default": "./dist/react/beta.js"
},
"./package.json": "./package.json",
"./styles.css": {
"development": "./src/react/styles/globals.css",
Expand Down Expand Up @@ -111,7 +119,9 @@
"publishConfig": {
"exports": {
"./js": "./dist/js/index.js",
"./js/beta": "./dist/js/beta.js",
"./react": "./dist/react/index.js",
"./react/beta": "./dist/react/beta.js",
"./package.json": "./package.json",
"./styles.css": "./dist/styles.css"
}
Expand Down
2 changes: 2 additions & 0 deletions packages/appkit-ui/src/js/beta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Beta JS utilities -- APIs may change between minor releases.
// Import from '@databricks/appkit-ui/js' once graduated to stable.
2 changes: 2 additions & 0 deletions packages/appkit-ui/src/react/beta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Beta React components -- APIs may change between minor releases.
// Import from '@databricks/appkit-ui/react' once graduated to stable.
7 changes: 6 additions & 1 deletion packages/appkit-ui/tsdown.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ export default defineConfig([
{
publint: true,
name: "@databricks/appkit-ui",
entry: ["src/js/index.ts", "src/react/index.ts"],
entry: [
"src/js/index.ts",
"src/js/beta.ts",
"src/react/index.ts",
"src/react/beta.ts",
],
outDir: "dist",
platform: "browser",
minify: false,
Expand Down
7 changes: 6 additions & 1 deletion packages/appkit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
"development": "./src/index.ts",
"default": "./dist/index.js"
},
"./beta": {
"development": "./src/beta.ts",
"default": "./dist/beta.js"
},
"./type-generator": {
"types": "./dist/type-generator/index.d.ts",
"development": "./src/type-generator/index.ts",
Expand All @@ -41,7 +45,7 @@
"./package.json": "./package.json"
},
"scripts": {
"build:package": "pnpm exec tsx ../../tools/generate-registry-types.ts && tsdown --config tsdown.config.ts",
"build:package": "pnpm exec tsx ../../tools/generate-registry-types.ts && pnpm exec tsx ../../tools/generate-plugin-entries.ts && tsdown --config tsdown.config.ts",
"build:watch": "tsdown --config tsdown.config.ts --watch",
"clean:full": "rm -rf dist node_modules tmp",
"clean": "rm -rf dist tmp",
Expand Down Expand Up @@ -95,6 +99,7 @@
"publishConfig": {
"exports": {
".": "./dist/index.js",
"./beta": "./dist/beta.js",
"./dist/shared/src/plugin": "./dist/shared/src/plugin.d.ts",
"./type-generator": "./dist/type-generator/index.js",
"./package.json": "./package.json"
Expand Down
7 changes: 7 additions & 0 deletions packages/appkit/src/beta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Beta plugins -- APIs may change between minor releases.
// These plugins are on a path to GA and will graduate.
// Import from '@databricks/appkit' once a plugin graduates to GA.
//
// The exports below are auto-generated from each plugin's manifest.json
// "stability" field. See tools/generate-plugin-entries.ts.
export * from "./plugins/beta-exports.generated";
10 changes: 1 addition & 9 deletions packages/appkit/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,6 @@ export {
type ToPlugin,
toPlugin,
} from "./plugin";
export {
analytics,
files,
genie,
jobs,
lakebase,
server,
serving,
} from "./plugins";
// Files plugin types (for custom policy authoring)
export type {
FileAction,
Expand All @@ -75,6 +66,7 @@ export {
READ_ACTIONS,
WRITE_ACTIONS,
} from "./plugins/files/policy";
export * from "./plugins/ga-exports.generated";
export type {
IJobsConfig,
JobAPI,
Expand Down
8 changes: 8 additions & 0 deletions packages/appkit/src/plugins/beta-exports.generated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// AUTO-GENERATED from packages/appkit/src/plugins/<name>/manifest.json — do not edit.
// Run: pnpm exec tsx tools/generate-plugin-entries.ts
//
// The manifest's "stability" field is the single source of truth for which
// subpath ships each plugin. Editing this file by hand will drift it from the
// manifests and the synced appkit.plugins.json.

export {};
Loading
Loading