sunpeak 0.13.4 → 0.13.6
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/bin/commands/deploy.mjs +2 -1
- package/bin/commands/pull.mjs +12 -8
- package/bin/commands/push.mjs +26 -10
- package/dist/chatgpt/iframe-resource.d.ts +6 -1
- package/dist/chatgpt/index.cjs +2 -2
- package/dist/chatgpt/index.js +2 -2
- package/dist/chatgpt/simulator-url.d.ts +1 -7
- package/dist/{discovery-C7SIp_GP.js → discovery-COZUnY6a.js} +3 -6
- package/dist/{discovery-C7SIp_GP.js.map → discovery-COZUnY6a.js.map} +1 -1
- package/dist/{discovery-B9YsEQjv.cjs → discovery-CRR3SlyI.cjs} +3 -6
- package/dist/{discovery-B9YsEQjv.cjs.map → discovery-CRR3SlyI.cjs.map} +1 -1
- package/dist/{index-CCt2XfkG.cjs → index-B_In_BWg.cjs} +40 -12
- package/dist/{index-CCt2XfkG.cjs.map → index-B_In_BWg.cjs.map} +1 -1
- package/dist/{index-BlyJVyBA.js → index-CkEAx7FS.js} +39 -11
- package/dist/{index-BlyJVyBA.js.map → index-CkEAx7FS.js.map} +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.js +3 -3
- package/dist/lib/discovery-cli.cjs +1 -1
- package/dist/lib/discovery-cli.js +1 -1
- package/dist/types/runtime.d.ts +2 -4
- package/package.json +2 -2
- package/template/dist/albums/albums.json +1 -1
- package/template/dist/carousel/carousel.json +1 -1
- package/template/dist/map/map.json +1 -1
- package/template/dist/review/review.json +1 -1
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_SegmentedControl.js +3 -3
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Select.js +9 -9
- package/template/node_modules/.vite/deps/_metadata.json +37 -37
- package/template/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/template/playwright.config.ts +6 -1
- package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/@modelcontextprotocol_ext-apps.js +0 -0
- package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/@modelcontextprotocol_ext-apps.js.map +0 -0
- package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/@modelcontextprotocol_ext-apps_app-bridge.js +0 -0
- package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/@modelcontextprotocol_ext-apps_app-bridge.js.map +0 -0
- package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/@modelcontextprotocol_ext-apps_react.js +3 -3
- package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/@modelcontextprotocol_ext-apps_react.js.map +0 -0
- package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/@openai_apps-sdk-ui_components_Avatar.js +0 -0
- package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/@openai_apps-sdk-ui_components_Avatar.js.map +0 -0
- package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/@openai_apps-sdk-ui_components_Button.js +0 -0
- package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/@openai_apps-sdk-ui_components_Button.js.map +0 -0
- package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/@openai_apps-sdk-ui_components_Checkbox.js +1 -1
- package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/@openai_apps-sdk-ui_components_Checkbox.js.map +0 -0
- package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/@openai_apps-sdk-ui_components_Icon.js +0 -0
- package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/@openai_apps-sdk-ui_components_Icon.js.map +0 -0
- package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/@openai_apps-sdk-ui_components_Input.js +0 -0
- package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/@openai_apps-sdk-ui_components_Input.js.map +0 -0
- package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/@openai_apps-sdk-ui_components_SegmentedControl.js +1 -1
- package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/@openai_apps-sdk-ui_components_SegmentedControl.js.map +0 -0
- package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/@openai_apps-sdk-ui_components_Select.js +4 -4
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/@openai_apps-sdk-ui_components_Select.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/@openai_apps-sdk-ui_components_Textarea.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/@openai_apps-sdk-ui_components_Textarea.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/@openai_apps-sdk-ui_theme.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/@openai_apps-sdk-ui_theme.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-3FUH6LFP.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-3FUH6LFP.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-4EQ7FTMQ.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-4EQ7FTMQ.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-4WVD247F.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-4WVD247F.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-ABGJ7IDC.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-ABGJ7IDC.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-DP4XHQAG.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-DP4XHQAG.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-EGRHWZRV.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-EGRHWZRV.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-EHI2XMPP.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-EHI2XMPP.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-JWMBYPFX.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-JWMBYPFX.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-PZDCUP6P.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-PZDCUP6P.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-Q2RBUOJ3.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-Q2RBUOJ3.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-SPDZ46BB.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-SPDZ46BB.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-WEIC4XKX.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-WEIC4XKX.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-WSHFT23M.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-WSHFT23M.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-XQARMNNG.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/chunk-XQARMNNG.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/clsx.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/clsx.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/embla-carousel-react.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/embla-carousel-react.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/embla-carousel-wheel-gestures.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/embla-carousel-wheel-gestures.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/mapbox-gl.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/mapbox-gl.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/package.json +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/react-dom.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/react-dom.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/react-dom_client.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/react-dom_client.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/react.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/react.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/react_jsx-dev-runtime.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/react_jsx-dev-runtime.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/react_jsx-runtime.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/react_jsx-runtime.js.map +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/tailwind-merge.js +0 -0
- /package/template/node_modules/.vite-mcp/{deps_temp_59db64d1 → deps_temp_170f8fb8}/tailwind-merge.js.map +0 -0
package/bin/commands/deploy.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { join } from 'path';
|
|
2
3
|
import { push, findResources, defaultDeps as pushDefaultDeps } from './push.mjs';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -60,7 +61,7 @@ This command is equivalent to: sunpeak push --tag prod
|
|
|
60
61
|
|
|
61
62
|
// If no specific directory provided, check current directory first, then dist/
|
|
62
63
|
if (!deployOptions.dir) {
|
|
63
|
-
const cwdResources = findResources(projectRoot, d);
|
|
64
|
+
const cwdResources = findResources(projectRoot, join(projectRoot, 'tests/simulations'), d);
|
|
64
65
|
if (cwdResources.length > 0) {
|
|
65
66
|
// Found resources in current directory, push each one
|
|
66
67
|
for (const resource of cwdResources) {
|
package/bin/commands/pull.mjs
CHANGED
|
@@ -187,20 +187,24 @@ Examples:
|
|
|
187
187
|
d.writeFileSync(outputFile, htmlContent);
|
|
188
188
|
d.console.log(` ✓ Saved ${resource.name}.html`);
|
|
189
189
|
|
|
190
|
-
// Write metadata JSON
|
|
190
|
+
// Write metadata JSON (reconstruct McpUiResourceMeta from server flat fields)
|
|
191
|
+
const ui = { domain: resource.widget_domain };
|
|
192
|
+
const csp = {};
|
|
193
|
+
if (resource.widget_csp_connect_domains?.length) csp.connectDomains = resource.widget_csp_connect_domains;
|
|
194
|
+
if (resource.widget_csp_resource_domains?.length) csp.resourceDomains = resource.widget_csp_resource_domains;
|
|
195
|
+
if (resource.widget_csp_frame_domains?.length) csp.frameDomains = resource.widget_csp_frame_domains;
|
|
196
|
+
if (resource.widget_csp_base_uri_domains?.length) csp.baseUriDomains = resource.widget_csp_base_uri_domains;
|
|
197
|
+
if (Object.keys(csp).length > 0) ui.csp = csp;
|
|
198
|
+
if (resource.permissions) ui.permissions = resource.permissions;
|
|
199
|
+
if (resource.prefers_border != null) ui.prefersBorder = resource.prefers_border;
|
|
200
|
+
|
|
191
201
|
const meta = {
|
|
192
202
|
uri: resource.uri,
|
|
193
203
|
name: resource.name,
|
|
194
204
|
title: resource.title,
|
|
195
205
|
description: resource.description,
|
|
196
206
|
mimeType: resource.mime_type,
|
|
197
|
-
_meta: {
|
|
198
|
-
'openai/widgetDomain': resource.widget_domain,
|
|
199
|
-
'openai/widgetCSP': {
|
|
200
|
-
connect_domains: resource.widget_csp_connect_domains || [],
|
|
201
|
-
resource_domains: resource.widget_csp_resource_domains || [],
|
|
202
|
-
},
|
|
203
|
-
},
|
|
207
|
+
_meta: { ui },
|
|
204
208
|
};
|
|
205
209
|
d.writeFileSync(metaFile, JSON.stringify(meta, null, 2));
|
|
206
210
|
d.console.log(` ✓ Saved ${resource.name}.json\n`);
|
package/bin/commands/push.mjs
CHANGED
|
@@ -203,23 +203,39 @@ async function pushResource(resource, repository, tags, accessToken, deps = defa
|
|
|
203
203
|
formData.append('mime_type', resource.meta.mimeType || 'text/html+skybridge');
|
|
204
204
|
formData.append('uri', resource.meta.uri);
|
|
205
205
|
|
|
206
|
-
// Handle
|
|
207
|
-
if (resource.meta._meta) {
|
|
208
|
-
|
|
209
|
-
|
|
206
|
+
// Handle UI resource metadata (_meta.ui matches McpUiResourceMeta from ext-apps SDK)
|
|
207
|
+
if (resource.meta._meta?.ui) {
|
|
208
|
+
const ui = resource.meta._meta.ui;
|
|
209
|
+
if (ui.domain) {
|
|
210
|
+
formData.append('widget_domain', ui.domain);
|
|
210
211
|
}
|
|
211
|
-
if (
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
212
|
+
if (ui.prefersBorder != null) {
|
|
213
|
+
formData.append('prefers_border', String(ui.prefersBorder));
|
|
214
|
+
}
|
|
215
|
+
if (ui.permissions) {
|
|
216
|
+
formData.append('permissions', JSON.stringify(ui.permissions));
|
|
217
|
+
}
|
|
218
|
+
if (ui.csp) {
|
|
219
|
+
if (ui.csp.connectDomains) {
|
|
220
|
+
ui.csp.connectDomains.forEach((domain) => {
|
|
215
221
|
formData.append('widget_csp_connect_domains[]', domain);
|
|
216
222
|
});
|
|
217
223
|
}
|
|
218
|
-
if (csp.
|
|
219
|
-
csp.
|
|
224
|
+
if (ui.csp.resourceDomains) {
|
|
225
|
+
ui.csp.resourceDomains.forEach((domain) => {
|
|
220
226
|
formData.append('widget_csp_resource_domains[]', domain);
|
|
221
227
|
});
|
|
222
228
|
}
|
|
229
|
+
if (ui.csp.frameDomains) {
|
|
230
|
+
ui.csp.frameDomains.forEach((domain) => {
|
|
231
|
+
formData.append('widget_csp_frame_domains[]', domain);
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
if (ui.csp.baseUriDomains) {
|
|
235
|
+
ui.csp.baseUriDomains.forEach((domain) => {
|
|
236
|
+
formData.append('widget_csp_base_uri_domains[]', domain);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
223
239
|
}
|
|
224
240
|
}
|
|
225
241
|
} else {
|
|
@@ -20,6 +20,11 @@ export interface ResourceCSP {
|
|
|
20
20
|
/** Domains allowed for scripts, images, styles, fonts */
|
|
21
21
|
resourceDomains?: string[];
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Validates a CSP source entry is a safe origin URL (scheme + host + optional port).
|
|
25
|
+
* Rejects wildcards, CSP keywords, and whitespace that could inject extra directives.
|
|
26
|
+
*/
|
|
27
|
+
declare function isValidCspSource(source: string): boolean;
|
|
23
28
|
/**
|
|
24
29
|
* Generates a Content Security Policy string.
|
|
25
30
|
*/
|
|
@@ -85,7 +90,7 @@ export declare function IframeResource({ src, scriptSrc, hostContext, toolInput,
|
|
|
85
90
|
export declare const _testExports: {
|
|
86
91
|
escapeHtml: typeof escapeHtml;
|
|
87
92
|
isAllowedUrl: typeof isAllowedUrl;
|
|
88
|
-
|
|
93
|
+
isValidCspSource: typeof isValidCspSource;
|
|
89
94
|
generateCSP: typeof generateCSP;
|
|
90
95
|
generateScriptHtml: typeof generateScriptHtml;
|
|
91
96
|
ALLOWED_SCRIPT_ORIGINS: string[];
|
package/dist/chatgpt/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const chatgpt_index = require("../index-
|
|
4
|
-
const discovery = require("../discovery-
|
|
3
|
+
const chatgpt_index = require("../index-B_In_BWg.cjs");
|
|
4
|
+
const discovery = require("../discovery-CRR3SlyI.cjs");
|
|
5
5
|
exports.ChatGPTSimulator = chatgpt_index.ChatGPTSimulator;
|
|
6
6
|
exports.IframeResource = chatgpt_index.IframeResource;
|
|
7
7
|
exports.McpAppHost = chatgpt_index.McpAppHost;
|
package/dist/chatgpt/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C, I, M, S, T, a, u } from "../index-
|
|
2
|
-
import { a as a2, b, d, c, e, f, g, h, i, j, k, l, t } from "../discovery-
|
|
1
|
+
import { C, I, M, S, T, a, u } from "../index-CkEAx7FS.js";
|
|
2
|
+
import { a as a2, b, d, c, e, f, g, h, i, j, k, l, t } from "../discovery-COZUnY6a.js";
|
|
3
3
|
export {
|
|
4
4
|
C as ChatGPTSimulator,
|
|
5
5
|
I as IframeResource,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Theme, DisplayMode, DeviceType
|
|
1
|
+
import { Theme, DisplayMode, DeviceType } from '../types/runtime';
|
|
2
2
|
/**
|
|
3
3
|
* Strongly-typed URL parameters for the ChatGPT Simulator.
|
|
4
4
|
*
|
|
@@ -78,12 +78,6 @@ export interface SimulatorUrlParams {
|
|
|
78
78
|
* Safe area inset from the right of the screen (in pixels).
|
|
79
79
|
*/
|
|
80
80
|
safeAreaRight?: number;
|
|
81
|
-
/**
|
|
82
|
-
* The view mode for the widget.
|
|
83
|
-
* - 'modal': Display as a modal dialog
|
|
84
|
-
* - 'default': Normal display
|
|
85
|
-
*/
|
|
86
|
-
viewMode?: ViewMode;
|
|
87
81
|
}
|
|
88
82
|
/**
|
|
89
83
|
* Creates a URL path with query parameters for the ChatGPT Simulator.
|
|
@@ -91,7 +91,7 @@ function buildDevSimulations(options) {
|
|
|
91
91
|
simulationModules,
|
|
92
92
|
resourcesMap,
|
|
93
93
|
resourceComponents,
|
|
94
|
-
createSimulation: (simulationKey, simulationData, resource
|
|
94
|
+
createSimulation: (simulationKey, simulationData, resource) => {
|
|
95
95
|
const resourceKey = findResourceKey(simulationKey, Array.from(resourcesMap.keys()));
|
|
96
96
|
const componentName = resourceKey ? getComponentName(resourceKey) : "";
|
|
97
97
|
return {
|
|
@@ -102,10 +102,7 @@ function buildDevSimulations(options) {
|
|
|
102
102
|
...resource
|
|
103
103
|
},
|
|
104
104
|
// Generate URL to the resource loader with component name as query param
|
|
105
|
-
resourceUrl: `/.sunpeak/resource-loader.html?component=${componentName}
|
|
106
|
-
// Keep resourceComponent for backwards compatibility during transition
|
|
107
|
-
// but it won't be used by the simulator anymore
|
|
108
|
-
_resourceComponent: resourceComponent
|
|
105
|
+
resourceUrl: `/.sunpeak/resource-loader.html?component=${componentName}`
|
|
109
106
|
};
|
|
110
107
|
}
|
|
111
108
|
});
|
|
@@ -157,4 +154,4 @@ export {
|
|
|
157
154
|
isSimulationFile as l,
|
|
158
155
|
toPascalCase as t
|
|
159
156
|
};
|
|
160
|
-
//# sourceMappingURL=discovery-
|
|
157
|
+
//# sourceMappingURL=discovery-COZUnY6a.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discovery-C7SIp_GP.js","sources":["../src/lib/discovery.ts"],"sourcesContent":["/**\n * Discovery utilities for auto-discovering resources and simulations\n *\n * These helpers process the results of import.meta.glob() calls to extract\n * keys, build component maps, and connect simulations to resources.\n *\n * The glob calls themselves must remain in the template (Vite compile-time),\n * but all the processing logic lives here for easy updates across templates.\n *\n * Node.js utilities (findResourceDirs, isSimulationFile, etc.) can be used\n * by CLI commands for build-time and runtime discovery.\n */\n\nimport type { Simulation } from '../types/simulation.js';\n\n/**\n * Convert a kebab-case string to PascalCase\n * @example toPascalCase('review') // 'Review'\n * @example toPascalCase('album-art') // 'AlbumArt'\n */\nexport function toPascalCase(str: string): string {\n return str\n .split('-')\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join('');\n}\n\n/**\n * Extract the resource key from a resource file path\n * @example extractResourceKey('./review-resource.tsx') // 'review'\n * @example extractResourceKey('../src/resources/albums-resource.tsx') // 'albums'\n */\nexport function extractResourceKey(path: string): string | undefined {\n const match = path.match(/([^/]+)-resource\\.(tsx|json)$/);\n return match?.[1];\n}\n\n/**\n * Extract the simulation key from a simulation file path\n * @example extractSimulationKey('./albums-show-simulation.json') // 'albums-show'\n */\nexport function extractSimulationKey(path: string): string | undefined {\n const match = path.match(/([^/]+)-simulation\\.json$/);\n return match?.[1];\n}\n\n/**\n * Find the best matching resource key for a simulation key.\n * Matches the longest resource name that is a prefix of the simulation key.\n * @example findResourceKey('albums-show', ['albums', 'album']) // 'albums'\n * @example findResourceKey('review-diff', ['review', 'carousel']) // 'review'\n */\nexport function findResourceKey(simulationKey: string, resourceKeys: string[]): string | undefined {\n // Sort by length descending to find longest match first\n const sorted = [...resourceKeys].sort((a, b) => b.length - a.length);\n for (const resourceKey of sorted) {\n if (simulationKey === resourceKey || simulationKey.startsWith(resourceKey + '-')) {\n return resourceKey;\n }\n }\n return undefined;\n}\n\n/**\n * Get the expected component export name for a resource\n * @example getComponentName('review') // 'ReviewResource'\n * @example getComponentName('album-art') // 'AlbumArtResource'\n */\nexport function getComponentName(resourceKey: string): string {\n return `${toPascalCase(resourceKey)}Resource`;\n}\n\n// --- Glob processing helpers ---\n\ntype GlobModules = Record<string, unknown>;\n\n/**\n * Process resource component modules from import.meta.glob() result.\n * Extracts components and exports them with PascalCase names.\n *\n * @example\n * const modules = import.meta.glob('./**\\/*-resource.tsx', { eager: true });\n * export default createResourceExports(modules);\n */\nexport function createResourceExports(modules: GlobModules): Record<string, React.ComponentType> {\n const resources: Record<string, React.ComponentType> = {};\n\n for (const [path, module] of Object.entries(modules)) {\n const key = extractResourceKey(path);\n if (!key) continue;\n\n const exportName = getComponentName(key);\n const mod = module as Record<string, unknown>;\n\n // Try default export first, then named export matching the expected name\n const component = mod.default ?? mod[exportName];\n\n // Accept functions (regular components) or objects (forwardRef/memo components)\n if (component && (typeof component === 'function' || typeof component === 'object')) {\n resources[exportName] = component as React.ComponentType;\n }\n }\n\n return resources;\n}\n\n/**\n * Build a resource metadata map from import.meta.glob() result.\n * Used for connecting simulations to their resource definitions.\n *\n * @example\n * const modules = import.meta.glob('../src/resources/**\\/*-resource.tsx', { eager: true });\n * const resourcesMap = buildResourceMap(modules);\n */\nexport function buildResourceMap<T>(modules: GlobModules): Map<string, T> {\n const map = new Map<string, T>();\n\n for (const [path, module] of Object.entries(modules)) {\n const key = extractResourceKey(path);\n if (key) {\n map.set(key, (module as { resource: T }).resource);\n }\n }\n\n return map;\n}\n\n/**\n * Options for building simulations from discovered modules\n */\nexport interface BuildSimulationsOptions<TResource, TSimulation> {\n /** Glob result of simulation JSON files */\n simulationModules: GlobModules;\n /** Map of resource key -> resource metadata */\n resourcesMap: Map<string, TResource>;\n /** Map of component name -> React component */\n resourceComponents: Record<string, React.ComponentType>;\n /** Create the final simulation object */\n createSimulation: (\n simulationKey: string,\n simulationData: unknown,\n resource: TResource,\n resourceComponent: React.ComponentType\n ) => TSimulation;\n /** Optional warning handler for missing resources */\n onMissingResource?: (simulationKey: string, expectedPrefix: string) => void;\n}\n\n/**\n * Build simulations by connecting simulation data with resources and components.\n * This is the main orchestration function for dev server bootstrap.\n */\nexport function buildSimulations<TResource, TSimulation>(\n options: BuildSimulationsOptions<TResource, TSimulation>\n): Record<string, TSimulation> {\n const {\n simulationModules,\n resourcesMap,\n resourceComponents,\n createSimulation,\n onMissingResource = (key, prefix) =>\n console.warn(\n `No matching resource found for simulation \"${key}\". ` +\n `Expected a resource file like src/resources/${prefix}/${prefix}-resource.tsx`\n ),\n } = options;\n\n const resourceKeys = Array.from(resourcesMap.keys());\n const simulations: Record<string, TSimulation> = {};\n\n for (const [path, module] of Object.entries(simulationModules)) {\n const simulationKey = extractSimulationKey(path);\n if (!simulationKey) continue;\n\n const simulationData = (module as { default: unknown }).default;\n\n // Find matching resource\n const resourceKey = findResourceKey(simulationKey, resourceKeys);\n if (!resourceKey) {\n onMissingResource(simulationKey, simulationKey.split('-')[0]);\n continue;\n }\n\n const resource = resourcesMap.get(resourceKey)!;\n\n // Get component\n const componentName = getComponentName(resourceKey);\n const resourceComponent = resourceComponents[componentName];\n\n if (!resourceComponent) {\n console.warn(\n `Resource component \"${componentName}\" not found for resource \"${resourceKey}\". ` +\n `Make sure src/resources/${resourceKey}/${resourceKey}-resource.tsx exists with a default export.`\n );\n continue;\n }\n\n simulations[simulationKey] = createSimulation(\n simulationKey,\n simulationData,\n resource,\n resourceComponent\n );\n }\n\n return simulations;\n}\n\n// --- Dev server helpers ---\n\n/**\n * Resource metadata from *-resource.tsx files\n */\nexport interface ResourceMetadata {\n name: string;\n [key: string]: unknown;\n}\n\n/**\n * Options for building dev simulations\n */\nexport interface BuildDevSimulationsOptions {\n /** Glob result of simulation JSON files: import.meta.glob('*-simulation.json', { eager: true }) */\n simulationModules: GlobModules;\n /** Glob result of resource JSON files: import.meta.glob('*-resource.tsx', { eager: true }) */\n resourceModules: GlobModules;\n /** Resource components map from src/resources/index.ts */\n resourceComponents: Record<string, React.ComponentType>;\n}\n\n/**\n * Build simulations for the dev server from glob results.\n * This is the main entry point for dev.tsx bootstrap.\n *\n * @example\n * const simulations = buildDevSimulations({\n * simulationModules: import.meta.glob('../src/resources/**\\/*-simulation.json', { eager: true }),\n * resourceModules: import.meta.glob('../src/resources/**\\/*-resource.tsx', { eager: true }),\n * resourceComponents: resourceComponents,\n * });\n */\nexport function buildDevSimulations(\n options: BuildDevSimulationsOptions\n): Record<string, Simulation> {\n const { simulationModules, resourceModules, resourceComponents } = options;\n\n // Build resource metadata map\n const resourcesMap = buildResourceMap<ResourceMetadata>(resourceModules);\n\n // Build simulations with the standard dev server format\n return buildSimulations<ResourceMetadata, Simulation>({\n simulationModules,\n resourcesMap,\n resourceComponents,\n createSimulation: (simulationKey, simulationData, resource, resourceComponent) => {\n // Get the component name for the resource URL\n const resourceKey = findResourceKey(simulationKey, Array.from(resourcesMap.keys()));\n const componentName = resourceKey ? getComponentName(resourceKey) : '';\n\n return {\n ...(simulationData as Omit<Simulation, 'name' | 'resourceUrl' | 'resource'>),\n name: simulationKey,\n resource: {\n uri: `ui://${resource.name}`,\n ...resource,\n },\n // Generate URL to the resource loader with component name as query param\n resourceUrl: `/.sunpeak/resource-loader.html?component=${componentName}`,\n // Keep resourceComponent for backwards compatibility during transition\n // but it won't be used by the simulator anymore\n _resourceComponent: resourceComponent,\n } as Simulation;\n },\n });\n}\n\n// --- Node.js utilities for CLI commands ---\n// These utilities use standard Node.js APIs and can be imported by build/push/mcp commands.\n\n/**\n * Information about a discovered resource directory\n */\nexport interface ResourceDirInfo {\n /** Resource key (directory name), e.g., 'albums', 'carousel' */\n key: string;\n /** Full path to the resource directory */\n dir: string;\n /** Full path to the main resource file (tsx or json depending on context) */\n resourcePath: string;\n}\n\n/**\n * File system operations interface for dependency injection in tests\n */\nexport interface FsOps {\n readdirSync: (\n path: string,\n options: { withFileTypes: true }\n ) => Array<{ name: string; isDirectory: () => boolean }>;\n existsSync: (path: string) => boolean;\n}\n\n/**\n * Find all resource directories in a base directory.\n * Each valid resource directory contains a file matching the expected pattern.\n *\n * @param baseDir - Base directory to scan (e.g., 'src/resources' or 'dist')\n * @param filePattern - Function to generate expected filename from resource key\n * @param fs - File system operations (for testing)\n *\n * @example\n * // Find source resources (tsx files)\n * const resources = findResourceDirs('src/resources', key => `${key}-resource.tsx`);\n *\n * @example\n * // Find built resources (js files)\n * const resources = findResourceDirs('dist', key => `${key}.js`);\n */\nexport function findResourceDirs(\n baseDir: string,\n filePattern: (key: string) => string,\n fs: FsOps\n): ResourceDirInfo[] {\n if (!fs.existsSync(baseDir)) {\n return [];\n }\n\n const entries = fs.readdirSync(baseDir, { withFileTypes: true });\n\n return entries\n .filter((entry) => entry.isDirectory())\n .map((entry) => {\n const key = entry.name;\n const dir = `${baseDir}/${key}`;\n const resourcePath = `${dir}/${filePattern(key)}`;\n\n if (!fs.existsSync(resourcePath)) {\n return null;\n }\n\n return { key, dir, resourcePath };\n })\n .filter((info): info is ResourceDirInfo => info !== null);\n}\n\n/**\n * Check if a filename is a simulation file for a given resource.\n * Matches pattern: {resourceKey}-*-simulation.json\n *\n * @example\n * isSimulationFile('albums-show-simulation.json', 'albums') // true\n * isSimulationFile('albums-show-simulation.json', 'carousel') // false\n * isSimulationFile('albums-resource.tsx', 'albums') // false\n */\nexport function isSimulationFile(filename: string, resourceKey: string): boolean {\n return filename.startsWith(`${resourceKey}-`) && filename.endsWith('-simulation.json');\n}\n\n/**\n * Extract the simulation name from a simulation filename.\n * Given \"{resourceKey}-{name}-simulation.json\", returns \"{name}\".\n *\n * @example\n * extractSimulationName('albums-show-simulation.json', 'albums') // 'show'\n * extractSimulationName('carousel-hero-simulation.json', 'carousel') // 'hero'\n */\nexport function extractSimulationName(filename: string, resourceKey: string): string {\n return filename.replace(`${resourceKey}-`, '').replace('-simulation.json', '');\n}\n\n/**\n * Find all simulation files in a resource directory.\n *\n * @param resourceDir - Path to the resource directory\n * @param resourceKey - Resource key (e.g., 'albums')\n * @param fs - File system operations (for testing)\n * @returns Array of { filename, name } objects\n */\nexport function findSimulationFiles(\n resourceDir: string,\n resourceKey: string,\n fs: Pick<FsOps, 'readdirSync' | 'existsSync'>\n): Array<{ filename: string; name: string; path: string }> {\n if (!fs.existsSync(resourceDir)) {\n return [];\n }\n\n const entries = fs.readdirSync(resourceDir, { withFileTypes: true });\n\n return entries\n .filter((entry) => !entry.isDirectory() && isSimulationFile(entry.name, resourceKey))\n .map((entry) => ({\n filename: entry.name,\n name: extractSimulationName(entry.name, resourceKey),\n path: `${resourceDir}/${entry.name}`,\n }));\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"discovery-COZUnY6a.js","sources":["../src/lib/discovery.ts"],"sourcesContent":["/**\n * Discovery utilities for auto-discovering resources and simulations\n *\n * These helpers process the results of import.meta.glob() calls to extract\n * keys, build component maps, and connect simulations to resources.\n *\n * The glob calls themselves must remain in the template (Vite compile-time),\n * but all the processing logic lives here for easy updates across templates.\n *\n * Node.js utilities (findResourceDirs, isSimulationFile, etc.) can be used\n * by CLI commands for build-time and runtime discovery.\n */\n\nimport type { Simulation } from '../types/simulation.js';\n\n/**\n * Convert a kebab-case string to PascalCase\n * @example toPascalCase('review') // 'Review'\n * @example toPascalCase('album-art') // 'AlbumArt'\n */\nexport function toPascalCase(str: string): string {\n return str\n .split('-')\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join('');\n}\n\n/**\n * Extract the resource key from a resource file path\n * @example extractResourceKey('./review-resource.tsx') // 'review'\n * @example extractResourceKey('../src/resources/albums-resource.tsx') // 'albums'\n */\nexport function extractResourceKey(path: string): string | undefined {\n const match = path.match(/([^/]+)-resource\\.(tsx|json)$/);\n return match?.[1];\n}\n\n/**\n * Extract the simulation key from a simulation file path\n * @example extractSimulationKey('./albums-show-simulation.json') // 'albums-show'\n */\nexport function extractSimulationKey(path: string): string | undefined {\n const match = path.match(/([^/]+)-simulation\\.json$/);\n return match?.[1];\n}\n\n/**\n * Find the best matching resource key for a simulation key.\n * Matches the longest resource name that is a prefix of the simulation key.\n * @example findResourceKey('albums-show', ['albums', 'album']) // 'albums'\n * @example findResourceKey('review-diff', ['review', 'carousel']) // 'review'\n */\nexport function findResourceKey(simulationKey: string, resourceKeys: string[]): string | undefined {\n // Sort by length descending to find longest match first\n const sorted = [...resourceKeys].sort((a, b) => b.length - a.length);\n for (const resourceKey of sorted) {\n if (simulationKey === resourceKey || simulationKey.startsWith(resourceKey + '-')) {\n return resourceKey;\n }\n }\n return undefined;\n}\n\n/**\n * Get the expected component export name for a resource\n * @example getComponentName('review') // 'ReviewResource'\n * @example getComponentName('album-art') // 'AlbumArtResource'\n */\nexport function getComponentName(resourceKey: string): string {\n return `${toPascalCase(resourceKey)}Resource`;\n}\n\n// --- Glob processing helpers ---\n\ntype GlobModules = Record<string, unknown>;\n\n/**\n * Process resource component modules from import.meta.glob() result.\n * Extracts components and exports them with PascalCase names.\n *\n * @example\n * const modules = import.meta.glob('./**\\/*-resource.tsx', { eager: true });\n * export default createResourceExports(modules);\n */\nexport function createResourceExports(modules: GlobModules): Record<string, React.ComponentType> {\n const resources: Record<string, React.ComponentType> = {};\n\n for (const [path, module] of Object.entries(modules)) {\n const key = extractResourceKey(path);\n if (!key) continue;\n\n const exportName = getComponentName(key);\n const mod = module as Record<string, unknown>;\n\n // Try default export first, then named export matching the expected name\n const component = mod.default ?? mod[exportName];\n\n // Accept functions (regular components) or objects (forwardRef/memo components)\n if (component && (typeof component === 'function' || typeof component === 'object')) {\n resources[exportName] = component as React.ComponentType;\n }\n }\n\n return resources;\n}\n\n/**\n * Build a resource metadata map from import.meta.glob() result.\n * Used for connecting simulations to their resource definitions.\n *\n * @example\n * const modules = import.meta.glob('../src/resources/**\\/*-resource.tsx', { eager: true });\n * const resourcesMap = buildResourceMap(modules);\n */\nexport function buildResourceMap<T>(modules: GlobModules): Map<string, T> {\n const map = new Map<string, T>();\n\n for (const [path, module] of Object.entries(modules)) {\n const key = extractResourceKey(path);\n if (key) {\n map.set(key, (module as { resource: T }).resource);\n }\n }\n\n return map;\n}\n\n/**\n * Options for building simulations from discovered modules\n */\nexport interface BuildSimulationsOptions<TResource, TSimulation> {\n /** Glob result of simulation JSON files */\n simulationModules: GlobModules;\n /** Map of resource key -> resource metadata */\n resourcesMap: Map<string, TResource>;\n /** Map of component name -> React component */\n resourceComponents: Record<string, React.ComponentType>;\n /** Create the final simulation object */\n createSimulation: (\n simulationKey: string,\n simulationData: unknown,\n resource: TResource,\n resourceComponent: React.ComponentType\n ) => TSimulation;\n /** Optional warning handler for missing resources */\n onMissingResource?: (simulationKey: string, expectedPrefix: string) => void;\n}\n\n/**\n * Build simulations by connecting simulation data with resources and components.\n * This is the main orchestration function for dev server bootstrap.\n */\nexport function buildSimulations<TResource, TSimulation>(\n options: BuildSimulationsOptions<TResource, TSimulation>\n): Record<string, TSimulation> {\n const {\n simulationModules,\n resourcesMap,\n resourceComponents,\n createSimulation,\n onMissingResource = (key, prefix) =>\n console.warn(\n `No matching resource found for simulation \"${key}\". ` +\n `Expected a resource file like src/resources/${prefix}/${prefix}-resource.tsx`\n ),\n } = options;\n\n const resourceKeys = Array.from(resourcesMap.keys());\n const simulations: Record<string, TSimulation> = {};\n\n for (const [path, module] of Object.entries(simulationModules)) {\n const simulationKey = extractSimulationKey(path);\n if (!simulationKey) continue;\n\n const simulationData = (module as { default: unknown }).default;\n\n // Find matching resource\n const resourceKey = findResourceKey(simulationKey, resourceKeys);\n if (!resourceKey) {\n onMissingResource(simulationKey, simulationKey.split('-')[0]);\n continue;\n }\n\n const resource = resourcesMap.get(resourceKey)!;\n\n // Get component\n const componentName = getComponentName(resourceKey);\n const resourceComponent = resourceComponents[componentName];\n\n if (!resourceComponent) {\n console.warn(\n `Resource component \"${componentName}\" not found for resource \"${resourceKey}\". ` +\n `Make sure src/resources/${resourceKey}/${resourceKey}-resource.tsx exists with a default export.`\n );\n continue;\n }\n\n simulations[simulationKey] = createSimulation(\n simulationKey,\n simulationData,\n resource,\n resourceComponent\n );\n }\n\n return simulations;\n}\n\n// --- Dev server helpers ---\n\n/**\n * Resource metadata from *-resource.tsx files\n */\nexport interface ResourceMetadata {\n name: string;\n [key: string]: unknown;\n}\n\n/**\n * Options for building dev simulations\n */\nexport interface BuildDevSimulationsOptions {\n /** Glob result of simulation JSON files: import.meta.glob('*-simulation.json', { eager: true }) */\n simulationModules: GlobModules;\n /** Glob result of resource JSON files: import.meta.glob('*-resource.tsx', { eager: true }) */\n resourceModules: GlobModules;\n /** Resource components map from src/resources/index.ts */\n resourceComponents: Record<string, React.ComponentType>;\n}\n\n/**\n * Build simulations for the dev server from glob results.\n * This is the main entry point for dev.tsx bootstrap.\n *\n * @example\n * const simulations = buildDevSimulations({\n * simulationModules: import.meta.glob('../src/resources/**\\/*-simulation.json', { eager: true }),\n * resourceModules: import.meta.glob('../src/resources/**\\/*-resource.tsx', { eager: true }),\n * resourceComponents: resourceComponents,\n * });\n */\nexport function buildDevSimulations(\n options: BuildDevSimulationsOptions\n): Record<string, Simulation> {\n const { simulationModules, resourceModules, resourceComponents } = options;\n\n // Build resource metadata map\n const resourcesMap = buildResourceMap<ResourceMetadata>(resourceModules);\n\n // Build simulations with the standard dev server format\n return buildSimulations<ResourceMetadata, Simulation>({\n simulationModules,\n resourcesMap,\n resourceComponents,\n createSimulation: (simulationKey, simulationData, resource) => {\n // Get the component name for the resource URL\n const resourceKey = findResourceKey(simulationKey, Array.from(resourcesMap.keys()));\n const componentName = resourceKey ? getComponentName(resourceKey) : '';\n\n return {\n ...(simulationData as Omit<Simulation, 'name' | 'resourceUrl' | 'resource'>),\n name: simulationKey,\n resource: {\n uri: `ui://${resource.name}`,\n ...resource,\n },\n // Generate URL to the resource loader with component name as query param\n resourceUrl: `/.sunpeak/resource-loader.html?component=${componentName}`,\n } as Simulation;\n },\n });\n}\n\n// --- Node.js utilities for CLI commands ---\n// These utilities use standard Node.js APIs and can be imported by build/push/mcp commands.\n\n/**\n * Information about a discovered resource directory\n */\nexport interface ResourceDirInfo {\n /** Resource key (directory name), e.g., 'albums', 'carousel' */\n key: string;\n /** Full path to the resource directory */\n dir: string;\n /** Full path to the main resource file (tsx or json depending on context) */\n resourcePath: string;\n}\n\n/**\n * File system operations interface for dependency injection in tests\n */\nexport interface FsOps {\n readdirSync: (\n path: string,\n options: { withFileTypes: true }\n ) => Array<{ name: string; isDirectory: () => boolean }>;\n existsSync: (path: string) => boolean;\n}\n\n/**\n * Find all resource directories in a base directory.\n * Each valid resource directory contains a file matching the expected pattern.\n *\n * @param baseDir - Base directory to scan (e.g., 'src/resources' or 'dist')\n * @param filePattern - Function to generate expected filename from resource key\n * @param fs - File system operations (for testing)\n *\n * @example\n * // Find source resources (tsx files)\n * const resources = findResourceDirs('src/resources', key => `${key}-resource.tsx`);\n *\n * @example\n * // Find built resources (js files)\n * const resources = findResourceDirs('dist', key => `${key}.js`);\n */\nexport function findResourceDirs(\n baseDir: string,\n filePattern: (key: string) => string,\n fs: FsOps\n): ResourceDirInfo[] {\n if (!fs.existsSync(baseDir)) {\n return [];\n }\n\n const entries = fs.readdirSync(baseDir, { withFileTypes: true });\n\n return entries\n .filter((entry) => entry.isDirectory())\n .map((entry) => {\n const key = entry.name;\n const dir = `${baseDir}/${key}`;\n const resourcePath = `${dir}/${filePattern(key)}`;\n\n if (!fs.existsSync(resourcePath)) {\n return null;\n }\n\n return { key, dir, resourcePath };\n })\n .filter((info): info is ResourceDirInfo => info !== null);\n}\n\n/**\n * Check if a filename is a simulation file for a given resource.\n * Matches pattern: {resourceKey}-*-simulation.json\n *\n * @example\n * isSimulationFile('albums-show-simulation.json', 'albums') // true\n * isSimulationFile('albums-show-simulation.json', 'carousel') // false\n * isSimulationFile('albums-resource.tsx', 'albums') // false\n */\nexport function isSimulationFile(filename: string, resourceKey: string): boolean {\n return filename.startsWith(`${resourceKey}-`) && filename.endsWith('-simulation.json');\n}\n\n/**\n * Extract the simulation name from a simulation filename.\n * Given \"{resourceKey}-{name}-simulation.json\", returns \"{name}\".\n *\n * @example\n * extractSimulationName('albums-show-simulation.json', 'albums') // 'show'\n * extractSimulationName('carousel-hero-simulation.json', 'carousel') // 'hero'\n */\nexport function extractSimulationName(filename: string, resourceKey: string): string {\n return filename.replace(`${resourceKey}-`, '').replace('-simulation.json', '');\n}\n\n/**\n * Find all simulation files in a resource directory.\n *\n * @param resourceDir - Path to the resource directory\n * @param resourceKey - Resource key (e.g., 'albums')\n * @param fs - File system operations (for testing)\n * @returns Array of { filename, name } objects\n */\nexport function findSimulationFiles(\n resourceDir: string,\n resourceKey: string,\n fs: Pick<FsOps, 'readdirSync' | 'existsSync'>\n): Array<{ filename: string; name: string; path: string }> {\n if (!fs.existsSync(resourceDir)) {\n return [];\n }\n\n const entries = fs.readdirSync(resourceDir, { withFileTypes: true });\n\n return entries\n .filter((entry) => !entry.isDirectory() && isSimulationFile(entry.name, resourceKey))\n .map((entry) => ({\n filename: entry.name,\n name: extractSimulationName(entry.name, resourceKey),\n path: `${resourceDir}/${entry.name}`,\n }));\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -92,7 +92,7 @@ function buildDevSimulations(options) {
|
|
|
92
92
|
simulationModules,
|
|
93
93
|
resourcesMap,
|
|
94
94
|
resourceComponents,
|
|
95
|
-
createSimulation: (simulationKey, simulationData, resource
|
|
95
|
+
createSimulation: (simulationKey, simulationData, resource) => {
|
|
96
96
|
const resourceKey = findResourceKey(simulationKey, Array.from(resourcesMap.keys()));
|
|
97
97
|
const componentName = resourceKey ? getComponentName(resourceKey) : "";
|
|
98
98
|
return {
|
|
@@ -103,10 +103,7 @@ function buildDevSimulations(options) {
|
|
|
103
103
|
...resource
|
|
104
104
|
},
|
|
105
105
|
// Generate URL to the resource loader with component name as query param
|
|
106
|
-
resourceUrl: `/.sunpeak/resource-loader.html?component=${componentName}
|
|
107
|
-
// Keep resourceComponent for backwards compatibility during transition
|
|
108
|
-
// but it won't be used by the simulator anymore
|
|
109
|
-
_resourceComponent: resourceComponent
|
|
106
|
+
resourceUrl: `/.sunpeak/resource-loader.html?component=${componentName}`
|
|
110
107
|
};
|
|
111
108
|
}
|
|
112
109
|
});
|
|
@@ -156,4 +153,4 @@ exports.findSimulationFiles = findSimulationFiles;
|
|
|
156
153
|
exports.getComponentName = getComponentName;
|
|
157
154
|
exports.isSimulationFile = isSimulationFile;
|
|
158
155
|
exports.toPascalCase = toPascalCase;
|
|
159
|
-
//# sourceMappingURL=discovery-
|
|
156
|
+
//# sourceMappingURL=discovery-CRR3SlyI.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discovery-B9YsEQjv.cjs","sources":["../src/lib/discovery.ts"],"sourcesContent":["/**\n * Discovery utilities for auto-discovering resources and simulations\n *\n * These helpers process the results of import.meta.glob() calls to extract\n * keys, build component maps, and connect simulations to resources.\n *\n * The glob calls themselves must remain in the template (Vite compile-time),\n * but all the processing logic lives here for easy updates across templates.\n *\n * Node.js utilities (findResourceDirs, isSimulationFile, etc.) can be used\n * by CLI commands for build-time and runtime discovery.\n */\n\nimport type { Simulation } from '../types/simulation.js';\n\n/**\n * Convert a kebab-case string to PascalCase\n * @example toPascalCase('review') // 'Review'\n * @example toPascalCase('album-art') // 'AlbumArt'\n */\nexport function toPascalCase(str: string): string {\n return str\n .split('-')\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join('');\n}\n\n/**\n * Extract the resource key from a resource file path\n * @example extractResourceKey('./review-resource.tsx') // 'review'\n * @example extractResourceKey('../src/resources/albums-resource.tsx') // 'albums'\n */\nexport function extractResourceKey(path: string): string | undefined {\n const match = path.match(/([^/]+)-resource\\.(tsx|json)$/);\n return match?.[1];\n}\n\n/**\n * Extract the simulation key from a simulation file path\n * @example extractSimulationKey('./albums-show-simulation.json') // 'albums-show'\n */\nexport function extractSimulationKey(path: string): string | undefined {\n const match = path.match(/([^/]+)-simulation\\.json$/);\n return match?.[1];\n}\n\n/**\n * Find the best matching resource key for a simulation key.\n * Matches the longest resource name that is a prefix of the simulation key.\n * @example findResourceKey('albums-show', ['albums', 'album']) // 'albums'\n * @example findResourceKey('review-diff', ['review', 'carousel']) // 'review'\n */\nexport function findResourceKey(simulationKey: string, resourceKeys: string[]): string | undefined {\n // Sort by length descending to find longest match first\n const sorted = [...resourceKeys].sort((a, b) => b.length - a.length);\n for (const resourceKey of sorted) {\n if (simulationKey === resourceKey || simulationKey.startsWith(resourceKey + '-')) {\n return resourceKey;\n }\n }\n return undefined;\n}\n\n/**\n * Get the expected component export name for a resource\n * @example getComponentName('review') // 'ReviewResource'\n * @example getComponentName('album-art') // 'AlbumArtResource'\n */\nexport function getComponentName(resourceKey: string): string {\n return `${toPascalCase(resourceKey)}Resource`;\n}\n\n// --- Glob processing helpers ---\n\ntype GlobModules = Record<string, unknown>;\n\n/**\n * Process resource component modules from import.meta.glob() result.\n * Extracts components and exports them with PascalCase names.\n *\n * @example\n * const modules = import.meta.glob('./**\\/*-resource.tsx', { eager: true });\n * export default createResourceExports(modules);\n */\nexport function createResourceExports(modules: GlobModules): Record<string, React.ComponentType> {\n const resources: Record<string, React.ComponentType> = {};\n\n for (const [path, module] of Object.entries(modules)) {\n const key = extractResourceKey(path);\n if (!key) continue;\n\n const exportName = getComponentName(key);\n const mod = module as Record<string, unknown>;\n\n // Try default export first, then named export matching the expected name\n const component = mod.default ?? mod[exportName];\n\n // Accept functions (regular components) or objects (forwardRef/memo components)\n if (component && (typeof component === 'function' || typeof component === 'object')) {\n resources[exportName] = component as React.ComponentType;\n }\n }\n\n return resources;\n}\n\n/**\n * Build a resource metadata map from import.meta.glob() result.\n * Used for connecting simulations to their resource definitions.\n *\n * @example\n * const modules = import.meta.glob('../src/resources/**\\/*-resource.tsx', { eager: true });\n * const resourcesMap = buildResourceMap(modules);\n */\nexport function buildResourceMap<T>(modules: GlobModules): Map<string, T> {\n const map = new Map<string, T>();\n\n for (const [path, module] of Object.entries(modules)) {\n const key = extractResourceKey(path);\n if (key) {\n map.set(key, (module as { resource: T }).resource);\n }\n }\n\n return map;\n}\n\n/**\n * Options for building simulations from discovered modules\n */\nexport interface BuildSimulationsOptions<TResource, TSimulation> {\n /** Glob result of simulation JSON files */\n simulationModules: GlobModules;\n /** Map of resource key -> resource metadata */\n resourcesMap: Map<string, TResource>;\n /** Map of component name -> React component */\n resourceComponents: Record<string, React.ComponentType>;\n /** Create the final simulation object */\n createSimulation: (\n simulationKey: string,\n simulationData: unknown,\n resource: TResource,\n resourceComponent: React.ComponentType\n ) => TSimulation;\n /** Optional warning handler for missing resources */\n onMissingResource?: (simulationKey: string, expectedPrefix: string) => void;\n}\n\n/**\n * Build simulations by connecting simulation data with resources and components.\n * This is the main orchestration function for dev server bootstrap.\n */\nexport function buildSimulations<TResource, TSimulation>(\n options: BuildSimulationsOptions<TResource, TSimulation>\n): Record<string, TSimulation> {\n const {\n simulationModules,\n resourcesMap,\n resourceComponents,\n createSimulation,\n onMissingResource = (key, prefix) =>\n console.warn(\n `No matching resource found for simulation \"${key}\". ` +\n `Expected a resource file like src/resources/${prefix}/${prefix}-resource.tsx`\n ),\n } = options;\n\n const resourceKeys = Array.from(resourcesMap.keys());\n const simulations: Record<string, TSimulation> = {};\n\n for (const [path, module] of Object.entries(simulationModules)) {\n const simulationKey = extractSimulationKey(path);\n if (!simulationKey) continue;\n\n const simulationData = (module as { default: unknown }).default;\n\n // Find matching resource\n const resourceKey = findResourceKey(simulationKey, resourceKeys);\n if (!resourceKey) {\n onMissingResource(simulationKey, simulationKey.split('-')[0]);\n continue;\n }\n\n const resource = resourcesMap.get(resourceKey)!;\n\n // Get component\n const componentName = getComponentName(resourceKey);\n const resourceComponent = resourceComponents[componentName];\n\n if (!resourceComponent) {\n console.warn(\n `Resource component \"${componentName}\" not found for resource \"${resourceKey}\". ` +\n `Make sure src/resources/${resourceKey}/${resourceKey}-resource.tsx exists with a default export.`\n );\n continue;\n }\n\n simulations[simulationKey] = createSimulation(\n simulationKey,\n simulationData,\n resource,\n resourceComponent\n );\n }\n\n return simulations;\n}\n\n// --- Dev server helpers ---\n\n/**\n * Resource metadata from *-resource.tsx files\n */\nexport interface ResourceMetadata {\n name: string;\n [key: string]: unknown;\n}\n\n/**\n * Options for building dev simulations\n */\nexport interface BuildDevSimulationsOptions {\n /** Glob result of simulation JSON files: import.meta.glob('*-simulation.json', { eager: true }) */\n simulationModules: GlobModules;\n /** Glob result of resource JSON files: import.meta.glob('*-resource.tsx', { eager: true }) */\n resourceModules: GlobModules;\n /** Resource components map from src/resources/index.ts */\n resourceComponents: Record<string, React.ComponentType>;\n}\n\n/**\n * Build simulations for the dev server from glob results.\n * This is the main entry point for dev.tsx bootstrap.\n *\n * @example\n * const simulations = buildDevSimulations({\n * simulationModules: import.meta.glob('../src/resources/**\\/*-simulation.json', { eager: true }),\n * resourceModules: import.meta.glob('../src/resources/**\\/*-resource.tsx', { eager: true }),\n * resourceComponents: resourceComponents,\n * });\n */\nexport function buildDevSimulations(\n options: BuildDevSimulationsOptions\n): Record<string, Simulation> {\n const { simulationModules, resourceModules, resourceComponents } = options;\n\n // Build resource metadata map\n const resourcesMap = buildResourceMap<ResourceMetadata>(resourceModules);\n\n // Build simulations with the standard dev server format\n return buildSimulations<ResourceMetadata, Simulation>({\n simulationModules,\n resourcesMap,\n resourceComponents,\n createSimulation: (simulationKey, simulationData, resource, resourceComponent) => {\n // Get the component name for the resource URL\n const resourceKey = findResourceKey(simulationKey, Array.from(resourcesMap.keys()));\n const componentName = resourceKey ? getComponentName(resourceKey) : '';\n\n return {\n ...(simulationData as Omit<Simulation, 'name' | 'resourceUrl' | 'resource'>),\n name: simulationKey,\n resource: {\n uri: `ui://${resource.name}`,\n ...resource,\n },\n // Generate URL to the resource loader with component name as query param\n resourceUrl: `/.sunpeak/resource-loader.html?component=${componentName}`,\n // Keep resourceComponent for backwards compatibility during transition\n // but it won't be used by the simulator anymore\n _resourceComponent: resourceComponent,\n } as Simulation;\n },\n });\n}\n\n// --- Node.js utilities for CLI commands ---\n// These utilities use standard Node.js APIs and can be imported by build/push/mcp commands.\n\n/**\n * Information about a discovered resource directory\n */\nexport interface ResourceDirInfo {\n /** Resource key (directory name), e.g., 'albums', 'carousel' */\n key: string;\n /** Full path to the resource directory */\n dir: string;\n /** Full path to the main resource file (tsx or json depending on context) */\n resourcePath: string;\n}\n\n/**\n * File system operations interface for dependency injection in tests\n */\nexport interface FsOps {\n readdirSync: (\n path: string,\n options: { withFileTypes: true }\n ) => Array<{ name: string; isDirectory: () => boolean }>;\n existsSync: (path: string) => boolean;\n}\n\n/**\n * Find all resource directories in a base directory.\n * Each valid resource directory contains a file matching the expected pattern.\n *\n * @param baseDir - Base directory to scan (e.g., 'src/resources' or 'dist')\n * @param filePattern - Function to generate expected filename from resource key\n * @param fs - File system operations (for testing)\n *\n * @example\n * // Find source resources (tsx files)\n * const resources = findResourceDirs('src/resources', key => `${key}-resource.tsx`);\n *\n * @example\n * // Find built resources (js files)\n * const resources = findResourceDirs('dist', key => `${key}.js`);\n */\nexport function findResourceDirs(\n baseDir: string,\n filePattern: (key: string) => string,\n fs: FsOps\n): ResourceDirInfo[] {\n if (!fs.existsSync(baseDir)) {\n return [];\n }\n\n const entries = fs.readdirSync(baseDir, { withFileTypes: true });\n\n return entries\n .filter((entry) => entry.isDirectory())\n .map((entry) => {\n const key = entry.name;\n const dir = `${baseDir}/${key}`;\n const resourcePath = `${dir}/${filePattern(key)}`;\n\n if (!fs.existsSync(resourcePath)) {\n return null;\n }\n\n return { key, dir, resourcePath };\n })\n .filter((info): info is ResourceDirInfo => info !== null);\n}\n\n/**\n * Check if a filename is a simulation file for a given resource.\n * Matches pattern: {resourceKey}-*-simulation.json\n *\n * @example\n * isSimulationFile('albums-show-simulation.json', 'albums') // true\n * isSimulationFile('albums-show-simulation.json', 'carousel') // false\n * isSimulationFile('albums-resource.tsx', 'albums') // false\n */\nexport function isSimulationFile(filename: string, resourceKey: string): boolean {\n return filename.startsWith(`${resourceKey}-`) && filename.endsWith('-simulation.json');\n}\n\n/**\n * Extract the simulation name from a simulation filename.\n * Given \"{resourceKey}-{name}-simulation.json\", returns \"{name}\".\n *\n * @example\n * extractSimulationName('albums-show-simulation.json', 'albums') // 'show'\n * extractSimulationName('carousel-hero-simulation.json', 'carousel') // 'hero'\n */\nexport function extractSimulationName(filename: string, resourceKey: string): string {\n return filename.replace(`${resourceKey}-`, '').replace('-simulation.json', '');\n}\n\n/**\n * Find all simulation files in a resource directory.\n *\n * @param resourceDir - Path to the resource directory\n * @param resourceKey - Resource key (e.g., 'albums')\n * @param fs - File system operations (for testing)\n * @returns Array of { filename, name } objects\n */\nexport function findSimulationFiles(\n resourceDir: string,\n resourceKey: string,\n fs: Pick<FsOps, 'readdirSync' | 'existsSync'>\n): Array<{ filename: string; name: string; path: string }> {\n if (!fs.existsSync(resourceDir)) {\n return [];\n }\n\n const entries = fs.readdirSync(resourceDir, { withFileTypes: true });\n\n return entries\n .filter((entry) => !entry.isDirectory() && isSimulationFile(entry.name, resourceKey))\n .map((entry) => ({\n filename: entry.name,\n name: extractSimulationName(entry.name, resourceKey),\n path: `${resourceDir}/${entry.name}`,\n }));\n}\n"],"names":["module"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"discovery-CRR3SlyI.cjs","sources":["../src/lib/discovery.ts"],"sourcesContent":["/**\n * Discovery utilities for auto-discovering resources and simulations\n *\n * These helpers process the results of import.meta.glob() calls to extract\n * keys, build component maps, and connect simulations to resources.\n *\n * The glob calls themselves must remain in the template (Vite compile-time),\n * but all the processing logic lives here for easy updates across templates.\n *\n * Node.js utilities (findResourceDirs, isSimulationFile, etc.) can be used\n * by CLI commands for build-time and runtime discovery.\n */\n\nimport type { Simulation } from '../types/simulation.js';\n\n/**\n * Convert a kebab-case string to PascalCase\n * @example toPascalCase('review') // 'Review'\n * @example toPascalCase('album-art') // 'AlbumArt'\n */\nexport function toPascalCase(str: string): string {\n return str\n .split('-')\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join('');\n}\n\n/**\n * Extract the resource key from a resource file path\n * @example extractResourceKey('./review-resource.tsx') // 'review'\n * @example extractResourceKey('../src/resources/albums-resource.tsx') // 'albums'\n */\nexport function extractResourceKey(path: string): string | undefined {\n const match = path.match(/([^/]+)-resource\\.(tsx|json)$/);\n return match?.[1];\n}\n\n/**\n * Extract the simulation key from a simulation file path\n * @example extractSimulationKey('./albums-show-simulation.json') // 'albums-show'\n */\nexport function extractSimulationKey(path: string): string | undefined {\n const match = path.match(/([^/]+)-simulation\\.json$/);\n return match?.[1];\n}\n\n/**\n * Find the best matching resource key for a simulation key.\n * Matches the longest resource name that is a prefix of the simulation key.\n * @example findResourceKey('albums-show', ['albums', 'album']) // 'albums'\n * @example findResourceKey('review-diff', ['review', 'carousel']) // 'review'\n */\nexport function findResourceKey(simulationKey: string, resourceKeys: string[]): string | undefined {\n // Sort by length descending to find longest match first\n const sorted = [...resourceKeys].sort((a, b) => b.length - a.length);\n for (const resourceKey of sorted) {\n if (simulationKey === resourceKey || simulationKey.startsWith(resourceKey + '-')) {\n return resourceKey;\n }\n }\n return undefined;\n}\n\n/**\n * Get the expected component export name for a resource\n * @example getComponentName('review') // 'ReviewResource'\n * @example getComponentName('album-art') // 'AlbumArtResource'\n */\nexport function getComponentName(resourceKey: string): string {\n return `${toPascalCase(resourceKey)}Resource`;\n}\n\n// --- Glob processing helpers ---\n\ntype GlobModules = Record<string, unknown>;\n\n/**\n * Process resource component modules from import.meta.glob() result.\n * Extracts components and exports them with PascalCase names.\n *\n * @example\n * const modules = import.meta.glob('./**\\/*-resource.tsx', { eager: true });\n * export default createResourceExports(modules);\n */\nexport function createResourceExports(modules: GlobModules): Record<string, React.ComponentType> {\n const resources: Record<string, React.ComponentType> = {};\n\n for (const [path, module] of Object.entries(modules)) {\n const key = extractResourceKey(path);\n if (!key) continue;\n\n const exportName = getComponentName(key);\n const mod = module as Record<string, unknown>;\n\n // Try default export first, then named export matching the expected name\n const component = mod.default ?? mod[exportName];\n\n // Accept functions (regular components) or objects (forwardRef/memo components)\n if (component && (typeof component === 'function' || typeof component === 'object')) {\n resources[exportName] = component as React.ComponentType;\n }\n }\n\n return resources;\n}\n\n/**\n * Build a resource metadata map from import.meta.glob() result.\n * Used for connecting simulations to their resource definitions.\n *\n * @example\n * const modules = import.meta.glob('../src/resources/**\\/*-resource.tsx', { eager: true });\n * const resourcesMap = buildResourceMap(modules);\n */\nexport function buildResourceMap<T>(modules: GlobModules): Map<string, T> {\n const map = new Map<string, T>();\n\n for (const [path, module] of Object.entries(modules)) {\n const key = extractResourceKey(path);\n if (key) {\n map.set(key, (module as { resource: T }).resource);\n }\n }\n\n return map;\n}\n\n/**\n * Options for building simulations from discovered modules\n */\nexport interface BuildSimulationsOptions<TResource, TSimulation> {\n /** Glob result of simulation JSON files */\n simulationModules: GlobModules;\n /** Map of resource key -> resource metadata */\n resourcesMap: Map<string, TResource>;\n /** Map of component name -> React component */\n resourceComponents: Record<string, React.ComponentType>;\n /** Create the final simulation object */\n createSimulation: (\n simulationKey: string,\n simulationData: unknown,\n resource: TResource,\n resourceComponent: React.ComponentType\n ) => TSimulation;\n /** Optional warning handler for missing resources */\n onMissingResource?: (simulationKey: string, expectedPrefix: string) => void;\n}\n\n/**\n * Build simulations by connecting simulation data with resources and components.\n * This is the main orchestration function for dev server bootstrap.\n */\nexport function buildSimulations<TResource, TSimulation>(\n options: BuildSimulationsOptions<TResource, TSimulation>\n): Record<string, TSimulation> {\n const {\n simulationModules,\n resourcesMap,\n resourceComponents,\n createSimulation,\n onMissingResource = (key, prefix) =>\n console.warn(\n `No matching resource found for simulation \"${key}\". ` +\n `Expected a resource file like src/resources/${prefix}/${prefix}-resource.tsx`\n ),\n } = options;\n\n const resourceKeys = Array.from(resourcesMap.keys());\n const simulations: Record<string, TSimulation> = {};\n\n for (const [path, module] of Object.entries(simulationModules)) {\n const simulationKey = extractSimulationKey(path);\n if (!simulationKey) continue;\n\n const simulationData = (module as { default: unknown }).default;\n\n // Find matching resource\n const resourceKey = findResourceKey(simulationKey, resourceKeys);\n if (!resourceKey) {\n onMissingResource(simulationKey, simulationKey.split('-')[0]);\n continue;\n }\n\n const resource = resourcesMap.get(resourceKey)!;\n\n // Get component\n const componentName = getComponentName(resourceKey);\n const resourceComponent = resourceComponents[componentName];\n\n if (!resourceComponent) {\n console.warn(\n `Resource component \"${componentName}\" not found for resource \"${resourceKey}\". ` +\n `Make sure src/resources/${resourceKey}/${resourceKey}-resource.tsx exists with a default export.`\n );\n continue;\n }\n\n simulations[simulationKey] = createSimulation(\n simulationKey,\n simulationData,\n resource,\n resourceComponent\n );\n }\n\n return simulations;\n}\n\n// --- Dev server helpers ---\n\n/**\n * Resource metadata from *-resource.tsx files\n */\nexport interface ResourceMetadata {\n name: string;\n [key: string]: unknown;\n}\n\n/**\n * Options for building dev simulations\n */\nexport interface BuildDevSimulationsOptions {\n /** Glob result of simulation JSON files: import.meta.glob('*-simulation.json', { eager: true }) */\n simulationModules: GlobModules;\n /** Glob result of resource JSON files: import.meta.glob('*-resource.tsx', { eager: true }) */\n resourceModules: GlobModules;\n /** Resource components map from src/resources/index.ts */\n resourceComponents: Record<string, React.ComponentType>;\n}\n\n/**\n * Build simulations for the dev server from glob results.\n * This is the main entry point for dev.tsx bootstrap.\n *\n * @example\n * const simulations = buildDevSimulations({\n * simulationModules: import.meta.glob('../src/resources/**\\/*-simulation.json', { eager: true }),\n * resourceModules: import.meta.glob('../src/resources/**\\/*-resource.tsx', { eager: true }),\n * resourceComponents: resourceComponents,\n * });\n */\nexport function buildDevSimulations(\n options: BuildDevSimulationsOptions\n): Record<string, Simulation> {\n const { simulationModules, resourceModules, resourceComponents } = options;\n\n // Build resource metadata map\n const resourcesMap = buildResourceMap<ResourceMetadata>(resourceModules);\n\n // Build simulations with the standard dev server format\n return buildSimulations<ResourceMetadata, Simulation>({\n simulationModules,\n resourcesMap,\n resourceComponents,\n createSimulation: (simulationKey, simulationData, resource) => {\n // Get the component name for the resource URL\n const resourceKey = findResourceKey(simulationKey, Array.from(resourcesMap.keys()));\n const componentName = resourceKey ? getComponentName(resourceKey) : '';\n\n return {\n ...(simulationData as Omit<Simulation, 'name' | 'resourceUrl' | 'resource'>),\n name: simulationKey,\n resource: {\n uri: `ui://${resource.name}`,\n ...resource,\n },\n // Generate URL to the resource loader with component name as query param\n resourceUrl: `/.sunpeak/resource-loader.html?component=${componentName}`,\n } as Simulation;\n },\n });\n}\n\n// --- Node.js utilities for CLI commands ---\n// These utilities use standard Node.js APIs and can be imported by build/push/mcp commands.\n\n/**\n * Information about a discovered resource directory\n */\nexport interface ResourceDirInfo {\n /** Resource key (directory name), e.g., 'albums', 'carousel' */\n key: string;\n /** Full path to the resource directory */\n dir: string;\n /** Full path to the main resource file (tsx or json depending on context) */\n resourcePath: string;\n}\n\n/**\n * File system operations interface for dependency injection in tests\n */\nexport interface FsOps {\n readdirSync: (\n path: string,\n options: { withFileTypes: true }\n ) => Array<{ name: string; isDirectory: () => boolean }>;\n existsSync: (path: string) => boolean;\n}\n\n/**\n * Find all resource directories in a base directory.\n * Each valid resource directory contains a file matching the expected pattern.\n *\n * @param baseDir - Base directory to scan (e.g., 'src/resources' or 'dist')\n * @param filePattern - Function to generate expected filename from resource key\n * @param fs - File system operations (for testing)\n *\n * @example\n * // Find source resources (tsx files)\n * const resources = findResourceDirs('src/resources', key => `${key}-resource.tsx`);\n *\n * @example\n * // Find built resources (js files)\n * const resources = findResourceDirs('dist', key => `${key}.js`);\n */\nexport function findResourceDirs(\n baseDir: string,\n filePattern: (key: string) => string,\n fs: FsOps\n): ResourceDirInfo[] {\n if (!fs.existsSync(baseDir)) {\n return [];\n }\n\n const entries = fs.readdirSync(baseDir, { withFileTypes: true });\n\n return entries\n .filter((entry) => entry.isDirectory())\n .map((entry) => {\n const key = entry.name;\n const dir = `${baseDir}/${key}`;\n const resourcePath = `${dir}/${filePattern(key)}`;\n\n if (!fs.existsSync(resourcePath)) {\n return null;\n }\n\n return { key, dir, resourcePath };\n })\n .filter((info): info is ResourceDirInfo => info !== null);\n}\n\n/**\n * Check if a filename is a simulation file for a given resource.\n * Matches pattern: {resourceKey}-*-simulation.json\n *\n * @example\n * isSimulationFile('albums-show-simulation.json', 'albums') // true\n * isSimulationFile('albums-show-simulation.json', 'carousel') // false\n * isSimulationFile('albums-resource.tsx', 'albums') // false\n */\nexport function isSimulationFile(filename: string, resourceKey: string): boolean {\n return filename.startsWith(`${resourceKey}-`) && filename.endsWith('-simulation.json');\n}\n\n/**\n * Extract the simulation name from a simulation filename.\n * Given \"{resourceKey}-{name}-simulation.json\", returns \"{name}\".\n *\n * @example\n * extractSimulationName('albums-show-simulation.json', 'albums') // 'show'\n * extractSimulationName('carousel-hero-simulation.json', 'carousel') // 'hero'\n */\nexport function extractSimulationName(filename: string, resourceKey: string): string {\n return filename.replace(`${resourceKey}-`, '').replace('-simulation.json', '');\n}\n\n/**\n * Find all simulation files in a resource directory.\n *\n * @param resourceDir - Path to the resource directory\n * @param resourceKey - Resource key (e.g., 'albums')\n * @param fs - File system operations (for testing)\n * @returns Array of { filename, name } objects\n */\nexport function findSimulationFiles(\n resourceDir: string,\n resourceKey: string,\n fs: Pick<FsOps, 'readdirSync' | 'existsSync'>\n): Array<{ filename: string; name: string; path: string }> {\n if (!fs.existsSync(resourceDir)) {\n return [];\n }\n\n const entries = fs.readdirSync(resourceDir, { withFileTypes: true });\n\n return entries\n .filter((entry) => !entry.isDirectory() && isSimulationFile(entry.name, resourceKey))\n .map((entry) => ({\n filename: entry.name,\n name: extractSimulationName(entry.name, resourceKey),\n path: `${resourceDir}/${entry.name}`,\n }));\n}\n"],"names":["module"],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@ const React = require("react");
|
|
|
4
4
|
const _commonjsHelpers = require("./_commonjsHelpers-Bc2YnDe1.cjs");
|
|
5
5
|
const ReactDOM = require("react-dom");
|
|
6
6
|
const protocol = require("./protocol-CL4_Npj5.cjs");
|
|
7
|
-
const discovery = require("./discovery-
|
|
7
|
+
const discovery = require("./discovery-CRR3SlyI.cjs");
|
|
8
8
|
var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
|
|
9
9
|
function _interopNamespaceDefault(e) {
|
|
10
10
|
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
@@ -5442,7 +5442,7 @@ const useEscCloseStack = (listening, cb) => {
|
|
|
5442
5442
|
}, [id, listening, latestCallback]);
|
|
5443
5443
|
};
|
|
5444
5444
|
const __vite_import_meta_env__ = { "DEV": false, "MODE": "production" };
|
|
5445
|
-
const META_ENV = typeof { url: typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index-
|
|
5445
|
+
const META_ENV = typeof { url: typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index-B_In_BWg.cjs", document.baseURI).href } !== "undefined" ? __vite_import_meta_env__ : void 0;
|
|
5446
5446
|
const NODE_ENV = typeof process !== "undefined" && process.env?.NODE_ENV ? process.env?.NODE_ENV : "production";
|
|
5447
5447
|
const isDev = NODE_ENV === "development" || !!META_ENV?.DEV;
|
|
5448
5448
|
const isJSDomLike = typeof navigator !== "undefined" && /(jsdom|happy-dom)/i.test(navigator.userAgent) || typeof globalThis.happyDOM === "object";
|
|
@@ -14094,6 +14094,16 @@ class McpAppHost {
|
|
|
14094
14094
|
if (this.options.onOpenLink) {
|
|
14095
14095
|
this.options.onOpenLink(url);
|
|
14096
14096
|
} else {
|
|
14097
|
+
try {
|
|
14098
|
+
const parsed = new URL(url);
|
|
14099
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
14100
|
+
console.warn("[MCP App] openLink blocked non-http(s) URL:", url);
|
|
14101
|
+
return {};
|
|
14102
|
+
}
|
|
14103
|
+
} catch {
|
|
14104
|
+
console.warn("[MCP App] openLink blocked invalid URL:", url);
|
|
14105
|
+
return {};
|
|
14106
|
+
}
|
|
14097
14107
|
window.open(url, "_blank");
|
|
14098
14108
|
}
|
|
14099
14109
|
return {};
|
|
@@ -14175,6 +14185,7 @@ class McpAppHost {
|
|
|
14175
14185
|
const id = ++this._fenceId;
|
|
14176
14186
|
return new Promise((resolve) => {
|
|
14177
14187
|
const handler = (event) => {
|
|
14188
|
+
if (event.source !== win) return;
|
|
14178
14189
|
if (event.data?.method === "sunpeak/fence-ack" && event.data.params?.fenceId === id) {
|
|
14179
14190
|
cleanup();
|
|
14180
14191
|
resolve();
|
|
@@ -14317,6 +14328,15 @@ function isAllowedUrl(src) {
|
|
|
14317
14328
|
}
|
|
14318
14329
|
}
|
|
14319
14330
|
const SDK_RESOURCE_DOMAINS = ["https://cdn.openai.com"];
|
|
14331
|
+
function isValidCspSource(source) {
|
|
14332
|
+
if (!source || /[\s;,']/.test(source) || source === "*") return false;
|
|
14333
|
+
try {
|
|
14334
|
+
const url = new URL(source);
|
|
14335
|
+
return url.protocol === "http:" || url.protocol === "https:" || url.protocol === "ws:" || url.protocol === "wss:";
|
|
14336
|
+
} catch {
|
|
14337
|
+
return false;
|
|
14338
|
+
}
|
|
14339
|
+
}
|
|
14320
14340
|
function generateCSP(csp, scriptSrc) {
|
|
14321
14341
|
let scriptOrigin = "";
|
|
14322
14342
|
try {
|
|
@@ -14334,14 +14354,26 @@ function generateCSP(csp, scriptSrc) {
|
|
|
14334
14354
|
const connectSources = /* @__PURE__ */ new Set(["'self'"]);
|
|
14335
14355
|
if (scriptOrigin) connectSources.add(scriptOrigin);
|
|
14336
14356
|
if (csp?.connectDomains) {
|
|
14337
|
-
for (const domain of csp.connectDomains)
|
|
14357
|
+
for (const domain of csp.connectDomains) {
|
|
14358
|
+
if (isValidCspSource(domain)) {
|
|
14359
|
+
connectSources.add(domain);
|
|
14360
|
+
} else {
|
|
14361
|
+
console.warn("[IframeResource] Ignoring invalid CSP connect domain:", domain);
|
|
14362
|
+
}
|
|
14363
|
+
}
|
|
14338
14364
|
}
|
|
14339
14365
|
directives.push(`connect-src ${Array.from(connectSources).join(" ")}`);
|
|
14340
14366
|
const resourceSources = /* @__PURE__ */ new Set(["'self'", "data:", "blob:"]);
|
|
14341
14367
|
if (scriptOrigin) resourceSources.add(scriptOrigin);
|
|
14342
14368
|
for (const domain of SDK_RESOURCE_DOMAINS) resourceSources.add(domain);
|
|
14343
14369
|
if (csp?.resourceDomains) {
|
|
14344
|
-
for (const domain of csp.resourceDomains)
|
|
14370
|
+
for (const domain of csp.resourceDomains) {
|
|
14371
|
+
if (isValidCspSource(domain)) {
|
|
14372
|
+
resourceSources.add(domain);
|
|
14373
|
+
} else {
|
|
14374
|
+
console.warn("[IframeResource] Ignoring invalid CSP resource domain:", domain);
|
|
14375
|
+
}
|
|
14376
|
+
}
|
|
14345
14377
|
}
|
|
14346
14378
|
const resourceList = Array.from(resourceSources).join(" ");
|
|
14347
14379
|
directives.push(`img-src ${resourceList}`);
|
|
@@ -14352,8 +14384,9 @@ function generateCSP(csp, scriptSrc) {
|
|
|
14352
14384
|
function generateScriptHtml(scriptSrc, theme, cspPolicy) {
|
|
14353
14385
|
const safeScriptSrc = escapeHtml(scriptSrc);
|
|
14354
14386
|
const safeCsp = escapeHtml(cspPolicy);
|
|
14387
|
+
const safeTheme = escapeHtml(theme);
|
|
14355
14388
|
return `<!DOCTYPE html>
|
|
14356
|
-
<html lang="en" data-theme="${
|
|
14389
|
+
<html lang="en" data-theme="${safeTheme}">
|
|
14357
14390
|
<head>
|
|
14358
14391
|
<meta charset="UTF-8" />
|
|
14359
14392
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
@@ -14595,8 +14628,6 @@ function parseUrlParams() {
|
|
|
14595
14628
|
platform2 = "mobile";
|
|
14596
14629
|
} else if (deviceType === "desktop") {
|
|
14597
14630
|
platform2 = "desktop";
|
|
14598
|
-
} else if (deviceType) {
|
|
14599
|
-
platform2 = "web";
|
|
14600
14631
|
}
|
|
14601
14632
|
const hoverParam = params.get("hover");
|
|
14602
14633
|
const touchParam = params.get("touch");
|
|
@@ -14761,7 +14792,7 @@ function ChatGPTSimulator({
|
|
|
14761
14792
|
const resourceScript = selectedSim?.resourceScript;
|
|
14762
14793
|
const resourceMeta = selectedSim?.resource._meta;
|
|
14763
14794
|
const resourceUi = resourceMeta?.ui;
|
|
14764
|
-
const csp = resourceUi?.csp
|
|
14795
|
+
const csp = resourceUi?.csp;
|
|
14765
14796
|
const hasIframeContent = !!(resourceUrl || resourceScript);
|
|
14766
14797
|
const isTransitioning = hasIframeContent && displayMode !== readyDisplayMode;
|
|
14767
14798
|
let content;
|
|
@@ -15067,9 +15098,6 @@ function createSimulatorUrl(params, basePath = "/") {
|
|
|
15067
15098
|
if (params.safeAreaRight !== void 0) {
|
|
15068
15099
|
searchParams.set("safeAreaRight", String(params.safeAreaRight));
|
|
15069
15100
|
}
|
|
15070
|
-
if (params.viewMode !== void 0) {
|
|
15071
|
-
searchParams.set("viewMode", params.viewMode);
|
|
15072
|
-
}
|
|
15073
15101
|
const queryString = searchParams.toString();
|
|
15074
15102
|
return queryString ? `${basePath}?${queryString}` : basePath;
|
|
15075
15103
|
}
|
|
@@ -15105,4 +15133,4 @@ exports.clsx = clsx;
|
|
|
15105
15133
|
exports.createSimulatorUrl = createSimulatorUrl;
|
|
15106
15134
|
exports.index = index;
|
|
15107
15135
|
exports.useThemeContext = useThemeContext;
|
|
15108
|
-
//# sourceMappingURL=index-
|
|
15136
|
+
//# sourceMappingURL=index-B_In_BWg.cjs.map
|