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