@frontmcp/ui 0.6.1 → 0.6.2
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/bridge/core/bridge-factory.d.ts +1 -0
- package/bridge/core/bridge-factory.d.ts.map +1 -1
- package/bridge/index.d.ts +1 -1
- package/bridge/index.d.ts.map +1 -1
- package/bridge/index.js +39 -881
- package/bundler/browser-components.d.ts +42 -0
- package/bundler/browser-components.d.ts.map +1 -0
- package/bundler/bundler.d.ts +78 -4
- package/bundler/bundler.d.ts.map +1 -1
- package/bundler/index.d.ts +8 -8
- package/bundler/index.d.ts.map +1 -1
- package/bundler/index.js +1315 -1854
- package/bundler/types.d.ts +188 -7
- package/bundler/types.d.ts.map +1 -1
- package/esm/bridge/{index.js → index.mjs} +40 -877
- package/esm/bundler/{index.js → index.mjs} +1391 -1895
- package/esm/{index.js → index.mjs} +215 -3091
- package/esm/layouts/{index.js → index.mjs} +3 -3
- package/esm/package.json +9 -8
- package/esm/react/index.mjs +1183 -0
- package/esm/renderers/index.mjs +611 -0
- package/esm/universal/{index.js → index.mjs} +266 -70
- package/index.d.ts +1 -4
- package/index.d.ts.map +1 -1
- package/index.js +208 -3113
- package/layouts/base.d.ts.map +1 -1
- package/layouts/index.js +3 -3
- package/layouts/presets.d.ts.map +1 -1
- package/package.json +9 -8
- package/react/Badge.d.ts.map +1 -1
- package/react/hooks/context.d.ts.map +1 -1
- package/react/index.d.ts +0 -1
- package/react/index.d.ts.map +1 -1
- package/react/index.js +57 -2001
- package/react/types.d.ts.map +1 -1
- package/renderers/index.d.ts +9 -4
- package/renderers/index.d.ts.map +1 -1
- package/renderers/index.js +328 -88
- package/renderers/mdx.renderer.d.ts +99 -0
- package/renderers/mdx.renderer.d.ts.map +1 -0
- package/renderers/react.renderer.d.ts +22 -13
- package/renderers/react.renderer.d.ts.map +1 -1
- package/renderers/transpiler.d.ts +49 -0
- package/renderers/transpiler.d.ts.map +1 -0
- package/universal/cached-runtime.d.ts +25 -1
- package/universal/cached-runtime.d.ts.map +1 -1
- package/universal/index.js +266 -70
- package/universal/runtime-builder.d.ts.map +1 -1
- package/universal/types.d.ts.map +1 -1
- package/web-components/elements/fmcp-input.d.ts.map +1 -1
- package/web-components/elements/fmcp-select.d.ts.map +1 -1
- package/web-components/index.d.ts +0 -1
- package/web-components/index.d.ts.map +1 -1
- package/bundler/cache.d.ts +0 -173
- package/bundler/cache.d.ts.map +0 -1
- package/bundler/file-cache/component-builder.d.ts +0 -167
- package/bundler/file-cache/component-builder.d.ts.map +0 -1
- package/bundler/file-cache/hash-calculator.d.ts +0 -155
- package/bundler/file-cache/hash-calculator.d.ts.map +0 -1
- package/bundler/file-cache/index.d.ts +0 -12
- package/bundler/file-cache/index.d.ts.map +0 -1
- package/bundler/file-cache/storage/filesystem.d.ts +0 -149
- package/bundler/file-cache/storage/filesystem.d.ts.map +0 -1
- package/bundler/file-cache/storage/index.d.ts +0 -11
- package/bundler/file-cache/storage/index.d.ts.map +0 -1
- package/bundler/file-cache/storage/interface.d.ts +0 -152
- package/bundler/file-cache/storage/interface.d.ts.map +0 -1
- package/bundler/file-cache/storage/redis.d.ts +0 -139
- package/bundler/file-cache/storage/redis.d.ts.map +0 -1
- package/bundler/sandbox/enclave-adapter.d.ts +0 -121
- package/bundler/sandbox/enclave-adapter.d.ts.map +0 -1
- package/bundler/sandbox/executor.d.ts +0 -14
- package/bundler/sandbox/executor.d.ts.map +0 -1
- package/bundler/sandbox/policy.d.ts +0 -62
- package/bundler/sandbox/policy.d.ts.map +0 -1
- package/esm/bridge/adapters/base-adapter.d.ts +0 -104
- package/esm/bridge/adapters/base-adapter.d.ts.map +0 -1
- package/esm/bridge/adapters/claude.adapter.d.ts +0 -67
- package/esm/bridge/adapters/claude.adapter.d.ts.map +0 -1
- package/esm/bridge/adapters/ext-apps.adapter.d.ts +0 -143
- package/esm/bridge/adapters/ext-apps.adapter.d.ts.map +0 -1
- package/esm/bridge/adapters/gemini.adapter.d.ts +0 -64
- package/esm/bridge/adapters/gemini.adapter.d.ts.map +0 -1
- package/esm/bridge/adapters/generic.adapter.d.ts +0 -56
- package/esm/bridge/adapters/generic.adapter.d.ts.map +0 -1
- package/esm/bridge/adapters/index.d.ts +0 -26
- package/esm/bridge/adapters/index.d.ts.map +0 -1
- package/esm/bridge/adapters/openai.adapter.d.ts +0 -65
- package/esm/bridge/adapters/openai.adapter.d.ts.map +0 -1
- package/esm/bridge/core/adapter-registry.d.ts +0 -122
- package/esm/bridge/core/adapter-registry.d.ts.map +0 -1
- package/esm/bridge/core/bridge-factory.d.ts +0 -199
- package/esm/bridge/core/bridge-factory.d.ts.map +0 -1
- package/esm/bridge/core/index.d.ts +0 -10
- package/esm/bridge/core/index.d.ts.map +0 -1
- package/esm/bridge/index.d.ts +0 -62
- package/esm/bridge/index.d.ts.map +0 -1
- package/esm/bridge/runtime/iife-generator.d.ts +0 -62
- package/esm/bridge/runtime/iife-generator.d.ts.map +0 -1
- package/esm/bridge/runtime/index.d.ts +0 -10
- package/esm/bridge/runtime/index.d.ts.map +0 -1
- package/esm/bridge/types.d.ts +0 -386
- package/esm/bridge/types.d.ts.map +0 -1
- package/esm/bundler/bundler.d.ts +0 -208
- package/esm/bundler/bundler.d.ts.map +0 -1
- package/esm/bundler/cache.d.ts +0 -173
- package/esm/bundler/cache.d.ts.map +0 -1
- package/esm/bundler/file-cache/component-builder.d.ts +0 -167
- package/esm/bundler/file-cache/component-builder.d.ts.map +0 -1
- package/esm/bundler/file-cache/hash-calculator.d.ts +0 -155
- package/esm/bundler/file-cache/hash-calculator.d.ts.map +0 -1
- package/esm/bundler/file-cache/index.d.ts +0 -12
- package/esm/bundler/file-cache/index.d.ts.map +0 -1
- package/esm/bundler/file-cache/storage/filesystem.d.ts +0 -149
- package/esm/bundler/file-cache/storage/filesystem.d.ts.map +0 -1
- package/esm/bundler/file-cache/storage/index.d.ts +0 -11
- package/esm/bundler/file-cache/storage/index.d.ts.map +0 -1
- package/esm/bundler/file-cache/storage/interface.d.ts +0 -152
- package/esm/bundler/file-cache/storage/interface.d.ts.map +0 -1
- package/esm/bundler/file-cache/storage/redis.d.ts +0 -139
- package/esm/bundler/file-cache/storage/redis.d.ts.map +0 -1
- package/esm/bundler/index.d.ts +0 -43
- package/esm/bundler/index.d.ts.map +0 -1
- package/esm/bundler/sandbox/enclave-adapter.d.ts +0 -121
- package/esm/bundler/sandbox/enclave-adapter.d.ts.map +0 -1
- package/esm/bundler/sandbox/executor.d.ts +0 -14
- package/esm/bundler/sandbox/executor.d.ts.map +0 -1
- package/esm/bundler/sandbox/policy.d.ts +0 -62
- package/esm/bundler/sandbox/policy.d.ts.map +0 -1
- package/esm/bundler/types.d.ts +0 -702
- package/esm/bundler/types.d.ts.map +0 -1
- package/esm/components/alert.d.ts +0 -66
- package/esm/components/alert.d.ts.map +0 -1
- package/esm/components/alert.schema.d.ts +0 -98
- package/esm/components/alert.schema.d.ts.map +0 -1
- package/esm/components/avatar.d.ts +0 -77
- package/esm/components/avatar.d.ts.map +0 -1
- package/esm/components/avatar.schema.d.ts +0 -170
- package/esm/components/avatar.schema.d.ts.map +0 -1
- package/esm/components/badge.d.ts +0 -64
- package/esm/components/badge.d.ts.map +0 -1
- package/esm/components/badge.schema.d.ts +0 -91
- package/esm/components/badge.schema.d.ts.map +0 -1
- package/esm/components/button.d.ts +0 -100
- package/esm/components/button.d.ts.map +0 -1
- package/esm/components/button.schema.d.ts +0 -120
- package/esm/components/button.schema.d.ts.map +0 -1
- package/esm/components/card.d.ts +0 -53
- package/esm/components/card.d.ts.map +0 -1
- package/esm/components/card.schema.d.ts +0 -93
- package/esm/components/card.schema.d.ts.map +0 -1
- package/esm/components/form.d.ts +0 -212
- package/esm/components/form.d.ts.map +0 -1
- package/esm/components/form.schema.d.ts +0 -365
- package/esm/components/form.schema.d.ts.map +0 -1
- package/esm/components/index.d.ts +0 -29
- package/esm/components/index.d.ts.map +0 -1
- package/esm/components/list.d.ts +0 -121
- package/esm/components/list.d.ts.map +0 -1
- package/esm/components/list.schema.d.ts +0 -129
- package/esm/components/list.schema.d.ts.map +0 -1
- package/esm/components/modal.d.ts +0 -100
- package/esm/components/modal.d.ts.map +0 -1
- package/esm/components/modal.schema.d.ts +0 -151
- package/esm/components/modal.schema.d.ts.map +0 -1
- package/esm/components/table.d.ts +0 -91
- package/esm/components/table.d.ts.map +0 -1
- package/esm/components/table.schema.d.ts +0 -123
- package/esm/components/table.schema.d.ts.map +0 -1
- package/esm/index.d.ts +0 -40
- package/esm/index.d.ts.map +0 -1
- package/esm/layouts/base.d.ts +0 -86
- package/esm/layouts/base.d.ts.map +0 -1
- package/esm/layouts/index.d.ts +0 -8
- package/esm/layouts/index.d.ts.map +0 -1
- package/esm/layouts/presets.d.ts +0 -134
- package/esm/layouts/presets.d.ts.map +0 -1
- package/esm/pages/consent.d.ts +0 -117
- package/esm/pages/consent.d.ts.map +0 -1
- package/esm/pages/error.d.ts +0 -101
- package/esm/pages/error.d.ts.map +0 -1
- package/esm/pages/index.d.ts +0 -9
- package/esm/pages/index.d.ts.map +0 -1
- package/esm/pages/index.js +0 -1036
- package/esm/react/Alert.d.ts +0 -101
- package/esm/react/Alert.d.ts.map +0 -1
- package/esm/react/Badge.d.ts +0 -100
- package/esm/react/Badge.d.ts.map +0 -1
- package/esm/react/Button.d.ts +0 -108
- package/esm/react/Button.d.ts.map +0 -1
- package/esm/react/Card.d.ts +0 -103
- package/esm/react/Card.d.ts.map +0 -1
- package/esm/react/hooks/context.d.ts +0 -179
- package/esm/react/hooks/context.d.ts.map +0 -1
- package/esm/react/hooks/index.d.ts +0 -42
- package/esm/react/hooks/index.d.ts.map +0 -1
- package/esm/react/hooks/tools.d.ts +0 -284
- package/esm/react/hooks/tools.d.ts.map +0 -1
- package/esm/react/index.d.ts +0 -80
- package/esm/react/index.d.ts.map +0 -1
- package/esm/react/index.js +0 -3124
- package/esm/react/types.d.ts +0 -105
- package/esm/react/types.d.ts.map +0 -1
- package/esm/react/utils.d.ts +0 -43
- package/esm/react/utils.d.ts.map +0 -1
- package/esm/render/index.d.ts +0 -8
- package/esm/render/index.d.ts.map +0 -1
- package/esm/render/prerender.d.ts +0 -57
- package/esm/render/prerender.d.ts.map +0 -1
- package/esm/renderers/index.d.ts +0 -21
- package/esm/renderers/index.d.ts.map +0 -1
- package/esm/renderers/index.js +0 -381
- package/esm/renderers/react.adapter.d.ts +0 -70
- package/esm/renderers/react.adapter.d.ts.map +0 -1
- package/esm/renderers/react.renderer.d.ts +0 -96
- package/esm/renderers/react.renderer.d.ts.map +0 -1
- package/esm/universal/UniversalApp.d.ts +0 -108
- package/esm/universal/UniversalApp.d.ts.map +0 -1
- package/esm/universal/cached-runtime.d.ts +0 -115
- package/esm/universal/cached-runtime.d.ts.map +0 -1
- package/esm/universal/context.d.ts +0 -122
- package/esm/universal/context.d.ts.map +0 -1
- package/esm/universal/index.d.ts +0 -57
- package/esm/universal/index.d.ts.map +0 -1
- package/esm/universal/renderers/html.renderer.d.ts +0 -37
- package/esm/universal/renderers/html.renderer.d.ts.map +0 -1
- package/esm/universal/renderers/index.d.ts +0 -112
- package/esm/universal/renderers/index.d.ts.map +0 -1
- package/esm/universal/renderers/markdown.renderer.d.ts +0 -33
- package/esm/universal/renderers/markdown.renderer.d.ts.map +0 -1
- package/esm/universal/renderers/mdx.renderer.d.ts +0 -38
- package/esm/universal/renderers/mdx.renderer.d.ts.map +0 -1
- package/esm/universal/renderers/react.renderer.d.ts +0 -46
- package/esm/universal/renderers/react.renderer.d.ts.map +0 -1
- package/esm/universal/runtime-builder.d.ts +0 -33
- package/esm/universal/runtime-builder.d.ts.map +0 -1
- package/esm/universal/store.d.ts +0 -135
- package/esm/universal/store.d.ts.map +0 -1
- package/esm/universal/types.d.ts +0 -199
- package/esm/universal/types.d.ts.map +0 -1
- package/esm/web-components/core/attribute-parser.d.ts +0 -82
- package/esm/web-components/core/attribute-parser.d.ts.map +0 -1
- package/esm/web-components/core/base-element.d.ts +0 -197
- package/esm/web-components/core/base-element.d.ts.map +0 -1
- package/esm/web-components/core/index.d.ts +0 -9
- package/esm/web-components/core/index.d.ts.map +0 -1
- package/esm/web-components/elements/fmcp-alert.d.ts +0 -46
- package/esm/web-components/elements/fmcp-alert.d.ts.map +0 -1
- package/esm/web-components/elements/fmcp-badge.d.ts +0 -47
- package/esm/web-components/elements/fmcp-badge.d.ts.map +0 -1
- package/esm/web-components/elements/fmcp-button.d.ts +0 -117
- package/esm/web-components/elements/fmcp-button.d.ts.map +0 -1
- package/esm/web-components/elements/fmcp-card.d.ts +0 -53
- package/esm/web-components/elements/fmcp-card.d.ts.map +0 -1
- package/esm/web-components/elements/fmcp-input.d.ts +0 -96
- package/esm/web-components/elements/fmcp-input.d.ts.map +0 -1
- package/esm/web-components/elements/fmcp-select.d.ts +0 -100
- package/esm/web-components/elements/fmcp-select.d.ts.map +0 -1
- package/esm/web-components/elements/index.d.ts +0 -13
- package/esm/web-components/elements/index.d.ts.map +0 -1
- package/esm/web-components/index.d.ts +0 -50
- package/esm/web-components/index.d.ts.map +0 -1
- package/esm/web-components/register.d.ts +0 -57
- package/esm/web-components/register.d.ts.map +0 -1
- package/esm/web-components/types.d.ts +0 -122
- package/esm/web-components/types.d.ts.map +0 -1
- package/esm/widgets/index.d.ts +0 -8
- package/esm/widgets/index.d.ts.map +0 -1
- package/esm/widgets/index.js +0 -883
- package/esm/widgets/progress.d.ts +0 -133
- package/esm/widgets/progress.d.ts.map +0 -1
- package/esm/widgets/resource.d.ts +0 -163
- package/esm/widgets/resource.d.ts.map +0 -1
- package/pages/consent.d.ts +0 -117
- package/pages/consent.d.ts.map +0 -1
- package/pages/error.d.ts +0 -101
- package/pages/error.d.ts.map +0 -1
- package/pages/index.d.ts +0 -9
- package/pages/index.d.ts.map +0 -1
- package/pages/index.js +0 -1065
- package/react/utils.d.ts +0 -43
- package/react/utils.d.ts.map +0 -1
- package/widgets/index.d.ts +0 -8
- package/widgets/index.d.ts.map +0 -1
- package/widgets/index.js +0 -910
- package/widgets/progress.d.ts +0 -133
- package/widgets/progress.d.ts.map +0 -1
- package/widgets/resource.d.ts +0 -163
- package/widgets/resource.d.ts.map +0 -1
- /package/esm/components/{index.js → index.mjs} +0 -0
- /package/esm/render/{index.js → index.mjs} +0 -0
- /package/esm/web-components/{index.js → index.mjs} +0 -0
|
@@ -1,641 +1,13 @@
|
|
|
1
|
-
var
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
// libs/ui/src/bundler/file-cache/storage/interface.ts
|
|
12
|
-
function calculateManifestSize(manifest) {
|
|
13
|
-
try {
|
|
14
|
-
return Buffer.byteLength(JSON.stringify(manifest), "utf8");
|
|
15
|
-
} catch {
|
|
16
|
-
return 0;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
var DEFAULT_STORAGE_OPTIONS;
|
|
20
|
-
var init_interface = __esm({
|
|
21
|
-
"libs/ui/src/bundler/file-cache/storage/interface.ts"() {
|
|
22
|
-
"use strict";
|
|
23
|
-
DEFAULT_STORAGE_OPTIONS = {
|
|
24
|
-
maxEntries: 1e3,
|
|
25
|
-
maxSize: 100 * 1024 * 1024,
|
|
26
|
-
// 100MB
|
|
27
|
-
defaultTtl: 24 * 60 * 60,
|
|
28
|
-
// 24 hours
|
|
29
|
-
compress: false
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
// libs/ui/src/bundler/file-cache/storage/filesystem.ts
|
|
35
|
-
var filesystem_exports = {};
|
|
36
|
-
__export(filesystem_exports, {
|
|
37
|
-
CacheInitializationError: () => CacheInitializationError,
|
|
38
|
-
CacheOperationError: () => CacheOperationError,
|
|
39
|
-
FilesystemStorage: () => FilesystemStorage,
|
|
40
|
-
StorageNotInitializedError: () => StorageNotInitializedError,
|
|
41
|
-
createFilesystemStorage: () => createFilesystemStorage
|
|
42
|
-
});
|
|
43
|
-
import { mkdir, readFile, writeFile, readdir, unlink, rm } from "fs/promises";
|
|
44
|
-
import { join, dirname } from "path";
|
|
45
|
-
import { existsSync } from "fs";
|
|
46
|
-
import { createHash } from "crypto";
|
|
47
|
-
function createFilesystemStorage(options) {
|
|
48
|
-
return new FilesystemStorage(options);
|
|
49
|
-
}
|
|
50
|
-
var CacheInitializationError, CacheOperationError, StorageNotInitializedError, DEFAULT_FS_OPTIONS, FilesystemStorage;
|
|
51
|
-
var init_filesystem = __esm({
|
|
52
|
-
"libs/ui/src/bundler/file-cache/storage/filesystem.ts"() {
|
|
53
|
-
"use strict";
|
|
54
|
-
init_interface();
|
|
55
|
-
CacheInitializationError = class extends Error {
|
|
56
|
-
cause;
|
|
57
|
-
constructor(message, cause) {
|
|
58
|
-
super(message);
|
|
59
|
-
this.name = "CacheInitializationError";
|
|
60
|
-
this.cause = cause;
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
CacheOperationError = class extends Error {
|
|
64
|
-
cause;
|
|
65
|
-
constructor(message, cause) {
|
|
66
|
-
super(message);
|
|
67
|
-
this.name = "CacheOperationError";
|
|
68
|
-
this.cause = cause;
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
StorageNotInitializedError = class extends Error {
|
|
72
|
-
constructor() {
|
|
73
|
-
super("Storage not initialized. Call initialize() first.");
|
|
74
|
-
this.name = "StorageNotInitializedError";
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
DEFAULT_FS_OPTIONS = {
|
|
78
|
-
...DEFAULT_STORAGE_OPTIONS,
|
|
79
|
-
cacheDir: ".frontmcp-cache/builds",
|
|
80
|
-
extension: ".json"
|
|
81
|
-
};
|
|
82
|
-
FilesystemStorage = class {
|
|
83
|
-
type = "filesystem";
|
|
84
|
-
options;
|
|
85
|
-
initialized = false;
|
|
86
|
-
stats = {
|
|
87
|
-
entries: 0,
|
|
88
|
-
totalSize: 0,
|
|
89
|
-
hits: 0,
|
|
90
|
-
misses: 0,
|
|
91
|
-
hitRate: 0
|
|
92
|
-
};
|
|
93
|
-
constructor(options = {}) {
|
|
94
|
-
this.options = {
|
|
95
|
-
...DEFAULT_FS_OPTIONS,
|
|
96
|
-
...options
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Initialize the storage directory.
|
|
101
|
-
*/
|
|
102
|
-
async initialize() {
|
|
103
|
-
if (this.initialized) return;
|
|
104
|
-
try {
|
|
105
|
-
await mkdir(this.options.cacheDir, { recursive: true });
|
|
106
|
-
await this.loadStats();
|
|
107
|
-
this.initialized = true;
|
|
108
|
-
} catch (error) {
|
|
109
|
-
throw new CacheInitializationError(`Failed to initialize cache directory: ${error}`, error);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Get a cached manifest.
|
|
114
|
-
*/
|
|
115
|
-
async get(key) {
|
|
116
|
-
this.ensureInitialized();
|
|
117
|
-
const filePath = this.getFilePath(key);
|
|
118
|
-
try {
|
|
119
|
-
if (!existsSync(filePath)) {
|
|
120
|
-
this.stats.misses++;
|
|
121
|
-
this.updateHitRate();
|
|
122
|
-
return void 0;
|
|
123
|
-
}
|
|
124
|
-
const content = await readFile(filePath, "utf8");
|
|
125
|
-
const entry = JSON.parse(content);
|
|
126
|
-
if (Date.now() > entry.metadata.expiresAt) {
|
|
127
|
-
await this.delete(key);
|
|
128
|
-
this.stats.misses++;
|
|
129
|
-
this.updateHitRate();
|
|
130
|
-
return void 0;
|
|
131
|
-
}
|
|
132
|
-
entry.metadata.lastAccessedAt = Date.now();
|
|
133
|
-
entry.metadata.accessCount++;
|
|
134
|
-
this.writeEntry(filePath, entry).catch((err) => {
|
|
135
|
-
if (process.env["DEBUG"]) {
|
|
136
|
-
console.debug(`[FilesystemStorage] Failed to update cache metadata for ${key}: ${err}`);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
this.stats.hits++;
|
|
140
|
-
this.updateHitRate();
|
|
141
|
-
return entry.data;
|
|
142
|
-
} catch {
|
|
143
|
-
this.stats.misses++;
|
|
144
|
-
this.updateHitRate();
|
|
145
|
-
return void 0;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Store a manifest in cache.
|
|
150
|
-
*/
|
|
151
|
-
async set(key, manifest, ttl) {
|
|
152
|
-
this.ensureInitialized();
|
|
153
|
-
const filePath = this.getFilePath(key);
|
|
154
|
-
const size = calculateManifestSize(manifest);
|
|
155
|
-
const effectiveTtl = ttl ?? this.options.defaultTtl;
|
|
156
|
-
await this.ensureCapacity(size);
|
|
157
|
-
const entry = {
|
|
158
|
-
data: manifest,
|
|
159
|
-
metadata: {
|
|
160
|
-
key,
|
|
161
|
-
size,
|
|
162
|
-
createdAt: Date.now(),
|
|
163
|
-
expiresAt: Date.now() + effectiveTtl * 1e3,
|
|
164
|
-
lastAccessedAt: Date.now(),
|
|
165
|
-
accessCount: 0
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
await this.writeEntry(filePath, entry);
|
|
169
|
-
this.stats.entries++;
|
|
170
|
-
this.stats.totalSize += size;
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Check if a key exists.
|
|
174
|
-
*/
|
|
175
|
-
async has(key) {
|
|
176
|
-
this.ensureInitialized();
|
|
177
|
-
const filePath = this.getFilePath(key);
|
|
178
|
-
try {
|
|
179
|
-
if (!existsSync(filePath)) return false;
|
|
180
|
-
const content = await readFile(filePath, "utf8");
|
|
181
|
-
const entry = JSON.parse(content);
|
|
182
|
-
if (Date.now() > entry.metadata.expiresAt) {
|
|
183
|
-
await this.delete(key);
|
|
184
|
-
return false;
|
|
185
|
-
}
|
|
186
|
-
return true;
|
|
187
|
-
} catch {
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Delete a cached entry.
|
|
193
|
-
*/
|
|
194
|
-
async delete(key) {
|
|
195
|
-
this.ensureInitialized();
|
|
196
|
-
const filePath = this.getFilePath(key);
|
|
197
|
-
try {
|
|
198
|
-
if (!existsSync(filePath)) return false;
|
|
199
|
-
const content = await readFile(filePath, "utf8");
|
|
200
|
-
const entry = JSON.parse(content);
|
|
201
|
-
await unlink(filePath);
|
|
202
|
-
this.stats.entries = Math.max(0, this.stats.entries - 1);
|
|
203
|
-
this.stats.totalSize = Math.max(0, this.stats.totalSize - entry.metadata.size);
|
|
204
|
-
return true;
|
|
205
|
-
} catch {
|
|
206
|
-
return false;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Clear all cached entries.
|
|
211
|
-
*/
|
|
212
|
-
async clear() {
|
|
213
|
-
this.ensureInitialized();
|
|
214
|
-
try {
|
|
215
|
-
await rm(this.options.cacheDir, { recursive: true, force: true });
|
|
216
|
-
await mkdir(this.options.cacheDir, { recursive: true });
|
|
217
|
-
this.stats = {
|
|
218
|
-
entries: 0,
|
|
219
|
-
totalSize: 0,
|
|
220
|
-
hits: 0,
|
|
221
|
-
misses: 0,
|
|
222
|
-
hitRate: 0
|
|
223
|
-
};
|
|
224
|
-
} catch (error) {
|
|
225
|
-
throw new CacheOperationError(`Failed to clear cache: ${error}`, error);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Get cache statistics.
|
|
230
|
-
*/
|
|
231
|
-
async getStats() {
|
|
232
|
-
return { ...this.stats };
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* Clean up expired entries.
|
|
236
|
-
*/
|
|
237
|
-
async cleanup() {
|
|
238
|
-
this.ensureInitialized();
|
|
239
|
-
let removed = 0;
|
|
240
|
-
try {
|
|
241
|
-
const files = await readdir(this.options.cacheDir);
|
|
242
|
-
for (const file of files) {
|
|
243
|
-
if (!file.endsWith(this.options.extension)) continue;
|
|
244
|
-
const filePath = join(this.options.cacheDir, file);
|
|
245
|
-
try {
|
|
246
|
-
const content = await readFile(filePath, "utf8");
|
|
247
|
-
const entry = JSON.parse(content);
|
|
248
|
-
if (Date.now() > entry.metadata.expiresAt) {
|
|
249
|
-
await unlink(filePath);
|
|
250
|
-
this.stats.entries = Math.max(0, this.stats.entries - 1);
|
|
251
|
-
this.stats.totalSize = Math.max(0, this.stats.totalSize - entry.metadata.size);
|
|
252
|
-
removed++;
|
|
253
|
-
}
|
|
254
|
-
} catch {
|
|
255
|
-
await unlink(filePath).catch(() => {
|
|
256
|
-
});
|
|
257
|
-
removed++;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
} catch {
|
|
261
|
-
}
|
|
262
|
-
return removed;
|
|
263
|
-
}
|
|
264
|
-
/**
|
|
265
|
-
* Close the storage (no-op for filesystem).
|
|
266
|
-
*/
|
|
267
|
-
async close() {
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Get the file path for a cache key.
|
|
271
|
-
* Uses SHA-256 hash to avoid collisions from key sanitization.
|
|
272
|
-
*/
|
|
273
|
-
getFilePath(key) {
|
|
274
|
-
const hash = createHash("sha256").update(key).digest("hex").slice(0, 16);
|
|
275
|
-
return join(this.options.cacheDir, `${hash}${this.options.extension}`);
|
|
276
|
-
}
|
|
277
|
-
/**
|
|
278
|
-
* Write a cache entry to disk.
|
|
279
|
-
*/
|
|
280
|
-
async writeEntry(filePath, entry) {
|
|
281
|
-
await mkdir(dirname(filePath), { recursive: true });
|
|
282
|
-
await writeFile(filePath, JSON.stringify(entry, null, 2), "utf8");
|
|
283
|
-
}
|
|
284
|
-
/**
|
|
285
|
-
* Ensure the storage is initialized.
|
|
286
|
-
*/
|
|
287
|
-
ensureInitialized() {
|
|
288
|
-
if (!this.initialized) {
|
|
289
|
-
throw new StorageNotInitializedError();
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
/**
|
|
293
|
-
* Load stats from existing cache files.
|
|
294
|
-
* Reads entry metadata to get accurate manifest sizes.
|
|
295
|
-
*/
|
|
296
|
-
async loadStats() {
|
|
297
|
-
try {
|
|
298
|
-
const files = await readdir(this.options.cacheDir);
|
|
299
|
-
let entries = 0;
|
|
300
|
-
let totalSize = 0;
|
|
301
|
-
for (const file of files) {
|
|
302
|
-
if (!file.endsWith(this.options.extension)) continue;
|
|
303
|
-
const filePath = join(this.options.cacheDir, file);
|
|
304
|
-
try {
|
|
305
|
-
const content = await readFile(filePath, "utf8");
|
|
306
|
-
const entry = JSON.parse(content);
|
|
307
|
-
entries++;
|
|
308
|
-
totalSize += entry.metadata.size;
|
|
309
|
-
} catch {
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
this.stats.entries = entries;
|
|
313
|
-
this.stats.totalSize = totalSize;
|
|
314
|
-
} catch {
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
/**
|
|
318
|
-
* Ensure there's capacity for a new entry.
|
|
319
|
-
*/
|
|
320
|
-
async ensureCapacity(newEntrySize) {
|
|
321
|
-
if (this.stats.entries >= this.options.maxEntries) {
|
|
322
|
-
await this.evictLRU();
|
|
323
|
-
}
|
|
324
|
-
while (this.stats.totalSize + newEntrySize > this.options.maxSize) {
|
|
325
|
-
const evicted = await this.evictLRU();
|
|
326
|
-
if (!evicted) break;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
/**
|
|
330
|
-
* Evict the least recently used entry.
|
|
331
|
-
*/
|
|
332
|
-
async evictLRU() {
|
|
333
|
-
try {
|
|
334
|
-
const files = await readdir(this.options.cacheDir);
|
|
335
|
-
let oldestKey = null;
|
|
336
|
-
let oldestTime = Infinity;
|
|
337
|
-
let corruptedFile = null;
|
|
338
|
-
for (const file of files) {
|
|
339
|
-
if (!file.endsWith(this.options.extension)) continue;
|
|
340
|
-
const filePath = join(this.options.cacheDir, file);
|
|
341
|
-
try {
|
|
342
|
-
const content = await readFile(filePath, "utf8");
|
|
343
|
-
const entry = JSON.parse(content);
|
|
344
|
-
if (entry.metadata.lastAccessedAt < oldestTime) {
|
|
345
|
-
oldestTime = entry.metadata.lastAccessedAt;
|
|
346
|
-
oldestKey = entry.metadata.key;
|
|
347
|
-
}
|
|
348
|
-
} catch {
|
|
349
|
-
corruptedFile = filePath;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
if (corruptedFile) {
|
|
353
|
-
try {
|
|
354
|
-
await unlink(corruptedFile);
|
|
355
|
-
this.stats.entries = Math.max(0, this.stats.entries - 1);
|
|
356
|
-
return true;
|
|
357
|
-
} catch {
|
|
358
|
-
return false;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
if (oldestKey) {
|
|
362
|
-
return await this.delete(oldestKey);
|
|
363
|
-
}
|
|
364
|
-
return false;
|
|
365
|
-
} catch {
|
|
366
|
-
return false;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
/**
|
|
370
|
-
* Update hit rate statistic.
|
|
371
|
-
*/
|
|
372
|
-
updateHitRate() {
|
|
373
|
-
const total = this.stats.hits + this.stats.misses;
|
|
374
|
-
this.stats.hitRate = total > 0 ? this.stats.hits / total : 0;
|
|
375
|
-
}
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
// libs/ui/src/bundler/file-cache/storage/redis.ts
|
|
381
|
-
var redis_exports = {};
|
|
382
|
-
__export(redis_exports, {
|
|
383
|
-
RedisStorage: () => RedisStorage,
|
|
384
|
-
createRedisStorage: () => createRedisStorage
|
|
385
|
-
});
|
|
386
|
-
function createRedisStorage(options) {
|
|
387
|
-
return new RedisStorage(options);
|
|
388
|
-
}
|
|
389
|
-
var STATS_KEY_SUFFIX, RedisStorage;
|
|
390
|
-
var init_redis = __esm({
|
|
391
|
-
"libs/ui/src/bundler/file-cache/storage/redis.ts"() {
|
|
392
|
-
"use strict";
|
|
393
|
-
init_interface();
|
|
394
|
-
STATS_KEY_SUFFIX = ":__stats__";
|
|
395
|
-
RedisStorage = class {
|
|
396
|
-
type = "redis";
|
|
397
|
-
options;
|
|
398
|
-
initialized = false;
|
|
399
|
-
localStats = {
|
|
400
|
-
entries: 0,
|
|
401
|
-
totalSize: 0,
|
|
402
|
-
hits: 0,
|
|
403
|
-
misses: 0,
|
|
404
|
-
hitRate: 0
|
|
405
|
-
};
|
|
406
|
-
constructor(options) {
|
|
407
|
-
if (!options.client) {
|
|
408
|
-
throw new Error("Redis client is required");
|
|
409
|
-
}
|
|
410
|
-
this.options = {
|
|
411
|
-
...DEFAULT_STORAGE_OPTIONS,
|
|
412
|
-
keyPrefix: "frontmcp:ui:build:",
|
|
413
|
-
json: true,
|
|
414
|
-
...options
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
/**
|
|
418
|
-
* Initialize the Redis connection.
|
|
419
|
-
*/
|
|
420
|
-
async initialize() {
|
|
421
|
-
if (this.initialized) return;
|
|
422
|
-
try {
|
|
423
|
-
await this.options.client.ping();
|
|
424
|
-
await this.loadStats();
|
|
425
|
-
this.initialized = true;
|
|
426
|
-
} catch (error) {
|
|
427
|
-
throw new Error(`Failed to connect to Redis: ${error}`);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
/**
|
|
431
|
-
* Get a cached manifest.
|
|
432
|
-
*/
|
|
433
|
-
async get(key) {
|
|
434
|
-
this.ensureInitialized();
|
|
435
|
-
const redisKey = this.getRedisKey(key);
|
|
436
|
-
try {
|
|
437
|
-
const data = await this.options.client.get(redisKey);
|
|
438
|
-
if (!data) {
|
|
439
|
-
this.localStats.misses++;
|
|
440
|
-
this.updateHitRate();
|
|
441
|
-
await this.persistStats();
|
|
442
|
-
return void 0;
|
|
443
|
-
}
|
|
444
|
-
const entry = JSON.parse(data);
|
|
445
|
-
entry.metadata.lastAccessedAt = Date.now();
|
|
446
|
-
entry.metadata.accessCount++;
|
|
447
|
-
const ttl = await this.options.client.ttl(redisKey);
|
|
448
|
-
if (ttl > 0) {
|
|
449
|
-
const serialized = JSON.stringify(entry);
|
|
450
|
-
await this.options.client.setex(redisKey, ttl, serialized);
|
|
451
|
-
}
|
|
452
|
-
this.localStats.hits++;
|
|
453
|
-
this.updateHitRate();
|
|
454
|
-
await this.persistStats();
|
|
455
|
-
return entry.data;
|
|
456
|
-
} catch (error) {
|
|
457
|
-
console.warn?.(`Redis cache get failed for key "${key}": ${error}`);
|
|
458
|
-
this.localStats.misses++;
|
|
459
|
-
this.updateHitRate();
|
|
460
|
-
await this.persistStats().catch(() => {
|
|
461
|
-
});
|
|
462
|
-
return void 0;
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
/**
|
|
466
|
-
* Store a manifest in cache.
|
|
467
|
-
*/
|
|
468
|
-
async set(key, manifest, ttl) {
|
|
469
|
-
this.ensureInitialized();
|
|
470
|
-
const redisKey = this.getRedisKey(key);
|
|
471
|
-
const size = calculateManifestSize(manifest);
|
|
472
|
-
const effectiveTtl = ttl ?? this.options.defaultTtl;
|
|
473
|
-
const entry = {
|
|
474
|
-
data: manifest,
|
|
475
|
-
metadata: {
|
|
476
|
-
key,
|
|
477
|
-
size,
|
|
478
|
-
createdAt: Date.now(),
|
|
479
|
-
expiresAt: Date.now() + effectiveTtl * 1e3,
|
|
480
|
-
lastAccessedAt: Date.now(),
|
|
481
|
-
accessCount: 0
|
|
482
|
-
}
|
|
483
|
-
};
|
|
484
|
-
const serialized = JSON.stringify(entry);
|
|
485
|
-
await this.options.client.setex(redisKey, effectiveTtl, serialized);
|
|
486
|
-
this.localStats.entries++;
|
|
487
|
-
this.localStats.totalSize += size;
|
|
488
|
-
await this.persistStats();
|
|
489
|
-
}
|
|
490
|
-
/**
|
|
491
|
-
* Check if a key exists.
|
|
492
|
-
*/
|
|
493
|
-
async has(key) {
|
|
494
|
-
this.ensureInitialized();
|
|
495
|
-
const redisKey = this.getRedisKey(key);
|
|
496
|
-
const exists = await this.options.client.exists(redisKey);
|
|
497
|
-
return exists > 0;
|
|
498
|
-
}
|
|
499
|
-
/**
|
|
500
|
-
* Delete a cached entry.
|
|
501
|
-
*/
|
|
502
|
-
async delete(key) {
|
|
503
|
-
this.ensureInitialized();
|
|
504
|
-
const redisKey = this.getRedisKey(key);
|
|
505
|
-
try {
|
|
506
|
-
const data = await this.options.client.get(redisKey);
|
|
507
|
-
if (data) {
|
|
508
|
-
const entry = JSON.parse(data);
|
|
509
|
-
this.localStats.totalSize = Math.max(0, this.localStats.totalSize - entry.metadata.size);
|
|
510
|
-
}
|
|
511
|
-
} catch {
|
|
512
|
-
}
|
|
513
|
-
const deleted = await this.options.client.del(redisKey);
|
|
514
|
-
if (deleted > 0) {
|
|
515
|
-
this.localStats.entries = Math.max(0, this.localStats.entries - 1);
|
|
516
|
-
await this.persistStats();
|
|
517
|
-
return true;
|
|
518
|
-
}
|
|
519
|
-
return false;
|
|
520
|
-
}
|
|
521
|
-
/**
|
|
522
|
-
* Clear all cached entries.
|
|
523
|
-
*/
|
|
524
|
-
async clear() {
|
|
525
|
-
this.ensureInitialized();
|
|
526
|
-
const pattern = `${this.options.keyPrefix}*`;
|
|
527
|
-
const keys = await this.options.client.keys(pattern);
|
|
528
|
-
if (keys.length > 0) {
|
|
529
|
-
await this.options.client.del(keys);
|
|
530
|
-
}
|
|
531
|
-
this.localStats = {
|
|
532
|
-
entries: 0,
|
|
533
|
-
totalSize: 0,
|
|
534
|
-
hits: 0,
|
|
535
|
-
misses: 0,
|
|
536
|
-
hitRate: 0
|
|
537
|
-
};
|
|
538
|
-
await this.persistStats();
|
|
539
|
-
}
|
|
540
|
-
/**
|
|
541
|
-
* Get cache statistics.
|
|
542
|
-
*/
|
|
543
|
-
async getStats() {
|
|
544
|
-
await this.loadStats();
|
|
545
|
-
return { ...this.localStats };
|
|
546
|
-
}
|
|
547
|
-
/**
|
|
548
|
-
* Clean up expired entries.
|
|
549
|
-
* Redis handles TTL expiration automatically, so this just refreshes stats.
|
|
550
|
-
*/
|
|
551
|
-
async cleanup() {
|
|
552
|
-
this.ensureInitialized();
|
|
553
|
-
const pattern = `${this.options.keyPrefix}*`;
|
|
554
|
-
const keys = await this.options.client.keys(pattern);
|
|
555
|
-
const dataKeys = keys.filter((k) => !k.endsWith(STATS_KEY_SUFFIX));
|
|
556
|
-
const previousCount = this.localStats.entries;
|
|
557
|
-
this.localStats.entries = dataKeys.length;
|
|
558
|
-
let totalSize = 0;
|
|
559
|
-
for (const key of dataKeys) {
|
|
560
|
-
try {
|
|
561
|
-
const data = await this.options.client.get(key);
|
|
562
|
-
if (data) {
|
|
563
|
-
const entry = JSON.parse(data);
|
|
564
|
-
totalSize += entry.metadata.size;
|
|
565
|
-
}
|
|
566
|
-
} catch {
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
this.localStats.totalSize = totalSize;
|
|
570
|
-
await this.persistStats();
|
|
571
|
-
return Math.max(0, previousCount - this.localStats.entries);
|
|
572
|
-
}
|
|
573
|
-
/**
|
|
574
|
-
* Close the Redis connection.
|
|
575
|
-
*/
|
|
576
|
-
async close() {
|
|
577
|
-
await this.options.client.quit();
|
|
578
|
-
}
|
|
579
|
-
/**
|
|
580
|
-
* Get the Redis key for a cache key.
|
|
581
|
-
*/
|
|
582
|
-
getRedisKey(key) {
|
|
583
|
-
return `${this.options.keyPrefix}${key}`;
|
|
584
|
-
}
|
|
585
|
-
/**
|
|
586
|
-
* Get the Redis key for stats.
|
|
587
|
-
*/
|
|
588
|
-
getStatsKey() {
|
|
589
|
-
return `${this.options.keyPrefix}${STATS_KEY_SUFFIX}`;
|
|
590
|
-
}
|
|
591
|
-
/**
|
|
592
|
-
* Ensure the storage is initialized.
|
|
593
|
-
*/
|
|
594
|
-
ensureInitialized() {
|
|
595
|
-
if (!this.initialized) {
|
|
596
|
-
throw new Error("Storage not initialized. Call initialize() first.");
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
/**
|
|
600
|
-
* Load stats from Redis.
|
|
601
|
-
*/
|
|
602
|
-
async loadStats() {
|
|
603
|
-
try {
|
|
604
|
-
const statsKey = this.getStatsKey();
|
|
605
|
-
const data = await this.options.client.get(statsKey);
|
|
606
|
-
if (data) {
|
|
607
|
-
const savedStats = JSON.parse(data);
|
|
608
|
-
this.localStats = {
|
|
609
|
-
...this.localStats,
|
|
610
|
-
...savedStats
|
|
611
|
-
};
|
|
612
|
-
}
|
|
613
|
-
} catch {
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
/**
|
|
617
|
-
* Persist stats to Redis.
|
|
618
|
-
*/
|
|
619
|
-
async persistStats() {
|
|
620
|
-
try {
|
|
621
|
-
const statsKey = this.getStatsKey();
|
|
622
|
-
const serialized = JSON.stringify(this.localStats);
|
|
623
|
-
await this.options.client.set(statsKey, serialized);
|
|
624
|
-
} catch {
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
/**
|
|
628
|
-
* Update hit rate statistic.
|
|
629
|
-
*/
|
|
630
|
-
updateHitRate() {
|
|
631
|
-
const total = this.localStats.hits + this.localStats.misses;
|
|
632
|
-
this.localStats.hitRate = total > 0 ? this.localStats.hits / total : 0;
|
|
633
|
-
}
|
|
634
|
-
};
|
|
635
|
-
}
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
636
6
|
});
|
|
637
7
|
|
|
638
8
|
// libs/ui/src/bundler/types.ts
|
|
9
|
+
var HYBRID_DATA_PLACEHOLDER = "__FRONTMCP_OUTPUT_PLACEHOLDER__";
|
|
10
|
+
var HYBRID_INPUT_PLACEHOLDER = "__FRONTMCP_INPUT_PLACEHOLDER__";
|
|
639
11
|
var DEFAULT_SECURITY_POLICY = {
|
|
640
12
|
allowedImports: [/^react$/, /^react-dom$/, /^react\/jsx-runtime$/, /^react\/jsx-dev-runtime$/, /^@frontmcp\/ui/],
|
|
641
13
|
blockedImports: [
|
|
@@ -748,6 +120,13 @@ var DEFAULT_BUNDLER_OPTIONS = {
|
|
|
748
120
|
verbose: false,
|
|
749
121
|
esbuildOptions: {}
|
|
750
122
|
};
|
|
123
|
+
var ALL_PLATFORMS = [
|
|
124
|
+
"openai",
|
|
125
|
+
"claude",
|
|
126
|
+
"cursor",
|
|
127
|
+
"ext-apps",
|
|
128
|
+
"generic"
|
|
129
|
+
];
|
|
751
130
|
var STATIC_HTML_CDN = {
|
|
752
131
|
/**
|
|
753
132
|
* ES modules from esm.sh (React 19, modern platforms)
|
|
@@ -763,11 +142,6 @@ var STATIC_HTML_CDN = {
|
|
|
763
142
|
react: "https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js",
|
|
764
143
|
reactDom: "https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"
|
|
765
144
|
},
|
|
766
|
-
/**
|
|
767
|
-
* Tailwind CSS from cdnjs (cloudflare) - works on all platforms
|
|
768
|
-
* Using CSS file instead of JS browser version to avoid style normalization issues
|
|
769
|
-
*/
|
|
770
|
-
tailwind: "https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/3.4.1/tailwind.min.css",
|
|
771
145
|
/**
|
|
772
146
|
* Font CDN URLs
|
|
773
147
|
*/
|
|
@@ -797,667 +171,94 @@ var DEFAULT_STATIC_HTML_OPTIONS = {
|
|
|
797
171
|
universal: false,
|
|
798
172
|
contentType: "auto",
|
|
799
173
|
includeMarkdown: false,
|
|
800
|
-
includeMdx: false
|
|
174
|
+
includeMdx: false,
|
|
175
|
+
// Build mode defaults
|
|
176
|
+
buildMode: "static"
|
|
801
177
|
};
|
|
802
178
|
|
|
803
|
-
// libs/ui/src/bundler/
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
const entry = this.cache.get(key);
|
|
826
|
-
if (!entry) {
|
|
827
|
-
this.stats.misses++;
|
|
828
|
-
return void 0;
|
|
829
|
-
}
|
|
830
|
-
if (this.isExpired(entry)) {
|
|
831
|
-
this.cache.delete(key);
|
|
832
|
-
this.stats.misses++;
|
|
833
|
-
this.stats.evictions++;
|
|
834
|
-
return void 0;
|
|
835
|
-
}
|
|
836
|
-
entry.lastAccessedAt = Date.now();
|
|
837
|
-
entry.accessCount++;
|
|
838
|
-
this.stats.hits++;
|
|
839
|
-
this.cache.delete(key);
|
|
840
|
-
this.cache.set(key, entry);
|
|
841
|
-
return entry.result;
|
|
842
|
-
}
|
|
843
|
-
/**
|
|
844
|
-
* Store a bundle result in the cache.
|
|
845
|
-
*
|
|
846
|
-
* @param key - Cache key (typically content hash)
|
|
847
|
-
* @param result - Bundle result to cache
|
|
848
|
-
*/
|
|
849
|
-
set(key, result) {
|
|
850
|
-
while (this.cache.size >= this.options.maxSize) {
|
|
851
|
-
this.evictOldest();
|
|
852
|
-
}
|
|
853
|
-
const now = Date.now();
|
|
854
|
-
const entry = {
|
|
855
|
-
result,
|
|
856
|
-
createdAt: now,
|
|
857
|
-
lastAccessedAt: now,
|
|
858
|
-
accessCount: 1
|
|
859
|
-
};
|
|
860
|
-
this.cache.set(key, entry);
|
|
861
|
-
}
|
|
862
|
-
/**
|
|
863
|
-
* Check if a key exists in the cache (and is not expired).
|
|
864
|
-
*
|
|
865
|
-
* @param key - Cache key to check
|
|
866
|
-
* @returns true if key exists and is not expired
|
|
867
|
-
*/
|
|
868
|
-
has(key) {
|
|
869
|
-
const entry = this.cache.get(key);
|
|
870
|
-
if (!entry) return false;
|
|
871
|
-
if (this.isExpired(entry)) {
|
|
872
|
-
this.cache.delete(key);
|
|
873
|
-
this.stats.evictions++;
|
|
874
|
-
return false;
|
|
875
|
-
}
|
|
876
|
-
return true;
|
|
877
|
-
}
|
|
878
|
-
/**
|
|
879
|
-
* Delete a specific entry from the cache.
|
|
880
|
-
*
|
|
881
|
-
* @param key - Cache key to delete
|
|
882
|
-
* @returns true if the key was found and deleted
|
|
883
|
-
*/
|
|
884
|
-
delete(key) {
|
|
885
|
-
return this.cache.delete(key);
|
|
886
|
-
}
|
|
887
|
-
/**
|
|
888
|
-
* Clear all entries from the cache.
|
|
889
|
-
*/
|
|
890
|
-
clear() {
|
|
891
|
-
this.cache.clear();
|
|
892
|
-
this.stats = {
|
|
893
|
-
hits: 0,
|
|
894
|
-
misses: 0,
|
|
895
|
-
evictions: 0
|
|
896
|
-
};
|
|
897
|
-
}
|
|
898
|
-
/**
|
|
899
|
-
* Get cache statistics.
|
|
900
|
-
*
|
|
901
|
-
* @returns Current cache statistics
|
|
902
|
-
*/
|
|
903
|
-
getStats() {
|
|
904
|
-
const total = this.stats.hits + this.stats.misses;
|
|
905
|
-
const hitRate = total > 0 ? this.stats.hits / total : 0;
|
|
906
|
-
let memoryUsage = 0;
|
|
907
|
-
for (const entry of this.cache.values()) {
|
|
908
|
-
memoryUsage += entry.result.size;
|
|
909
|
-
if (entry.result.map) {
|
|
910
|
-
memoryUsage += entry.result.map.length;
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
return {
|
|
914
|
-
size: this.cache.size,
|
|
915
|
-
hits: this.stats.hits,
|
|
916
|
-
misses: this.stats.misses,
|
|
917
|
-
hitRate,
|
|
918
|
-
evictions: this.stats.evictions,
|
|
919
|
-
memoryUsage
|
|
920
|
-
};
|
|
179
|
+
// libs/ui/src/bundler/bundler.ts
|
|
180
|
+
import { buildUIMeta } from "@frontmcp/uipack/adapters";
|
|
181
|
+
import { DEFAULT_THEME, buildThemeCss } from "@frontmcp/uipack/theme";
|
|
182
|
+
import {
|
|
183
|
+
BundlerCache,
|
|
184
|
+
createCacheKey,
|
|
185
|
+
hashContent,
|
|
186
|
+
validateSource,
|
|
187
|
+
validateSize,
|
|
188
|
+
mergePolicy,
|
|
189
|
+
throwOnViolations,
|
|
190
|
+
executeDefault,
|
|
191
|
+
ExecutionError
|
|
192
|
+
} from "@frontmcp/uipack/bundler";
|
|
193
|
+
import { escapeHtml } from "@frontmcp/uipack/utils";
|
|
194
|
+
|
|
195
|
+
// libs/ui/src/universal/types.ts
|
|
196
|
+
var UNIVERSAL_CDN = {
|
|
197
|
+
esm: {
|
|
198
|
+
reactMarkdown: "https://esm.sh/react-markdown@9",
|
|
199
|
+
mdxReact: "https://esm.sh/@mdx-js/react@3",
|
|
200
|
+
remarkGfm: "https://esm.sh/remark-gfm@4"
|
|
921
201
|
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
let removed = 0;
|
|
929
|
-
for (const [key, entry] of this.cache.entries()) {
|
|
930
|
-
if (this.isExpired(entry)) {
|
|
931
|
-
this.cache.delete(key);
|
|
932
|
-
removed++;
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
this.stats.evictions += removed;
|
|
936
|
-
return removed;
|
|
202
|
+
// Note: These libraries are not available on cdnjs
|
|
203
|
+
// For Claude, we use inline implementations
|
|
204
|
+
};
|
|
205
|
+
function detectContentType(source) {
|
|
206
|
+
if (typeof source === "function") {
|
|
207
|
+
return "react";
|
|
937
208
|
}
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
*
|
|
941
|
-
* @returns Array of cache keys
|
|
942
|
-
*/
|
|
943
|
-
keys() {
|
|
944
|
-
return Array.from(this.cache.keys());
|
|
209
|
+
if (typeof source !== "string") {
|
|
210
|
+
return "html";
|
|
945
211
|
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
212
|
+
const hasModuleSyntax = /^import\s+/m.test(source) || /^export\s+(default\s+)?/m.test(source) || /^const\s+\w+\s*=\s*\([^)]*\)\s*=>/m.test(source) || // Arrow function components
|
|
213
|
+
/^function\s+\w+\s*\(/m.test(source);
|
|
214
|
+
const hasJsxTags = /<[A-Z][a-zA-Z]*/.test(source);
|
|
215
|
+
const hasMarkdown = /^#{1,6}\s/m.test(source) || /^\*\s/m.test(source) || /^-\s/m.test(source) || /^\d+\.\s/m.test(source);
|
|
216
|
+
if (hasModuleSyntax && hasJsxTags) {
|
|
217
|
+
return "react";
|
|
951
218
|
}
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
*/
|
|
955
|
-
isExpired(entry) {
|
|
956
|
-
return Date.now() - entry.createdAt > this.options.ttl;
|
|
219
|
+
if (hasJsxTags && hasMarkdown && !hasModuleSyntax) {
|
|
220
|
+
return "mdx";
|
|
957
221
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
*/
|
|
961
|
-
evictOldest() {
|
|
962
|
-
const oldestKey = this.cache.keys().next().value;
|
|
963
|
-
if (oldestKey !== void 0) {
|
|
964
|
-
this.cache.delete(oldestKey);
|
|
965
|
-
this.stats.evictions++;
|
|
966
|
-
}
|
|
222
|
+
if (hasMarkdown || /\*\*[^*]+\*\*/.test(source) || /\[[^\]]+\]\([^)]+\)/.test(source)) {
|
|
223
|
+
return "markdown";
|
|
967
224
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
let hash = 2166136261;
|
|
971
|
-
for (let i = 0; i < content.length; i++) {
|
|
972
|
-
hash ^= content.charCodeAt(i);
|
|
973
|
-
hash = Math.imul(hash, 16777619);
|
|
225
|
+
if (hasJsxTags && !hasModuleSyntax) {
|
|
226
|
+
return "mdx";
|
|
974
227
|
}
|
|
975
|
-
return
|
|
976
|
-
}
|
|
977
|
-
function createCacheKey(source, options) {
|
|
978
|
-
const sourceHash = hashContent(source);
|
|
979
|
-
const optionsHash = hashContent(
|
|
980
|
-
JSON.stringify({
|
|
981
|
-
sourceType: options.sourceType,
|
|
982
|
-
format: options.format,
|
|
983
|
-
minify: options.minify,
|
|
984
|
-
externals: options.externals?.sort(),
|
|
985
|
-
target: options.target
|
|
986
|
-
})
|
|
987
|
-
);
|
|
988
|
-
return `${sourceHash}-${optionsHash}`;
|
|
228
|
+
return "html";
|
|
989
229
|
}
|
|
990
230
|
|
|
991
|
-
// libs/ui/src/
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
innerHTML: /\.innerHTML\s*=/g,
|
|
1002
|
-
outerHTML: /\.outerHTML\s*=/g,
|
|
1003
|
-
document_write: /\bdocument\.write\s*\(/g
|
|
231
|
+
// libs/ui/src/universal/cached-runtime.ts
|
|
232
|
+
import { getMCPBridgeScript } from "@frontmcp/uipack/runtime";
|
|
233
|
+
import { buildUIComponentsRuntime as buildBrowserUIComponents } from "@frontmcp/uipack/build";
|
|
234
|
+
var RUNTIME_PLACEHOLDERS = {
|
|
235
|
+
/** Placeholder for transpiled component code */
|
|
236
|
+
COMPONENT_CODE: "/*__FRONTMCP_COMPONENT_CODE__*/",
|
|
237
|
+
/** Placeholder for data injection */
|
|
238
|
+
DATA_INJECTION: "/*__FRONTMCP_DATA_INJECTION__*/",
|
|
239
|
+
/** Placeholder for custom components */
|
|
240
|
+
CUSTOM_COMPONENTS: "/*__FRONTMCP_CUSTOM_COMPONENTS__*/"
|
|
1004
241
|
};
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
if (policy.noDynamicImports !== false) {
|
|
1028
|
-
const matches = [...source.matchAll(UNSAFE_PATTERNS.dynamicImport)];
|
|
1029
|
-
for (const match of matches) {
|
|
1030
|
-
violations.push({
|
|
1031
|
-
type: "dynamic-import",
|
|
1032
|
-
message: "Dynamic imports are not allowed for security reasons",
|
|
1033
|
-
location: getLocation(source, match.index ?? 0),
|
|
1034
|
-
value: match[0]
|
|
1035
|
-
});
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
if (policy.noRequire !== false) {
|
|
1039
|
-
const matches = [...source.matchAll(UNSAFE_PATTERNS.require)];
|
|
1040
|
-
for (const match of matches) {
|
|
1041
|
-
violations.push({
|
|
1042
|
-
type: "require-usage",
|
|
1043
|
-
message: "require() is not allowed for security reasons",
|
|
1044
|
-
location: getLocation(source, match.index ?? 0),
|
|
1045
|
-
value: match[0]
|
|
1046
|
-
});
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
const importViolations = validateImports(source, policy);
|
|
1050
|
-
violations.push(...importViolations);
|
|
1051
|
-
return violations;
|
|
1052
|
-
}
|
|
1053
|
-
function validateImports(source, policy = DEFAULT_SECURITY_POLICY) {
|
|
1054
|
-
const violations = [];
|
|
1055
|
-
const importPattern = /import\s+(?:(?:\{[^}]*\}|[\w*]+)\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
1056
|
-
const imports = [];
|
|
1057
|
-
let match;
|
|
1058
|
-
while ((match = importPattern.exec(source)) !== null) {
|
|
1059
|
-
imports.push({ module: match[1], index: match.index });
|
|
1060
|
-
}
|
|
1061
|
-
const requirePattern = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
1062
|
-
while ((match = requirePattern.exec(source)) !== null) {
|
|
1063
|
-
imports.push({ module: match[1], index: match.index });
|
|
1064
|
-
}
|
|
1065
|
-
for (const imp of imports) {
|
|
1066
|
-
if (policy.blockedImports) {
|
|
1067
|
-
for (const blocked of policy.blockedImports) {
|
|
1068
|
-
if (blocked.test(imp.module)) {
|
|
1069
|
-
violations.push({
|
|
1070
|
-
type: "blocked-import",
|
|
1071
|
-
message: `Import '${imp.module}' is blocked by security policy`,
|
|
1072
|
-
location: getLocation(source, imp.index),
|
|
1073
|
-
value: imp.module
|
|
1074
|
-
});
|
|
1075
|
-
break;
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
if (policy.allowedImports && policy.allowedImports.length > 0) {
|
|
1080
|
-
const isAllowed = policy.allowedImports.some((pattern) => pattern.test(imp.module));
|
|
1081
|
-
if (!isAllowed) {
|
|
1082
|
-
const alreadyBlocked = violations.some((v) => v.type === "blocked-import" && v.value === imp.module);
|
|
1083
|
-
if (!alreadyBlocked) {
|
|
1084
|
-
violations.push({
|
|
1085
|
-
type: "disallowed-import",
|
|
1086
|
-
message: `Import '${imp.module}' is not in the allowed imports list`,
|
|
1087
|
-
location: getLocation(source, imp.index),
|
|
1088
|
-
value: imp.module
|
|
1089
|
-
});
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
return violations;
|
|
1095
|
-
}
|
|
1096
|
-
function validateSize(size, policy = DEFAULT_SECURITY_POLICY) {
|
|
1097
|
-
const maxSize = policy.maxBundleSize ?? DEFAULT_SECURITY_POLICY.maxBundleSize ?? 512e3;
|
|
1098
|
-
if (size > maxSize) {
|
|
1099
|
-
return {
|
|
1100
|
-
type: "size-exceeded",
|
|
1101
|
-
message: `Bundle size (${formatBytes(size)}) exceeds maximum allowed (${formatBytes(maxSize)})`,
|
|
1102
|
-
value: String(size)
|
|
1103
|
-
};
|
|
1104
|
-
}
|
|
1105
|
-
return void 0;
|
|
1106
|
-
}
|
|
1107
|
-
function mergePolicy(userPolicy) {
|
|
1108
|
-
if (!userPolicy) {
|
|
1109
|
-
return { ...DEFAULT_SECURITY_POLICY };
|
|
1110
|
-
}
|
|
1111
|
-
return {
|
|
1112
|
-
allowedImports: userPolicy.allowedImports ?? DEFAULT_SECURITY_POLICY.allowedImports,
|
|
1113
|
-
blockedImports: userPolicy.blockedImports ?? DEFAULT_SECURITY_POLICY.blockedImports,
|
|
1114
|
-
maxBundleSize: userPolicy.maxBundleSize ?? DEFAULT_SECURITY_POLICY.maxBundleSize,
|
|
1115
|
-
maxTransformTime: userPolicy.maxTransformTime ?? DEFAULT_SECURITY_POLICY.maxTransformTime,
|
|
1116
|
-
noEval: userPolicy.noEval ?? DEFAULT_SECURITY_POLICY.noEval,
|
|
1117
|
-
noDynamicImports: userPolicy.noDynamicImports ?? DEFAULT_SECURITY_POLICY.noDynamicImports,
|
|
1118
|
-
noRequire: userPolicy.noRequire ?? DEFAULT_SECURITY_POLICY.noRequire,
|
|
1119
|
-
allowedGlobals: userPolicy.allowedGlobals ?? DEFAULT_SECURITY_POLICY.allowedGlobals
|
|
1120
|
-
};
|
|
1121
|
-
}
|
|
1122
|
-
function getLocation(source, index) {
|
|
1123
|
-
const lines = source.slice(0, index).split("\n");
|
|
1124
|
-
return {
|
|
1125
|
-
line: lines.length,
|
|
1126
|
-
column: lines[lines.length - 1].length + 1
|
|
1127
|
-
};
|
|
1128
|
-
}
|
|
1129
|
-
function formatBytes(bytes) {
|
|
1130
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
1131
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
1132
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
1133
|
-
}
|
|
1134
|
-
var SecurityError = class extends Error {
|
|
1135
|
-
violations;
|
|
1136
|
-
constructor(message, violations) {
|
|
1137
|
-
super(message);
|
|
1138
|
-
this.name = "SecurityError";
|
|
1139
|
-
this.violations = violations;
|
|
1140
|
-
}
|
|
1141
|
-
};
|
|
1142
|
-
function throwOnViolations(violations) {
|
|
1143
|
-
if (violations.length > 0) {
|
|
1144
|
-
const message = violations.map((v) => v.message).join("; ");
|
|
1145
|
-
throw new SecurityError(`Security policy violation: ${message}`, violations);
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
// libs/ui/src/bundler/sandbox/enclave-adapter.ts
|
|
1150
|
-
import { Enclave } from "enclave-vm";
|
|
1151
|
-
var DEFAULT_ENCLAVE_OPTIONS = {
|
|
1152
|
-
securityLevel: "SECURE",
|
|
1153
|
-
timeout: 5e3,
|
|
1154
|
-
maxIterations: 1e4,
|
|
1155
|
-
validate: true,
|
|
1156
|
-
transform: true
|
|
1157
|
-
};
|
|
1158
|
-
var STRICT_SECURITY_BLOCKED_IMPORTS_THRESHOLD = 10;
|
|
1159
|
-
function mapSecurityLevel(policy) {
|
|
1160
|
-
if (policy?.blockedImports && policy.blockedImports.length > STRICT_SECURITY_BLOCKED_IMPORTS_THRESHOLD) {
|
|
1161
|
-
return "STRICT";
|
|
1162
|
-
}
|
|
1163
|
-
return "SECURE";
|
|
1164
|
-
}
|
|
1165
|
-
function createJSXRuntime(React) {
|
|
1166
|
-
const R = React;
|
|
1167
|
-
return {
|
|
1168
|
-
jsx: (type, props, key) => {
|
|
1169
|
-
const { children, ...rest } = props;
|
|
1170
|
-
return R.createElement(type, key ? { ...rest, key } : rest, children);
|
|
1171
|
-
},
|
|
1172
|
-
jsxs: (type, props, key) => {
|
|
1173
|
-
const { children, ...rest } = props;
|
|
1174
|
-
return R.createElement(type, key ? { ...rest, key } : rest, children);
|
|
1175
|
-
},
|
|
1176
|
-
jsxDEV: (type, props, key, _isStaticChildren, _source, _self) => {
|
|
1177
|
-
const { children, ...rest } = props;
|
|
1178
|
-
return R.createElement(type, key ? { ...rest, key } : rest, children);
|
|
1179
|
-
},
|
|
1180
|
-
Fragment: R.Fragment
|
|
1181
|
-
};
|
|
1182
|
-
}
|
|
1183
|
-
var DANGEROUS_GLOBAL_KEYS = /* @__PURE__ */ new Set([
|
|
1184
|
-
"process",
|
|
1185
|
-
"require",
|
|
1186
|
-
"__dirname",
|
|
1187
|
-
"__filename",
|
|
1188
|
-
"Buffer",
|
|
1189
|
-
"eval",
|
|
1190
|
-
"Function",
|
|
1191
|
-
"constructor",
|
|
1192
|
-
"global",
|
|
1193
|
-
"globalThis",
|
|
1194
|
-
"module",
|
|
1195
|
-
"exports",
|
|
1196
|
-
"__proto__"
|
|
1197
|
-
]);
|
|
1198
|
-
function sanitizeGlobalKey(key) {
|
|
1199
|
-
return key.replace(/[^a-zA-Z0-9_$]/g, "_");
|
|
1200
|
-
}
|
|
1201
|
-
function buildGlobals(context) {
|
|
1202
|
-
const globals = {};
|
|
1203
|
-
if (context.React) {
|
|
1204
|
-
globals["React"] = context.React;
|
|
1205
|
-
}
|
|
1206
|
-
if (context.ReactDOM) {
|
|
1207
|
-
globals["ReactDOM"] = context.ReactDOM;
|
|
1208
|
-
}
|
|
1209
|
-
if (context.React) {
|
|
1210
|
-
const jsxRuntime = createJSXRuntime(context.React);
|
|
1211
|
-
globals["__jsx"] = jsxRuntime["jsx"];
|
|
1212
|
-
globals["__jsxs"] = jsxRuntime["jsxs"];
|
|
1213
|
-
globals["__jsxDEV"] = jsxRuntime["jsxDEV"];
|
|
1214
|
-
globals["Fragment"] = jsxRuntime["Fragment"];
|
|
1215
|
-
}
|
|
1216
|
-
if (context.modules) {
|
|
1217
|
-
for (const [key, value] of Object.entries(context.modules)) {
|
|
1218
|
-
const sanitizedKey = sanitizeGlobalKey(key);
|
|
1219
|
-
if (DANGEROUS_GLOBAL_KEYS.has(sanitizedKey)) {
|
|
1220
|
-
throw new ExecutionError(
|
|
1221
|
-
`Dangerous module key '${key}' (sanitized: '${sanitizedKey}') is not allowed in execution context`,
|
|
1222
|
-
{ code: "SECURITY_VIOLATION" }
|
|
1223
|
-
);
|
|
1224
|
-
}
|
|
1225
|
-
globals[sanitizedKey] = value;
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
if (context.globals) {
|
|
1229
|
-
for (const [key, value] of Object.entries(context.globals)) {
|
|
1230
|
-
if (DANGEROUS_GLOBAL_KEYS.has(key)) {
|
|
1231
|
-
throw new ExecutionError(`Dangerous global key '${key}' is not allowed in execution context`, {
|
|
1232
|
-
code: "SECURITY_VIOLATION"
|
|
1233
|
-
});
|
|
1234
|
-
}
|
|
1235
|
-
const sanitizedKey = sanitizeGlobalKey(key);
|
|
1236
|
-
if (DANGEROUS_GLOBAL_KEYS.has(sanitizedKey)) {
|
|
1237
|
-
throw new ExecutionError(
|
|
1238
|
-
`Dangerous global key '${key}' (sanitized: '${sanitizedKey}') is not allowed in execution context`,
|
|
1239
|
-
{ code: "SECURITY_VIOLATION" }
|
|
1240
|
-
);
|
|
1241
|
-
}
|
|
1242
|
-
globals[sanitizedKey] = value;
|
|
1243
|
-
}
|
|
1244
|
-
}
|
|
1245
|
-
return globals;
|
|
1246
|
-
}
|
|
1247
|
-
function buildRequireFunction(context) {
|
|
1248
|
-
const normalizedContextModules = {};
|
|
1249
|
-
if (context.modules) {
|
|
1250
|
-
for (const [key, value] of Object.entries(context.modules)) {
|
|
1251
|
-
normalizedContextModules[key.toLowerCase()] = value;
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
const modules = {
|
|
1255
|
-
react: context.React,
|
|
1256
|
-
"react-dom": context.ReactDOM,
|
|
1257
|
-
"react/jsx-runtime": context.React ? createJSXRuntime(context.React) : void 0,
|
|
1258
|
-
"react/jsx-dev-runtime": context.React ? createJSXRuntime(context.React) : void 0,
|
|
1259
|
-
...normalizedContextModules
|
|
1260
|
-
};
|
|
1261
|
-
return (id) => {
|
|
1262
|
-
const normalizedId = id.toLowerCase();
|
|
1263
|
-
if (normalizedId in modules) {
|
|
1264
|
-
const mod = modules[normalizedId];
|
|
1265
|
-
if (mod === void 0) {
|
|
1266
|
-
throw new Error(`Module '${id}' is not available. Did you forget to provide it in the context?`);
|
|
1267
|
-
}
|
|
1268
|
-
return mod;
|
|
1269
|
-
}
|
|
1270
|
-
throw new Error(`Module '${id}' is not available in the sandbox environment`);
|
|
1271
|
-
};
|
|
1272
|
-
}
|
|
1273
|
-
async function executeCode(code, context = {}) {
|
|
1274
|
-
const consoleOutput = [];
|
|
1275
|
-
const globals = buildGlobals(context);
|
|
1276
|
-
globals["console"] = {
|
|
1277
|
-
log: (...args) => {
|
|
1278
|
-
consoleOutput.push(args.map(String).join(" "));
|
|
1279
|
-
},
|
|
1280
|
-
info: (...args) => {
|
|
1281
|
-
consoleOutput.push(`[INFO] ${args.map(String).join(" ")}`);
|
|
1282
|
-
},
|
|
1283
|
-
warn: (...args) => {
|
|
1284
|
-
consoleOutput.push(`[WARN] ${args.map(String).join(" ")}`);
|
|
1285
|
-
},
|
|
1286
|
-
error: (...args) => {
|
|
1287
|
-
consoleOutput.push(`[ERROR] ${args.map(String).join(" ")}`);
|
|
1288
|
-
},
|
|
1289
|
-
debug: (...args) => {
|
|
1290
|
-
consoleOutput.push(`[DEBUG] ${args.map(String).join(" ")}`);
|
|
1291
|
-
},
|
|
1292
|
-
trace: () => {
|
|
1293
|
-
},
|
|
1294
|
-
dir: () => {
|
|
1295
|
-
},
|
|
1296
|
-
table: () => {
|
|
1297
|
-
},
|
|
1298
|
-
group: () => {
|
|
1299
|
-
},
|
|
1300
|
-
groupEnd: () => {
|
|
1301
|
-
},
|
|
1302
|
-
time: () => {
|
|
1303
|
-
},
|
|
1304
|
-
timeEnd: () => {
|
|
1305
|
-
},
|
|
1306
|
-
assert: () => {
|
|
1307
|
-
},
|
|
1308
|
-
clear: () => {
|
|
1309
|
-
},
|
|
1310
|
-
count: () => {
|
|
1311
|
-
},
|
|
1312
|
-
countReset: () => {
|
|
1313
|
-
}
|
|
1314
|
-
};
|
|
1315
|
-
globals["require"] = buildRequireFunction(context);
|
|
1316
|
-
const enclave = new Enclave({
|
|
1317
|
-
...DEFAULT_ENCLAVE_OPTIONS,
|
|
1318
|
-
timeout: context.timeout ?? DEFAULT_ENCLAVE_OPTIONS.timeout,
|
|
1319
|
-
maxIterations: context.maxIterations ?? DEFAULT_ENCLAVE_OPTIONS.maxIterations,
|
|
1320
|
-
securityLevel: mapSecurityLevel(context.security),
|
|
1321
|
-
globals,
|
|
1322
|
-
allowFunctionsInGlobals: true
|
|
1323
|
-
// Required for React components
|
|
1324
|
-
});
|
|
1325
|
-
try {
|
|
1326
|
-
const wrappedCode = `
|
|
1327
|
-
const module = { exports: {} };
|
|
1328
|
-
const exports = module.exports;
|
|
1329
|
-
const __filename = 'widget.js';
|
|
1330
|
-
const __dirname = '/';
|
|
1331
|
-
${code}
|
|
1332
|
-
return module.exports;
|
|
1333
|
-
`;
|
|
1334
|
-
const result = await enclave.run(wrappedCode);
|
|
1335
|
-
if (!result.success) {
|
|
1336
|
-
const errorMessage = result.error?.message ?? "Execution failed";
|
|
1337
|
-
const errorCode = result.error?.code;
|
|
1338
|
-
if (errorCode === "TIMEOUT") {
|
|
1339
|
-
throw new ExecutionError(`Execution timed out after ${context.timeout ?? DEFAULT_ENCLAVE_OPTIONS.timeout}ms`, {
|
|
1340
|
-
code: "TIMEOUT"
|
|
1341
|
-
});
|
|
1342
|
-
}
|
|
1343
|
-
if (errorCode === "MAX_ITERATIONS") {
|
|
1344
|
-
throw new ExecutionError(
|
|
1345
|
-
`Maximum iterations exceeded (${context.maxIterations ?? DEFAULT_ENCLAVE_OPTIONS.maxIterations})`,
|
|
1346
|
-
{
|
|
1347
|
-
code: "MAX_ITERATIONS"
|
|
1348
|
-
}
|
|
1349
|
-
);
|
|
1350
|
-
}
|
|
1351
|
-
if (errorCode === "VALIDATION_ERROR") {
|
|
1352
|
-
throw new ExecutionError(`Security validation failed: ${errorMessage}`, { code: "SECURITY_VIOLATION" });
|
|
1353
|
-
}
|
|
1354
|
-
throw new ExecutionError(errorMessage, result.error);
|
|
1355
|
-
}
|
|
1356
|
-
return {
|
|
1357
|
-
exports: result.value,
|
|
1358
|
-
executionTime: result.stats.duration,
|
|
1359
|
-
consoleOutput: consoleOutput.length > 0 ? consoleOutput : void 0
|
|
1360
|
-
};
|
|
1361
|
-
} finally {
|
|
1362
|
-
enclave.dispose();
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
|
-
async function executeDefault(code, context = {}) {
|
|
1366
|
-
const result = await executeCode(code, context);
|
|
1367
|
-
if ("default" in result.exports) {
|
|
1368
|
-
return result.exports.default;
|
|
1369
|
-
}
|
|
1370
|
-
const exportKeys = Object.keys(result.exports);
|
|
1371
|
-
if (exportKeys.length === 0) {
|
|
1372
|
-
throw new ExecutionError("Code did not export any values");
|
|
1373
|
-
}
|
|
1374
|
-
if (exportKeys.length === 1) {
|
|
1375
|
-
return result.exports[exportKeys[0]];
|
|
1376
|
-
}
|
|
1377
|
-
return result.exports;
|
|
1378
|
-
}
|
|
1379
|
-
var ExecutionError = class extends Error {
|
|
1380
|
-
/** Error code for categorization */
|
|
1381
|
-
code;
|
|
1382
|
-
constructor(message, cause) {
|
|
1383
|
-
super(message, { cause });
|
|
1384
|
-
this.name = "ExecutionError";
|
|
1385
|
-
if (cause && typeof cause === "object" && "code" in cause) {
|
|
1386
|
-
this.code = cause.code;
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
};
|
|
1390
|
-
function isExecutionError(error) {
|
|
1391
|
-
return error instanceof ExecutionError;
|
|
1392
|
-
}
|
|
1393
|
-
|
|
1394
|
-
// libs/ui/src/bundler/bundler.ts
|
|
1395
|
-
import { escapeHtml } from "@frontmcp/uipack/utils";
|
|
1396
|
-
|
|
1397
|
-
// libs/ui/src/universal/types.ts
|
|
1398
|
-
var UNIVERSAL_CDN = {
|
|
1399
|
-
esm: {
|
|
1400
|
-
reactMarkdown: "https://esm.sh/react-markdown@9",
|
|
1401
|
-
mdxReact: "https://esm.sh/@mdx-js/react@3",
|
|
1402
|
-
remarkGfm: "https://esm.sh/remark-gfm@4"
|
|
1403
|
-
}
|
|
1404
|
-
// Note: These libraries are not available on cdnjs
|
|
1405
|
-
// For Claude, we use inline implementations
|
|
1406
|
-
};
|
|
1407
|
-
function detectContentType(source) {
|
|
1408
|
-
if (typeof source === "function") {
|
|
1409
|
-
return "react";
|
|
1410
|
-
}
|
|
1411
|
-
if (typeof source !== "string") {
|
|
1412
|
-
return "html";
|
|
1413
|
-
}
|
|
1414
|
-
const hasModuleSyntax = /^import\s+/m.test(source) || /^export\s+(default\s+)?/m.test(source) || /^const\s+\w+\s*=\s*\([^)]*\)\s*=>/m.test(source) || // Arrow function components
|
|
1415
|
-
/^function\s+\w+\s*\(/m.test(source);
|
|
1416
|
-
const hasJsxTags = /<[A-Z][a-zA-Z]*/.test(source);
|
|
1417
|
-
const hasMarkdown = /^#{1,6}\s/m.test(source) || /^\*\s/m.test(source) || /^-\s/m.test(source) || /^\d+\.\s/m.test(source);
|
|
1418
|
-
if (hasModuleSyntax && hasJsxTags) {
|
|
1419
|
-
return "react";
|
|
1420
|
-
}
|
|
1421
|
-
if (hasJsxTags && hasMarkdown && !hasModuleSyntax) {
|
|
1422
|
-
return "mdx";
|
|
1423
|
-
}
|
|
1424
|
-
if (hasMarkdown || /\*\*[^*]+\*\*/.test(source) || /\[[^\]]+\]\([^)]+\)/.test(source)) {
|
|
1425
|
-
return "markdown";
|
|
1426
|
-
}
|
|
1427
|
-
if (hasJsxTags && !hasModuleSyntax) {
|
|
1428
|
-
return "mdx";
|
|
1429
|
-
}
|
|
1430
|
-
return "html";
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
// libs/ui/src/universal/cached-runtime.ts
|
|
1434
|
-
var RUNTIME_PLACEHOLDERS = {
|
|
1435
|
-
/** Placeholder for transpiled component code */
|
|
1436
|
-
COMPONENT_CODE: "/*__FRONTMCP_COMPONENT_CODE__*/",
|
|
1437
|
-
/** Placeholder for data injection */
|
|
1438
|
-
DATA_INJECTION: "/*__FRONTMCP_DATA_INJECTION__*/",
|
|
1439
|
-
/** Placeholder for custom components */
|
|
1440
|
-
CUSTOM_COMPONENTS: "/*__FRONTMCP_CUSTOM_COMPONENTS__*/"
|
|
1441
|
-
};
|
|
1442
|
-
var runtimeCache = /* @__PURE__ */ new Map();
|
|
1443
|
-
var DEFAULT_CACHE_CONFIG = {
|
|
1444
|
-
maxEntries: 10,
|
|
1445
|
-
ttl: 0
|
|
1446
|
-
// Forever by default
|
|
1447
|
-
};
|
|
1448
|
-
function generateCacheKey(options) {
|
|
1449
|
-
const securityKey = options.contentSecurity ? [
|
|
1450
|
-
options.contentSecurity.allowUnsafeLinks ? "unsafeLinks" : "",
|
|
1451
|
-
options.contentSecurity.allowInlineScripts ? "unsafeScripts" : "",
|
|
1452
|
-
options.contentSecurity.bypassSanitization ? "bypass" : ""
|
|
1453
|
-
].filter(Boolean).join("+") || "secure" : "secure";
|
|
1454
|
-
return [
|
|
1455
|
-
options.cdnType,
|
|
1456
|
-
options.includeMarkdown ? "md" : "",
|
|
1457
|
-
options.includeMdx ? "mdx" : "",
|
|
1458
|
-
options.minify ? "min" : "",
|
|
1459
|
-
securityKey
|
|
1460
|
-
].filter(Boolean).join(":");
|
|
242
|
+
var runtimeCache = /* @__PURE__ */ new Map();
|
|
243
|
+
var DEFAULT_CACHE_CONFIG = {
|
|
244
|
+
maxEntries: 10,
|
|
245
|
+
ttl: 0
|
|
246
|
+
// Forever by default
|
|
247
|
+
};
|
|
248
|
+
function generateCacheKey(options) {
|
|
249
|
+
const securityKey = options.contentSecurity ? [
|
|
250
|
+
options.contentSecurity.allowUnsafeLinks ? "unsafeLinks" : "",
|
|
251
|
+
options.contentSecurity.allowInlineScripts ? "unsafeScripts" : "",
|
|
252
|
+
options.contentSecurity.bypassSanitization ? "bypass" : ""
|
|
253
|
+
].filter(Boolean).join("+") || "secure" : "secure";
|
|
254
|
+
return [
|
|
255
|
+
options.cdnType,
|
|
256
|
+
options.includeMarkdown ? "md" : "",
|
|
257
|
+
options.includeMdx ? "mdx" : "",
|
|
258
|
+
options.minify ? "min" : "",
|
|
259
|
+
options.includeBridge ? "bridge" : "",
|
|
260
|
+
securityKey
|
|
261
|
+
].filter(Boolean).join(":");
|
|
1461
262
|
}
|
|
1462
263
|
function buildStoreRuntime() {
|
|
1463
264
|
return `
|
|
@@ -1499,6 +300,17 @@ function buildStoreRuntime() {
|
|
|
1499
300
|
context: state,
|
|
1500
301
|
setContext: function(ctx) {
|
|
1501
302
|
this.setState(ctx);
|
|
303
|
+
},
|
|
304
|
+
// Dynamic mode: update output and re-render
|
|
305
|
+
updateOutput: function(output) {
|
|
306
|
+
this.setState({ output: output, loading: false });
|
|
307
|
+
// Also update the global window variable for compatibility
|
|
308
|
+
window.__mcpToolOutput = output;
|
|
309
|
+
},
|
|
310
|
+
// Dynamic mode: update input and re-render
|
|
311
|
+
updateInput: function(input) {
|
|
312
|
+
this.setState({ input: input });
|
|
313
|
+
window.__mcpToolInput = input;
|
|
1502
314
|
}
|
|
1503
315
|
};
|
|
1504
316
|
|
|
@@ -1523,6 +335,32 @@ function buildStoreRuntime() {
|
|
|
1523
335
|
window.useContent = function() {
|
|
1524
336
|
return window.useFrontMCPStore().content;
|
|
1525
337
|
};
|
|
338
|
+
|
|
339
|
+
// Connect to MCP Bridge for platform data detection
|
|
340
|
+
function initFromBridge() {
|
|
341
|
+
// Check for data from mcpBridge (handles OpenAI, ext-apps, etc.)
|
|
342
|
+
if (window.mcpBridge && window.mcpBridge.toolOutput != null) {
|
|
343
|
+
window.__frontmcp.setState({
|
|
344
|
+
output: window.mcpBridge.toolOutput,
|
|
345
|
+
loading: false
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Subscribe to bridge updates via onToolResult
|
|
350
|
+
if (window.mcpBridge && window.mcpBridge.onToolResult) {
|
|
351
|
+
window.mcpBridge.onToolResult(function(result) {
|
|
352
|
+
window.__frontmcp.updateOutput(result);
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Initialize from bridge when ready
|
|
358
|
+
if (window.mcpBridge) {
|
|
359
|
+
initFromBridge();
|
|
360
|
+
} else {
|
|
361
|
+
// Wait for bridge to be ready
|
|
362
|
+
window.addEventListener('mcp:bridge-ready', initFromBridge);
|
|
363
|
+
}
|
|
1526
364
|
})();
|
|
1527
365
|
`;
|
|
1528
366
|
}
|
|
@@ -1742,71 +580,7 @@ function buildRenderersRuntime(options) {
|
|
|
1742
580
|
`;
|
|
1743
581
|
}
|
|
1744
582
|
function buildUIComponentsRuntime() {
|
|
1745
|
-
return
|
|
1746
|
-
// UI Components (Vendor)
|
|
1747
|
-
(function() {
|
|
1748
|
-
window.Card = function(props) {
|
|
1749
|
-
var children = props.children;
|
|
1750
|
-
var title = props.title;
|
|
1751
|
-
var className = props.className || '';
|
|
1752
|
-
return React.createElement('div', {
|
|
1753
|
-
className: 'bg-white rounded-lg shadow border border-gray-200 overflow-hidden ' + className
|
|
1754
|
-
}, [
|
|
1755
|
-
title && React.createElement('div', {
|
|
1756
|
-
key: 'header',
|
|
1757
|
-
className: 'px-4 py-3 border-b border-gray-200 bg-gray-50'
|
|
1758
|
-
}, React.createElement('h3', { className: 'text-sm font-medium text-gray-900' }, title)),
|
|
1759
|
-
React.createElement('div', { key: 'body', className: 'p-4' }, children)
|
|
1760
|
-
]);
|
|
1761
|
-
};
|
|
1762
|
-
|
|
1763
|
-
window.Badge = function(props) {
|
|
1764
|
-
var children = props.children;
|
|
1765
|
-
var variant = props.variant || 'default';
|
|
1766
|
-
var variantClasses = {
|
|
1767
|
-
default: 'bg-gray-100 text-gray-800',
|
|
1768
|
-
success: 'bg-green-100 text-green-800',
|
|
1769
|
-
warning: 'bg-yellow-100 text-yellow-800',
|
|
1770
|
-
error: 'bg-red-100 text-red-800',
|
|
1771
|
-
info: 'bg-blue-100 text-blue-800'
|
|
1772
|
-
};
|
|
1773
|
-
return React.createElement('span', {
|
|
1774
|
-
className: 'inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ' + (variantClasses[variant] || variantClasses.default)
|
|
1775
|
-
}, children);
|
|
1776
|
-
};
|
|
1777
|
-
|
|
1778
|
-
window.Button = function(props) {
|
|
1779
|
-
var children = props.children;
|
|
1780
|
-
var variant = props.variant || 'primary';
|
|
1781
|
-
var onClick = props.onClick;
|
|
1782
|
-
var disabled = props.disabled;
|
|
1783
|
-
var variantClasses = {
|
|
1784
|
-
primary: 'bg-blue-600 text-white hover:bg-blue-700',
|
|
1785
|
-
secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200',
|
|
1786
|
-
outline: 'border border-gray-300 text-gray-700 hover:bg-gray-50',
|
|
1787
|
-
danger: 'bg-red-600 text-white hover:bg-red-700'
|
|
1788
|
-
};
|
|
1789
|
-
return React.createElement('button', {
|
|
1790
|
-
className: 'px-4 py-2 rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 ' +
|
|
1791
|
-
(disabled ? 'opacity-50 cursor-not-allowed ' : '') +
|
|
1792
|
-
(variantClasses[variant] || variantClasses.primary),
|
|
1793
|
-
onClick: onClick,
|
|
1794
|
-
disabled: disabled
|
|
1795
|
-
}, children);
|
|
1796
|
-
};
|
|
1797
|
-
|
|
1798
|
-
// Export to namespace
|
|
1799
|
-
window.frontmcp_ui_namespaceObject = Object.assign({}, window.React || {}, {
|
|
1800
|
-
useToolOutput: window.useToolOutput,
|
|
1801
|
-
useToolInput: window.useToolInput,
|
|
1802
|
-
useMcpBridgeContext: function() { return window.__frontmcp.context; },
|
|
1803
|
-
useCallTool: function() { return function() { return Promise.resolve(null); }; },
|
|
1804
|
-
Card: window.Card,
|
|
1805
|
-
Badge: window.Badge,
|
|
1806
|
-
Button: window.Button
|
|
1807
|
-
});
|
|
1808
|
-
})();
|
|
1809
|
-
`;
|
|
583
|
+
return buildBrowserUIComponents();
|
|
1810
584
|
}
|
|
1811
585
|
function buildUniversalAppRuntime() {
|
|
1812
586
|
return `
|
|
@@ -1931,7 +705,11 @@ function getCachedRuntime(options, config = {}) {
|
|
|
1931
705
|
}
|
|
1932
706
|
runtimeCache.delete(cacheKey);
|
|
1933
707
|
}
|
|
1934
|
-
const vendorParts = [
|
|
708
|
+
const vendorParts = [];
|
|
709
|
+
if (options.includeBridge) {
|
|
710
|
+
vendorParts.push(getMCPBridgeScript());
|
|
711
|
+
}
|
|
712
|
+
vendorParts.push(buildStoreRuntime(), buildRequireShim());
|
|
1935
713
|
if (options.cdnType === "umd" || options.includeMarkdown) {
|
|
1936
714
|
vendorParts.push(buildInlineMarkdownParser(options));
|
|
1937
715
|
}
|
|
@@ -1970,12 +748,51 @@ function buildAppTemplate() {
|
|
|
1970
748
|
return [buildCustomComponentsWrapper(), buildComponentWrapper(), buildDataInjectionWrapper()].join("\n");
|
|
1971
749
|
}
|
|
1972
750
|
function minifyScript(script) {
|
|
1973
|
-
return script.replace(
|
|
751
|
+
return script.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/[^\n]*$/gm, "").replace(/\n\s*\n/g, "\n").replace(/^\s+/gm, "").trim();
|
|
1974
752
|
}
|
|
1975
753
|
function buildAppScript(appTemplate, componentCode, dataInjection, customComponents = "") {
|
|
1976
754
|
return appTemplate.replace(RUNTIME_PLACEHOLDERS.CUSTOM_COMPONENTS, customComponents || "// No custom components").replace(RUNTIME_PLACEHOLDERS.COMPONENT_CODE, componentCode || "// No component code").replace(RUNTIME_PLACEHOLDERS.DATA_INJECTION, dataInjection);
|
|
1977
755
|
}
|
|
1978
|
-
|
|
756
|
+
var DEFAULT_OUTPUT_PLACEHOLDER = "__FRONTMCP_OUTPUT_PLACEHOLDER__";
|
|
757
|
+
var DEFAULT_INPUT_PLACEHOLDER = "__FRONTMCP_INPUT_PLACEHOLDER__";
|
|
758
|
+
function buildDataInjectionCode(toolName, input, output, structuredContent, contentType, source, hasComponent, options) {
|
|
759
|
+
const buildMode = options?.buildMode ?? "static";
|
|
760
|
+
const cdnType = options?.cdnType ?? "esm";
|
|
761
|
+
switch (buildMode) {
|
|
762
|
+
case "dynamic":
|
|
763
|
+
return buildDynamicDataInjectionCode(
|
|
764
|
+
toolName,
|
|
765
|
+
input,
|
|
766
|
+
output,
|
|
767
|
+
structuredContent,
|
|
768
|
+
contentType,
|
|
769
|
+
source,
|
|
770
|
+
hasComponent,
|
|
771
|
+
cdnType,
|
|
772
|
+
options?.dynamicOptions
|
|
773
|
+
);
|
|
774
|
+
case "hybrid":
|
|
775
|
+
return buildHybridDataInjectionCode(
|
|
776
|
+
toolName,
|
|
777
|
+
structuredContent,
|
|
778
|
+
contentType,
|
|
779
|
+
source,
|
|
780
|
+
hasComponent,
|
|
781
|
+
options?.hybridOptions
|
|
782
|
+
);
|
|
783
|
+
default:
|
|
784
|
+
return buildStaticDataInjectionCode(
|
|
785
|
+
toolName,
|
|
786
|
+
input,
|
|
787
|
+
output,
|
|
788
|
+
structuredContent,
|
|
789
|
+
contentType,
|
|
790
|
+
source,
|
|
791
|
+
hasComponent
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
function buildStaticDataInjectionCode(toolName, input, output, structuredContent, contentType, source, hasComponent) {
|
|
1979
796
|
const safeJson = (value) => {
|
|
1980
797
|
try {
|
|
1981
798
|
return JSON.stringify(value);
|
|
@@ -1985,6 +802,7 @@ function buildDataInjectionCode(toolName, input, output, structuredContent, cont
|
|
|
1985
802
|
};
|
|
1986
803
|
if (hasComponent) {
|
|
1987
804
|
return `
|
|
805
|
+
// Static Mode - Data baked at build time
|
|
1988
806
|
window.__frontmcp.setState({
|
|
1989
807
|
toolName: ${safeJson(toolName)},
|
|
1990
808
|
input: ${safeJson(input ?? null)},
|
|
@@ -1999,6 +817,7 @@ function buildDataInjectionCode(toolName, input, output, structuredContent, cont
|
|
|
1999
817
|
});`;
|
|
2000
818
|
}
|
|
2001
819
|
return `
|
|
820
|
+
// Static Mode - Data baked at build time
|
|
2002
821
|
window.__frontmcp.setState({
|
|
2003
822
|
toolName: ${safeJson(toolName)},
|
|
2004
823
|
input: ${safeJson(input ?? null)},
|
|
@@ -2012,51 +831,601 @@ function buildDataInjectionCode(toolName, input, output, structuredContent, cont
|
|
|
2012
831
|
error: null
|
|
2013
832
|
});`;
|
|
2014
833
|
}
|
|
2015
|
-
function
|
|
834
|
+
function buildDynamicDataInjectionCode(toolName, input, output, structuredContent, contentType, source, hasComponent, cdnType, dynamicOptions) {
|
|
835
|
+
if (cdnType === "umd") {
|
|
836
|
+
return buildDynamicWithPlaceholdersCode(
|
|
837
|
+
toolName,
|
|
838
|
+
structuredContent,
|
|
839
|
+
contentType,
|
|
840
|
+
source,
|
|
841
|
+
hasComponent,
|
|
842
|
+
dynamicOptions
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
return buildDynamicWithSubscriptionCode(
|
|
846
|
+
toolName,
|
|
847
|
+
input,
|
|
848
|
+
output,
|
|
849
|
+
structuredContent,
|
|
850
|
+
contentType,
|
|
851
|
+
source,
|
|
852
|
+
hasComponent,
|
|
853
|
+
dynamicOptions
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
function buildDynamicWithPlaceholdersCode(toolName, structuredContent, contentType, source, hasComponent, dynamicOptions) {
|
|
857
|
+
const safeJson = (value) => {
|
|
858
|
+
try {
|
|
859
|
+
return JSON.stringify(value);
|
|
860
|
+
} catch {
|
|
861
|
+
return "null";
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
const outputPlaceholder = DEFAULT_OUTPUT_PLACEHOLDER;
|
|
865
|
+
const inputPlaceholder = DEFAULT_INPUT_PLACEHOLDER;
|
|
866
|
+
const includeInitialData = dynamicOptions?.includeInitialData ?? true;
|
|
867
|
+
const contentBlock = hasComponent ? `content: { type: 'react', source: window.__frontmcp_component }` : `content: { type: ${safeJson(contentType)}, source: ${safeJson(source)} }`;
|
|
2016
868
|
return `
|
|
2017
|
-
//
|
|
2018
|
-
var
|
|
2019
|
-
var
|
|
869
|
+
// Dynamic Mode - Placeholder-based for non-OpenAI platforms
|
|
870
|
+
var __outputRaw = "${outputPlaceholder}";
|
|
871
|
+
var __inputRaw = "${inputPlaceholder}";
|
|
872
|
+
var __output = null;
|
|
873
|
+
var __input = null;
|
|
874
|
+
var __error = null;
|
|
875
|
+
var __outputNotReplaced = false;
|
|
876
|
+
var __includeInitialData = ${includeInitialData};
|
|
2020
877
|
|
|
2021
|
-
//
|
|
2022
|
-
${
|
|
878
|
+
// Parse output placeholder
|
|
879
|
+
if (typeof __outputRaw === 'string' && __outputRaw !== "${outputPlaceholder}") {
|
|
880
|
+
try { __output = JSON.parse(__outputRaw); } catch (e) {
|
|
881
|
+
console.warn('[FrontMCP] Failed to parse output:', e);
|
|
882
|
+
__error = 'Failed to parse output data';
|
|
883
|
+
}
|
|
884
|
+
} else if (__outputRaw === "${outputPlaceholder}") {
|
|
885
|
+
__outputNotReplaced = true;
|
|
886
|
+
}
|
|
2023
887
|
|
|
2024
|
-
//
|
|
2025
|
-
|
|
2026
|
-
}
|
|
888
|
+
// Parse input placeholder
|
|
889
|
+
if (typeof __inputRaw === 'string' && __inputRaw !== "${inputPlaceholder}") {
|
|
890
|
+
try { __input = JSON.parse(__inputRaw); } catch (e) { console.warn('[FrontMCP] Failed to parse input:', e); }
|
|
891
|
+
}
|
|
2027
892
|
|
|
2028
|
-
//
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
if (esbuildTransform !== null) {
|
|
2032
|
-
return esbuildTransform;
|
|
893
|
+
// Handle placeholder not replaced - show error if expecting initial data
|
|
894
|
+
if (__outputNotReplaced && __includeInitialData) {
|
|
895
|
+
__error = 'No data provided. The output placeholder was not replaced.';
|
|
2033
896
|
}
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
897
|
+
|
|
898
|
+
window.__frontmcp.setState({
|
|
899
|
+
toolName: ${safeJson(toolName)},
|
|
900
|
+
input: __input,
|
|
901
|
+
output: __output,
|
|
902
|
+
structuredContent: ${safeJson(structuredContent ?? null)},
|
|
903
|
+
${contentBlock},
|
|
904
|
+
loading: !__includeInitialData && __output === null && !__error,
|
|
905
|
+
error: __error
|
|
906
|
+
});`;
|
|
907
|
+
}
|
|
908
|
+
function buildDynamicWithSubscriptionCode(toolName, input, output, structuredContent, contentType, source, hasComponent, dynamicOptions) {
|
|
909
|
+
const safeJson = (value) => {
|
|
2039
910
|
try {
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
911
|
+
return JSON.stringify(value);
|
|
912
|
+
} catch {
|
|
913
|
+
return "null";
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
const includeInitialData = dynamicOptions?.includeInitialData ?? true;
|
|
917
|
+
const subscribeToUpdates = dynamicOptions?.subscribeToUpdates ?? true;
|
|
918
|
+
const contentBlock = hasComponent ? `content: { type: 'react', source: window.__frontmcp_component }` : `content: { type: ${safeJson(contentType)}, source: ${safeJson(source)} }`;
|
|
919
|
+
const initialState = includeInitialData ? `{
|
|
920
|
+
toolName: ${safeJson(toolName)},
|
|
921
|
+
input: ${safeJson(input ?? null)},
|
|
922
|
+
output: ${safeJson(output ?? null)},
|
|
923
|
+
structuredContent: ${safeJson(structuredContent ?? null)},
|
|
924
|
+
${contentBlock},
|
|
925
|
+
loading: false,
|
|
926
|
+
error: null
|
|
927
|
+
}` : `{
|
|
928
|
+
toolName: ${safeJson(toolName)},
|
|
929
|
+
input: ${safeJson(input ?? null)},
|
|
930
|
+
output: null,
|
|
931
|
+
structuredContent: ${safeJson(structuredContent ?? null)},
|
|
932
|
+
${contentBlock},
|
|
933
|
+
loading: true,
|
|
934
|
+
error: null
|
|
935
|
+
}`;
|
|
936
|
+
const subscriptionBlock = subscribeToUpdates ? `
|
|
937
|
+
// Subscribe to platform tool result events
|
|
938
|
+
(function() {
|
|
939
|
+
function subscribeToUpdates() {
|
|
940
|
+
if (window.openai && window.openai.canvas && window.openai.canvas.onToolResult) {
|
|
941
|
+
window.openai.canvas.onToolResult(function(result) {
|
|
942
|
+
window.__frontmcp.updateOutput(result);
|
|
943
|
+
window.dispatchEvent(new CustomEvent('frontmcp:toolResult', { detail: result }));
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
if (document.readyState === 'loading') {
|
|
948
|
+
document.addEventListener('DOMContentLoaded', subscribeToUpdates);
|
|
949
|
+
} else {
|
|
950
|
+
subscribeToUpdates();
|
|
951
|
+
}
|
|
952
|
+
})();` : "";
|
|
953
|
+
return `
|
|
954
|
+
// Dynamic Mode - OpenAI Subscription
|
|
955
|
+
window.__frontmcp.setState(${initialState});
|
|
956
|
+
${subscriptionBlock}`;
|
|
957
|
+
}
|
|
958
|
+
function buildHybridDataInjectionCode(toolName, structuredContent, contentType, source, hasComponent, hybridOptions) {
|
|
959
|
+
const safeJson = (value) => {
|
|
960
|
+
try {
|
|
961
|
+
return JSON.stringify(value);
|
|
962
|
+
} catch {
|
|
963
|
+
return "null";
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
const outputPlaceholder = hybridOptions?.placeholder ?? DEFAULT_OUTPUT_PLACEHOLDER;
|
|
967
|
+
const inputPlaceholder = hybridOptions?.inputPlaceholder ?? DEFAULT_INPUT_PLACEHOLDER;
|
|
968
|
+
const contentBlock = hasComponent ? `content: { type: 'react', source: window.__frontmcp_component }` : `content: { type: ${safeJson(contentType)}, source: ${safeJson(source)} }`;
|
|
969
|
+
return `
|
|
970
|
+
// Hybrid Mode - Placeholders replaced at runtime
|
|
971
|
+
var __outputRaw = "${outputPlaceholder}";
|
|
972
|
+
var __inputRaw = "${inputPlaceholder}";
|
|
973
|
+
var __output = null;
|
|
974
|
+
var __input = null;
|
|
975
|
+
var __error = null;
|
|
976
|
+
var __outputNotReplaced = false;
|
|
977
|
+
|
|
978
|
+
// Parse output placeholder
|
|
979
|
+
if (typeof __outputRaw === 'string' && __outputRaw !== "${outputPlaceholder}") {
|
|
980
|
+
try { __output = JSON.parse(__outputRaw); } catch (e) {
|
|
981
|
+
console.warn('[FrontMCP] Failed to parse output:', e);
|
|
982
|
+
__error = 'Failed to parse output data';
|
|
983
|
+
}
|
|
984
|
+
} else if (__outputRaw === "${outputPlaceholder}") {
|
|
985
|
+
// Placeholder not replaced - no data was injected
|
|
986
|
+
__outputNotReplaced = true;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// Parse input placeholder
|
|
990
|
+
if (typeof __inputRaw === 'string' && __inputRaw !== "${inputPlaceholder}") {
|
|
991
|
+
try { __input = JSON.parse(__inputRaw); } catch (e) { console.warn('[FrontMCP] Failed to parse input:', e); }
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// Set error if output placeholder was not replaced (no data provided)
|
|
995
|
+
if (__outputNotReplaced) {
|
|
996
|
+
__error = 'No data provided. The output placeholder was not replaced.';
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
window.__frontmcp.setState({
|
|
1000
|
+
toolName: ${safeJson(toolName)},
|
|
1001
|
+
input: __input,
|
|
1002
|
+
output: __output,
|
|
1003
|
+
structuredContent: ${safeJson(structuredContent ?? null)},
|
|
1004
|
+
${contentBlock},
|
|
1005
|
+
loading: false,
|
|
1006
|
+
error: __error
|
|
1007
|
+
});`;
|
|
1008
|
+
}
|
|
1009
|
+
function buildComponentCode(transpiledCode) {
|
|
1010
|
+
return `
|
|
1011
|
+
// CommonJS module shim
|
|
1012
|
+
var module = { exports: {} };
|
|
1013
|
+
var exports = module.exports;
|
|
1014
|
+
|
|
1015
|
+
// Execute transpiled component
|
|
1016
|
+
${transpiledCode}
|
|
1017
|
+
|
|
1018
|
+
// Capture component
|
|
1019
|
+
window.__frontmcp_component = module.exports.default || module.exports;`;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// libs/ui/src/bundler/bundler.ts
|
|
1023
|
+
import {
|
|
1024
|
+
buildCDNScriptTag,
|
|
1025
|
+
CLOUDFLARE_CDN,
|
|
1026
|
+
buildUIComponentsRuntime as buildFallbackUIComponents
|
|
1027
|
+
} from "@frontmcp/uipack/build";
|
|
1028
|
+
|
|
1029
|
+
// libs/ui/src/bundler/browser-components.ts
|
|
1030
|
+
import * as path from "path";
|
|
1031
|
+
var cachedBrowserComponents = null;
|
|
1032
|
+
var buildingPromise = null;
|
|
1033
|
+
function getComponentsEntrySource() {
|
|
1034
|
+
return `
|
|
1035
|
+
// Browser Components Entry Point
|
|
1036
|
+
// This gets transpiled by esbuild to create browser-compatible code
|
|
1037
|
+
|
|
1038
|
+
import {
|
|
1039
|
+
// Card styles
|
|
1040
|
+
CARD_VARIANTS,
|
|
1041
|
+
CARD_SIZES,
|
|
1042
|
+
// Button styles
|
|
1043
|
+
BUTTON_VARIANTS,
|
|
1044
|
+
BUTTON_SIZES,
|
|
1045
|
+
BUTTON_ICON_SIZES,
|
|
1046
|
+
BUTTON_BASE_CLASSES,
|
|
1047
|
+
LOADING_SPINNER,
|
|
1048
|
+
// Badge styles
|
|
1049
|
+
BADGE_VARIANTS,
|
|
1050
|
+
BADGE_SIZES,
|
|
1051
|
+
BADGE_DOT_SIZES,
|
|
1052
|
+
BADGE_DOT_VARIANTS,
|
|
1053
|
+
// Alert styles
|
|
1054
|
+
ALERT_VARIANTS,
|
|
1055
|
+
ALERT_BASE_CLASSES,
|
|
1056
|
+
ALERT_ICONS,
|
|
1057
|
+
CLOSE_ICON,
|
|
1058
|
+
// Utility
|
|
1059
|
+
cn,
|
|
1060
|
+
} from '@frontmcp/uipack/styles';
|
|
1061
|
+
|
|
1062
|
+
// Re-export for the IIFE
|
|
1063
|
+
export {
|
|
1064
|
+
CARD_VARIANTS,
|
|
1065
|
+
CARD_SIZES,
|
|
1066
|
+
BUTTON_VARIANTS,
|
|
1067
|
+
BUTTON_SIZES,
|
|
1068
|
+
BUTTON_ICON_SIZES,
|
|
1069
|
+
BUTTON_BASE_CLASSES,
|
|
1070
|
+
LOADING_SPINNER,
|
|
1071
|
+
BADGE_VARIANTS,
|
|
1072
|
+
BADGE_SIZES,
|
|
1073
|
+
BADGE_DOT_SIZES,
|
|
1074
|
+
BADGE_DOT_VARIANTS,
|
|
1075
|
+
ALERT_VARIANTS,
|
|
1076
|
+
ALERT_BASE_CLASSES,
|
|
1077
|
+
ALERT_ICONS,
|
|
1078
|
+
CLOSE_ICON,
|
|
1079
|
+
cn,
|
|
1080
|
+
};
|
|
1081
|
+
|
|
1082
|
+
// Card Component
|
|
1083
|
+
export function Card(props: any) {
|
|
1084
|
+
const {
|
|
1085
|
+
title,
|
|
1086
|
+
subtitle,
|
|
1087
|
+
headerActions,
|
|
1088
|
+
footer,
|
|
1089
|
+
variant = 'default',
|
|
1090
|
+
size = 'md',
|
|
1091
|
+
className,
|
|
1092
|
+
id,
|
|
1093
|
+
clickable,
|
|
1094
|
+
href,
|
|
1095
|
+
children,
|
|
1096
|
+
} = props;
|
|
1097
|
+
|
|
1098
|
+
const variantClasses = CARD_VARIANTS[variant] || CARD_VARIANTS.default;
|
|
1099
|
+
const sizeClasses = CARD_SIZES[size] || CARD_SIZES.md;
|
|
1100
|
+
const clickableClasses = clickable ? 'cursor-pointer hover:shadow-md transition-shadow' : '';
|
|
1101
|
+
const allClasses = cn(variantClasses, sizeClasses, clickableClasses, className);
|
|
1102
|
+
|
|
1103
|
+
const hasHeader = title || subtitle || headerActions;
|
|
1104
|
+
|
|
1105
|
+
const headerElement = hasHeader ? React.createElement('div', {
|
|
1106
|
+
className: 'flex items-start justify-between mb-4'
|
|
1107
|
+
}, [
|
|
1108
|
+
React.createElement('div', { key: 'titles' }, [
|
|
1109
|
+
title && React.createElement('h3', {
|
|
1110
|
+
key: 'title',
|
|
1111
|
+
className: 'text-lg font-semibold text-text-primary'
|
|
1112
|
+
}, title),
|
|
1113
|
+
subtitle && React.createElement('p', {
|
|
1114
|
+
key: 'subtitle',
|
|
1115
|
+
className: 'text-sm text-text-secondary mt-1'
|
|
1116
|
+
}, subtitle)
|
|
1117
|
+
]),
|
|
1118
|
+
headerActions && React.createElement('div', {
|
|
1119
|
+
key: 'actions',
|
|
1120
|
+
className: 'flex items-center gap-2'
|
|
1121
|
+
}, headerActions)
|
|
1122
|
+
]) : null;
|
|
1123
|
+
|
|
1124
|
+
const footerElement = footer ? React.createElement('div', {
|
|
1125
|
+
className: 'mt-4 pt-4 border-t border-divider'
|
|
1126
|
+
}, footer) : null;
|
|
1127
|
+
|
|
1128
|
+
const content = React.createElement(React.Fragment, null, headerElement, children, footerElement);
|
|
1129
|
+
|
|
1130
|
+
if (href) {
|
|
1131
|
+
return React.createElement('a', { href, className: allClasses, id }, content);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
return React.createElement('div', { className: allClasses, id }, content);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// Button Component
|
|
1138
|
+
export function Button(props: any) {
|
|
1139
|
+
const {
|
|
1140
|
+
variant = 'primary',
|
|
1141
|
+
size = 'md',
|
|
1142
|
+
disabled = false,
|
|
1143
|
+
loading = false,
|
|
1144
|
+
fullWidth = false,
|
|
1145
|
+
iconPosition = 'left',
|
|
1146
|
+
icon,
|
|
1147
|
+
iconOnly = false,
|
|
1148
|
+
type = 'button',
|
|
1149
|
+
className,
|
|
1150
|
+
onClick,
|
|
1151
|
+
children,
|
|
1152
|
+
} = props;
|
|
1153
|
+
|
|
1154
|
+
const variantClasses = BUTTON_VARIANTS[variant] || BUTTON_VARIANTS.primary;
|
|
1155
|
+
const sizeClasses = iconOnly
|
|
1156
|
+
? (BUTTON_ICON_SIZES[size] || BUTTON_ICON_SIZES.md)
|
|
1157
|
+
: (BUTTON_SIZES[size] || BUTTON_SIZES.md);
|
|
1158
|
+
|
|
1159
|
+
const disabledClasses = (disabled || loading) ? 'opacity-50 cursor-not-allowed' : '';
|
|
1160
|
+
const widthClasses = fullWidth ? 'w-full' : '';
|
|
1161
|
+
|
|
1162
|
+
const allClasses = cn(BUTTON_BASE_CLASSES, variantClasses, sizeClasses, disabledClasses, widthClasses, className);
|
|
1163
|
+
|
|
1164
|
+
const iconElement = icon ? React.createElement('span', {
|
|
1165
|
+
className: iconPosition === 'left' ? 'mr-2' : 'ml-2'
|
|
1166
|
+
}, icon) : null;
|
|
1167
|
+
|
|
1168
|
+
const loadingSpinner = loading ? React.createElement('span', {
|
|
1169
|
+
className: 'mr-2',
|
|
1170
|
+
dangerouslySetInnerHTML: { __html: LOADING_SPINNER }
|
|
1171
|
+
}) : null;
|
|
1172
|
+
|
|
1173
|
+
return React.createElement('button', {
|
|
1174
|
+
type,
|
|
1175
|
+
className: allClasses,
|
|
1176
|
+
disabled: disabled || loading,
|
|
1177
|
+
onClick
|
|
1178
|
+
},
|
|
1179
|
+
loadingSpinner,
|
|
1180
|
+
!loading && icon && iconPosition === 'left' ? iconElement : null,
|
|
1181
|
+
!iconOnly ? children : null,
|
|
1182
|
+
!loading && icon && iconPosition === 'right' ? iconElement : null
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
// Badge Component
|
|
1187
|
+
export function Badge(props: any) {
|
|
1188
|
+
const {
|
|
1189
|
+
variant = 'default',
|
|
1190
|
+
size = 'md',
|
|
1191
|
+
pill = false,
|
|
1192
|
+
icon,
|
|
1193
|
+
dot = false,
|
|
1194
|
+
className,
|
|
1195
|
+
removable = false,
|
|
1196
|
+
onRemove,
|
|
1197
|
+
children,
|
|
1198
|
+
} = props;
|
|
1199
|
+
|
|
1200
|
+
// Handle dot badge
|
|
1201
|
+
if (dot) {
|
|
1202
|
+
const dotSizeClasses = BADGE_DOT_SIZES[size] || BADGE_DOT_SIZES.md;
|
|
1203
|
+
const dotVariantClasses = BADGE_DOT_VARIANTS[variant] || BADGE_DOT_VARIANTS.default;
|
|
1204
|
+
const dotClasses = cn('inline-block rounded-full', dotSizeClasses, dotVariantClasses, className);
|
|
1205
|
+
const label = typeof children === 'string' ? children : undefined;
|
|
1206
|
+
return React.createElement('span', {
|
|
1207
|
+
className: dotClasses,
|
|
1208
|
+
'aria-label': label,
|
|
1209
|
+
title: label
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
const variantClasses = BADGE_VARIANTS[variant] || BADGE_VARIANTS.default;
|
|
1214
|
+
const sizeClasses = BADGE_SIZES[size] || BADGE_SIZES.md;
|
|
1215
|
+
|
|
1216
|
+
const baseClasses = cn(
|
|
1217
|
+
'inline-flex items-center font-medium',
|
|
1218
|
+
pill ? 'rounded-full' : 'rounded-md',
|
|
1219
|
+
variantClasses,
|
|
1220
|
+
sizeClasses,
|
|
1221
|
+
className
|
|
1222
|
+
);
|
|
1223
|
+
|
|
1224
|
+
const closeButton = removable ? React.createElement('button', {
|
|
1225
|
+
type: 'button',
|
|
1226
|
+
className: 'ml-1.5 -mr-1 hover:opacity-70 transition-opacity',
|
|
1227
|
+
'aria-label': 'Remove',
|
|
1228
|
+
onClick: onRemove
|
|
1229
|
+
}, React.createElement('svg', {
|
|
1230
|
+
className: 'w-3 h-3',
|
|
1231
|
+
fill: 'none',
|
|
1232
|
+
stroke: 'currentColor',
|
|
1233
|
+
viewBox: '0 0 24 24'
|
|
1234
|
+
}, React.createElement('path', {
|
|
1235
|
+
strokeLinecap: 'round',
|
|
1236
|
+
strokeLinejoin: 'round',
|
|
1237
|
+
strokeWidth: '2',
|
|
1238
|
+
d: 'M6 18L18 6M6 6l12 12'
|
|
1239
|
+
}))) : null;
|
|
1240
|
+
|
|
1241
|
+
return React.createElement('span', { className: baseClasses },
|
|
1242
|
+
icon ? React.createElement('span', { className: 'mr-1' }, icon) : null,
|
|
1243
|
+
children,
|
|
1244
|
+
closeButton
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
// Alert Component
|
|
1249
|
+
export function Alert(props: any) {
|
|
1250
|
+
const {
|
|
1251
|
+
variant = 'info',
|
|
1252
|
+
title,
|
|
1253
|
+
icon,
|
|
1254
|
+
showIcon = true,
|
|
1255
|
+
dismissible = false,
|
|
1256
|
+
onDismiss,
|
|
1257
|
+
className,
|
|
1258
|
+
children,
|
|
1259
|
+
} = props;
|
|
1260
|
+
|
|
1261
|
+
const variantStyles = ALERT_VARIANTS[variant] || ALERT_VARIANTS.info;
|
|
1262
|
+
const allClasses = cn(ALERT_BASE_CLASSES, variantStyles.container, className);
|
|
1263
|
+
|
|
1264
|
+
const iconContent = icon || (showIcon ? React.createElement('span', {
|
|
1265
|
+
className: cn('flex-shrink-0', variantStyles.icon),
|
|
1266
|
+
dangerouslySetInnerHTML: { __html: ALERT_ICONS[variant] || ALERT_ICONS.info }
|
|
1267
|
+
}) : null);
|
|
1268
|
+
|
|
1269
|
+
const dismissButton = dismissible ? React.createElement('button', {
|
|
1270
|
+
type: 'button',
|
|
1271
|
+
className: 'flex-shrink-0 ml-3 hover:opacity-70 transition-opacity',
|
|
1272
|
+
'aria-label': 'Dismiss',
|
|
1273
|
+
onClick: onDismiss
|
|
1274
|
+
}, React.createElement('span', {
|
|
1275
|
+
dangerouslySetInnerHTML: { __html: CLOSE_ICON }
|
|
1276
|
+
})) : null;
|
|
1277
|
+
|
|
1278
|
+
return React.createElement('div', { className: allClasses, role: 'alert' },
|
|
1279
|
+
React.createElement('div', { className: 'flex' },
|
|
1280
|
+
iconContent ? React.createElement('div', { className: 'flex-shrink-0 mr-3' }, iconContent) : null,
|
|
1281
|
+
React.createElement('div', { className: 'flex-1' },
|
|
1282
|
+
title ? React.createElement('h4', { className: 'font-semibold mb-1' }, title) : null,
|
|
1283
|
+
React.createElement('div', { className: 'text-sm' }, children)
|
|
1284
|
+
),
|
|
1285
|
+
dismissButton
|
|
1286
|
+
)
|
|
1287
|
+
);
|
|
1288
|
+
}
|
|
1289
|
+
`;
|
|
1290
|
+
}
|
|
1291
|
+
function getBrowserRuntimeWrapper() {
|
|
1292
|
+
return `
|
|
1293
|
+
// Assign components to window for require() shim
|
|
1294
|
+
window.Card = Card;
|
|
1295
|
+
window.Button = Button;
|
|
1296
|
+
window.Badge = Badge;
|
|
1297
|
+
window.Alert = Alert;
|
|
1298
|
+
|
|
1299
|
+
// Build the namespace object for @frontmcp/ui/react imports
|
|
1300
|
+
window.frontmcp_ui_namespaceObject = Object.assign({}, window.React || {}, {
|
|
1301
|
+
// Hooks
|
|
1302
|
+
useToolOutput: window.useToolOutput,
|
|
1303
|
+
useToolInput: window.useToolInput,
|
|
1304
|
+
useMcpBridgeContext: function() { return window.__frontmcp.context; },
|
|
1305
|
+
useMcpBridge: function() { return window.__frontmcp.context; },
|
|
1306
|
+
useCallTool: function() {
|
|
1307
|
+
return function(name, args) {
|
|
1308
|
+
if (window.__frontmcp.context && window.__frontmcp.context.callTool) {
|
|
1309
|
+
return window.__frontmcp.context.callTool(name, args);
|
|
1310
|
+
}
|
|
1311
|
+
console.warn('[FrontMCP] callTool not available');
|
|
1312
|
+
return Promise.resolve(null);
|
|
1313
|
+
};
|
|
1314
|
+
},
|
|
1315
|
+
useTheme: function() { return window.__frontmcp.theme || 'light'; },
|
|
1316
|
+
useDisplayMode: function() { return window.__frontmcp.displayMode || 'embedded'; },
|
|
1317
|
+
useHostContext: function() { return window.__frontmcp.hostContext || {}; },
|
|
1318
|
+
useCapability: function(cap) { return window.__frontmcp.capabilities && window.__frontmcp.capabilities[cap] || false; },
|
|
1319
|
+
useStructuredContent: function() { return window.__frontmcp.getState().structuredContent; },
|
|
1320
|
+
useToolCalls: function() { return []; },
|
|
1321
|
+
useSendMessage: function() { return function() { return Promise.resolve(); }; },
|
|
1322
|
+
useOpenLink: function() { return function() {}; },
|
|
1323
|
+
|
|
1324
|
+
// Components
|
|
1325
|
+
Card: window.Card,
|
|
1326
|
+
Badge: window.Badge,
|
|
1327
|
+
Button: window.Button,
|
|
1328
|
+
Alert: window.Alert,
|
|
1329
|
+
|
|
1330
|
+
// Re-export React for convenience
|
|
1331
|
+
createElement: React.createElement,
|
|
1332
|
+
Fragment: React.Fragment,
|
|
1333
|
+
useState: React.useState,
|
|
1334
|
+
useEffect: React.useEffect,
|
|
1335
|
+
useCallback: React.useCallback,
|
|
1336
|
+
useMemo: React.useMemo,
|
|
1337
|
+
useRef: React.useRef,
|
|
1338
|
+
useContext: React.useContext
|
|
1339
|
+
});
|
|
1340
|
+
`;
|
|
1341
|
+
}
|
|
1342
|
+
async function buildWithEsbuild() {
|
|
1343
|
+
try {
|
|
1344
|
+
const esbuild = await import("esbuild");
|
|
1345
|
+
const stylesPath = __require.resolve("@frontmcp/uipack/styles");
|
|
1346
|
+
const entrySource = getComponentsEntrySource();
|
|
1347
|
+
const result = await esbuild.build({
|
|
1348
|
+
stdin: {
|
|
1349
|
+
contents: entrySource,
|
|
1350
|
+
loader: "tsx",
|
|
1351
|
+
resolveDir: path.dirname(stylesPath)
|
|
1352
|
+
},
|
|
1353
|
+
bundle: true,
|
|
1354
|
+
format: "iife",
|
|
1355
|
+
globalName: "__frontmcp_components",
|
|
1356
|
+
target: "es2020",
|
|
1357
|
+
minify: false,
|
|
1358
|
+
write: false,
|
|
1359
|
+
external: ["react", "react-dom"],
|
|
1360
|
+
define: {
|
|
1361
|
+
React: "window.React"
|
|
1362
|
+
},
|
|
1363
|
+
platform: "browser"
|
|
1364
|
+
});
|
|
1365
|
+
if (result.outputFiles && result.outputFiles.length > 0) {
|
|
1366
|
+
let code = result.outputFiles[0].text;
|
|
1367
|
+
code += "\n" + getBrowserRuntimeWrapper();
|
|
1368
|
+
return code;
|
|
1369
|
+
}
|
|
1370
|
+
throw new Error("No output from esbuild");
|
|
1371
|
+
} catch (error) {
|
|
1372
|
+
console.warn(
|
|
1373
|
+
`[FrontMCP] esbuild bundle failed, falling back to manual components: ${error instanceof Error ? error.message : String(error)}`
|
|
1374
|
+
);
|
|
1375
|
+
throw error;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
async function getBrowserComponents() {
|
|
1379
|
+
if (cachedBrowserComponents !== null) {
|
|
1380
|
+
return cachedBrowserComponents;
|
|
1381
|
+
}
|
|
1382
|
+
if (buildingPromise !== null) {
|
|
1383
|
+
return buildingPromise;
|
|
1384
|
+
}
|
|
1385
|
+
buildingPromise = buildWithEsbuild().then((code) => {
|
|
1386
|
+
cachedBrowserComponents = code;
|
|
1387
|
+
buildingPromise = null;
|
|
1388
|
+
return code;
|
|
1389
|
+
}).catch((error) => {
|
|
1390
|
+
buildingPromise = null;
|
|
1391
|
+
throw error;
|
|
1392
|
+
});
|
|
1393
|
+
return buildingPromise;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// libs/ui/src/bundler/bundler.ts
|
|
1397
|
+
import { SecurityError, ExecutionError as ExecutionError2 } from "@frontmcp/uipack/bundler";
|
|
1398
|
+
var esbuildTransform = null;
|
|
1399
|
+
async function loadEsbuild() {
|
|
1400
|
+
if (esbuildTransform !== null) {
|
|
1401
|
+
return esbuildTransform;
|
|
1402
|
+
}
|
|
1403
|
+
try {
|
|
1404
|
+
const esbuild = await import("esbuild");
|
|
1405
|
+
esbuildTransform = esbuild.transform;
|
|
1406
|
+
return esbuildTransform;
|
|
1407
|
+
} catch {
|
|
1408
|
+
try {
|
|
1409
|
+
const swc = await import("@swc/core");
|
|
1410
|
+
esbuildTransform = async (source, options) => {
|
|
1411
|
+
const opts = options;
|
|
1412
|
+
const result = await swc.transform(source, {
|
|
1413
|
+
jsc: {
|
|
1414
|
+
parser: {
|
|
1415
|
+
syntax: "typescript",
|
|
1416
|
+
tsx: opts.loader === "tsx" || opts.loader === "jsx"
|
|
1417
|
+
},
|
|
1418
|
+
transform: {
|
|
1419
|
+
react: {
|
|
1420
|
+
runtime: "automatic",
|
|
1421
|
+
development: false
|
|
1422
|
+
}
|
|
1423
|
+
},
|
|
1424
|
+
target: "es2020",
|
|
1425
|
+
minify: opts.minify ? { compress: true, mangle: true } : void 0
|
|
1426
|
+
},
|
|
1427
|
+
module: {
|
|
1428
|
+
type: "commonjs"
|
|
2060
1429
|
},
|
|
2061
1430
|
sourceMaps: opts.sourcemap ? true : false
|
|
2062
1431
|
});
|
|
@@ -2282,10 +1651,19 @@ var InMemoryBundler = class {
|
|
|
2282
1651
|
security: opts.security,
|
|
2283
1652
|
skipCache: opts.skipCache
|
|
2284
1653
|
});
|
|
2285
|
-
const head = this.buildStaticHTMLHead({ externals: opts.externals, customCss: opts.customCss });
|
|
1654
|
+
const head = this.buildStaticHTMLHead({ externals: opts.externals, customCss: opts.customCss, theme: opts.theme });
|
|
2286
1655
|
const reactRuntime = this.buildReactRuntimeScripts(opts.externals, platform, cdnType);
|
|
2287
|
-
const frontmcpRuntime = this.buildFrontMCPRuntime();
|
|
2288
|
-
const dataScript = this.buildDataInjectionScript(
|
|
1656
|
+
const frontmcpRuntime = await this.buildFrontMCPRuntime();
|
|
1657
|
+
const dataScript = this.buildDataInjectionScript(
|
|
1658
|
+
opts.toolName,
|
|
1659
|
+
opts.input,
|
|
1660
|
+
opts.output,
|
|
1661
|
+
opts.structuredContent,
|
|
1662
|
+
opts.buildMode,
|
|
1663
|
+
cdnType,
|
|
1664
|
+
opts.dynamicOptions,
|
|
1665
|
+
opts.hybridOptions
|
|
1666
|
+
);
|
|
2289
1667
|
const componentScript = this.buildComponentRenderScript(bundleResult.code, opts.rootId, cdnType);
|
|
2290
1668
|
const html = this.assembleStaticHTML({
|
|
2291
1669
|
title: opts.title || `${opts.toolName} - Widget`,
|
|
@@ -2298,6 +1676,8 @@ var InMemoryBundler = class {
|
|
|
2298
1676
|
cdnType
|
|
2299
1677
|
});
|
|
2300
1678
|
const hash = hashContent(html);
|
|
1679
|
+
const dataPlaceholder = opts.buildMode === "hybrid" ? opts.hybridOptions?.placeholder ?? HYBRID_DATA_PLACEHOLDER : void 0;
|
|
1680
|
+
const inputPlaceholder = opts.buildMode === "hybrid" ? opts.hybridOptions?.inputPlaceholder ?? HYBRID_INPUT_PLACEHOLDER : void 0;
|
|
2301
1681
|
return {
|
|
2302
1682
|
html,
|
|
2303
1683
|
componentCode: bundleResult.code,
|
|
@@ -2309,9 +1689,240 @@ var InMemoryBundler = class {
|
|
|
2309
1689
|
size: html.length,
|
|
2310
1690
|
cached: bundleResult.cached,
|
|
2311
1691
|
sourceType: bundleResult.sourceType,
|
|
2312
|
-
targetPlatform: platform
|
|
1692
|
+
targetPlatform: platform,
|
|
1693
|
+
buildMode: opts.buildMode,
|
|
1694
|
+
dataPlaceholder,
|
|
1695
|
+
inputPlaceholder
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
/**
|
|
1699
|
+
* Bundle a component to static HTML for all target platforms at once.
|
|
1700
|
+
*
|
|
1701
|
+
* This method is optimized for efficiency:
|
|
1702
|
+
* - Transpiles the component source code only once
|
|
1703
|
+
* - Generates platform-specific HTML variations from the shared transpiled code
|
|
1704
|
+
* - Returns complete platform metadata ready for MCP responses
|
|
1705
|
+
*
|
|
1706
|
+
* @param options - Multi-platform build options
|
|
1707
|
+
* @returns Multi-platform build result with all platforms
|
|
1708
|
+
*
|
|
1709
|
+
* @example
|
|
1710
|
+
* ```typescript
|
|
1711
|
+
* const result = await bundler.bundleToStaticHTMLAll({
|
|
1712
|
+
* source: `
|
|
1713
|
+
* import { Card, useToolOutput } from '@frontmcp/ui/react';
|
|
1714
|
+
* export default function Weather() {
|
|
1715
|
+
* const output = useToolOutput();
|
|
1716
|
+
* return <Card title="Weather">{output?.temperature}°F</Card>;
|
|
1717
|
+
* }
|
|
1718
|
+
* `,
|
|
1719
|
+
* toolName: 'get_weather',
|
|
1720
|
+
* output: { temperature: 72 },
|
|
1721
|
+
* });
|
|
1722
|
+
*
|
|
1723
|
+
* // Access platform-specific results
|
|
1724
|
+
* const openaiHtml = result.platforms.openai.html;
|
|
1725
|
+
* const claudeHtml = result.platforms.claude.html;
|
|
1726
|
+
*
|
|
1727
|
+
* // Get metadata for MCP response
|
|
1728
|
+
* const openaiMeta = result.platforms.openai.meta;
|
|
1729
|
+
* ```
|
|
1730
|
+
*/
|
|
1731
|
+
async bundleToStaticHTMLAll(options) {
|
|
1732
|
+
const startTime = performance.now();
|
|
1733
|
+
const opts = this.mergeStaticHTMLOptions(options);
|
|
1734
|
+
const platforms = options.platforms ?? [...ALL_PLATFORMS];
|
|
1735
|
+
const transpileStart = performance.now();
|
|
1736
|
+
let transpiledCode = null;
|
|
1737
|
+
let bundleResult = null;
|
|
1738
|
+
const isUniversal = opts.universal;
|
|
1739
|
+
const rawContentType = options.contentType ?? "auto";
|
|
1740
|
+
const contentType = isUniversal ? rawContentType === "auto" ? detectContentType(options.source) : rawContentType : "react";
|
|
1741
|
+
if (contentType === "react" || !isUniversal) {
|
|
1742
|
+
bundleResult = await this.bundle({
|
|
1743
|
+
source: options.source,
|
|
1744
|
+
sourceType: opts.sourceType,
|
|
1745
|
+
format: "cjs",
|
|
1746
|
+
minify: opts.minify,
|
|
1747
|
+
sourceMaps: false,
|
|
1748
|
+
externals: ["react", "react-dom", "react/jsx-runtime", "@frontmcp/ui", "@frontmcp/ui/react"],
|
|
1749
|
+
security: opts.security,
|
|
1750
|
+
skipCache: opts.skipCache
|
|
1751
|
+
});
|
|
1752
|
+
transpiledCode = bundleResult.code;
|
|
1753
|
+
}
|
|
1754
|
+
const transpileTime = performance.now() - transpileStart;
|
|
1755
|
+
const generationStart = performance.now();
|
|
1756
|
+
const platformResults = {};
|
|
1757
|
+
for (const platform of platforms) {
|
|
1758
|
+
const platformResult = await this.buildForPlatform({
|
|
1759
|
+
options,
|
|
1760
|
+
opts,
|
|
1761
|
+
platform,
|
|
1762
|
+
transpiledCode,
|
|
1763
|
+
bundleResult,
|
|
1764
|
+
contentType,
|
|
1765
|
+
isUniversal
|
|
1766
|
+
});
|
|
1767
|
+
platformResults[platform] = platformResult;
|
|
1768
|
+
}
|
|
1769
|
+
const generationTime = performance.now() - generationStart;
|
|
1770
|
+
return {
|
|
1771
|
+
platforms: platformResults,
|
|
1772
|
+
sharedComponentCode: transpiledCode ?? "",
|
|
1773
|
+
metrics: {
|
|
1774
|
+
transpileTime,
|
|
1775
|
+
generationTime,
|
|
1776
|
+
totalTime: performance.now() - startTime
|
|
1777
|
+
},
|
|
1778
|
+
cached: bundleResult?.cached ?? false
|
|
2313
1779
|
};
|
|
2314
1780
|
}
|
|
1781
|
+
/**
|
|
1782
|
+
* Build for a specific platform with pre-transpiled code.
|
|
1783
|
+
* Internal helper for bundleToStaticHTMLAll.
|
|
1784
|
+
*/
|
|
1785
|
+
async buildForPlatform(params) {
|
|
1786
|
+
const { options, opts, platform, transpiledCode, bundleResult, contentType, isUniversal } = params;
|
|
1787
|
+
const cdnType = getCdnTypeForPlatform(platform);
|
|
1788
|
+
const buildStart = performance.now();
|
|
1789
|
+
let html;
|
|
1790
|
+
let componentCode;
|
|
1791
|
+
if (isUniversal) {
|
|
1792
|
+
const shouldIncludeBridge = opts.buildMode === "dynamic" || opts.buildMode === "hybrid";
|
|
1793
|
+
const cachedRuntime = getCachedRuntime({
|
|
1794
|
+
cdnType,
|
|
1795
|
+
includeMarkdown: opts.includeMarkdown || contentType === "markdown",
|
|
1796
|
+
includeMdx: opts.includeMdx || contentType === "mdx",
|
|
1797
|
+
minify: opts.minify,
|
|
1798
|
+
includeBridge: shouldIncludeBridge
|
|
1799
|
+
});
|
|
1800
|
+
const componentCodeStr = transpiledCode ? buildComponentCode(transpiledCode) : "";
|
|
1801
|
+
const dataInjectionStr = buildDataInjectionCode(
|
|
1802
|
+
opts.toolName,
|
|
1803
|
+
opts.input,
|
|
1804
|
+
opts.output,
|
|
1805
|
+
opts.structuredContent,
|
|
1806
|
+
contentType,
|
|
1807
|
+
transpiledCode ? null : options.source,
|
|
1808
|
+
transpiledCode !== null,
|
|
1809
|
+
{
|
|
1810
|
+
buildMode: opts.buildMode,
|
|
1811
|
+
cdnType,
|
|
1812
|
+
dynamicOptions: opts.dynamicOptions,
|
|
1813
|
+
hybridOptions: opts.hybridOptions
|
|
1814
|
+
}
|
|
1815
|
+
);
|
|
1816
|
+
const appScript = buildAppScript(
|
|
1817
|
+
cachedRuntime.appTemplate,
|
|
1818
|
+
componentCodeStr,
|
|
1819
|
+
dataInjectionStr,
|
|
1820
|
+
opts.customComponents ?? ""
|
|
1821
|
+
);
|
|
1822
|
+
const head = this.buildStaticHTMLHead({
|
|
1823
|
+
externals: opts.externals,
|
|
1824
|
+
customCss: opts.customCss,
|
|
1825
|
+
theme: opts.theme
|
|
1826
|
+
});
|
|
1827
|
+
const reactRuntime = this.buildReactRuntimeScripts(opts.externals, platform, cdnType);
|
|
1828
|
+
const renderScript = this.buildUniversalRenderScript(opts.rootId, cdnType);
|
|
1829
|
+
html = this.assembleUniversalStaticHTMLCached({
|
|
1830
|
+
title: opts.title || `${opts.toolName} - Widget`,
|
|
1831
|
+
head,
|
|
1832
|
+
reactRuntime,
|
|
1833
|
+
cdnImports: cachedRuntime.cdnImports,
|
|
1834
|
+
vendorScript: cachedRuntime.vendorScript,
|
|
1835
|
+
appScript,
|
|
1836
|
+
renderScript,
|
|
1837
|
+
rootId: opts.rootId,
|
|
1838
|
+
cdnType
|
|
1839
|
+
});
|
|
1840
|
+
componentCode = transpiledCode ?? appScript;
|
|
1841
|
+
} else {
|
|
1842
|
+
if (!transpiledCode) {
|
|
1843
|
+
throw new Error("Failed to transpile component source");
|
|
1844
|
+
}
|
|
1845
|
+
const head = this.buildStaticHTMLHead({
|
|
1846
|
+
externals: opts.externals,
|
|
1847
|
+
customCss: opts.customCss,
|
|
1848
|
+
theme: opts.theme
|
|
1849
|
+
});
|
|
1850
|
+
const reactRuntime = this.buildReactRuntimeScripts(opts.externals, platform, cdnType);
|
|
1851
|
+
const frontmcpRuntime = await this.buildFrontMCPRuntime();
|
|
1852
|
+
const dataScript = this.buildDataInjectionScript(
|
|
1853
|
+
opts.toolName,
|
|
1854
|
+
opts.input,
|
|
1855
|
+
opts.output,
|
|
1856
|
+
opts.structuredContent,
|
|
1857
|
+
opts.buildMode,
|
|
1858
|
+
cdnType,
|
|
1859
|
+
opts.dynamicOptions,
|
|
1860
|
+
opts.hybridOptions
|
|
1861
|
+
);
|
|
1862
|
+
const componentScript = this.buildComponentRenderScript(transpiledCode, opts.rootId, cdnType);
|
|
1863
|
+
html = this.assembleStaticHTML({
|
|
1864
|
+
title: opts.title || `${opts.toolName} - Widget`,
|
|
1865
|
+
head,
|
|
1866
|
+
reactRuntime,
|
|
1867
|
+
frontmcpRuntime,
|
|
1868
|
+
dataScript,
|
|
1869
|
+
componentScript,
|
|
1870
|
+
rootId: opts.rootId,
|
|
1871
|
+
cdnType
|
|
1872
|
+
});
|
|
1873
|
+
componentCode = transpiledCode;
|
|
1874
|
+
}
|
|
1875
|
+
const hash = hashContent(html);
|
|
1876
|
+
const meta = buildUIMeta({
|
|
1877
|
+
uiConfig: {
|
|
1878
|
+
template: () => html,
|
|
1879
|
+
widgetAccessible: opts.widgetAccessible
|
|
1880
|
+
},
|
|
1881
|
+
platformType: this.mapTargetPlatformToAIPlatform(platform),
|
|
1882
|
+
html
|
|
1883
|
+
});
|
|
1884
|
+
const dataPlaceholder = opts.buildMode === "hybrid" ? opts.hybridOptions?.placeholder ?? HYBRID_DATA_PLACEHOLDER : void 0;
|
|
1885
|
+
const inputPlaceholder = opts.buildMode === "hybrid" ? opts.hybridOptions?.inputPlaceholder ?? HYBRID_INPUT_PLACEHOLDER : void 0;
|
|
1886
|
+
return {
|
|
1887
|
+
html,
|
|
1888
|
+
componentCode,
|
|
1889
|
+
metrics: bundleResult?.metrics ?? {
|
|
1890
|
+
transformTime: 0,
|
|
1891
|
+
bundleTime: 0,
|
|
1892
|
+
totalTime: performance.now() - buildStart
|
|
1893
|
+
},
|
|
1894
|
+
hash,
|
|
1895
|
+
size: html.length,
|
|
1896
|
+
cached: bundleResult?.cached ?? false,
|
|
1897
|
+
sourceType: bundleResult?.sourceType ?? opts.sourceType,
|
|
1898
|
+
targetPlatform: platform,
|
|
1899
|
+
universal: isUniversal,
|
|
1900
|
+
contentType: isUniversal ? contentType : void 0,
|
|
1901
|
+
buildMode: opts.buildMode,
|
|
1902
|
+
dataPlaceholder,
|
|
1903
|
+
inputPlaceholder,
|
|
1904
|
+
meta
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1907
|
+
/**
|
|
1908
|
+
* Map TargetPlatform to AIPlatformType for metadata generation.
|
|
1909
|
+
*/
|
|
1910
|
+
mapTargetPlatformToAIPlatform(platform) {
|
|
1911
|
+
switch (platform) {
|
|
1912
|
+
case "openai":
|
|
1913
|
+
return "openai";
|
|
1914
|
+
case "claude":
|
|
1915
|
+
return "claude";
|
|
1916
|
+
case "cursor":
|
|
1917
|
+
return "cursor";
|
|
1918
|
+
case "ext-apps":
|
|
1919
|
+
return "ext-apps";
|
|
1920
|
+
case "generic":
|
|
1921
|
+
return "generic-mcp";
|
|
1922
|
+
default:
|
|
1923
|
+
return "generic-mcp";
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
2315
1926
|
/**
|
|
2316
1927
|
* Bundle to static HTML with universal rendering mode.
|
|
2317
1928
|
* Uses the universal renderer that can handle multiple content types.
|
|
@@ -2343,11 +1954,13 @@ var InMemoryBundler = class {
|
|
|
2343
1954
|
transpiledCode = bundleResult.code;
|
|
2344
1955
|
transformTime = bundleResult.metrics.transformTime;
|
|
2345
1956
|
}
|
|
1957
|
+
const shouldIncludeBridge = opts.buildMode === "dynamic" || opts.buildMode === "hybrid";
|
|
2346
1958
|
const cachedRuntime = getCachedRuntime({
|
|
2347
1959
|
cdnType,
|
|
2348
1960
|
includeMarkdown: opts.includeMarkdown || contentType === "markdown",
|
|
2349
1961
|
includeMdx: opts.includeMdx || contentType === "mdx",
|
|
2350
|
-
minify: opts.minify
|
|
1962
|
+
minify: opts.minify,
|
|
1963
|
+
includeBridge: shouldIncludeBridge
|
|
2351
1964
|
});
|
|
2352
1965
|
const componentCodeStr = transpiledCode ? buildComponentCode(transpiledCode) : "";
|
|
2353
1966
|
const dataInjectionStr = buildDataInjectionCode(
|
|
@@ -2358,7 +1971,13 @@ var InMemoryBundler = class {
|
|
|
2358
1971
|
contentType,
|
|
2359
1972
|
transpiledCode ? null : options.source,
|
|
2360
1973
|
// Pass source only if not a component
|
|
2361
|
-
transpiledCode !== null
|
|
1974
|
+
transpiledCode !== null,
|
|
1975
|
+
{
|
|
1976
|
+
buildMode: opts.buildMode,
|
|
1977
|
+
cdnType,
|
|
1978
|
+
dynamicOptions: opts.dynamicOptions,
|
|
1979
|
+
hybridOptions: opts.hybridOptions
|
|
1980
|
+
}
|
|
2362
1981
|
);
|
|
2363
1982
|
const appScript = buildAppScript(
|
|
2364
1983
|
cachedRuntime.appTemplate,
|
|
@@ -2366,7 +1985,7 @@ var InMemoryBundler = class {
|
|
|
2366
1985
|
dataInjectionStr,
|
|
2367
1986
|
opts.customComponents ?? ""
|
|
2368
1987
|
);
|
|
2369
|
-
const head = this.buildStaticHTMLHead({ externals: opts.externals, customCss: opts.customCss });
|
|
1988
|
+
const head = this.buildStaticHTMLHead({ externals: opts.externals, customCss: opts.customCss, theme: opts.theme });
|
|
2370
1989
|
const reactRuntime = this.buildReactRuntimeScripts(opts.externals, platform, cdnType);
|
|
2371
1990
|
const renderScript = this.buildUniversalRenderScript(opts.rootId, cdnType);
|
|
2372
1991
|
const html = this.assembleUniversalStaticHTMLCached({
|
|
@@ -2795,6 +2414,10 @@ ${parts.appScript}
|
|
|
2795
2414
|
contentType: options.contentType ?? DEFAULT_STATIC_HTML_OPTIONS.contentType,
|
|
2796
2415
|
includeMarkdown: options.includeMarkdown ?? DEFAULT_STATIC_HTML_OPTIONS.includeMarkdown,
|
|
2797
2416
|
includeMdx: options.includeMdx ?? DEFAULT_STATIC_HTML_OPTIONS.includeMdx,
|
|
2417
|
+
// Build mode options
|
|
2418
|
+
buildMode: options.buildMode ?? DEFAULT_STATIC_HTML_OPTIONS.buildMode,
|
|
2419
|
+
dynamicOptions: options.dynamicOptions,
|
|
2420
|
+
hybridOptions: options.hybridOptions,
|
|
2798
2421
|
// Pass-through options
|
|
2799
2422
|
toolName: options.toolName,
|
|
2800
2423
|
input: options.input,
|
|
@@ -2803,7 +2426,8 @@ ${parts.appScript}
|
|
|
2803
2426
|
title: options.title,
|
|
2804
2427
|
security: options.security,
|
|
2805
2428
|
customCss: options.customCss,
|
|
2806
|
-
customComponents: options.customComponents
|
|
2429
|
+
customComponents: options.customComponents,
|
|
2430
|
+
theme: options.theme
|
|
2807
2431
|
};
|
|
2808
2432
|
}
|
|
2809
2433
|
/**
|
|
@@ -2817,12 +2441,8 @@ ${parts.appScript}
|
|
|
2817
2441
|
parts.push(`<link rel="preconnect" href="${url}" crossorigin>`);
|
|
2818
2442
|
}
|
|
2819
2443
|
parts.push(`<link rel="stylesheet" href="${STATIC_HTML_CDN.fonts.inter}">`);
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
parts.push(`<link rel="stylesheet" href="${STATIC_HTML_CDN.tailwind}">`);
|
|
2823
|
-
} else if (tailwindConfig !== "inline" && tailwindConfig) {
|
|
2824
|
-
parts.push(`<link rel="stylesheet" href="${tailwindConfig}">`);
|
|
2825
|
-
}
|
|
2444
|
+
parts.push(buildCDNScriptTag(CLOUDFLARE_CDN.tailwindCss));
|
|
2445
|
+
parts.push(this.buildThemeStyleBlock(opts.theme));
|
|
2826
2446
|
parts.push(`<style>
|
|
2827
2447
|
body { margin: 0; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
|
|
2828
2448
|
.frontmcp-loading { display: flex; align-items: center; justify-content: center; min-height: 200px; }
|
|
@@ -2836,6 +2456,18 @@ ${sanitizeCss(opts.customCss)}
|
|
|
2836
2456
|
}
|
|
2837
2457
|
return parts.join("\n ");
|
|
2838
2458
|
}
|
|
2459
|
+
/**
|
|
2460
|
+
* Build theme CSS variables as a :root style block.
|
|
2461
|
+
* Uses DEFAULT_THEME if no theme is provided.
|
|
2462
|
+
*/
|
|
2463
|
+
buildThemeStyleBlock(theme = DEFAULT_THEME) {
|
|
2464
|
+
const cssVars = buildThemeCss(theme);
|
|
2465
|
+
return `<style>
|
|
2466
|
+
:root {
|
|
2467
|
+
${cssVars}
|
|
2468
|
+
}
|
|
2469
|
+
</style>`;
|
|
2470
|
+
}
|
|
2839
2471
|
/**
|
|
2840
2472
|
* Build React runtime scripts for static HTML.
|
|
2841
2473
|
*/
|
|
@@ -2900,9 +2532,17 @@ ${sanitizeCss(opts.customCss)}
|
|
|
2900
2532
|
}
|
|
2901
2533
|
/**
|
|
2902
2534
|
* Build FrontMCP runtime (hooks and UI components).
|
|
2535
|
+
* Uses esbuild to transpile real React components at first use, then caches.
|
|
2536
|
+
* Falls back to manual implementation if esbuild fails.
|
|
2903
2537
|
* Always inlined for reliability across platforms.
|
|
2904
2538
|
*/
|
|
2905
|
-
buildFrontMCPRuntime() {
|
|
2539
|
+
async buildFrontMCPRuntime() {
|
|
2540
|
+
let uiComponents;
|
|
2541
|
+
try {
|
|
2542
|
+
uiComponents = await getBrowserComponents();
|
|
2543
|
+
} catch {
|
|
2544
|
+
uiComponents = buildFallbackUIComponents();
|
|
2545
|
+
}
|
|
2906
2546
|
return `
|
|
2907
2547
|
<!-- FrontMCP Runtime (always inline) -->
|
|
2908
2548
|
<script>
|
|
@@ -2922,8 +2562,8 @@ ${sanitizeCss(opts.customCss)}
|
|
|
2922
2562
|
'react-dom/client': function() { return window.ReactDOM; },
|
|
2923
2563
|
'react/jsx-runtime': function() { return window.jsx_runtime_namespaceObject; },
|
|
2924
2564
|
'react/jsx-dev-runtime': function() { return window.jsx_runtime_namespaceObject; },
|
|
2925
|
-
'@frontmcp/ui': function() { return window.
|
|
2926
|
-
'@frontmcp/ui/react': function() { return window.
|
|
2565
|
+
'@frontmcp/ui': function() { return window.frontmcp_ui_namespaceObject; },
|
|
2566
|
+
'@frontmcp/ui/react': function() { return window.frontmcp_ui_namespaceObject; },
|
|
2927
2567
|
};
|
|
2928
2568
|
|
|
2929
2569
|
var resolver = moduleMap[moduleName];
|
|
@@ -2954,7 +2594,7 @@ ${sanitizeCss(opts.customCss)}
|
|
|
2954
2594
|
});
|
|
2955
2595
|
};
|
|
2956
2596
|
|
|
2957
|
-
// FrontMCP Hook implementations
|
|
2597
|
+
// FrontMCP Hook implementations and state
|
|
2958
2598
|
window.__frontmcp = {
|
|
2959
2599
|
// Context for MCP bridge
|
|
2960
2600
|
context: {
|
|
@@ -2965,10 +2605,35 @@ ${sanitizeCss(opts.customCss)}
|
|
|
2965
2605
|
callTool: null,
|
|
2966
2606
|
},
|
|
2967
2607
|
|
|
2608
|
+
// Theme and display settings
|
|
2609
|
+
theme: 'light',
|
|
2610
|
+
displayMode: 'embedded',
|
|
2611
|
+
hostContext: {},
|
|
2612
|
+
capabilities: {},
|
|
2613
|
+
|
|
2968
2614
|
// Set context from data injection
|
|
2969
2615
|
setContext: function(ctx) {
|
|
2970
2616
|
Object.assign(this.context, ctx);
|
|
2971
2617
|
},
|
|
2618
|
+
|
|
2619
|
+
// State management (for universal mode compatibility)
|
|
2620
|
+
getState: function() {
|
|
2621
|
+
return {
|
|
2622
|
+
toolName: this.context.toolName,
|
|
2623
|
+
input: this.context.toolInput,
|
|
2624
|
+
output: this.context.toolOutput,
|
|
2625
|
+
structuredContent: this.context.structuredContent,
|
|
2626
|
+
loading: false,
|
|
2627
|
+
error: null
|
|
2628
|
+
};
|
|
2629
|
+
},
|
|
2630
|
+
|
|
2631
|
+
setState: function(partial) {
|
|
2632
|
+
if (partial.toolName !== undefined) this.context.toolName = partial.toolName;
|
|
2633
|
+
if (partial.input !== undefined) this.context.toolInput = partial.input;
|
|
2634
|
+
if (partial.output !== undefined) this.context.toolOutput = partial.output;
|
|
2635
|
+
if (partial.structuredContent !== undefined) this.context.structuredContent = partial.structuredContent;
|
|
2636
|
+
}
|
|
2972
2637
|
};
|
|
2973
2638
|
|
|
2974
2639
|
// Hook: useToolOutput - returns the tool output data
|
|
@@ -2996,74 +2661,31 @@ ${sanitizeCss(opts.customCss)}
|
|
|
2996
2661
|
return Promise.resolve(null);
|
|
2997
2662
|
};
|
|
2998
2663
|
};
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
var title = props.title;
|
|
3004
|
-
var className = props.className || '';
|
|
3005
|
-
return React.createElement('div', {
|
|
3006
|
-
className: 'bg-white rounded-lg shadow border border-gray-200 overflow-hidden ' + className
|
|
3007
|
-
}, [
|
|
3008
|
-
title && React.createElement('div', {
|
|
3009
|
-
key: 'header',
|
|
3010
|
-
className: 'px-4 py-3 border-b border-gray-200 bg-gray-50'
|
|
3011
|
-
}, React.createElement('h3', { className: 'text-sm font-medium text-gray-900' }, title)),
|
|
3012
|
-
React.createElement('div', { key: 'body', className: 'p-4' }, children)
|
|
3013
|
-
]);
|
|
3014
|
-
};
|
|
3015
|
-
|
|
3016
|
-
window.Badge = function(props) {
|
|
3017
|
-
var children = props.children;
|
|
3018
|
-
var variant = props.variant || 'default';
|
|
3019
|
-
var variantClasses = {
|
|
3020
|
-
default: 'bg-gray-100 text-gray-800',
|
|
3021
|
-
success: 'bg-green-100 text-green-800',
|
|
3022
|
-
warning: 'bg-yellow-100 text-yellow-800',
|
|
3023
|
-
error: 'bg-red-100 text-red-800',
|
|
3024
|
-
info: 'bg-blue-100 text-blue-800',
|
|
3025
|
-
};
|
|
3026
|
-
return React.createElement('span', {
|
|
3027
|
-
className: 'inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ' + (variantClasses[variant] || variantClasses.default)
|
|
3028
|
-
}, children);
|
|
3029
|
-
};
|
|
3030
|
-
|
|
3031
|
-
window.Button = function(props) {
|
|
3032
|
-
var children = props.children;
|
|
3033
|
-
var variant = props.variant || 'primary';
|
|
3034
|
-
var onClick = props.onClick;
|
|
3035
|
-
var disabled = props.disabled;
|
|
3036
|
-
var variantClasses = {
|
|
3037
|
-
primary: 'bg-blue-600 text-white hover:bg-blue-700',
|
|
3038
|
-
secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200',
|
|
3039
|
-
outline: 'border border-gray-300 text-gray-700 hover:bg-gray-50',
|
|
3040
|
-
danger: 'bg-red-600 text-white hover:bg-red-700',
|
|
3041
|
-
};
|
|
3042
|
-
return React.createElement('button', {
|
|
3043
|
-
className: 'px-4 py-2 rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 ' +
|
|
3044
|
-
(disabled ? 'opacity-50 cursor-not-allowed ' : '') +
|
|
3045
|
-
(variantClasses[variant] || variantClasses.primary),
|
|
3046
|
-
onClick: onClick,
|
|
3047
|
-
disabled: disabled,
|
|
3048
|
-
}, children);
|
|
3049
|
-
};
|
|
3050
|
-
|
|
3051
|
-
// Make hooks available on react_namespaceObject for bundled imports
|
|
3052
|
-
window.react_namespaceObject = Object.assign({}, window.React || {}, {
|
|
3053
|
-
useToolOutput: window.useToolOutput,
|
|
3054
|
-
useToolInput: window.useToolInput,
|
|
3055
|
-
useMcpBridgeContext: window.useMcpBridgeContext,
|
|
3056
|
-
useCallTool: window.useCallTool,
|
|
3057
|
-
Card: window.Card,
|
|
3058
|
-
Badge: window.Badge,
|
|
3059
|
-
Button: window.Button,
|
|
3060
|
-
});
|
|
2664
|
+
</script>
|
|
2665
|
+
<!-- UI Components (Full-Featured, Browser-Compatible) -->
|
|
2666
|
+
<script>
|
|
2667
|
+
${uiComponents}
|
|
3061
2668
|
</script>`;
|
|
3062
2669
|
}
|
|
3063
2670
|
/**
|
|
3064
2671
|
* Build data injection script for tool input/output.
|
|
2672
|
+
* Dispatches to mode-specific builders based on buildMode.
|
|
2673
|
+
*/
|
|
2674
|
+
buildDataInjectionScript(toolName, input, output, structuredContent, buildMode = "static", cdnType = "esm", dynamicOptions, hybridOptions) {
|
|
2675
|
+
switch (buildMode) {
|
|
2676
|
+
case "dynamic":
|
|
2677
|
+
return this.buildDynamicDataScript(toolName, input, output, structuredContent, cdnType, dynamicOptions);
|
|
2678
|
+
case "hybrid":
|
|
2679
|
+
return this.buildHybridDataScript(toolName, input, structuredContent, hybridOptions);
|
|
2680
|
+
case "static":
|
|
2681
|
+
default:
|
|
2682
|
+
return this.buildStaticDataScript(toolName, input, output, structuredContent);
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
/**
|
|
2686
|
+
* Build static data injection - data baked in at build time (current default).
|
|
3065
2687
|
*/
|
|
3066
|
-
|
|
2688
|
+
buildStaticDataScript(toolName, input, output, structuredContent) {
|
|
3067
2689
|
const safeJson = (value) => {
|
|
3068
2690
|
try {
|
|
3069
2691
|
return JSON.stringify(value);
|
|
@@ -3072,7 +2694,7 @@ ${sanitizeCss(opts.customCss)}
|
|
|
3072
2694
|
}
|
|
3073
2695
|
};
|
|
3074
2696
|
return `
|
|
3075
|
-
<!-- Tool Data Injection -->
|
|
2697
|
+
<!-- Tool Data Injection (Static Mode) -->
|
|
3076
2698
|
<script>
|
|
3077
2699
|
window.__mcpToolName = ${safeJson(toolName)};
|
|
3078
2700
|
window.__mcpToolInput = ${safeJson(input ?? null)};
|
|
@@ -3088,6 +2710,256 @@ ${sanitizeCss(opts.customCss)}
|
|
|
3088
2710
|
});
|
|
3089
2711
|
</script>`;
|
|
3090
2712
|
}
|
|
2713
|
+
/**
|
|
2714
|
+
* Build dynamic data injection - platform-aware.
|
|
2715
|
+
* For OpenAI (ESM): subscribes to platform events for updates.
|
|
2716
|
+
* For non-OpenAI (UMD/Claude): uses placeholders for data injection.
|
|
2717
|
+
*/
|
|
2718
|
+
buildDynamicDataScript(toolName, input, output, structuredContent, cdnType = "esm", options) {
|
|
2719
|
+
if (cdnType === "umd") {
|
|
2720
|
+
return this.buildDynamicWithPlaceholdersScript(toolName, structuredContent, options);
|
|
2721
|
+
}
|
|
2722
|
+
return this.buildDynamicWithSubscriptionScript(toolName, input, output, structuredContent, options);
|
|
2723
|
+
}
|
|
2724
|
+
/**
|
|
2725
|
+
* Build dynamic data injection for non-OpenAI platforms using placeholders.
|
|
2726
|
+
* Similar to hybrid mode but with platform-appropriate loading/error states.
|
|
2727
|
+
*/
|
|
2728
|
+
buildDynamicWithPlaceholdersScript(toolName, structuredContent, options) {
|
|
2729
|
+
const safeJson = (value) => {
|
|
2730
|
+
try {
|
|
2731
|
+
return JSON.stringify(value);
|
|
2732
|
+
} catch {
|
|
2733
|
+
return "null";
|
|
2734
|
+
}
|
|
2735
|
+
};
|
|
2736
|
+
const outputPlaceholder = HYBRID_DATA_PLACEHOLDER;
|
|
2737
|
+
const inputPlaceholder = HYBRID_INPUT_PLACEHOLDER;
|
|
2738
|
+
const includeInitialData = options?.includeInitialData !== false;
|
|
2739
|
+
return `
|
|
2740
|
+
<!-- Tool Data Injection (Dynamic Mode - Placeholder-based for non-OpenAI) -->
|
|
2741
|
+
<script>
|
|
2742
|
+
window.__mcpToolName = ${safeJson(toolName)};
|
|
2743
|
+
window.__mcpToolInput = "${inputPlaceholder}";
|
|
2744
|
+
window.__mcpToolOutput = "${outputPlaceholder}";
|
|
2745
|
+
window.__mcpStructuredContent = ${safeJson(structuredContent ?? null)};
|
|
2746
|
+
window.__mcpHybridError = null;
|
|
2747
|
+
|
|
2748
|
+
(function() {
|
|
2749
|
+
var outputNotReplaced = false;
|
|
2750
|
+
var includeInitialData = ${includeInitialData};
|
|
2751
|
+
|
|
2752
|
+
// Parse output placeholder
|
|
2753
|
+
var rawOutput = window.__mcpToolOutput;
|
|
2754
|
+
if (typeof rawOutput === 'string' && rawOutput !== "${outputPlaceholder}") {
|
|
2755
|
+
try {
|
|
2756
|
+
window.__mcpToolOutput = JSON.parse(rawOutput);
|
|
2757
|
+
} catch (e) {
|
|
2758
|
+
console.warn('[FrontMCP] Failed to parse injected output data:', e);
|
|
2759
|
+
window.__mcpToolOutput = null;
|
|
2760
|
+
window.__mcpHybridError = 'Failed to parse output data';
|
|
2761
|
+
}
|
|
2762
|
+
} else if (rawOutput === "${outputPlaceholder}") {
|
|
2763
|
+
window.__mcpToolOutput = null;
|
|
2764
|
+
outputNotReplaced = true;
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
// Parse input placeholder
|
|
2768
|
+
var rawInput = window.__mcpToolInput;
|
|
2769
|
+
if (typeof rawInput === 'string' && rawInput !== "${inputPlaceholder}") {
|
|
2770
|
+
try {
|
|
2771
|
+
window.__mcpToolInput = JSON.parse(rawInput);
|
|
2772
|
+
} catch (e) {
|
|
2773
|
+
console.warn('[FrontMCP] Failed to parse injected input data:', e);
|
|
2774
|
+
window.__mcpToolInput = null;
|
|
2775
|
+
}
|
|
2776
|
+
} else if (rawInput === "${inputPlaceholder}") {
|
|
2777
|
+
window.__mcpToolInput = null;
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2780
|
+
// Handle placeholder not replaced - show error if expecting initial data
|
|
2781
|
+
if (outputNotReplaced && includeInitialData) {
|
|
2782
|
+
window.__mcpHybridError = 'No data provided. The output placeholder was not replaced.';
|
|
2783
|
+
}
|
|
2784
|
+
})();
|
|
2785
|
+
|
|
2786
|
+
// Initialize FrontMCP context with appropriate loading/error state
|
|
2787
|
+
if (window.__frontmcp && window.__frontmcp.setContext) {
|
|
2788
|
+
window.__frontmcp.setContext({
|
|
2789
|
+
toolName: window.__mcpToolName,
|
|
2790
|
+
toolInput: window.__mcpToolInput,
|
|
2791
|
+
toolOutput: window.__mcpToolOutput,
|
|
2792
|
+
structuredContent: window.__mcpStructuredContent,
|
|
2793
|
+
loading: ${!includeInitialData} && window.__mcpToolOutput === null && !window.__mcpHybridError,
|
|
2794
|
+
error: window.__mcpHybridError,
|
|
2795
|
+
});
|
|
2796
|
+
}
|
|
2797
|
+
</script>`;
|
|
2798
|
+
}
|
|
2799
|
+
/**
|
|
2800
|
+
* Build dynamic data injection for OpenAI using subscription pattern.
|
|
2801
|
+
*/
|
|
2802
|
+
buildDynamicWithSubscriptionScript(toolName, input, output, structuredContent, options) {
|
|
2803
|
+
const safeJson = (value) => {
|
|
2804
|
+
try {
|
|
2805
|
+
return JSON.stringify(value);
|
|
2806
|
+
} catch {
|
|
2807
|
+
return "null";
|
|
2808
|
+
}
|
|
2809
|
+
};
|
|
2810
|
+
const includeInitial = options?.includeInitialData !== false;
|
|
2811
|
+
const subscribeToUpdates = options?.subscribeToUpdates !== false;
|
|
2812
|
+
const initialDataBlock = includeInitial ? `
|
|
2813
|
+
window.__mcpToolOutput = ${safeJson(output ?? null)};
|
|
2814
|
+
if (window.__frontmcp && window.__frontmcp.setState) {
|
|
2815
|
+
window.__frontmcp.setState({
|
|
2816
|
+
output: window.__mcpToolOutput,
|
|
2817
|
+
loading: false,
|
|
2818
|
+
});
|
|
2819
|
+
}` : `
|
|
2820
|
+
window.__mcpToolOutput = null;
|
|
2821
|
+
if (window.__frontmcp && window.__frontmcp.setState) {
|
|
2822
|
+
window.__frontmcp.setState({
|
|
2823
|
+
output: null,
|
|
2824
|
+
loading: true,
|
|
2825
|
+
});
|
|
2826
|
+
}`;
|
|
2827
|
+
const subscriptionBlock = subscribeToUpdates ? `
|
|
2828
|
+
// Subscribe to platform tool result updates
|
|
2829
|
+
(function() {
|
|
2830
|
+
function subscribeToUpdates() {
|
|
2831
|
+
// OpenAI Apps SDK
|
|
2832
|
+
if (window.openai && window.openai.canvas && window.openai.canvas.onToolResult) {
|
|
2833
|
+
window.openai.canvas.onToolResult(function(result) {
|
|
2834
|
+
window.__mcpToolOutput = result;
|
|
2835
|
+
if (window.__frontmcp && window.__frontmcp.setState) {
|
|
2836
|
+
window.__frontmcp.setState({
|
|
2837
|
+
output: result,
|
|
2838
|
+
loading: false,
|
|
2839
|
+
});
|
|
2840
|
+
}
|
|
2841
|
+
// Dispatch custom event for React hooks
|
|
2842
|
+
window.dispatchEvent(new CustomEvent('frontmcp:toolResult', { detail: result }));
|
|
2843
|
+
});
|
|
2844
|
+
return;
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
// Fallback: listen for custom events (for testing/other platforms)
|
|
2848
|
+
window.addEventListener('frontmcp:injectData', function(e) {
|
|
2849
|
+
if (e.detail && e.detail.output !== undefined) {
|
|
2850
|
+
window.__mcpToolOutput = e.detail.output;
|
|
2851
|
+
if (window.__frontmcp && window.__frontmcp.setState) {
|
|
2852
|
+
window.__frontmcp.setState({
|
|
2853
|
+
output: e.detail.output,
|
|
2854
|
+
loading: false,
|
|
2855
|
+
});
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
});
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2861
|
+
// Subscribe when DOM is ready
|
|
2862
|
+
if (document.readyState === 'loading') {
|
|
2863
|
+
document.addEventListener('DOMContentLoaded', subscribeToUpdates);
|
|
2864
|
+
} else {
|
|
2865
|
+
subscribeToUpdates();
|
|
2866
|
+
}
|
|
2867
|
+
})();` : "";
|
|
2868
|
+
return `
|
|
2869
|
+
<!-- Tool Data Injection (Dynamic Mode - OpenAI Subscription) -->
|
|
2870
|
+
<script>
|
|
2871
|
+
window.__mcpToolName = ${safeJson(toolName)};
|
|
2872
|
+
window.__mcpToolInput = ${safeJson(input ?? null)};
|
|
2873
|
+
window.__mcpStructuredContent = ${safeJson(structuredContent ?? null)};
|
|
2874
|
+
${initialDataBlock}
|
|
2875
|
+
|
|
2876
|
+
// Initialize FrontMCP context
|
|
2877
|
+
if (window.__frontmcp && window.__frontmcp.setContext) {
|
|
2878
|
+
window.__frontmcp.setContext({
|
|
2879
|
+
toolName: window.__mcpToolName,
|
|
2880
|
+
toolInput: window.__mcpToolInput,
|
|
2881
|
+
toolOutput: window.__mcpToolOutput,
|
|
2882
|
+
structuredContent: window.__mcpStructuredContent,
|
|
2883
|
+
});
|
|
2884
|
+
}
|
|
2885
|
+
${subscriptionBlock}
|
|
2886
|
+
</script>`;
|
|
2887
|
+
}
|
|
2888
|
+
/**
|
|
2889
|
+
* Build hybrid data injection - shell with placeholders for runtime injection.
|
|
2890
|
+
* Use injectHybridData() or injectHybridDataFull() from @frontmcp/uipack to replace the placeholders.
|
|
2891
|
+
*/
|
|
2892
|
+
buildHybridDataScript(toolName, _input, structuredContent, options) {
|
|
2893
|
+
const safeJson = (value) => {
|
|
2894
|
+
try {
|
|
2895
|
+
return JSON.stringify(value);
|
|
2896
|
+
} catch {
|
|
2897
|
+
return "null";
|
|
2898
|
+
}
|
|
2899
|
+
};
|
|
2900
|
+
const outputPlaceholder = options?.placeholder ?? HYBRID_DATA_PLACEHOLDER;
|
|
2901
|
+
const inputPlaceholder = options?.inputPlaceholder ?? HYBRID_INPUT_PLACEHOLDER;
|
|
2902
|
+
return `
|
|
2903
|
+
<!-- Tool Data Injection (Hybrid Mode - Replace placeholders with JSON) -->
|
|
2904
|
+
<script>
|
|
2905
|
+
window.__mcpToolName = ${safeJson(toolName)};
|
|
2906
|
+
window.__mcpToolInput = "${inputPlaceholder}";
|
|
2907
|
+
window.__mcpToolOutput = "${outputPlaceholder}";
|
|
2908
|
+
window.__mcpStructuredContent = ${safeJson(structuredContent ?? null)};
|
|
2909
|
+
window.__mcpHybridError = null;
|
|
2910
|
+
|
|
2911
|
+
// Parse placeholders if they've been replaced with actual JSON
|
|
2912
|
+
(function() {
|
|
2913
|
+
var outputNotReplaced = false;
|
|
2914
|
+
|
|
2915
|
+
// Parse output placeholder
|
|
2916
|
+
var rawOutput = window.__mcpToolOutput;
|
|
2917
|
+
if (typeof rawOutput === 'string' && rawOutput !== "${outputPlaceholder}") {
|
|
2918
|
+
try {
|
|
2919
|
+
window.__mcpToolOutput = JSON.parse(rawOutput);
|
|
2920
|
+
} catch (e) {
|
|
2921
|
+
console.warn('[FrontMCP] Failed to parse injected output data:', e);
|
|
2922
|
+
window.__mcpToolOutput = null;
|
|
2923
|
+
window.__mcpHybridError = 'Failed to parse output data';
|
|
2924
|
+
}
|
|
2925
|
+
} else if (rawOutput === "${outputPlaceholder}") {
|
|
2926
|
+
// Placeholder not replaced - no data was injected
|
|
2927
|
+
window.__mcpToolOutput = null;
|
|
2928
|
+
outputNotReplaced = true;
|
|
2929
|
+
}
|
|
2930
|
+
|
|
2931
|
+
// Parse input placeholder
|
|
2932
|
+
var rawInput = window.__mcpToolInput;
|
|
2933
|
+
if (typeof rawInput === 'string' && rawInput !== "${inputPlaceholder}") {
|
|
2934
|
+
try {
|
|
2935
|
+
window.__mcpToolInput = JSON.parse(rawInput);
|
|
2936
|
+
} catch (e) {
|
|
2937
|
+
console.warn('[FrontMCP] Failed to parse injected input data:', e);
|
|
2938
|
+
window.__mcpToolInput = null;
|
|
2939
|
+
}
|
|
2940
|
+
} else if (rawInput === "${inputPlaceholder}") {
|
|
2941
|
+
window.__mcpToolInput = null;
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
// Set error if output placeholder was not replaced (no data provided)
|
|
2945
|
+
if (outputNotReplaced) {
|
|
2946
|
+
window.__mcpHybridError = 'No data provided. The output placeholder was not replaced.';
|
|
2947
|
+
}
|
|
2948
|
+
})();
|
|
2949
|
+
|
|
2950
|
+
// Initialize FrontMCP context with appropriate loading/error state
|
|
2951
|
+
if (window.__frontmcp && window.__frontmcp.setContext) {
|
|
2952
|
+
window.__frontmcp.setContext({
|
|
2953
|
+
toolName: window.__mcpToolName,
|
|
2954
|
+
toolInput: window.__mcpToolInput,
|
|
2955
|
+
toolOutput: window.__mcpToolOutput,
|
|
2956
|
+
structuredContent: window.__mcpStructuredContent,
|
|
2957
|
+
loading: false,
|
|
2958
|
+
error: window.__mcpHybridError,
|
|
2959
|
+
});
|
|
2960
|
+
}
|
|
2961
|
+
</script>`;
|
|
2962
|
+
}
|
|
3091
2963
|
/**
|
|
3092
2964
|
* Build component render script.
|
|
3093
2965
|
* Wraps CommonJS code with module/exports shim to capture the component.
|
|
@@ -3190,424 +3062,48 @@ function createBundler(options) {
|
|
|
3190
3062
|
return new InMemoryBundler(options);
|
|
3191
3063
|
}
|
|
3192
3064
|
|
|
3193
|
-
// libs/ui/src/bundler/
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
import {
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
hashes.push(`${filePath}:${hash}`);
|
|
3223
|
-
}
|
|
3224
|
-
}
|
|
3225
|
-
return sha256(hashes.join("\n"));
|
|
3226
|
-
}
|
|
3227
|
-
async function calculateComponentHash(options) {
|
|
3228
|
-
const {
|
|
3229
|
-
entryPath,
|
|
3230
|
-
baseDir = dirname2(entryPath),
|
|
3231
|
-
externals = [],
|
|
3232
|
-
dependencies = {},
|
|
3233
|
-
bundleOptions = {},
|
|
3234
|
-
maxDepth = 10
|
|
3235
|
-
} = options;
|
|
3236
|
-
const absoluteEntryPath = resolve(entryPath);
|
|
3237
|
-
const files = /* @__PURE__ */ new Set();
|
|
3238
|
-
const fileHashes = {};
|
|
3239
|
-
await collectLocalDependencies(absoluteEntryPath, baseDir, files, maxDepth, 0);
|
|
3240
|
-
for (const file of files) {
|
|
3241
|
-
const hash = await hashFile(file);
|
|
3242
|
-
if (hash) {
|
|
3243
|
-
fileHashes[file] = hash;
|
|
3244
|
-
}
|
|
3245
|
-
}
|
|
3246
|
-
const sortedFiles = Array.from(files).sort();
|
|
3247
|
-
const fileHashContent = sortedFiles.map((f) => `${f}:${fileHashes[f] || "missing"}`).join("\n");
|
|
3248
|
-
const filesHash = sha256(fileHashContent);
|
|
3249
|
-
const optionsHash = sha256(JSON.stringify(sortedObject(bundleOptions)));
|
|
3250
|
-
const dependenciesHash = sha256(JSON.stringify(sortedObject(dependencies)));
|
|
3251
|
-
const combinedHash = sha256([filesHash, optionsHash, dependenciesHash].join(":"));
|
|
3252
|
-
return {
|
|
3253
|
-
hash: combinedHash,
|
|
3254
|
-
entryHash: fileHashes[absoluteEntryPath] || "",
|
|
3255
|
-
files: sortedFiles,
|
|
3256
|
-
fileHashes,
|
|
3257
|
-
optionsHash,
|
|
3258
|
-
dependenciesHash
|
|
3259
|
-
};
|
|
3260
|
-
}
|
|
3261
|
-
async function calculateQuickHash(entryPath, bundleOptions) {
|
|
3262
|
-
const entryHash = await hashFile(entryPath);
|
|
3263
|
-
const optionsHash = bundleOptions ? sha256(JSON.stringify(sortedObject(bundleOptions))) : "";
|
|
3264
|
-
return sha256(`${entryHash || "missing"}:${optionsHash}`);
|
|
3265
|
-
}
|
|
3266
|
-
async function collectLocalDependencies(filePath, baseDir, collected, maxDepth, currentDepth) {
|
|
3267
|
-
if (currentDepth >= maxDepth) return;
|
|
3268
|
-
if (collected.has(filePath)) return;
|
|
3269
|
-
if (!existsSync2(filePath)) return;
|
|
3270
|
-
collected.add(filePath);
|
|
3271
|
-
try {
|
|
3272
|
-
const content = await readFile2(filePath, "utf8");
|
|
3273
|
-
const imports = extractImportPaths(content);
|
|
3274
|
-
for (const importPath of imports) {
|
|
3275
|
-
if (!importPath.startsWith(".") && !importPath.startsWith("/")) {
|
|
3276
|
-
continue;
|
|
3277
|
-
}
|
|
3278
|
-
const resolvedPath = resolveImportPath(importPath, dirname2(filePath));
|
|
3279
|
-
if (resolvedPath && existsSync2(resolvedPath)) {
|
|
3280
|
-
await collectLocalDependencies(resolvedPath, baseDir, collected, maxDepth, currentDepth + 1);
|
|
3281
|
-
}
|
|
3282
|
-
}
|
|
3283
|
-
} catch {
|
|
3284
|
-
}
|
|
3285
|
-
}
|
|
3286
|
-
function extractImportPaths(source) {
|
|
3287
|
-
const paths = [];
|
|
3288
|
-
const importRegex = /import\s+(?:[^'"]+\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
3289
|
-
let match;
|
|
3290
|
-
while ((match = importRegex.exec(source)) !== null) {
|
|
3291
|
-
paths.push(match[1]);
|
|
3292
|
-
}
|
|
3293
|
-
const exportRegex = /export\s+(?:\*|{[^}]+})\s+from\s+['"]([^'"]+)['"]/g;
|
|
3294
|
-
while ((match = exportRegex.exec(source)) !== null) {
|
|
3295
|
-
paths.push(match[1]);
|
|
3296
|
-
}
|
|
3297
|
-
const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
3298
|
-
while ((match = requireRegex.exec(source)) !== null) {
|
|
3299
|
-
paths.push(match[1]);
|
|
3300
|
-
}
|
|
3301
|
-
const dynamicRegex = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
3302
|
-
while ((match = dynamicRegex.exec(source)) !== null) {
|
|
3303
|
-
paths.push(match[1]);
|
|
3304
|
-
}
|
|
3305
|
-
return [...new Set(paths)];
|
|
3306
|
-
}
|
|
3307
|
-
function resolveImportPath(importPath, fromDir) {
|
|
3308
|
-
const extensions = ["", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
3309
|
-
for (const ext of extensions) {
|
|
3310
|
-
const fullPath = join2(fromDir, importPath + ext);
|
|
3311
|
-
if (existsSync2(fullPath)) {
|
|
3312
|
-
return fullPath;
|
|
3313
|
-
}
|
|
3314
|
-
}
|
|
3315
|
-
for (const ext of extensions) {
|
|
3316
|
-
const indexPath = join2(fromDir, importPath, `index${ext}`);
|
|
3317
|
-
if (existsSync2(indexPath)) {
|
|
3318
|
-
return indexPath;
|
|
3319
|
-
}
|
|
3320
|
-
}
|
|
3321
|
-
return void 0;
|
|
3322
|
-
}
|
|
3323
|
-
function sortedObject(obj) {
|
|
3324
|
-
const sorted = {};
|
|
3325
|
-
const keys = Object.keys(obj).sort();
|
|
3326
|
-
for (const key of keys) {
|
|
3327
|
-
const value = obj[key];
|
|
3328
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
3329
|
-
sorted[key] = sortedObject(value);
|
|
3330
|
-
} else {
|
|
3331
|
-
sorted[key] = value;
|
|
3332
|
-
}
|
|
3333
|
-
}
|
|
3334
|
-
return sorted;
|
|
3335
|
-
}
|
|
3336
|
-
function generateBuildId() {
|
|
3337
|
-
const timestamp = Date.now().toString(36);
|
|
3338
|
-
const random = Math.random().toString(36).substring(2, 10);
|
|
3339
|
-
return `${timestamp}-${random}`;
|
|
3340
|
-
}
|
|
3341
|
-
function buildIdFromHash(hash) {
|
|
3342
|
-
return hash.substring(0, 12);
|
|
3343
|
-
}
|
|
3344
|
-
|
|
3345
|
-
// libs/ui/src/bundler/file-cache/component-builder.ts
|
|
3346
|
-
import { readFile as readFile3 } from "fs/promises";
|
|
3347
|
-
import { existsSync as existsSync3 } from "fs";
|
|
3348
|
-
import { resolve as resolve2, extname } from "path";
|
|
3349
|
-
import { randomUUID } from "crypto";
|
|
3350
|
-
import { DependencyResolver, createImportMap, generateDependencyHTML } from "@frontmcp/uipack/dependency";
|
|
3351
|
-
var ComponentBuilder = class {
|
|
3352
|
-
storage;
|
|
3353
|
-
esbuild = null;
|
|
3354
|
-
constructor(storage) {
|
|
3355
|
-
this.storage = storage;
|
|
3356
|
-
}
|
|
3357
|
-
/**
|
|
3358
|
-
* Build a component from a file path.
|
|
3359
|
-
*/
|
|
3360
|
-
async build(options) {
|
|
3361
|
-
const startTime = performance.now();
|
|
3362
|
-
const {
|
|
3363
|
-
entryPath,
|
|
3364
|
-
toolName,
|
|
3365
|
-
externals = [],
|
|
3366
|
-
dependencies = {},
|
|
3367
|
-
bundleOptions = {},
|
|
3368
|
-
platform = "unknown",
|
|
3369
|
-
skipCache = false,
|
|
3370
|
-
ssr = false,
|
|
3371
|
-
ssrContext = {},
|
|
3372
|
-
executeCode: executeCode3
|
|
3373
|
-
} = options;
|
|
3374
|
-
const absoluteEntryPath = resolve2(entryPath);
|
|
3375
|
-
if (!existsSync3(absoluteEntryPath)) {
|
|
3376
|
-
throw new Error(`Entry file not found: ${absoluteEntryPath}`);
|
|
3377
|
-
}
|
|
3378
|
-
const hashResult = await calculateComponentHash({
|
|
3379
|
-
entryPath: absoluteEntryPath,
|
|
3380
|
-
externals,
|
|
3381
|
-
dependencies,
|
|
3382
|
-
bundleOptions
|
|
3383
|
-
});
|
|
3384
|
-
if (!skipCache) {
|
|
3385
|
-
const cached = await this.storage.get(hashResult.hash);
|
|
3386
|
-
if (cached) {
|
|
3387
|
-
return {
|
|
3388
|
-
manifest: cached,
|
|
3389
|
-
cached: true,
|
|
3390
|
-
buildTimeMs: performance.now() - startTime
|
|
3391
|
-
};
|
|
3392
|
-
}
|
|
3393
|
-
}
|
|
3394
|
-
const source = await readFile3(absoluteEntryPath, "utf8");
|
|
3395
|
-
const resolver = new DependencyResolver({ platform });
|
|
3396
|
-
const resolvedDeps = [];
|
|
3397
|
-
for (const pkg of externals) {
|
|
3398
|
-
try {
|
|
3399
|
-
const override = dependencies[pkg];
|
|
3400
|
-
const resolved = resolver.resolve(pkg, override);
|
|
3401
|
-
if (resolved) {
|
|
3402
|
-
resolvedDeps.push(resolved);
|
|
3403
|
-
}
|
|
3404
|
-
} catch (error) {
|
|
3405
|
-
console.warn(`Failed to resolve external "${pkg}": ${error}`);
|
|
3406
|
-
}
|
|
3407
|
-
}
|
|
3408
|
-
const allExternals = new Set(externals);
|
|
3409
|
-
for (const dep of resolvedDeps) {
|
|
3410
|
-
const entry = resolver.getRegistry()[dep.packageName];
|
|
3411
|
-
if (entry?.providers) {
|
|
3412
|
-
const providerConfig = Object.values(entry.providers)[0];
|
|
3413
|
-
if (providerConfig?.peerDependencies) {
|
|
3414
|
-
for (const peer of providerConfig.peerDependencies) {
|
|
3415
|
-
if (!allExternals.has(peer)) {
|
|
3416
|
-
allExternals.add(peer);
|
|
3417
|
-
try {
|
|
3418
|
-
const peerOverride = dependencies[peer];
|
|
3419
|
-
const resolved = resolver.resolve(peer, peerOverride);
|
|
3420
|
-
if (resolved) {
|
|
3421
|
-
resolvedDeps.push(resolved);
|
|
3422
|
-
}
|
|
3423
|
-
} catch {
|
|
3424
|
-
}
|
|
3425
|
-
}
|
|
3426
|
-
}
|
|
3427
|
-
}
|
|
3428
|
-
}
|
|
3429
|
-
}
|
|
3430
|
-
const importMap = createImportMap(resolvedDeps);
|
|
3431
|
-
const bundleResult = await this.bundleComponent({
|
|
3432
|
-
source,
|
|
3433
|
-
entryPath: absoluteEntryPath,
|
|
3434
|
-
externals: Array.from(allExternals),
|
|
3435
|
-
bundleOptions
|
|
3436
|
-
});
|
|
3437
|
-
let ssrHtml;
|
|
3438
|
-
if (ssr) {
|
|
3439
|
-
ssrHtml = await this.renderSSR(bundleResult.code, ssrContext, resolvedDeps, executeCode3);
|
|
3440
|
-
}
|
|
3441
|
-
const manifest = {
|
|
3442
|
-
version: "1.0",
|
|
3443
|
-
buildId: randomUUID(),
|
|
3444
|
-
toolName,
|
|
3445
|
-
entryPath: absoluteEntryPath,
|
|
3446
|
-
contentHash: hashResult.hash,
|
|
3447
|
-
dependencies: resolvedDeps,
|
|
3448
|
-
outputs: {
|
|
3449
|
-
code: bundleResult.code,
|
|
3450
|
-
sourceMap: bundleResult.map,
|
|
3451
|
-
ssrHtml
|
|
3452
|
-
},
|
|
3453
|
-
importMap,
|
|
3454
|
-
metadata: {
|
|
3455
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3456
|
-
buildTimeMs: performance.now() - startTime,
|
|
3457
|
-
totalSize: Buffer.byteLength(bundleResult.code, "utf8"),
|
|
3458
|
-
bundlerVersion: bundleResult.bundlerVersion
|
|
3459
|
-
}
|
|
3460
|
-
};
|
|
3461
|
-
await this.storage.set(hashResult.hash, manifest);
|
|
3462
|
-
return {
|
|
3463
|
-
manifest,
|
|
3464
|
-
cached: false,
|
|
3465
|
-
buildTimeMs: performance.now() - startTime
|
|
3466
|
-
};
|
|
3467
|
-
}
|
|
3468
|
-
/**
|
|
3469
|
-
* Build multiple components.
|
|
3470
|
-
*/
|
|
3471
|
-
async buildMany(options) {
|
|
3472
|
-
return Promise.all(options.map((opt) => this.build(opt)));
|
|
3473
|
-
}
|
|
3474
|
-
/**
|
|
3475
|
-
* Check if a component needs rebuilding.
|
|
3476
|
-
*/
|
|
3477
|
-
async needsRebuild(options) {
|
|
3478
|
-
const absoluteEntryPath = resolve2(options.entryPath);
|
|
3479
|
-
const hashResult = await calculateComponentHash({
|
|
3480
|
-
entryPath: absoluteEntryPath,
|
|
3481
|
-
externals: options.externals,
|
|
3482
|
-
dependencies: options.dependencies,
|
|
3483
|
-
bundleOptions: options.bundleOptions
|
|
3484
|
-
});
|
|
3485
|
-
const cached = await this.storage.has(hashResult.hash);
|
|
3486
|
-
return !cached;
|
|
3487
|
-
}
|
|
3488
|
-
/**
|
|
3489
|
-
* Get a cached build if it exists.
|
|
3490
|
-
*/
|
|
3491
|
-
async getCached(options) {
|
|
3492
|
-
const absoluteEntryPath = resolve2(options.entryPath);
|
|
3493
|
-
const hashResult = await calculateComponentHash({
|
|
3494
|
-
entryPath: absoluteEntryPath,
|
|
3495
|
-
externals: options.externals,
|
|
3496
|
-
dependencies: options.dependencies,
|
|
3497
|
-
bundleOptions: options.bundleOptions
|
|
3498
|
-
});
|
|
3499
|
-
return this.storage.get(hashResult.hash);
|
|
3500
|
-
}
|
|
3501
|
-
/**
|
|
3502
|
-
* Invalidate a cached build.
|
|
3503
|
-
*/
|
|
3504
|
-
async invalidate(contentHash) {
|
|
3505
|
-
return this.storage.delete(contentHash);
|
|
3506
|
-
}
|
|
3507
|
-
/**
|
|
3508
|
-
* Generate complete HTML for a built component.
|
|
3509
|
-
*/
|
|
3510
|
-
generateHTML(manifest, minify = false) {
|
|
3511
|
-
const parts = [];
|
|
3512
|
-
const dependencyHtml = generateDependencyHTML(manifest.dependencies, { minify });
|
|
3513
|
-
parts.push(dependencyHtml);
|
|
3514
|
-
parts.push(`<script type="module">${manifest.outputs.code}</script>`);
|
|
3515
|
-
return parts.join(minify ? "" : "\n");
|
|
3516
|
-
}
|
|
3517
|
-
/**
|
|
3518
|
-
* Bundle a component using esbuild.
|
|
3519
|
-
*/
|
|
3520
|
-
async bundleComponent(options) {
|
|
3521
|
-
const { source, entryPath, externals, bundleOptions } = options;
|
|
3522
|
-
if (!this.esbuild) {
|
|
3523
|
-
try {
|
|
3524
|
-
this.esbuild = await import("esbuild");
|
|
3525
|
-
} catch {
|
|
3526
|
-
throw new Error("esbuild is required for component building. Install with: npm install esbuild");
|
|
3527
|
-
}
|
|
3528
|
-
}
|
|
3529
|
-
const ext = extname(entryPath).toLowerCase();
|
|
3530
|
-
const loader = ext === ".tsx" ? "tsx" : ext === ".ts" ? "ts" : ext === ".jsx" ? "jsx" : "js";
|
|
3531
|
-
try {
|
|
3532
|
-
const result = await this.esbuild.transform(source, {
|
|
3533
|
-
loader,
|
|
3534
|
-
format: "esm",
|
|
3535
|
-
minify: bundleOptions.minify ?? process.env["NODE_ENV"] === "production",
|
|
3536
|
-
sourcemap: bundleOptions.sourceMaps ? "inline" : false,
|
|
3537
|
-
target: bundleOptions.target ?? "es2020",
|
|
3538
|
-
treeShaking: bundleOptions.treeShake ?? true,
|
|
3539
|
-
jsx: "automatic",
|
|
3540
|
-
jsxImportSource: bundleOptions.jsxImportSource ?? "react",
|
|
3541
|
-
// Mark externals for later import map resolution
|
|
3542
|
-
banner: externals.length > 0 ? `/* externals: ${externals.join(", ")} */` : void 0
|
|
3543
|
-
});
|
|
3544
|
-
return {
|
|
3545
|
-
code: result.code,
|
|
3546
|
-
map: result.map || void 0,
|
|
3547
|
-
bundlerVersion: this.esbuild.version
|
|
3548
|
-
};
|
|
3549
|
-
} catch (error) {
|
|
3550
|
-
throw new Error(`Bundle failed for ${entryPath}: ${error}`);
|
|
3551
|
-
}
|
|
3552
|
-
}
|
|
3553
|
-
/**
|
|
3554
|
-
* Perform server-side rendering.
|
|
3555
|
-
*/
|
|
3556
|
-
async renderSSR(code, context, dependencies, executeCode3) {
|
|
3557
|
-
const hasReact = dependencies.some((d) => d.packageName === "react");
|
|
3558
|
-
if (!hasReact) {
|
|
3559
|
-
console.warn("SSR requires React as an external dependency");
|
|
3560
|
-
return void 0;
|
|
3561
|
-
}
|
|
3562
|
-
try {
|
|
3563
|
-
const React = await import("react");
|
|
3564
|
-
const ReactDOMServer = await import("react-dom/server");
|
|
3565
|
-
const exports = {};
|
|
3566
|
-
const module = { exports };
|
|
3567
|
-
if (executeCode3) {
|
|
3568
|
-
executeCode3(code, exports, module, React);
|
|
3569
|
-
} else {
|
|
3570
|
-
const fn = new Function("exports", "module", "React", code);
|
|
3571
|
-
fn(exports, module, React);
|
|
3572
|
-
}
|
|
3573
|
-
const Component = module.exports.default || module.exports;
|
|
3574
|
-
if (typeof Component !== "function") {
|
|
3575
|
-
console.warn("SSR: No default component export found");
|
|
3576
|
-
return void 0;
|
|
3577
|
-
}
|
|
3578
|
-
const element = React.createElement(Component, context);
|
|
3579
|
-
return ReactDOMServer.renderToString(element);
|
|
3580
|
-
} catch (error) {
|
|
3581
|
-
console.warn(`SSR failed: ${error}`);
|
|
3582
|
-
return void 0;
|
|
3583
|
-
}
|
|
3584
|
-
}
|
|
3585
|
-
};
|
|
3586
|
-
async function createFilesystemBuilder(cacheDir = ".frontmcp-cache/builds") {
|
|
3587
|
-
const { FilesystemStorage: FilesystemStorage2 } = await Promise.resolve().then(() => (init_filesystem(), filesystem_exports));
|
|
3588
|
-
const storage = new FilesystemStorage2({ cacheDir });
|
|
3589
|
-
await storage.initialize();
|
|
3590
|
-
return new ComponentBuilder(storage);
|
|
3591
|
-
}
|
|
3592
|
-
async function createRedisBuilder(redisClient, keyPrefix = "frontmcp:ui:build:") {
|
|
3593
|
-
const { RedisStorage: RedisStorage2 } = await Promise.resolve().then(() => (init_redis(), redis_exports));
|
|
3594
|
-
const storage = new RedisStorage2({
|
|
3595
|
-
client: redisClient,
|
|
3596
|
-
keyPrefix
|
|
3597
|
-
});
|
|
3598
|
-
await storage.initialize();
|
|
3599
|
-
return new ComponentBuilder(storage);
|
|
3600
|
-
}
|
|
3065
|
+
// libs/ui/src/bundler/index.ts
|
|
3066
|
+
import { BundlerCache as BundlerCache2, hashContent as hashContent2, createCacheKey as createCacheKey2 } from "@frontmcp/uipack/bundler";
|
|
3067
|
+
import {
|
|
3068
|
+
validateSource as validateSource2,
|
|
3069
|
+
validateImports,
|
|
3070
|
+
validateSize as validateSize2,
|
|
3071
|
+
mergePolicy as mergePolicy2,
|
|
3072
|
+
throwOnViolations as throwOnViolations2
|
|
3073
|
+
} from "@frontmcp/uipack/bundler";
|
|
3074
|
+
import { executeCode, executeDefault as executeDefault2, isExecutionError } from "@frontmcp/uipack/bundler";
|
|
3075
|
+
import {
|
|
3076
|
+
DEFAULT_STORAGE_OPTIONS,
|
|
3077
|
+
calculateManifestSize,
|
|
3078
|
+
FilesystemStorage,
|
|
3079
|
+
createFilesystemStorage,
|
|
3080
|
+
RedisStorage,
|
|
3081
|
+
createRedisStorage,
|
|
3082
|
+
sha256,
|
|
3083
|
+
sha256Buffer,
|
|
3084
|
+
hashFile,
|
|
3085
|
+
hashFiles,
|
|
3086
|
+
calculateComponentHash,
|
|
3087
|
+
calculateQuickHash,
|
|
3088
|
+
generateBuildId,
|
|
3089
|
+
buildIdFromHash,
|
|
3090
|
+
ComponentBuilder,
|
|
3091
|
+
createFilesystemBuilder,
|
|
3092
|
+
createRedisBuilder
|
|
3093
|
+
} from "@frontmcp/uipack/bundler";
|
|
3601
3094
|
export {
|
|
3602
|
-
|
|
3095
|
+
ALL_PLATFORMS,
|
|
3096
|
+
BundlerCache2 as BundlerCache,
|
|
3603
3097
|
ComponentBuilder,
|
|
3604
3098
|
DEFAULT_BUNDLER_OPTIONS,
|
|
3605
3099
|
DEFAULT_BUNDLE_OPTIONS,
|
|
3606
3100
|
DEFAULT_SECURITY_POLICY,
|
|
3607
3101
|
DEFAULT_STATIC_HTML_OPTIONS,
|
|
3608
3102
|
DEFAULT_STORAGE_OPTIONS,
|
|
3609
|
-
ExecutionError,
|
|
3103
|
+
ExecutionError2 as ExecutionError,
|
|
3610
3104
|
FilesystemStorage,
|
|
3105
|
+
HYBRID_DATA_PLACEHOLDER,
|
|
3106
|
+
HYBRID_INPUT_PLACEHOLDER,
|
|
3611
3107
|
InMemoryBundler,
|
|
3612
3108
|
RedisStorage,
|
|
3613
3109
|
STATIC_HTML_CDN,
|
|
@@ -3617,24 +3113,24 @@ export {
|
|
|
3617
3113
|
calculateManifestSize,
|
|
3618
3114
|
calculateQuickHash,
|
|
3619
3115
|
createBundler,
|
|
3620
|
-
createCacheKey,
|
|
3116
|
+
createCacheKey2 as createCacheKey,
|
|
3621
3117
|
createFilesystemBuilder,
|
|
3622
3118
|
createFilesystemStorage,
|
|
3623
3119
|
createRedisBuilder,
|
|
3624
3120
|
createRedisStorage,
|
|
3625
3121
|
executeCode,
|
|
3626
|
-
executeDefault,
|
|
3122
|
+
executeDefault2 as executeDefault,
|
|
3627
3123
|
generateBuildId,
|
|
3628
3124
|
getCdnTypeForPlatform,
|
|
3629
|
-
hashContent,
|
|
3125
|
+
hashContent2 as hashContent,
|
|
3630
3126
|
hashFile,
|
|
3631
3127
|
hashFiles,
|
|
3632
3128
|
isExecutionError,
|
|
3633
|
-
mergePolicy,
|
|
3129
|
+
mergePolicy2 as mergePolicy,
|
|
3634
3130
|
sha256,
|
|
3635
3131
|
sha256Buffer,
|
|
3636
|
-
throwOnViolations,
|
|
3132
|
+
throwOnViolations2 as throwOnViolations,
|
|
3637
3133
|
validateImports,
|
|
3638
|
-
validateSize,
|
|
3639
|
-
validateSource
|
|
3134
|
+
validateSize2 as validateSize,
|
|
3135
|
+
validateSource2 as validateSource
|
|
3640
3136
|
};
|