@kazzle/app 0.1.693
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/README.md +63 -0
- package/dist/client.d.ts +25 -0
- package/dist/client.js +83 -0
- package/dist/index.d.ts +86 -0
- package/dist/index.js +15 -0
- package/dist/migrations.d.ts +19 -0
- package/dist/migrations.js +74 -0
- package/dist/templates.d.ts +33 -0
- package/dist/templates.js +89 -0
- package/dist/tools.d.ts +36 -0
- package/dist/tools.js +22 -0
- package/dist/vite.d.ts +29 -0
- package/dist/vite.js +77 -0
- package/package.json +47 -0
- package/templates/ai-app/KAZZLE.md +21 -0
- package/templates/ai-app/bun.lock +290 -0
- package/templates/ai-app/components/server/index.ts +238 -0
- package/templates/ai-app/components/server/package.json +20 -0
- package/templates/ai-app/components/server/tsconfig.json +11 -0
- package/templates/ai-app/components/ui/index.html +12 -0
- package/templates/ai-app/components/ui/package.json +28 -0
- package/templates/ai-app/components/ui/public/favicon.svg +4 -0
- package/templates/ai-app/components/ui/src/App.tsx +52 -0
- package/templates/ai-app/components/ui/src/api.ts +85 -0
- package/templates/ai-app/components/ui/src/main.tsx +11 -0
- package/templates/ai-app/components/ui/src/styles/app.css +210 -0
- package/templates/ai-app/components/ui/src/styles/theme.css +181 -0
- package/templates/ai-app/components/ui/src/tabs/chat-tab.tsx +75 -0
- package/templates/ai-app/components/ui/src/tabs/extract-tab.tsx +92 -0
- package/templates/ai-app/components/ui/src/tabs/image-tab.tsx +51 -0
- package/templates/ai-app/components/ui/src/tabs/speech-tab.tsx +47 -0
- package/templates/ai-app/components/ui/src/tabs/transcribe-tab.tsx +42 -0
- package/templates/ai-app/components/ui/src/tabs/video-tab.tsx +76 -0
- package/templates/ai-app/components/ui/src/types.ts +53 -0
- package/templates/ai-app/components/ui/src/vite-env.d.ts +1 -0
- package/templates/ai-app/components/ui/tsconfig.json +21 -0
- package/templates/ai-app/components/ui/vite.config.ts +22 -0
- package/templates/ai-app/kazzle.config.ts +9 -0
- package/templates/ai-app/package.json +17 -0
- package/templates/empty-app/KAZZLE.md +10 -0
- package/templates/empty-app/kazzle.config.ts +8 -0
- package/templates/empty-app/package.json +15 -0
- package/templates/manifest.json +10 -0
- package/templates/process-app/KAZZLE.md +10 -0
- package/templates/process-app/components/process/index.ts +155 -0
- package/templates/process-app/components/process/package.json +20 -0
- package/templates/process-app/components/process/tsconfig.json +12 -0
- package/templates/process-app/kazzle.config.ts +32 -0
- package/templates/process-app/package.json +18 -0
- package/templates/process-app/skills/tools/SKILL.md +37 -0
- package/templates/process-app/skills/tools/tools.ts +19 -0
- package/templates/realtime-app/KAZZLE.md +11 -0
- package/templates/realtime-app/bun.lock +887 -0
- package/templates/realtime-app/components/server/deploy.ts +105 -0
- package/templates/realtime-app/components/server/index.ts +40 -0
- package/templates/realtime-app/components/server/migrations/001_items.sql +6 -0
- package/templates/realtime-app/components/server/package.json +20 -0
- package/templates/realtime-app/components/server/routes.ts +137 -0
- package/templates/realtime-app/components/server/tsconfig.json +15 -0
- package/templates/realtime-app/components/ui/index.html +13 -0
- package/templates/realtime-app/components/ui/package.json +36 -0
- package/templates/realtime-app/components/ui/public/favicon.svg +4 -0
- package/templates/realtime-app/components/ui/src/App.tsx +77 -0
- package/templates/realtime-app/components/ui/src/lib/connector.ts +80 -0
- package/templates/realtime-app/components/ui/src/lib/schema.ts +35 -0
- package/templates/realtime-app/components/ui/src/lib/sync.ts +24 -0
- package/templates/realtime-app/components/ui/src/main.tsx +49 -0
- package/templates/realtime-app/components/ui/src/styles/theme.css +181 -0
- package/templates/realtime-app/components/ui/src/vite-env.d.ts +2 -0
- package/templates/realtime-app/components/ui/tsconfig.json +14 -0
- package/templates/realtime-app/components/ui/vite.config.ts +86 -0
- package/templates/realtime-app/kazzle.config.ts +11 -0
- package/templates/realtime-app/package.json +17 -0
- package/templates/shared/icon.svg +4 -0
- package/templates/ui-app/KAZZLE.md +16 -0
- package/templates/ui-app/components/ui/bun.lock +894 -0
- package/templates/ui-app/components/ui/index.html +13 -0
- package/templates/ui-app/components/ui/package.json +30 -0
- package/templates/ui-app/components/ui/public/favicon.svg +4 -0
- package/templates/ui-app/components/ui/src/App.tsx +41 -0
- package/templates/ui-app/components/ui/src/main.tsx +24 -0
- package/templates/ui-app/components/ui/src/styles/theme.css +181 -0
- package/templates/ui-app/components/ui/tsconfig.json +21 -0
- package/templates/ui-app/components/ui/vite.config.ts +97 -0
- package/templates/ui-app/kazzle.config.ts +10 -0
- package/templates/ui-app/package.json +16 -0
- package/templates/ui-db-app/KAZZLE.md +12 -0
- package/templates/ui-db-app/components/server/index.ts +218 -0
- package/templates/ui-db-app/components/server/package.json +21 -0
- package/templates/ui-db-app/components/server/tsconfig.json +11 -0
- package/templates/ui-db-app/components/ui/index.html +12 -0
- package/templates/ui-db-app/components/ui/package.json +28 -0
- package/templates/ui-db-app/components/ui/public/favicon.svg +4 -0
- package/templates/ui-db-app/components/ui/src/App.tsx +221 -0
- package/templates/ui-db-app/components/ui/src/api.ts +79 -0
- package/templates/ui-db-app/components/ui/src/main.tsx +10 -0
- package/templates/ui-db-app/components/ui/src/styles/theme.css +181 -0
- package/templates/ui-db-app/components/ui/src/types.ts +17 -0
- package/templates/ui-db-app/components/ui/src/vite-env.d.ts +1 -0
- package/templates/ui-db-app/components/ui/tsconfig.json +21 -0
- package/templates/ui-db-app/components/ui/vite.config.ts +19 -0
- package/templates/ui-db-app/kazzle.config.ts +9 -0
- package/templates/ui-db-app/package.json +17 -0
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# @kazzle/app
|
|
2
|
+
|
|
3
|
+
The one versioned package for building Kazzle apps. It owns the app-facing
|
|
4
|
+
TypeScript contracts, tool helpers, the Vite preview helper, the app templates,
|
|
5
|
+
and the app migration codemods.
|
|
6
|
+
|
|
7
|
+
This file is the ownership doc referenced by the code comments in this package
|
|
8
|
+
and in the server. Read it before changing exports, templates, or the
|
|
9
|
+
compatibility policy.
|
|
10
|
+
|
|
11
|
+
## Why this package exists
|
|
12
|
+
|
|
13
|
+
App contracts used to be copied into every generated app repo as vendored files
|
|
14
|
+
(`kazzle.types.ts`, `kazzle.vite.ts`). Copies drift, can't be versioned, and give
|
|
15
|
+
the AI stale types. This package replaces the copies with real imports:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { defineConfig } from '@kazzle/app';
|
|
19
|
+
import { defineTools } from '@kazzle/app/tools';
|
|
20
|
+
import { kazzleAppVite } from '@kazzle/app/vite';
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Subpath exports — keep them separate
|
|
24
|
+
|
|
25
|
+
One npm package, one version, many subpaths. The split is a hard contract, not a
|
|
26
|
+
convenience: process apps that only need tools must not pull in Vite/plugin code,
|
|
27
|
+
and the root export must stay tiny and dependency-free.
|
|
28
|
+
|
|
29
|
+
| Subpath | Owns | Runtime deps |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `@kazzle/app` | `defineConfig`, `KazzleConfig` + component/trigger/env types | none |
|
|
32
|
+
| `@kazzle/app/tools` | `defineTools`, `KazzleTool` types, Zod input helper types | `zod` (peer, type-only) |
|
|
33
|
+
| `@kazzle/app/client` | install-scoped client: per-install secrets, sibling component URLs | none |
|
|
34
|
+
| `@kazzle/app/vite` | `kazzleAppVite` preview/runtime Vite helper | `vite` (peer, type-only) |
|
|
35
|
+
| `@kazzle/app/templates` | programmatic template manifest + asset readers (server/generator only) | none |
|
|
36
|
+
| `@kazzle/app/migrations` | app migration metadata + codemods (server/CLI only) | none |
|
|
37
|
+
|
|
38
|
+
To add an export: add the `src/*.ts` file, add it to BOTH `exports` and
|
|
39
|
+
`publishConfig.exports` in `package.json`, and document it in the table above.
|
|
40
|
+
|
|
41
|
+
## Build model — source in dev, dist on npm
|
|
42
|
+
|
|
43
|
+
- Workspace consumers (server, tests, typecheck) resolve `exports` → raw `.ts`
|
|
44
|
+
source. Bun and `tsc` read TS directly, so there is no build step for the
|
|
45
|
+
monorepo. This mirrors `@kazzle/framework`.
|
|
46
|
+
- The published package resolves `publishConfig.exports` → compiled `dist/*.js`
|
|
47
|
+
+ `dist/*.d.ts`, because apps installed from npm need JS + types.
|
|
48
|
+
- `bun run build` (`scripts/build.ts`) emits `dist/` and copies `templates/`.
|
|
49
|
+
- Templates ship as package assets (`files: ["dist", "templates"]`) so the
|
|
50
|
+
server generator reads the exact versioned template for every app it creates.
|
|
51
|
+
|
|
52
|
+
## Versioning
|
|
53
|
+
|
|
54
|
+
`@kazzle/app` shares the root Kazzle version during pre-launch (`package.json`
|
|
55
|
+
`version` mirrors the repo root). CI's version-bump job syncs it before pack +
|
|
56
|
+
publish. Splitting into independent semver waits until there is a real
|
|
57
|
+
post-launch reason. See `documents/cicd.md`.
|
|
58
|
+
|
|
59
|
+
## Compatibility
|
|
60
|
+
|
|
61
|
+
Publish/check enforcement lives on the server in
|
|
62
|
+
`server/apps/apps.sdk-compat.ts` (supported / deprecated / blocked ranges +
|
|
63
|
+
migration command). Docs are not the enforcement layer.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Read the injected base URL of the Kazzle API for install-secret calls. */
|
|
2
|
+
export declare function kazzleApiUrl(): string;
|
|
3
|
+
/** Env var name Kazzle injects for a sibling component's internal URL. */
|
|
4
|
+
export declare function componentUrlEnvName(componentName: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Resolve a sibling component's internal URL. Throws if the platform has not
|
|
7
|
+
* injected it (start the named component first) — there is no fallback, because
|
|
8
|
+
* a wrong/empty address makes cross-component calls fail silently.
|
|
9
|
+
*/
|
|
10
|
+
export declare function componentUrl(componentName: string): string;
|
|
11
|
+
/** Extract the install identity Authorization header from an incoming tool request. */
|
|
12
|
+
export declare function installAuthHeader(req: Request): string;
|
|
13
|
+
/**
|
|
14
|
+
* Client scoped to one install, built from the identity header on an incoming
|
|
15
|
+
* tool request. Use it to persist and read per-install runtime secrets.
|
|
16
|
+
*/
|
|
17
|
+
export declare class KazzleInstallClient {
|
|
18
|
+
private readonly auth;
|
|
19
|
+
private readonly apiUrl;
|
|
20
|
+
constructor(reqOrAuthHeader: Request | string);
|
|
21
|
+
/** Read a per-install secret by fixed name. Returns null when not set yet (404). */
|
|
22
|
+
getSecret(name: string): Promise<string | null>;
|
|
23
|
+
/** Write a per-install secret by fixed name. */
|
|
24
|
+
setSecret(name: string, value: string): Promise<void>;
|
|
25
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// @kazzle/app/client — install-scoped helpers for app process/server code.
|
|
2
|
+
//
|
|
3
|
+
// Two things app backends repeatedly need and used to hand-roll from copy-paste
|
|
4
|
+
// comments in the templates:
|
|
5
|
+
//
|
|
6
|
+
// 1. Per-install secrets. When the AI calls an app tool, Kazzle POSTs the
|
|
7
|
+
// request with `Authorization: Bearer <install identity>`. The app reads
|
|
8
|
+
// or writes runtime secrets by calling Kazzle back with that SAME header;
|
|
9
|
+
// Kazzle scopes the secret to this install. Use a fixed secret name — never
|
|
10
|
+
// key by user yourself.
|
|
11
|
+
// 2. Sibling component URLs. A component reaches another component in the same
|
|
12
|
+
// app via the injected `KAZZLE_APP_COMPONENT_<NAME>_URL` env var. That URL
|
|
13
|
+
// is server-to-server only — never expose it to the browser.
|
|
14
|
+
//
|
|
15
|
+
// Dependency-free on purpose (uses global fetch). Do not add zod/vite here.
|
|
16
|
+
/** Read the injected base URL of the Kazzle API for install-secret calls. */
|
|
17
|
+
export function kazzleApiUrl() {
|
|
18
|
+
const url = process.env['KAZZLE_API_URL'];
|
|
19
|
+
if (!url) {
|
|
20
|
+
throw new Error('KAZZLE_API_URL is not set — this code must run as a deployed Kazzle app component.');
|
|
21
|
+
}
|
|
22
|
+
return url;
|
|
23
|
+
}
|
|
24
|
+
/** Env var name Kazzle injects for a sibling component's internal URL. */
|
|
25
|
+
export function componentUrlEnvName(componentName) {
|
|
26
|
+
const normalized = componentName.toUpperCase().replace(/[^A-Z0-9]+/g, '_').replace(/^_|_$/g, '');
|
|
27
|
+
return `KAZZLE_APP_COMPONENT_${normalized}_URL`;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Resolve a sibling component's internal URL. Throws if the platform has not
|
|
31
|
+
* injected it (start the named component first) — there is no fallback, because
|
|
32
|
+
* a wrong/empty address makes cross-component calls fail silently.
|
|
33
|
+
*/
|
|
34
|
+
export function componentUrl(componentName) {
|
|
35
|
+
const envName = componentUrlEnvName(componentName);
|
|
36
|
+
const url = process.env[envName];
|
|
37
|
+
if (!url) {
|
|
38
|
+
throw new Error(`${envName} is not set. Start the "${componentName}" component so Kazzle can resolve its address.`);
|
|
39
|
+
}
|
|
40
|
+
return url;
|
|
41
|
+
}
|
|
42
|
+
/** Extract the install identity Authorization header from an incoming tool request. */
|
|
43
|
+
export function installAuthHeader(req) {
|
|
44
|
+
const auth = req.headers.get('authorization');
|
|
45
|
+
if (!auth) {
|
|
46
|
+
throw new Error('Missing Authorization header — expected the install identity from a Kazzle tool call.');
|
|
47
|
+
}
|
|
48
|
+
return auth;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Client scoped to one install, built from the identity header on an incoming
|
|
52
|
+
* tool request. Use it to persist and read per-install runtime secrets.
|
|
53
|
+
*/
|
|
54
|
+
export class KazzleInstallClient {
|
|
55
|
+
auth;
|
|
56
|
+
apiUrl;
|
|
57
|
+
constructor(reqOrAuthHeader) {
|
|
58
|
+
this.auth = typeof reqOrAuthHeader === 'string' ? reqOrAuthHeader : installAuthHeader(reqOrAuthHeader);
|
|
59
|
+
this.apiUrl = kazzleApiUrl();
|
|
60
|
+
}
|
|
61
|
+
/** Read a per-install secret by fixed name. Returns null when not set yet (404). */
|
|
62
|
+
async getSecret(name) {
|
|
63
|
+
const res = await fetch(`${this.apiUrl}/apps/secrets/${encodeURIComponent(name)}`, {
|
|
64
|
+
headers: { authorization: this.auth },
|
|
65
|
+
});
|
|
66
|
+
if (res.status === 404)
|
|
67
|
+
return null;
|
|
68
|
+
if (!res.ok)
|
|
69
|
+
throw new Error(`Failed to read install secret "${name}": ${res.status}`);
|
|
70
|
+
const body = (await res.json());
|
|
71
|
+
return body.value ?? null;
|
|
72
|
+
}
|
|
73
|
+
/** Write a per-install secret by fixed name. */
|
|
74
|
+
async setSecret(name, value) {
|
|
75
|
+
const res = await fetch(`${this.apiUrl}/apps/secrets/${encodeURIComponent(name)}`, {
|
|
76
|
+
method: 'PUT',
|
|
77
|
+
headers: { authorization: this.auth, 'content-type': 'application/json' },
|
|
78
|
+
body: JSON.stringify({ value }),
|
|
79
|
+
});
|
|
80
|
+
if (!res.ok)
|
|
81
|
+
throw new Error(`Failed to write install secret "${name}": ${res.status}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export interface KazzleComponentRuntime {
|
|
2
|
+
/** Command to start the dev server (e.g. "bun run dev", "vite") */
|
|
3
|
+
dev?: string;
|
|
4
|
+
/** Command to build for production (e.g. "bun run build", "vite build") */
|
|
5
|
+
build?: string;
|
|
6
|
+
/** Command to start in production (e.g. "bun run start") */
|
|
7
|
+
run?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface KazzleComponentEnv {
|
|
10
|
+
/** Secret collection slug */
|
|
11
|
+
collection: string;
|
|
12
|
+
/** Environment slug for secret injection */
|
|
13
|
+
environment: string;
|
|
14
|
+
/** Only inject these env var names. If omitted, inject all from collection+environment. */
|
|
15
|
+
include?: string[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A single trigger entry on a process component. One component can declare
|
|
19
|
+
* many triggers — for example, a daily cron AND a webhook handler that both
|
|
20
|
+
* route into the same code.
|
|
21
|
+
*
|
|
22
|
+
* - `kind: 'schedule'` → fires on the cron expression in `schedule`.
|
|
23
|
+
* `schedule` is a 5-field cron expression with minute resolution.
|
|
24
|
+
*
|
|
25
|
+
* - `kind: 'webhook'` → fires when the platform receives
|
|
26
|
+
* `POST /webhooks/{spaceId}/{appId}/{componentName}/{name}`.
|
|
27
|
+
*
|
|
28
|
+
* `path` is the HTTP route on the running server that handles this trigger.
|
|
29
|
+
* Required when `processMode: 'persistent'` (the platform POSTs the trigger
|
|
30
|
+
* into the long-running server at this path with `Authorization: Bearer
|
|
31
|
+
* ${KAZZLE_TRIGGER_SECRET}`). Ignored for `processMode: 'triggered'` — those
|
|
32
|
+
* components run as one-off ephemeral processes that read `TRIGGER_NAME`
|
|
33
|
+
* from the environment instead.
|
|
34
|
+
*/
|
|
35
|
+
export interface KazzleTrigger {
|
|
36
|
+
/** Stable identifier, unique within the component (kebab-case, e.g. "daily-digest"). */
|
|
37
|
+
name: string;
|
|
38
|
+
/** Trigger kind — schedule (cron) or webhook (HTTP POST). */
|
|
39
|
+
kind: 'schedule' | 'webhook';
|
|
40
|
+
/** Cron expression (5-field, minute resolution). Required for kind="schedule". */
|
|
41
|
+
schedule?: string;
|
|
42
|
+
/** HTTP path on the running server. Required when processMode="persistent". */
|
|
43
|
+
path?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface KazzleComponent {
|
|
46
|
+
/** Unique component name within the app */
|
|
47
|
+
name: string;
|
|
48
|
+
/** Component type — ui (max 1) or process */
|
|
49
|
+
type: 'ui' | 'process';
|
|
50
|
+
/** Entry path within the app directory */
|
|
51
|
+
path: string;
|
|
52
|
+
/** Commands for dev, build, and run phases */
|
|
53
|
+
runtime?: KazzleComponentRuntime;
|
|
54
|
+
/**
|
|
55
|
+
* Execution lifecycle for process components.
|
|
56
|
+
*
|
|
57
|
+
* - 'persistent' — long-running server. Triggers are POSTed into the
|
|
58
|
+
* server at the declared `path`. This is the default.
|
|
59
|
+
* - 'triggered' — ephemeral one-off run per trigger. The platform
|
|
60
|
+
* spawns the entry script with `TRIGGER_NAME`, `RUN_ID`, and an
|
|
61
|
+
* optional `WEBHOOK_PAYLOAD` env var, then waits for exit.
|
|
62
|
+
*/
|
|
63
|
+
processMode?: 'persistent' | 'triggered';
|
|
64
|
+
/** Schedule and webhook triggers. One component may declare many. */
|
|
65
|
+
triggers?: KazzleTrigger[];
|
|
66
|
+
/** Secret collection + environment for env var injection. */
|
|
67
|
+
env?: KazzleComponentEnv;
|
|
68
|
+
}
|
|
69
|
+
export interface KazzleSkill {
|
|
70
|
+
/** Skill name */
|
|
71
|
+
name: string;
|
|
72
|
+
/** Path to the SKILL.md file relative to the app root */
|
|
73
|
+
path: string;
|
|
74
|
+
}
|
|
75
|
+
export interface KazzleConfig {
|
|
76
|
+
/** Where the published app runs — remote (Fly.io) or local (user computer). Defaults to remote. */
|
|
77
|
+
target?: 'local' | 'remote';
|
|
78
|
+
/** Path to the app icon file (png, jpg, svg, webp, ico) */
|
|
79
|
+
icon?: string;
|
|
80
|
+
/** Executable components — UI frontends or background processes */
|
|
81
|
+
components?: KazzleComponent[];
|
|
82
|
+
/** AI skill definitions — markdown files the AI reads for domain knowledge */
|
|
83
|
+
skills?: KazzleSkill[];
|
|
84
|
+
}
|
|
85
|
+
/** Type-safe config helper. Use: export default defineConfig({ ... }) */
|
|
86
|
+
export declare function defineConfig(config: KazzleConfig): KazzleConfig;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// @kazzle/app — root export. App config contract + `defineConfig`.
|
|
2
|
+
//
|
|
3
|
+
// SOURCE OF TRUTH for the app config types that generated apps author in
|
|
4
|
+
// kazzle.config.ts. The server validates the runtime shape with the Zod
|
|
5
|
+
// `ManifestSchema` in server/apps/apps.manifest.ts; these interfaces are the
|
|
6
|
+
// hand-authored TypeScript mirror app code imports. Keep the two in sync — if
|
|
7
|
+
// you add a config field here, add it to ManifestSchema in the same change.
|
|
8
|
+
//
|
|
9
|
+
// This module must stay dependency-free. Tools live in `@kazzle/app/tools`,
|
|
10
|
+
// the Vite helper in `@kazzle/app/vite` — do not re-export them here or the
|
|
11
|
+
// root import pulls in zod/vite for every consumer. See README.md.
|
|
12
|
+
/** Type-safe config helper. Use: export default defineConfig({ ... }) */
|
|
13
|
+
export function defineConfig(config) {
|
|
14
|
+
return config;
|
|
15
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** A file map: relative path -> file contents. */
|
|
2
|
+
export type AppFileMap = Record<string, string>;
|
|
3
|
+
export interface AppMigration {
|
|
4
|
+
/** Unique migration id, e.g. "app-api-v0.2". Referenced by compat policy. */
|
|
5
|
+
id: string;
|
|
6
|
+
/** Human range this migration upgrades FROM, e.g. "<0.2". Display + matching. */
|
|
7
|
+
appliesTo: string;
|
|
8
|
+
/** What the migration does. */
|
|
9
|
+
description: string;
|
|
10
|
+
/** Globs the migration may edit (for the dry-run summary). */
|
|
11
|
+
files: string[];
|
|
12
|
+
/** Pure transform: returns only the files it changed (or added). */
|
|
13
|
+
run(files: AppFileMap): AppFileMap;
|
|
14
|
+
}
|
|
15
|
+
export declare const APP_MIGRATIONS: AppMigration[];
|
|
16
|
+
/** Look up a migration by id. */
|
|
17
|
+
export declare function getAppMigration(id: string): AppMigration | undefined;
|
|
18
|
+
/** Add the `@kazzle/app` dependency to an app package.json string (idempotent). */
|
|
19
|
+
export declare function addPackageDependency(pkgJson: string, version: string): string;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// @kazzle/app/migrations — versioned app migration metadata + codemods.
|
|
2
|
+
//
|
|
3
|
+
// SERVER/CLI ONLY. A machine-readable registry of upgrade instructions for
|
|
4
|
+
// existing generated app repos — the same idea as Next codemods / Expo upgrade
|
|
5
|
+
// tooling. It is NOT product data: it tells `kazzle migrate` how to rewrite an
|
|
6
|
+
// app checkout when the app contract changes between package versions.
|
|
7
|
+
//
|
|
8
|
+
// Codemods are PURE: they take an in-memory `{ path -> contents }` map and
|
|
9
|
+
// return the changed files. The server/CLI owns reading and writing the actual
|
|
10
|
+
// checkout (see the thin-CLI rule in the plan). This keeps migrations testable
|
|
11
|
+
// without a filesystem and keeps fs/permission policy server-side.
|
|
12
|
+
//
|
|
13
|
+
// To add a migration:
|
|
14
|
+
// 1. Append an `AppMigration` to `APP_MIGRATIONS` with a unique `id`.
|
|
15
|
+
// 2. Set `appliesTo` (semver-ish range string, matched by the server compat
|
|
16
|
+
// policy) and `files` (globs it may touch, for the dry-run summary).
|
|
17
|
+
// 3. Implement `run` as a pure file-map transform.
|
|
18
|
+
// 4. Point the server compat blocker/deprecation `migrationCommand` at the id.
|
|
19
|
+
/** Rewrite a vendored `./kazzle.types` / `./kazzle.vite` import to the package. */
|
|
20
|
+
function rewriteImports(contents) {
|
|
21
|
+
return contents
|
|
22
|
+
// `from './kazzle.types'`, `from '../../kazzle.types'`, etc → '@kazzle/app'
|
|
23
|
+
.replace(/(from\s*['"])((?:\.\.?\/)+)kazzle\.types(['"])/g, '$1@kazzle/app$3')
|
|
24
|
+
// vite helper → '@kazzle/app/vite'
|
|
25
|
+
.replace(/(from\s*['"])((?:\.\.?\/)+)kazzle\.vite(['"])/g, '$1@kazzle/app/vite$3');
|
|
26
|
+
}
|
|
27
|
+
/** Ensure `@kazzle/app` is a dependency of the app root package.json. */
|
|
28
|
+
function ensurePackageDependency(pkgJson, version) {
|
|
29
|
+
const parsed = JSON.parse(pkgJson);
|
|
30
|
+
parsed.dependencies = parsed.dependencies ?? {};
|
|
31
|
+
if (parsed.dependencies['@kazzle/app'])
|
|
32
|
+
return pkgJson;
|
|
33
|
+
parsed.dependencies['@kazzle/app'] = `^${version}`;
|
|
34
|
+
return JSON.stringify(parsed, null, 2) + '\n';
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* v0.2: apps stop vendoring kazzle.types.ts / kazzle.vite.ts and import the
|
|
38
|
+
* contracts from `@kazzle/app`. Rewrites imports, adds the dependency, and
|
|
39
|
+
* deletes the vendored files (represented by an empty-string body, which the
|
|
40
|
+
* server/CLI turns into a delete).
|
|
41
|
+
*/
|
|
42
|
+
function migrateToPackageImports(files) {
|
|
43
|
+
const changed = {};
|
|
44
|
+
for (const [path, contents] of Object.entries(files)) {
|
|
45
|
+
const base = path.split('/').pop() ?? path;
|
|
46
|
+
if (base === 'kazzle.types.ts' || base === 'kazzle.vite.ts') {
|
|
47
|
+
changed[path] = ''; // empty body = delete (vendored file replaced by import)
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (path === 'package.json')
|
|
51
|
+
continue; // handled below with the version
|
|
52
|
+
const rewritten = rewriteImports(contents);
|
|
53
|
+
if (rewritten !== contents)
|
|
54
|
+
changed[path] = rewritten;
|
|
55
|
+
}
|
|
56
|
+
return changed;
|
|
57
|
+
}
|
|
58
|
+
export const APP_MIGRATIONS = [
|
|
59
|
+
{
|
|
60
|
+
id: 'app-api-v0.2',
|
|
61
|
+
appliesTo: '<0.2',
|
|
62
|
+
description: 'Import Kazzle app contracts from @kazzle/app instead of vendored kazzle.types.ts / kazzle.vite.ts.',
|
|
63
|
+
files: ['**/*.ts', '**/*.tsx', 'package.json'],
|
|
64
|
+
run: migrateToPackageImports,
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
/** Look up a migration by id. */
|
|
68
|
+
export function getAppMigration(id) {
|
|
69
|
+
return APP_MIGRATIONS.find(m => m.id === id);
|
|
70
|
+
}
|
|
71
|
+
/** Add the `@kazzle/app` dependency to an app package.json string (idempotent). */
|
|
72
|
+
export function addPackageDependency(pkgJson, version) {
|
|
73
|
+
return ensurePackageDependency(pkgJson, version);
|
|
74
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** One entry in the package template manifest (metadata only, no file bodies). */
|
|
2
|
+
export interface TemplateManifestEntry {
|
|
3
|
+
/** Template id, e.g. "ui-db". The scaffold dir is `${id}-app`. */
|
|
4
|
+
id: string;
|
|
5
|
+
/** Short human label. */
|
|
6
|
+
label: string;
|
|
7
|
+
/** One-line description for listing. */
|
|
8
|
+
description: string;
|
|
9
|
+
/** Optional discovery tags. */
|
|
10
|
+
tags?: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Absolute path to the package `templates/` root. The server's gitignore-aware
|
|
14
|
+
* file reader (`server/apps/apps.template-files.ts`) points its scan root here
|
|
15
|
+
* so there is exactly one on-disk source for scaffold files.
|
|
16
|
+
*/
|
|
17
|
+
export declare function templatesRoot(): string;
|
|
18
|
+
/** Read the template manifest (metadata for listing). */
|
|
19
|
+
export declare function templateManifest(): TemplateManifestEntry[];
|
|
20
|
+
/** Absolute path to a template's scaffold dir. */
|
|
21
|
+
export declare function templateDir(id: string): string;
|
|
22
|
+
/** Absolute path to the shared scaffold dir merged into every app. */
|
|
23
|
+
export declare function sharedTemplateDir(): string;
|
|
24
|
+
/** Whether a template id has a scaffold dir in this package. */
|
|
25
|
+
export declare function templateExists(id: string): boolean;
|
|
26
|
+
/** Relative file paths in a template's scaffold dir. */
|
|
27
|
+
export declare function listTemplateFiles(id: string): string[];
|
|
28
|
+
/** Relative file paths in the shared scaffold dir. */
|
|
29
|
+
export declare function listSharedFiles(): string[];
|
|
30
|
+
/** Read one scaffold file's raw contents. Throws if missing. */
|
|
31
|
+
export declare function readTemplateFile(id: string, relPath: string): string;
|
|
32
|
+
/** Read one shared scaffold file's raw contents. Throws if missing. */
|
|
33
|
+
export declare function readSharedFile(relPath: string): string;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// @kazzle/app/templates — programmatic template asset reader.
|
|
2
|
+
//
|
|
3
|
+
// SERVER/GENERATOR ONLY. This is the single source for the app scaffold files
|
|
4
|
+
// shipped with the package (under `packages/kazzle-app/templates/`). The server
|
|
5
|
+
// reads through these functions instead of hardcoding `server/templates` fs
|
|
6
|
+
// paths, so app generation and the AI `tpl_*` browse tools always see the exact
|
|
7
|
+
// versioned template that ships with this package version.
|
|
8
|
+
//
|
|
9
|
+
// What lives where:
|
|
10
|
+
// - Scaffold file assets + `manifest.json` → this package's `templates/` dir.
|
|
11
|
+
// - Template setup hooks + params (server-side, needs DB/checkout context) →
|
|
12
|
+
// `server/apps/templates/*.template.ts` (cannot move here; would create a
|
|
13
|
+
// package → server dependency).
|
|
14
|
+
//
|
|
15
|
+
// Resolution: `templates/` sits next to `src/` in dev and next to `dist/` when
|
|
16
|
+
// published, so `../templates` off this module resolves in both.
|
|
17
|
+
import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { dirname, join, relative } from 'node:path';
|
|
20
|
+
const TEMPLATES_DIR = fileURLToPath(new URL('../templates/', import.meta.url));
|
|
21
|
+
const ALWAYS_SKIP = new Set(['.git', '.DS_Store']);
|
|
22
|
+
function manifestPath() {
|
|
23
|
+
return join(TEMPLATES_DIR, 'manifest.json');
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Absolute path to the package `templates/` root. The server's gitignore-aware
|
|
27
|
+
* file reader (`server/apps/apps.template-files.ts`) points its scan root here
|
|
28
|
+
* so there is exactly one on-disk source for scaffold files.
|
|
29
|
+
*/
|
|
30
|
+
export function templatesRoot() {
|
|
31
|
+
return TEMPLATES_DIR;
|
|
32
|
+
}
|
|
33
|
+
/** Read the template manifest (metadata for listing). */
|
|
34
|
+
export function templateManifest() {
|
|
35
|
+
const path = manifestPath();
|
|
36
|
+
if (!existsSync(path))
|
|
37
|
+
return [];
|
|
38
|
+
const parsed = JSON.parse(readFileSync(path, 'utf-8'));
|
|
39
|
+
return parsed.entries;
|
|
40
|
+
}
|
|
41
|
+
/** Scaffold directory name for a template id. */
|
|
42
|
+
function templateDirName(id) {
|
|
43
|
+
return `${id}-app`;
|
|
44
|
+
}
|
|
45
|
+
/** Absolute path to a template's scaffold dir. */
|
|
46
|
+
export function templateDir(id) {
|
|
47
|
+
return join(TEMPLATES_DIR, templateDirName(id));
|
|
48
|
+
}
|
|
49
|
+
/** Absolute path to the shared scaffold dir merged into every app. */
|
|
50
|
+
export function sharedTemplateDir() {
|
|
51
|
+
return join(TEMPLATES_DIR, 'shared');
|
|
52
|
+
}
|
|
53
|
+
/** Whether a template id has a scaffold dir in this package. */
|
|
54
|
+
export function templateExists(id) {
|
|
55
|
+
const dir = templateDir(id);
|
|
56
|
+
return existsSync(dir) && statSync(dir).isDirectory();
|
|
57
|
+
}
|
|
58
|
+
function walk(dir, baseDir) {
|
|
59
|
+
const files = [];
|
|
60
|
+
if (!existsSync(dir))
|
|
61
|
+
return files;
|
|
62
|
+
for (const entry of readdirSync(dir)) {
|
|
63
|
+
if (ALWAYS_SKIP.has(entry))
|
|
64
|
+
continue;
|
|
65
|
+
const full = join(dir, entry);
|
|
66
|
+
if (statSync(full).isDirectory())
|
|
67
|
+
files.push(...walk(full, baseDir));
|
|
68
|
+
else
|
|
69
|
+
files.push(relative(baseDir, full));
|
|
70
|
+
}
|
|
71
|
+
return files;
|
|
72
|
+
}
|
|
73
|
+
/** Relative file paths in a template's scaffold dir. */
|
|
74
|
+
export function listTemplateFiles(id) {
|
|
75
|
+
return walk(templateDir(id), templateDir(id));
|
|
76
|
+
}
|
|
77
|
+
/** Relative file paths in the shared scaffold dir. */
|
|
78
|
+
export function listSharedFiles() {
|
|
79
|
+
const dir = sharedTemplateDir();
|
|
80
|
+
return walk(dir, dir);
|
|
81
|
+
}
|
|
82
|
+
/** Read one scaffold file's raw contents. Throws if missing. */
|
|
83
|
+
export function readTemplateFile(id, relPath) {
|
|
84
|
+
return readFileSync(join(templateDir(id), relPath), 'utf-8');
|
|
85
|
+
}
|
|
86
|
+
/** Read one shared scaffold file's raw contents. Throws if missing. */
|
|
87
|
+
export function readSharedFile(relPath) {
|
|
88
|
+
return readFileSync(join(sharedTemplateDir(), relPath), 'utf-8');
|
|
89
|
+
}
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
/** Zod schema for a tool's input object. Use .default(), .optional(), .transform(), etc. */
|
|
3
|
+
export type KazzleToolInputSchema = z.ZodType<Record<string, unknown>>;
|
|
4
|
+
interface KazzleToolBase {
|
|
5
|
+
/** Provider-safe identifier: letters, numbers, underscores, or hyphens. */
|
|
6
|
+
name: string;
|
|
7
|
+
/** Friendly title shown in chat cards, e.g. "Save bookmark". */
|
|
8
|
+
displayName: string;
|
|
9
|
+
/** AI-facing description for when to call this tool. */
|
|
10
|
+
description: string;
|
|
11
|
+
}
|
|
12
|
+
/** A tool served by one of this app's own components. */
|
|
13
|
+
export interface KazzleAppTool extends KazzleToolBase {
|
|
14
|
+
type: 'app';
|
|
15
|
+
/** Zod schema for tool input. Use .default(), .optional(), .transform(), etc. here. */
|
|
16
|
+
input: KazzleToolInputSchema;
|
|
17
|
+
/** Component name from kazzle.config.ts that serves this tool. */
|
|
18
|
+
component: string;
|
|
19
|
+
/** POST route on that component. */
|
|
20
|
+
path: string;
|
|
21
|
+
}
|
|
22
|
+
/** A tool served by an external publisher-hosted HTTP endpoint. */
|
|
23
|
+
export interface KazzleUrlTool extends KazzleToolBase {
|
|
24
|
+
type: 'url';
|
|
25
|
+
input: KazzleToolInputSchema;
|
|
26
|
+
/** Literal absolute URL, used verbatim. */
|
|
27
|
+
url: string;
|
|
28
|
+
}
|
|
29
|
+
/** A name-only reference to a platform/native tool resolved from the code registry. */
|
|
30
|
+
export interface KazzleNativeTool extends KazzleToolBase {
|
|
31
|
+
type: 'kazzle';
|
|
32
|
+
}
|
|
33
|
+
export type KazzleTool = KazzleAppTool | KazzleUrlTool | KazzleNativeTool;
|
|
34
|
+
/** Type-safe tools helper. Use: export const tools = defineTools([...]) */
|
|
35
|
+
export declare function defineTools<T extends readonly KazzleTool[]>(tools: T): T;
|
|
36
|
+
export {};
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// @kazzle/app/tools — app tool contract + `defineTools`.
|
|
2
|
+
//
|
|
3
|
+
// An app declares its AI tools in skills/tools/tools.ts as
|
|
4
|
+
// `export const tools = defineTools([...])`. Each tool's `input` is a Zod
|
|
5
|
+
// schema, which is the whole point of this file existing:
|
|
6
|
+
//
|
|
7
|
+
// - Zod gives real optional/`.default()`/`.transform()` semantics, so a
|
|
8
|
+
// tool with `maxResults: z.number().default(20)` does NOT mark maxResults
|
|
9
|
+
// as required in the model-facing schema. Plain JSON tool schemas treated
|
|
10
|
+
// every field as required, which caused wrong-param tool calls.
|
|
11
|
+
// - `displayName` is the friendly title shown in chat cards ("List Gmail
|
|
12
|
+
// messages"); `name` is the provider-safe id ([a-zA-Z0-9_-]{1,64}).
|
|
13
|
+
//
|
|
14
|
+
// Publish-time, the server compiles these Zod schemas into provider JSON schema
|
|
15
|
+
// for model exposure AND server-side repair validation. That compiler is
|
|
16
|
+
// server/apps/apps.skills.tools.ts — it reads the SAME shape defined here
|
|
17
|
+
// (`type`, `name`, `displayName`, `description`, `input`, `component`/`path`/`url`).
|
|
18
|
+
// If you change this contract, update that compiler and its tests together.
|
|
19
|
+
/** Type-safe tools helper. Use: export const tools = defineTools([...]) */
|
|
20
|
+
export function defineTools(tools) {
|
|
21
|
+
return tools;
|
|
22
|
+
}
|
package/dist/vite.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { UserConfig } from 'vite';
|
|
2
|
+
export interface KazzleAppViteOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Name of the sibling process component that owns this app's backend/API
|
|
5
|
+
* routes. Omit for a pure static UI with no backend component.
|
|
6
|
+
*
|
|
7
|
+
* When set, the dev server refuses to start until Kazzle injects that
|
|
8
|
+
* component's URL (KAZZLE_APP_COMPONENT_<NAME>_URL), and the routes in
|
|
9
|
+
* `proxyPaths` are forwarded to it. That internal URL is for this proxy
|
|
10
|
+
* only — never expose it to the browser via import.meta.env / VITE_*.
|
|
11
|
+
*/
|
|
12
|
+
apiComponent?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Relative root route names the browser calls (e.g. ['/todos', '/chat'])
|
|
15
|
+
* that must be proxied to `apiComponent`. Ignored when `apiComponent` is
|
|
16
|
+
* omitted. The browser always uses relative paths; the proxy hides the
|
|
17
|
+
* backend address.
|
|
18
|
+
*/
|
|
19
|
+
proxyPaths?: string[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Kazzle preview/runtime Vite config. Spread the result into your Vite config:
|
|
23
|
+
*
|
|
24
|
+
* export default defineConfig(({ command }) => ({
|
|
25
|
+
* plugins: [react(), tailwindcss()],
|
|
26
|
+
* ...kazzleAppVite(command, { apiComponent: 'server', proxyPaths: ['/todos'] }),
|
|
27
|
+
* }));
|
|
28
|
+
*/
|
|
29
|
+
export declare function kazzleAppVite(command: string, options?: KazzleAppViteOptions): UserConfig;
|
package/dist/vite.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// @kazzle/app/vite — Kazzle preview/runtime Vite helper.
|
|
2
|
+
//
|
|
3
|
+
// BUILD-TIME ONLY. This module imports vite types and is meant for a UI
|
|
4
|
+
// component's vite.config.ts. Never import it from process/runtime code — the
|
|
5
|
+
// subpath split exists so process apps don't pull vite into their dependency
|
|
6
|
+
// graph. See README.md.
|
|
7
|
+
//
|
|
8
|
+
// READ THIS BEFORE TOUCHING vite.config.ts (human or AI):
|
|
9
|
+
// Kazzle assigns every UI component a specific HOST and PORT at preview/run
|
|
10
|
+
// time and injects them through `kazzle run`. The preview tunnel forwards
|
|
11
|
+
// traffic to THAT exact port. If Vite binds any other port (e.g. its default
|
|
12
|
+
// 5173) the tunnel gets ECONNREFUSED and the preview looks broken. This helper
|
|
13
|
+
// is the ONE place that reads HOST/PORT and pins Vite to them (strictPort).
|
|
14
|
+
//
|
|
15
|
+
// Every UI component's vite.config.ts MUST spread `...kazzleAppVite(command)`
|
|
16
|
+
// into its config instead of hand-writing server.host / server.port.
|
|
17
|
+
function componentUrlEnvName(componentName) {
|
|
18
|
+
const normalized = componentName.toUpperCase().replace(/[^A-Z0-9]+/g, '_').replace(/^_|_$/g, '');
|
|
19
|
+
return `KAZZLE_APP_COMPONENT_${normalized}_URL`;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Kazzle preview/runtime Vite config. Spread the result into your Vite config:
|
|
23
|
+
*
|
|
24
|
+
* export default defineConfig(({ command }) => ({
|
|
25
|
+
* plugins: [react(), tailwindcss()],
|
|
26
|
+
* ...kazzleAppVite(command, { apiComponent: 'server', proxyPaths: ['/todos'] }),
|
|
27
|
+
* }));
|
|
28
|
+
*/
|
|
29
|
+
export function kazzleAppVite(command, options = {}) {
|
|
30
|
+
const isServe = command === 'serve';
|
|
31
|
+
// DO NOT ADD FALLBACKS. HOST/PORT are injected by `kazzle run`. Missing
|
|
32
|
+
// values must throw when a server actually needs to bind (dev/preview) — but
|
|
33
|
+
// production `vite build` emits static files and never binds, so we only
|
|
34
|
+
// enforce in serve mode. Throwing during build would break the deploy pipeline.
|
|
35
|
+
if (isServe && (!process.env['PORT'] || !process.env['HOST'])) {
|
|
36
|
+
throw new Error('PORT and HOST must be set by "kazzle run" — never hardcode them.');
|
|
37
|
+
}
|
|
38
|
+
const host = process.env['HOST'];
|
|
39
|
+
const port = process.env['PORT'] ? Number(process.env['PORT']) : undefined;
|
|
40
|
+
let proxy;
|
|
41
|
+
if (options.apiComponent) {
|
|
42
|
+
const envName = componentUrlEnvName(options.apiComponent);
|
|
43
|
+
const apiTarget = process.env[envName];
|
|
44
|
+
if (isServe && !apiTarget) {
|
|
45
|
+
// This app declares a backend component. The UI proxy MUST know the
|
|
46
|
+
// backend address before serving — there is no fallback. Start the
|
|
47
|
+
// named component first; Kazzle injects its URL from the sibling's
|
|
48
|
+
// sticky component_port.
|
|
49
|
+
throw new Error(`${envName} is required. Start the \`${options.apiComponent}\` component first ` +
|
|
50
|
+
'so Kazzle can resolve its address.');
|
|
51
|
+
}
|
|
52
|
+
if (apiTarget && options.proxyPaths?.length) {
|
|
53
|
+
proxy = Object.fromEntries(options.proxyPaths.map(path => [path, { target: apiTarget, changeOrigin: true }]));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
server: {
|
|
58
|
+
host,
|
|
59
|
+
port,
|
|
60
|
+
// DO NOT REMOVE. Fail loudly (EADDRINUSE) if PORT is taken instead of
|
|
61
|
+
// silently binding the next free port. The Kazzle preview proxy only
|
|
62
|
+
// knows the assigned PORT — if Vite drifted the preview would
|
|
63
|
+
// 404/ECONNREFUSED and look "broken". Kazzle frees this port first.
|
|
64
|
+
strictPort: true,
|
|
65
|
+
// The Kazzle preview tunnel forwards requests with a *.kazzle.app Host
|
|
66
|
+
// header, not localhost. Vite rejects unknown hosts unless enabled.
|
|
67
|
+
allowedHosts: true,
|
|
68
|
+
proxy,
|
|
69
|
+
},
|
|
70
|
+
preview: {
|
|
71
|
+
host,
|
|
72
|
+
port,
|
|
73
|
+
strictPort: true, // DO NOT REMOVE — see server.strictPort above.
|
|
74
|
+
allowedHosts: true,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|