@aprovan/mcp-app-server 0.1.0-dev.4d82df8
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/.turbo/turbo-build.log +23 -0
- package/E2E_TESTING.md +224 -0
- package/LICENSE +373 -0
- package/README.md +164 -0
- package/dist/index.d.ts +128 -0
- package/dist/index.js +990 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/assets/__vite-browser-external-BIHI7g3E.js +1 -0
- package/dist/runtime/assets/index-tRNuU9ap.js +1059 -0
- package/dist/runtime/index.html +38 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.js +1511 -0
- package/dist/server.js.map +1 -0
- package/dist/shell/shell.js +67 -0
- package/docs/widget-preview.png +0 -0
- package/e2e/__snapshots__/.gitkeep +0 -0
- package/e2e/global-setup.ts +114 -0
- package/e2e/global-teardown.ts +15 -0
- package/e2e/visual-regression.test.ts +86 -0
- package/e2e/widget-smoke.test.ts +109 -0
- package/index.html +32 -0
- package/package.json +51 -0
- package/playwright.config.ts +43 -0
- package/src/__tests__/live-update.test.ts +158 -0
- package/src/__tests__/local-backend.test.ts +100 -0
- package/src/__tests__/memory-backend.ts +144 -0
- package/src/__tests__/registry-backend.test.ts +256 -0
- package/src/__tests__/services.test.ts +153 -0
- package/src/__tests__/shim.test.ts +60 -0
- package/src/__tests__/widget-store.test.ts +188 -0
- package/src/e2e-visual.ts +148 -0
- package/src/index.ts +608 -0
- package/src/live-update.ts +150 -0
- package/src/logger.ts +44 -0
- package/src/reference-widgets/live-dashboard.ts +162 -0
- package/src/registry-backend.ts +306 -0
- package/src/runtime/index.html +38 -0
- package/src/runtime/main.ts +164 -0
- package/src/scripts/export-artifacts.ts +51 -0
- package/src/server.ts +398 -0
- package/src/services.ts +307 -0
- package/src/shell/main.ts +172 -0
- package/src/shim.ts +106 -0
- package/src/tunnel.ts +123 -0
- package/src/widget-store/index.ts +10 -0
- package/src/widget-store/local-backend.ts +77 -0
- package/src/widget-store/store.ts +242 -0
- package/src/widget-store/types.ts +53 -0
- package/tsconfig.json +10 -0
- package/tsup.config.ts +14 -0
- package/vite.runtime.config.ts +28 -0
- package/vite.shell.config.ts +30 -0
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { WidgetStore, getWidgetStore, resetWidgetStore } from "./store.js";
|
|
2
|
+
export { LocalFileBackend } from "./local-backend.js";
|
|
3
|
+
export type {
|
|
4
|
+
FSProvider,
|
|
5
|
+
FileStats,
|
|
6
|
+
DirEntry,
|
|
7
|
+
StoredWidget,
|
|
8
|
+
StoredWidgetInfo,
|
|
9
|
+
WidgetStoreOptions,
|
|
10
|
+
} from "./types.js";
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { readFile, writeFile, unlink, stat, mkdir, readdir, rm, access } from "node:fs/promises";
|
|
2
|
+
import { join, dirname, normalize } from "node:path";
|
|
3
|
+
import type { DirEntry, FileStats, FSProvider } from "./types.js";
|
|
4
|
+
|
|
5
|
+
function createDirEntry(name: string, isDir: boolean): DirEntry {
|
|
6
|
+
return {
|
|
7
|
+
name,
|
|
8
|
+
isFile: () => !isDir,
|
|
9
|
+
isDirectory: () => isDir,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function createFileStats(size: number, mtime: Date, isDir: boolean): FileStats {
|
|
14
|
+
return {
|
|
15
|
+
size,
|
|
16
|
+
mtime,
|
|
17
|
+
isFile: () => !isDir,
|
|
18
|
+
isDirectory: () => isDir,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class LocalFileBackend implements FSProvider {
|
|
23
|
+
constructor(private basePath: string) {}
|
|
24
|
+
|
|
25
|
+
private resolve(path: string): string {
|
|
26
|
+
return join(this.basePath, normalize(path));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async readFile(path: string): Promise<string> {
|
|
30
|
+
return readFile(this.resolve(path), "utf-8");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async writeFile(path: string, content: string): Promise<void> {
|
|
34
|
+
const fullPath = this.resolve(path);
|
|
35
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
36
|
+
await writeFile(fullPath, content, "utf-8");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async unlink(path: string): Promise<void> {
|
|
40
|
+
await unlink(this.resolve(path));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async stat(path: string): Promise<FileStats> {
|
|
44
|
+
const fullPath = this.resolve(path);
|
|
45
|
+
const s = await stat(fullPath);
|
|
46
|
+
return createFileStats(s.size, s.mtime, s.isDirectory());
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async mkdir(path: string, options?: { recursive?: boolean }): Promise<void> {
|
|
50
|
+
await mkdir(this.resolve(path), options);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async readdir(path: string): Promise<DirEntry[]> {
|
|
54
|
+
const fullPath = this.resolve(path);
|
|
55
|
+
const entries = await readdir(fullPath, { withFileTypes: true });
|
|
56
|
+
return entries.map((e) =>
|
|
57
|
+
createDirEntry(e.name, e.isDirectory()),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async rmdir(path: string, options?: { recursive?: boolean }): Promise<void> {
|
|
62
|
+
if (options?.recursive) {
|
|
63
|
+
await rm(this.resolve(path), { recursive: true, force: true });
|
|
64
|
+
} else {
|
|
65
|
+
await rm(this.resolve(path), { force: true });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async exists(path: string): Promise<boolean> {
|
|
70
|
+
try {
|
|
71
|
+
await access(this.resolve(path));
|
|
72
|
+
return true;
|
|
73
|
+
} catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { join, resolve } from "node:path";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { LocalFileBackend } from "./local-backend.js";
|
|
4
|
+
import type { FSProvider, StoredWidget, StoredWidgetInfo, WidgetStoreOptions } from "./types.js";
|
|
5
|
+
import type { Manifest, VirtualFile } from "@aprovan/patchwork-compiler";
|
|
6
|
+
|
|
7
|
+
const WIDGETS_PREFIX = "widgets";
|
|
8
|
+
const FILES_SUBDIR = "files";
|
|
9
|
+
const RESOURCE_URI_PREFIX = "ui://widgets/";
|
|
10
|
+
|
|
11
|
+
function getDefaultStorageDir(): string {
|
|
12
|
+
// Use WIDGET_STORE_PATH env var, or fall back to ~/.patchwork/widget-store
|
|
13
|
+
return process.env["WIDGET_STORE_PATH"] ?? join(homedir(), ".patchwork", "widget-store");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface StoredManifest extends Manifest {
|
|
17
|
+
entry: string;
|
|
18
|
+
createdAt: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Persistent store for **raw, uncompiled** widget source files.
|
|
23
|
+
*
|
|
24
|
+
* Widgets are saved as their original `.tsx`/`.ts` files plus a `manifest.json`;
|
|
25
|
+
* compilation happens in the browser via the shared `@aprovan/patchwork-compiler`
|
|
26
|
+
* runtime when the widget is rendered. Layout:
|
|
27
|
+
*
|
|
28
|
+
* ```
|
|
29
|
+
* widgets/<name>/<hash>/
|
|
30
|
+
* files/main.tsx
|
|
31
|
+
* files/price-card.tsx
|
|
32
|
+
* manifest.json { ...manifest, entry, createdAt }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export class WidgetStore {
|
|
36
|
+
private provider: FSProvider;
|
|
37
|
+
private storageDir: string;
|
|
38
|
+
|
|
39
|
+
constructor(options: WidgetStoreOptions = {}) {
|
|
40
|
+
this.storageDir = resolve(options.storageDir ?? getDefaultStorageDir());
|
|
41
|
+
this.provider = options.backend ?? new LocalFileBackend(this.storageDir);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private fullPath(virtualPath: string): string {
|
|
45
|
+
return join(WIDGETS_PREFIX, virtualPath);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private async readFilesRecursive(dir: string, base = ""): Promise<VirtualFile[]> {
|
|
49
|
+
const files: VirtualFile[] = [];
|
|
50
|
+
let entries;
|
|
51
|
+
try {
|
|
52
|
+
entries = await this.provider.readdir(dir);
|
|
53
|
+
} catch {
|
|
54
|
+
return files;
|
|
55
|
+
}
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
const childDir = join(dir, entry.name);
|
|
58
|
+
const relPath = base ? `${base}/${entry.name}` : entry.name;
|
|
59
|
+
if (entry.isDirectory()) {
|
|
60
|
+
files.push(...(await this.readFilesRecursive(childDir, relPath)));
|
|
61
|
+
} else {
|
|
62
|
+
const content = await this.provider.readFile(childDir);
|
|
63
|
+
files.push({ path: relPath, content });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return files;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async saveWidget(
|
|
70
|
+
hash: string,
|
|
71
|
+
files: VirtualFile[],
|
|
72
|
+
manifest: Manifest,
|
|
73
|
+
entry: string,
|
|
74
|
+
): Promise<StoredWidget> {
|
|
75
|
+
const widgetDir = `${manifest.name}/${hash}`;
|
|
76
|
+
const createdAt = Date.now();
|
|
77
|
+
|
|
78
|
+
await Promise.all(
|
|
79
|
+
files.map((file) =>
|
|
80
|
+
this.provider.writeFile(
|
|
81
|
+
this.fullPath(`${widgetDir}/${FILES_SUBDIR}/${file.path}`),
|
|
82
|
+
file.content,
|
|
83
|
+
),
|
|
84
|
+
),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const storedManifest: StoredManifest = { ...manifest, entry, createdAt };
|
|
88
|
+
await this.provider.writeFile(
|
|
89
|
+
this.fullPath(`${widgetDir}/manifest.json`),
|
|
90
|
+
JSON.stringify(storedManifest),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
path: this.fullPath(widgetDir),
|
|
95
|
+
resourceUri: `${RESOURCE_URI_PREFIX}${widgetDir}/view.html`,
|
|
96
|
+
files,
|
|
97
|
+
entry,
|
|
98
|
+
manifest,
|
|
99
|
+
createdAt,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async getWidget(name: string, hash: string): Promise<StoredWidget | null> {
|
|
104
|
+
const widgetDir = `${name}/${hash}`;
|
|
105
|
+
const manifestVirtualPath = this.fullPath(`${widgetDir}/manifest.json`);
|
|
106
|
+
|
|
107
|
+
const exists = await this.provider.exists(manifestVirtualPath);
|
|
108
|
+
if (!exists) return null;
|
|
109
|
+
|
|
110
|
+
const files = await this.readFilesRecursive(this.fullPath(`${widgetDir}/${FILES_SUBDIR}`));
|
|
111
|
+
|
|
112
|
+
let manifest: Manifest;
|
|
113
|
+
let entry = files[0]?.path ?? "main.tsx";
|
|
114
|
+
let createdAt = Date.now();
|
|
115
|
+
try {
|
|
116
|
+
const raw = await this.provider.readFile(manifestVirtualPath);
|
|
117
|
+
const parsed = JSON.parse(raw) as Partial<StoredManifest>;
|
|
118
|
+
manifest = {
|
|
119
|
+
name: (parsed.name as string) ?? name,
|
|
120
|
+
version: (parsed.version as string) ?? "0.1.0",
|
|
121
|
+
platform: parsed.platform ?? "browser",
|
|
122
|
+
image: (parsed.image as string) ?? "@aprovan/patchwork-image-shadcn",
|
|
123
|
+
description: parsed.description,
|
|
124
|
+
services: parsed.services,
|
|
125
|
+
};
|
|
126
|
+
if (parsed.entry) entry = parsed.entry;
|
|
127
|
+
if (typeof parsed.createdAt === "number") createdAt = parsed.createdAt;
|
|
128
|
+
} catch {
|
|
129
|
+
manifest = {
|
|
130
|
+
name,
|
|
131
|
+
version: "0.1.0",
|
|
132
|
+
platform: "browser",
|
|
133
|
+
image: "@aprovan/patchwork-image-shadcn",
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
path: this.fullPath(widgetDir),
|
|
139
|
+
resourceUri: `${RESOURCE_URI_PREFIX}${widgetDir}/view.html`,
|
|
140
|
+
files,
|
|
141
|
+
entry,
|
|
142
|
+
manifest,
|
|
143
|
+
createdAt,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async listWidgets(): Promise<StoredWidgetInfo[]> {
|
|
148
|
+
const results: StoredWidgetInfo[] = [];
|
|
149
|
+
const rootPath = this.fullPath("");
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const names = await this.provider.readdir(rootPath);
|
|
153
|
+
for (const nameEntry of names) {
|
|
154
|
+
if (!nameEntry.isDirectory()) continue;
|
|
155
|
+
const name = nameEntry.name;
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
const hashes = await this.provider.readdir(join(rootPath, name));
|
|
159
|
+
for (const hashEntry of hashes) {
|
|
160
|
+
if (!hashEntry.isDirectory()) continue;
|
|
161
|
+
const hash = hashEntry.name;
|
|
162
|
+
const manifestVirtualPath = this.fullPath(`${name}/${hash}/manifest.json`);
|
|
163
|
+
|
|
164
|
+
let manifest: Partial<StoredManifest> = {};
|
|
165
|
+
try {
|
|
166
|
+
const raw = await this.provider.readFile(manifestVirtualPath);
|
|
167
|
+
manifest = JSON.parse(raw) as Partial<StoredManifest>;
|
|
168
|
+
} catch {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
results.push({
|
|
173
|
+
path: this.fullPath(`${name}/${hash}`),
|
|
174
|
+
resourceUri: `${RESOURCE_URI_PREFIX}${name}/${hash}/view.html`,
|
|
175
|
+
name: manifest.name ?? name,
|
|
176
|
+
version: manifest.version ?? "0.1.0",
|
|
177
|
+
description: manifest.description,
|
|
178
|
+
services: manifest.services,
|
|
179
|
+
entry: manifest.entry,
|
|
180
|
+
createdAt: manifest.createdAt ?? 0,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
} catch {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} catch {
|
|
188
|
+
return results;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return results.sort((a, b) => b.createdAt - a.createdAt);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async deleteWidget(name: string, hash: string): Promise<boolean> {
|
|
195
|
+
const widgetDir = this.fullPath(`${name}/${hash}`);
|
|
196
|
+
const exists = await this.provider.exists(widgetDir);
|
|
197
|
+
if (!exists) return false;
|
|
198
|
+
|
|
199
|
+
await this.provider.rmdir(widgetDir, { recursive: true });
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async hasWidget(name: string, hash: string): Promise<boolean> {
|
|
204
|
+
return this.provider.exists(this.fullPath(`${name}/${hash}/manifest.json`));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
resourceUriFor(name: string, hash: string): string {
|
|
208
|
+
return `${RESOURCE_URI_PREFIX}${name}/${hash}/view.html`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async loadAll(): Promise<StoredWidget[]> {
|
|
212
|
+
const infos = await this.listWidgets();
|
|
213
|
+
const widgets: StoredWidget[] = [];
|
|
214
|
+
|
|
215
|
+
for (const info of infos) {
|
|
216
|
+
const uriPath = info.resourceUri.replace(RESOURCE_URI_PREFIX, "").replace(/\/view\.html$/, "");
|
|
217
|
+
const parts = uriPath.split("/");
|
|
218
|
+
const name = parts[0];
|
|
219
|
+
const hash = parts[1];
|
|
220
|
+
|
|
221
|
+
if (!name || !hash) continue;
|
|
222
|
+
|
|
223
|
+
const widget = await this.getWidget(name, hash);
|
|
224
|
+
if (widget) widgets.push(widget);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return widgets;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
let _instance: WidgetStore | null = null;
|
|
232
|
+
|
|
233
|
+
export function getWidgetStore(options?: WidgetStoreOptions): WidgetStore {
|
|
234
|
+
if (!_instance) {
|
|
235
|
+
_instance = new WidgetStore(options);
|
|
236
|
+
}
|
|
237
|
+
return _instance;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function resetWidgetStore(): void {
|
|
241
|
+
_instance = null;
|
|
242
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Manifest, VirtualFile } from "@aprovan/patchwork-compiler";
|
|
2
|
+
|
|
3
|
+
export interface FileStats {
|
|
4
|
+
size: number;
|
|
5
|
+
mtime: Date;
|
|
6
|
+
isFile(): boolean;
|
|
7
|
+
isDirectory(): boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface DirEntry {
|
|
11
|
+
name: string;
|
|
12
|
+
isFile(): boolean;
|
|
13
|
+
isDirectory(): boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface FSProvider {
|
|
17
|
+
readFile(path: string, encoding?: "utf8" | "base64"): Promise<string>;
|
|
18
|
+
writeFile(path: string, content: string): Promise<void>;
|
|
19
|
+
unlink(path: string): Promise<void>;
|
|
20
|
+
stat(path: string): Promise<FileStats>;
|
|
21
|
+
mkdir(path: string, options?: { recursive?: boolean }): Promise<void>;
|
|
22
|
+
readdir(path: string): Promise<DirEntry[]>;
|
|
23
|
+
rmdir(path: string, options?: { recursive?: boolean }): Promise<void>;
|
|
24
|
+
exists(path: string): Promise<boolean>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface StoredWidget {
|
|
28
|
+
/** Storage path of the widget directory (e.g. "widgets/timer/abc123"). */
|
|
29
|
+
path: string;
|
|
30
|
+
resourceUri: string;
|
|
31
|
+
/** Raw, uncompiled widget source files. Compilation happens in the browser. */
|
|
32
|
+
files: VirtualFile[];
|
|
33
|
+
/** Entry file path within {@link files} (e.g. "main.tsx"). */
|
|
34
|
+
entry: string;
|
|
35
|
+
manifest: Manifest;
|
|
36
|
+
createdAt: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface StoredWidgetInfo {
|
|
40
|
+
path: string;
|
|
41
|
+
resourceUri: string;
|
|
42
|
+
name: string;
|
|
43
|
+
version: string;
|
|
44
|
+
description?: string;
|
|
45
|
+
services?: string[];
|
|
46
|
+
entry?: string;
|
|
47
|
+
createdAt: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface WidgetStoreOptions {
|
|
51
|
+
storageDir?: string;
|
|
52
|
+
backend?: FSProvider;
|
|
53
|
+
}
|
package/tsconfig.json
ADDED
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineConfig } from 'tsup';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: ['src/index.ts', 'src/server.ts'],
|
|
5
|
+
format: ['esm'],
|
|
6
|
+
target: 'node20',
|
|
7
|
+
clean: true,
|
|
8
|
+
dts: true,
|
|
9
|
+
splitting: false,
|
|
10
|
+
sourcemap: true,
|
|
11
|
+
shims: true,
|
|
12
|
+
skipNodeModulesBundle: true,
|
|
13
|
+
loader: { '.html': 'text' },
|
|
14
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { defineConfig } from "vite";
|
|
3
|
+
|
|
4
|
+
const here = (p: string) => fileURLToPath(new URL(p, import.meta.url));
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Builds the browser widget runtime (src/runtime) into dist/runtime.
|
|
8
|
+
*
|
|
9
|
+
* This is the shared runtime host page that fetches a widget's raw source and
|
|
10
|
+
* compiles + mounts it in the browser via @aprovan/patchwork-compiler. It is
|
|
11
|
+
* served statically by the widget server. No React plugin is needed here — the
|
|
12
|
+
* host page is framework-agnostic; the widget's React is preloaded from the CDN
|
|
13
|
+
* by the compiler at mount time.
|
|
14
|
+
*/
|
|
15
|
+
export default defineConfig({
|
|
16
|
+
root: here("src/runtime"),
|
|
17
|
+
base: "./",
|
|
18
|
+
build: {
|
|
19
|
+
outDir: here("dist/runtime"),
|
|
20
|
+
emptyOutDir: true,
|
|
21
|
+
// esm.sh modules don't ship the modulepreload polyfill target.
|
|
22
|
+
modulePreload: false,
|
|
23
|
+
rollupOptions: {
|
|
24
|
+
input: here("src/runtime/index.html"),
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
logLevel: "warn",
|
|
28
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { defineConfig } from "vite";
|
|
3
|
+
|
|
4
|
+
const here = (p: string) => fileURLToPath(new URL(p, import.meta.url));
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Builds the MCP App "shell" (src/shell) into a single self-contained IIFE at
|
|
8
|
+
* dist/shell/shell.js.
|
|
9
|
+
*
|
|
10
|
+
* The shell runs inside Claude's strict-CSP resource sandbox: it bundles the
|
|
11
|
+
* ext-apps client (no wasm/eval) to perform the host handshake and bridge the
|
|
12
|
+
* widget, and embeds the CSP-free runtime iframe that does the actual compile.
|
|
13
|
+
* Served statically by the widget server at /shell.
|
|
14
|
+
*/
|
|
15
|
+
export default defineConfig({
|
|
16
|
+
build: {
|
|
17
|
+
outDir: here("dist/shell"),
|
|
18
|
+
emptyOutDir: true,
|
|
19
|
+
lib: {
|
|
20
|
+
entry: here("src/shell/main.ts"),
|
|
21
|
+
formats: ["iife"],
|
|
22
|
+
name: "PatchworkShell",
|
|
23
|
+
fileName: () => "shell.js",
|
|
24
|
+
},
|
|
25
|
+
rollupOptions: {
|
|
26
|
+
output: { inlineDynamicImports: true },
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
logLevel: "warn",
|
|
30
|
+
});
|