sunpeak 0.9.2 → 0.9.3
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/dist/discovery-a4WId9PC.cjs +125 -0
- package/dist/discovery-a4WId9PC.cjs.map +1 -0
- package/dist/discovery-ft3cd2dW.js +126 -0
- package/dist/discovery-ft3cd2dW.js.map +1 -0
- package/dist/index.cjs +11 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +27 -16
- package/dist/index.js.map +1 -1
- package/dist/lib/discovery.d.ts +110 -0
- package/dist/lib/index.d.ts +1 -0
- package/dist/mcp/entry.cjs +2 -10
- package/dist/mcp/entry.cjs.map +1 -1
- package/dist/mcp/entry.js +1 -9
- package/dist/mcp/entry.js.map +1 -1
- package/package.json +1 -1
- package/template/.sunpeak/dev.tsx +6 -91
- package/template/dist/albums.json +1 -1
- package/template/dist/carousel.json +1 -1
- package/template/dist/map.json +1 -1
- package/template/dist/review.json +1 -1
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_SegmentedControl.js +4 -4
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Select.js +6 -6
- package/template/node_modules/.vite/deps/_metadata.json +25 -25
- package/template/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/template/src/resources/index.ts +3 -25
- package/template/src/simulations/index.ts +3 -10
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Simulation } from '../types/simulation.js';
|
|
2
|
+
/**
|
|
3
|
+
* Convert a kebab-case string to PascalCase
|
|
4
|
+
* @example toPascalCase('review') // 'Review'
|
|
5
|
+
* @example toPascalCase('album-art') // 'AlbumArt'
|
|
6
|
+
*/
|
|
7
|
+
export declare function toPascalCase(str: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Extract the resource key from a resource file path
|
|
10
|
+
* @example extractResourceKey('./review-resource.tsx') // 'review'
|
|
11
|
+
* @example extractResourceKey('../src/resources/albums-resource.json') // 'albums'
|
|
12
|
+
*/
|
|
13
|
+
export declare function extractResourceKey(path: string): string | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Extract the simulation key from a simulation file path
|
|
16
|
+
* @example extractSimulationKey('./albums-show-simulation.json') // 'albums-show'
|
|
17
|
+
*/
|
|
18
|
+
export declare function extractSimulationKey(path: string): string | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Find the best matching resource key for a simulation key.
|
|
21
|
+
* Matches the longest resource name that is a prefix of the simulation key.
|
|
22
|
+
* @example findResourceKey('albums-show', ['albums', 'album']) // 'albums'
|
|
23
|
+
* @example findResourceKey('review-diff', ['review', 'carousel']) // 'review'
|
|
24
|
+
*/
|
|
25
|
+
export declare function findResourceKey(simulationKey: string, resourceKeys: string[]): string | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Get the expected component export name for a resource
|
|
28
|
+
* @example getComponentName('review') // 'ReviewResource'
|
|
29
|
+
* @example getComponentName('album-art') // 'AlbumArtResource'
|
|
30
|
+
*/
|
|
31
|
+
export declare function getComponentName(resourceKey: string): string;
|
|
32
|
+
type GlobModules = Record<string, unknown>;
|
|
33
|
+
/**
|
|
34
|
+
* Process resource component modules from import.meta.glob() result.
|
|
35
|
+
* Extracts components and exports them with PascalCase names.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* const modules = import.meta.glob('./*-resource.tsx', { eager: true });
|
|
39
|
+
* export default createResourceExports(modules);
|
|
40
|
+
*/
|
|
41
|
+
export declare function createResourceExports(modules: GlobModules): Record<string, React.ComponentType>;
|
|
42
|
+
/**
|
|
43
|
+
* Process simulation modules from import.meta.glob() result.
|
|
44
|
+
* Builds a map of simulation key -> simulation data.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* const modules = import.meta.glob('./*-simulation.json', { eager: true });
|
|
48
|
+
* export const SIMULATIONS = createSimulationIndex(modules);
|
|
49
|
+
*/
|
|
50
|
+
export declare function createSimulationIndex(modules: GlobModules): Record<string, unknown>;
|
|
51
|
+
/**
|
|
52
|
+
* Build a resource metadata map from import.meta.glob() result.
|
|
53
|
+
* Used for connecting simulations to their resource definitions.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* const modules = import.meta.glob('../src/resources/*-resource.json', { eager: true });
|
|
57
|
+
* const resourcesMap = buildResourceMap(modules);
|
|
58
|
+
*/
|
|
59
|
+
export declare function buildResourceMap<T>(modules: GlobModules): Map<string, T>;
|
|
60
|
+
/**
|
|
61
|
+
* Options for building simulations from discovered modules
|
|
62
|
+
*/
|
|
63
|
+
export interface BuildSimulationsOptions<TResource, TSimulation> {
|
|
64
|
+
/** Glob result of simulation JSON files */
|
|
65
|
+
simulationModules: GlobModules;
|
|
66
|
+
/** Map of resource key -> resource metadata */
|
|
67
|
+
resourcesMap: Map<string, TResource>;
|
|
68
|
+
/** Map of component name -> React component */
|
|
69
|
+
resourceComponents: Record<string, React.ComponentType>;
|
|
70
|
+
/** Create the final simulation object */
|
|
71
|
+
createSimulation: (simulationKey: string, simulationData: unknown, resource: TResource, resourceComponent: React.ComponentType) => TSimulation;
|
|
72
|
+
/** Optional warning handler for missing resources */
|
|
73
|
+
onMissingResource?: (simulationKey: string, expectedPrefix: string) => void;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Build simulations by connecting simulation data with resources and components.
|
|
77
|
+
* This is the main orchestration function for dev server bootstrap.
|
|
78
|
+
*/
|
|
79
|
+
export declare function buildSimulations<TResource, TSimulation>(options: BuildSimulationsOptions<TResource, TSimulation>): Record<string, TSimulation>;
|
|
80
|
+
/**
|
|
81
|
+
* Resource metadata from *-resource.json files
|
|
82
|
+
*/
|
|
83
|
+
export interface ResourceMetadata {
|
|
84
|
+
name: string;
|
|
85
|
+
[key: string]: unknown;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Options for building dev simulations
|
|
89
|
+
*/
|
|
90
|
+
export interface BuildDevSimulationsOptions {
|
|
91
|
+
/** Glob result of simulation JSON files: import.meta.glob('*-simulation.json', { eager: true }) */
|
|
92
|
+
simulationModules: GlobModules;
|
|
93
|
+
/** Glob result of resource JSON files: import.meta.glob('*-resource.json', { eager: true }) */
|
|
94
|
+
resourceModules: GlobModules;
|
|
95
|
+
/** Resource components map from src/resources/index.ts */
|
|
96
|
+
resourceComponents: Record<string, React.ComponentType>;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Build simulations for the dev server from glob results.
|
|
100
|
+
* This is the main entry point for dev.tsx bootstrap.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* const simulations = buildDevSimulations({
|
|
104
|
+
* simulationModules: import.meta.glob('../src/simulations/*-simulation.json', { eager: true }),
|
|
105
|
+
* resourceModules: import.meta.glob('../src/resources/*-resource.json', { eager: true }),
|
|
106
|
+
* resourceComponents: resourceComponents,
|
|
107
|
+
* });
|
|
108
|
+
*/
|
|
109
|
+
export declare function buildDevSimulations(options: BuildDevSimulationsOptions): Record<string, Simulation>;
|
|
110
|
+
export {};
|
package/dist/lib/index.d.ts
CHANGED
package/dist/mcp/entry.cjs
CHANGED
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
const server = require("../server-CSybLAYo.cjs");
|
|
4
|
+
const discovery = require("../discovery-a4WId9PC.cjs");
|
|
4
5
|
const path = require("path");
|
|
5
6
|
const fs = require("fs");
|
|
6
7
|
const projectRoot = process.cwd();
|
|
7
|
-
function findResourceKey(simulationKey, resourceKeys) {
|
|
8
|
-
const sorted = [...resourceKeys].sort((a, b) => b.length - a.length);
|
|
9
|
-
for (const resourceKey of sorted) {
|
|
10
|
-
if (simulationKey === resourceKey || simulationKey.startsWith(resourceKey + "-")) {
|
|
11
|
-
return resourceKey;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
return void 0;
|
|
15
|
-
}
|
|
16
8
|
async function startServer() {
|
|
17
9
|
const pkgPath = path.join(projectRoot, "package.json");
|
|
18
10
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
@@ -33,7 +25,7 @@ async function startServer() {
|
|
|
33
25
|
const simulationKey = filename.replace(/-simulation\.json$/, "");
|
|
34
26
|
const simulationPath = path.join(simulationsDir, filename);
|
|
35
27
|
const simulation = JSON.parse(fs.readFileSync(simulationPath, "utf-8"));
|
|
36
|
-
const resourceKey = findResourceKey(simulationKey, resourceKeys);
|
|
28
|
+
const resourceKey = discovery.findResourceKey(simulationKey, resourceKeys);
|
|
37
29
|
if (!resourceKey) {
|
|
38
30
|
console.warn(
|
|
39
31
|
`No matching resource found for simulation "${simulationKey}". Expected a resource file like src/resources/${simulationKey.split("-")[0]}-resource.json`
|
package/dist/mcp/entry.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry.cjs","sources":["../../src/mcp/entry.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * Internal MCP server entry point\n * This is run by nodemon or directly to start the MCP server\n *\n * Auto-discovers simulations and resources by file naming convention:\n * - simulations/{resource}-{tool}-simulation.json (e.g., albums-show-simulation.json)\n * - resources/{resource}-resource.json\n */\nimport { runMCPServer, type SimulationWithDist } from './index.js';\nimport path from 'path';\nimport { readFileSync, readdirSync } from 'fs';\nimport type { Resource } from '@modelcontextprotocol/sdk/types.js';\n\n// Determine project root (where this is being run from)\nconst projectRoot = process.cwd();\n\
|
|
1
|
+
{"version":3,"file":"entry.cjs","sources":["../../src/mcp/entry.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * Internal MCP server entry point\n * This is run by nodemon or directly to start the MCP server\n *\n * Auto-discovers simulations and resources by file naming convention:\n * - simulations/{resource}-{tool}-simulation.json (e.g., albums-show-simulation.json)\n * - resources/{resource}-resource.json\n */\nimport { runMCPServer, type SimulationWithDist } from './index.js';\nimport { findResourceKey } from '../lib/discovery.js';\nimport path from 'path';\nimport { readFileSync, readdirSync } from 'fs';\nimport type { Resource } from '@modelcontextprotocol/sdk/types.js';\n\n// Determine project root (where this is being run from)\nconst projectRoot = process.cwd();\n\nasync function startServer() {\n // Read package.json for app metadata\n const pkgPath = path.join(projectRoot, 'package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n\n // Auto-discover resource files first (to build lookup map)\n const resourcesDir = path.join(projectRoot, 'src/resources');\n const resourceFiles = readdirSync(resourcesDir).filter((f) => f.endsWith('-resource.json'));\n\n const resourcesMap = new Map<string, Resource>();\n for (const filename of resourceFiles) {\n // Extract key from filename: 'review-resource.json' -> 'review'\n const key = filename.replace(/-resource\\.json$/, '');\n const resourcePath = path.join(resourcesDir, filename);\n const resource = JSON.parse(readFileSync(resourcePath, 'utf-8')) as Resource;\n resourcesMap.set(key, resource);\n }\n\n const resourceKeys = Array.from(resourcesMap.keys());\n\n // Auto-discover simulation files\n const simulationsDir = path.join(projectRoot, 'src/simulations');\n const simulationFiles = readdirSync(simulationsDir).filter((f) => f.endsWith('-simulation.json'));\n\n // Build simulations array from discovered files\n const simulations: SimulationWithDist[] = [];\n\n for (const filename of simulationFiles) {\n // Extract simulation key from filename: 'albums-show-simulation.json' -> 'albums-show'\n const simulationKey = filename.replace(/-simulation\\.json$/, '');\n\n // Load simulation data\n const simulationPath = path.join(simulationsDir, filename);\n const simulation = JSON.parse(readFileSync(simulationPath, 'utf-8'));\n\n // Find matching resource by best prefix match\n const resourceKey = findResourceKey(simulationKey, resourceKeys);\n if (!resourceKey) {\n console.warn(\n `No matching resource found for simulation \"${simulationKey}\". ` +\n `Expected a resource file like src/resources/${simulationKey.split('-')[0]}-resource.json`\n );\n continue;\n }\n\n const resource = resourcesMap.get(resourceKey)!;\n\n simulations.push({\n ...simulation,\n name: simulationKey,\n distPath: path.join(projectRoot, `dist/${resourceKey}.js`),\n resource,\n });\n }\n\n runMCPServer({\n name: pkg.name || 'Sunpeak',\n version: pkg.version || '0.1.0',\n simulations,\n port: 6766,\n });\n}\n\nstartServer().catch((error) => {\n console.error('Failed to start MCP server:', error);\n process.exit(1);\n});\n"],"names":["readFileSync","readdirSync","findResourceKey","runMCPServer"],"mappings":";;;;;;AAgBA,MAAM,cAAc,QAAQ,IAAA;AAE5B,eAAe,cAAc;AAE3B,QAAM,UAAU,KAAK,KAAK,aAAa,cAAc;AACrD,QAAM,MAAM,KAAK,MAAMA,GAAAA,aAAa,SAAS,OAAO,CAAC;AAGrD,QAAM,eAAe,KAAK,KAAK,aAAa,eAAe;AAC3D,QAAM,gBAAgBC,eAAY,YAAY,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,gBAAgB,CAAC;AAE1F,QAAM,mCAAmB,IAAA;AACzB,aAAW,YAAY,eAAe;AAEpC,UAAM,MAAM,SAAS,QAAQ,oBAAoB,EAAE;AACnD,UAAM,eAAe,KAAK,KAAK,cAAc,QAAQ;AACrD,UAAM,WAAW,KAAK,MAAMD,GAAAA,aAAa,cAAc,OAAO,CAAC;AAC/D,iBAAa,IAAI,KAAK,QAAQ;AAAA,EAChC;AAEA,QAAM,eAAe,MAAM,KAAK,aAAa,MAAM;AAGnD,QAAM,iBAAiB,KAAK,KAAK,aAAa,iBAAiB;AAC/D,QAAM,kBAAkBC,eAAY,cAAc,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,kBAAkB,CAAC;AAGhG,QAAM,cAAoC,CAAA;AAE1C,aAAW,YAAY,iBAAiB;AAEtC,UAAM,gBAAgB,SAAS,QAAQ,sBAAsB,EAAE;AAG/D,UAAM,iBAAiB,KAAK,KAAK,gBAAgB,QAAQ;AACzD,UAAM,aAAa,KAAK,MAAMD,GAAAA,aAAa,gBAAgB,OAAO,CAAC;AAGnE,UAAM,cAAcE,UAAAA,gBAAgB,eAAe,YAAY;AAC/D,QAAI,CAAC,aAAa;AAChB,cAAQ;AAAA,QACN,8CAA8C,aAAa,kDACV,cAAc,MAAM,GAAG,EAAE,CAAC,CAAC;AAAA,MAAA;AAE9E;AAAA,IACF;AAEA,UAAM,WAAW,aAAa,IAAI,WAAW;AAE7C,gBAAY,KAAK;AAAA,MACf,GAAG;AAAA,MACH,MAAM;AAAA,MACN,UAAU,KAAK,KAAK,aAAa,QAAQ,WAAW,KAAK;AAAA,MACzD;AAAA,IAAA,CACD;AAAA,EACH;AAEAC,sBAAa;AAAA,IACX,MAAM,IAAI,QAAQ;AAAA,IAClB,SAAS,IAAI,WAAW;AAAA,IACxB;AAAA,IACA,MAAM;AAAA,EAAA,CACP;AACH;AAEA,cAAc,MAAM,CAAC,UAAU;AAC7B,UAAQ,MAAM,+BAA+B,KAAK;AAClD,UAAQ,KAAK,CAAC;AAChB,CAAC;"}
|
package/dist/mcp/entry.js
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { r as runMCPServer } from "../server-310A1k9o.js";
|
|
3
|
+
import { f as findResourceKey } from "../discovery-ft3cd2dW.js";
|
|
3
4
|
import path from "path";
|
|
4
5
|
import { readFileSync, readdirSync } from "fs";
|
|
5
6
|
const projectRoot = process.cwd();
|
|
6
|
-
function findResourceKey(simulationKey, resourceKeys) {
|
|
7
|
-
const sorted = [...resourceKeys].sort((a, b) => b.length - a.length);
|
|
8
|
-
for (const resourceKey of sorted) {
|
|
9
|
-
if (simulationKey === resourceKey || simulationKey.startsWith(resourceKey + "-")) {
|
|
10
|
-
return resourceKey;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
return void 0;
|
|
14
|
-
}
|
|
15
7
|
async function startServer() {
|
|
16
8
|
const pkgPath = path.join(projectRoot, "package.json");
|
|
17
9
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
package/dist/mcp/entry.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry.js","sources":["../../src/mcp/entry.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * Internal MCP server entry point\n * This is run by nodemon or directly to start the MCP server\n *\n * Auto-discovers simulations and resources by file naming convention:\n * - simulations/{resource}-{tool}-simulation.json (e.g., albums-show-simulation.json)\n * - resources/{resource}-resource.json\n */\nimport { runMCPServer, type SimulationWithDist } from './index.js';\nimport path from 'path';\nimport { readFileSync, readdirSync } from 'fs';\nimport type { Resource } from '@modelcontextprotocol/sdk/types.js';\n\n// Determine project root (where this is being run from)\nconst projectRoot = process.cwd();\n\
|
|
1
|
+
{"version":3,"file":"entry.js","sources":["../../src/mcp/entry.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * Internal MCP server entry point\n * This is run by nodemon or directly to start the MCP server\n *\n * Auto-discovers simulations and resources by file naming convention:\n * - simulations/{resource}-{tool}-simulation.json (e.g., albums-show-simulation.json)\n * - resources/{resource}-resource.json\n */\nimport { runMCPServer, type SimulationWithDist } from './index.js';\nimport { findResourceKey } from '../lib/discovery.js';\nimport path from 'path';\nimport { readFileSync, readdirSync } from 'fs';\nimport type { Resource } from '@modelcontextprotocol/sdk/types.js';\n\n// Determine project root (where this is being run from)\nconst projectRoot = process.cwd();\n\nasync function startServer() {\n // Read package.json for app metadata\n const pkgPath = path.join(projectRoot, 'package.json');\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n\n // Auto-discover resource files first (to build lookup map)\n const resourcesDir = path.join(projectRoot, 'src/resources');\n const resourceFiles = readdirSync(resourcesDir).filter((f) => f.endsWith('-resource.json'));\n\n const resourcesMap = new Map<string, Resource>();\n for (const filename of resourceFiles) {\n // Extract key from filename: 'review-resource.json' -> 'review'\n const key = filename.replace(/-resource\\.json$/, '');\n const resourcePath = path.join(resourcesDir, filename);\n const resource = JSON.parse(readFileSync(resourcePath, 'utf-8')) as Resource;\n resourcesMap.set(key, resource);\n }\n\n const resourceKeys = Array.from(resourcesMap.keys());\n\n // Auto-discover simulation files\n const simulationsDir = path.join(projectRoot, 'src/simulations');\n const simulationFiles = readdirSync(simulationsDir).filter((f) => f.endsWith('-simulation.json'));\n\n // Build simulations array from discovered files\n const simulations: SimulationWithDist[] = [];\n\n for (const filename of simulationFiles) {\n // Extract simulation key from filename: 'albums-show-simulation.json' -> 'albums-show'\n const simulationKey = filename.replace(/-simulation\\.json$/, '');\n\n // Load simulation data\n const simulationPath = path.join(simulationsDir, filename);\n const simulation = JSON.parse(readFileSync(simulationPath, 'utf-8'));\n\n // Find matching resource by best prefix match\n const resourceKey = findResourceKey(simulationKey, resourceKeys);\n if (!resourceKey) {\n console.warn(\n `No matching resource found for simulation \"${simulationKey}\". ` +\n `Expected a resource file like src/resources/${simulationKey.split('-')[0]}-resource.json`\n );\n continue;\n }\n\n const resource = resourcesMap.get(resourceKey)!;\n\n simulations.push({\n ...simulation,\n name: simulationKey,\n distPath: path.join(projectRoot, `dist/${resourceKey}.js`),\n resource,\n });\n }\n\n runMCPServer({\n name: pkg.name || 'Sunpeak',\n version: pkg.version || '0.1.0',\n simulations,\n port: 6766,\n });\n}\n\nstartServer().catch((error) => {\n console.error('Failed to start MCP server:', error);\n process.exit(1);\n});\n"],"names":[],"mappings":";;;;;AAgBA,MAAM,cAAc,QAAQ,IAAA;AAE5B,eAAe,cAAc;AAE3B,QAAM,UAAU,KAAK,KAAK,aAAa,cAAc;AACrD,QAAM,MAAM,KAAK,MAAM,aAAa,SAAS,OAAO,CAAC;AAGrD,QAAM,eAAe,KAAK,KAAK,aAAa,eAAe;AAC3D,QAAM,gBAAgB,YAAY,YAAY,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,gBAAgB,CAAC;AAE1F,QAAM,mCAAmB,IAAA;AACzB,aAAW,YAAY,eAAe;AAEpC,UAAM,MAAM,SAAS,QAAQ,oBAAoB,EAAE;AACnD,UAAM,eAAe,KAAK,KAAK,cAAc,QAAQ;AACrD,UAAM,WAAW,KAAK,MAAM,aAAa,cAAc,OAAO,CAAC;AAC/D,iBAAa,IAAI,KAAK,QAAQ;AAAA,EAChC;AAEA,QAAM,eAAe,MAAM,KAAK,aAAa,MAAM;AAGnD,QAAM,iBAAiB,KAAK,KAAK,aAAa,iBAAiB;AAC/D,QAAM,kBAAkB,YAAY,cAAc,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,kBAAkB,CAAC;AAGhG,QAAM,cAAoC,CAAA;AAE1C,aAAW,YAAY,iBAAiB;AAEtC,UAAM,gBAAgB,SAAS,QAAQ,sBAAsB,EAAE;AAG/D,UAAM,iBAAiB,KAAK,KAAK,gBAAgB,QAAQ;AACzD,UAAM,aAAa,KAAK,MAAM,aAAa,gBAAgB,OAAO,CAAC;AAGnE,UAAM,cAAc,gBAAgB,eAAe,YAAY;AAC/D,QAAI,CAAC,aAAa;AAChB,cAAQ;AAAA,QACN,8CAA8C,aAAa,kDACV,cAAc,MAAM,GAAG,EAAE,CAAC,CAAC;AAAA,MAAA;AAE9E;AAAA,IACF;AAEA,UAAM,WAAW,aAAa,IAAI,WAAW;AAE7C,gBAAY,KAAK;AAAA,MACf,GAAG;AAAA,MACH,MAAM;AAAA,MACN,UAAU,KAAK,KAAK,aAAa,QAAQ,WAAW,KAAK;AAAA,MACzD;AAAA,IAAA,CACD;AAAA,EACH;AAEA,eAAa;AAAA,IACX,MAAM,IAAI,QAAQ;AAAA,IAClB,SAAS,IAAI,WAAW;AAAA,IACxB;AAAA,IACA,MAAM;AAAA,EAAA,CACP;AACH;AAEA,cAAc,MAAM,CAAC,UAAU;AAC7B,UAAQ,MAAM,+BAA+B,KAAK;AAClD,UAAQ,KAAK,CAAC;AAChB,CAAC;"}
|
package/package.json
CHANGED
|
@@ -10,101 +10,16 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { StrictMode } from 'react';
|
|
12
12
|
import { createRoot } from 'react-dom/client';
|
|
13
|
-
import { ChatGPTSimulator,
|
|
13
|
+
import { ChatGPTSimulator, buildDevSimulations } from 'sunpeak';
|
|
14
14
|
import resourceComponents from '../src/resources';
|
|
15
15
|
import '../src/styles/globals.css';
|
|
16
16
|
|
|
17
|
-
//
|
|
18
|
-
const
|
|
19
|
-
eager: true,
|
|
17
|
+
// Build simulations from discovered files
|
|
18
|
+
const simulations = buildDevSimulations({
|
|
19
|
+
simulationModules: import.meta.glob('../src/simulations/*-simulation.json', { eager: true }),
|
|
20
|
+
resourceModules: import.meta.glob('../src/resources/*-resource.json', { eager: true }),
|
|
21
|
+
resourceComponents: resourceComponents as Record<string, React.ComponentType>,
|
|
20
22
|
});
|
|
21
|
-
const resourceModules = import.meta.glob('../src/resources/*-resource.json', { eager: true });
|
|
22
|
-
|
|
23
|
-
// Build resource map from discovered files
|
|
24
|
-
type ResourceData = { name: string; [key: string]: unknown };
|
|
25
|
-
const resourcesMap = new Map<string, ResourceData>();
|
|
26
|
-
for (const [path, module] of Object.entries(resourceModules)) {
|
|
27
|
-
// Extract key from path: '../src/resources/review-resource.json' -> 'review'
|
|
28
|
-
const match = path.match(/\/([^/]+)-resource\.json$/);
|
|
29
|
-
const key = match?.[1];
|
|
30
|
-
if (key) {
|
|
31
|
-
resourcesMap.set(key, (module as { default: ResourceData }).default);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Get sorted resource keys for best-match lookup (longest first)
|
|
36
|
-
const resourceKeys = Array.from(resourcesMap.keys()).sort((a, b) => b.length - a.length);
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Find the best matching resource for a simulation key.
|
|
40
|
-
* Matches the longest resource name that is a prefix of the simulation key.
|
|
41
|
-
* e.g., 'albums-show' matches 'albums' (not 'album' if both exist)
|
|
42
|
-
*/
|
|
43
|
-
function findResourceKey(simulationKey: string): string | undefined {
|
|
44
|
-
// Simulation key format: {resource}-{tool} (e.g., 'albums-show')
|
|
45
|
-
// Find the longest resource name that matches as a prefix followed by '-'
|
|
46
|
-
for (const resourceKey of resourceKeys) {
|
|
47
|
-
if (simulationKey === resourceKey || simulationKey.startsWith(resourceKey + '-')) {
|
|
48
|
-
return resourceKey;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return undefined;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Convert resource name to component name
|
|
56
|
-
* Example: 'carousel' -> 'CarouselResource', 'review' -> 'ReviewResource'
|
|
57
|
-
*/
|
|
58
|
-
function getResourceComponent(name: string): React.ComponentType {
|
|
59
|
-
const pascalName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
60
|
-
const componentName = `${pascalName}Resource`;
|
|
61
|
-
|
|
62
|
-
const component = (resourceComponents as Record<string, React.ComponentType>)[componentName];
|
|
63
|
-
|
|
64
|
-
if (!component) {
|
|
65
|
-
throw new Error(
|
|
66
|
-
`Resource component "${componentName}" not found for resource "${name}". ` +
|
|
67
|
-
`Make sure src/resources/${name}-resource.tsx exists with a default export.`
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return component;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Build simulations object from discovered files
|
|
75
|
-
type SimulationData = Omit<Simulation, 'name' | 'resourceComponent' | 'resource'>;
|
|
76
|
-
const simulations: Record<string, Simulation> = {};
|
|
77
|
-
|
|
78
|
-
for (const [path, module] of Object.entries(simulationModules)) {
|
|
79
|
-
// Extract simulation key from path: '../src/simulations/albums-show-simulation.json' -> 'albums-show'
|
|
80
|
-
const match = path.match(/\/([^/]+)-simulation\.json$/);
|
|
81
|
-
const simulationKey = match?.[1];
|
|
82
|
-
if (!simulationKey) continue;
|
|
83
|
-
|
|
84
|
-
const simulation = (module as { default: SimulationData }).default;
|
|
85
|
-
|
|
86
|
-
// Find matching resource by best prefix match
|
|
87
|
-
const resourceKey = findResourceKey(simulationKey);
|
|
88
|
-
if (!resourceKey) {
|
|
89
|
-
console.warn(
|
|
90
|
-
`No matching resource found for simulation "${simulationKey}". ` +
|
|
91
|
-
`Expected a resource file like src/resources/${simulationKey.split('-')[0]}-resource.json`
|
|
92
|
-
);
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const resource = resourcesMap.get(resourceKey)!;
|
|
97
|
-
|
|
98
|
-
simulations[simulationKey] = {
|
|
99
|
-
...simulation,
|
|
100
|
-
name: simulationKey,
|
|
101
|
-
resource: {
|
|
102
|
-
uri: `ui://${resource.name}`, // Dummy URI.
|
|
103
|
-
...resource,
|
|
104
|
-
},
|
|
105
|
-
resourceComponent: getResourceComponent(resource.name),
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
23
|
|
|
109
24
|
// Read app config from environment or use defaults
|
|
110
25
|
const appName = import.meta.env?.VITE_APP_NAME || 'Sunpeak';
|
package/template/dist/map.json
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
dist_exports4 as dist_exports
|
|
3
|
-
} from "./chunk-TSEQUROC.js";
|
|
4
|
-
import "./chunk-XZTIOEPG.js";
|
|
5
1
|
import {
|
|
6
2
|
useResizeObserver
|
|
7
3
|
} from "./chunk-JAGHY6H6.js";
|
|
@@ -10,6 +6,10 @@ import {
|
|
|
10
6
|
waitForAnimationFrame
|
|
11
7
|
} from "./chunk-DYQDWJMS.js";
|
|
12
8
|
import "./chunk-EGRHWZRV.js";
|
|
9
|
+
import {
|
|
10
|
+
dist_exports4 as dist_exports
|
|
11
|
+
} from "./chunk-TSEQUROC.js";
|
|
12
|
+
import "./chunk-XZTIOEPG.js";
|
|
13
13
|
import {
|
|
14
14
|
clsx_default
|
|
15
15
|
} from "./chunk-CNYJBM5F.js";
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Input
|
|
3
3
|
} from "./chunk-2DZGWGIP.js";
|
|
4
|
-
import {
|
|
5
|
-
dist_exports,
|
|
6
|
-
dist_exports3 as dist_exports2,
|
|
7
|
-
dist_exports5 as dist_exports3
|
|
8
|
-
} from "./chunk-TSEQUROC.js";
|
|
9
|
-
import "./chunk-XZTIOEPG.js";
|
|
10
4
|
import {
|
|
11
5
|
Button,
|
|
12
6
|
LoadingIndicator,
|
|
@@ -25,6 +19,12 @@ import {
|
|
|
25
19
|
waitForAnimationFrame
|
|
26
20
|
} from "./chunk-DYQDWJMS.js";
|
|
27
21
|
import "./chunk-EGRHWZRV.js";
|
|
22
|
+
import {
|
|
23
|
+
dist_exports,
|
|
24
|
+
dist_exports3 as dist_exports2,
|
|
25
|
+
dist_exports5 as dist_exports3
|
|
26
|
+
} from "./chunk-TSEQUROC.js";
|
|
27
|
+
import "./chunk-XZTIOEPG.js";
|
|
28
28
|
import {
|
|
29
29
|
clsx_default
|
|
30
30
|
} from "./chunk-CNYJBM5F.js";
|
|
@@ -7,115 +7,115 @@
|
|
|
7
7
|
"react": {
|
|
8
8
|
"src": "../../../../node_modules/.pnpm/react@19.2.3/node_modules/react/index.js",
|
|
9
9
|
"file": "react.js",
|
|
10
|
-
"fileHash": "
|
|
10
|
+
"fileHash": "16fe6b1d",
|
|
11
11
|
"needsInterop": true
|
|
12
12
|
},
|
|
13
13
|
"react-dom": {
|
|
14
14
|
"src": "../../../../node_modules/.pnpm/react-dom@19.2.3_react@19.2.3/node_modules/react-dom/index.js",
|
|
15
15
|
"file": "react-dom.js",
|
|
16
|
-
"fileHash": "
|
|
16
|
+
"fileHash": "51aae1bd",
|
|
17
17
|
"needsInterop": true
|
|
18
18
|
},
|
|
19
19
|
"react/jsx-dev-runtime": {
|
|
20
20
|
"src": "../../../../node_modules/.pnpm/react@19.2.3/node_modules/react/jsx-dev-runtime.js",
|
|
21
21
|
"file": "react_jsx-dev-runtime.js",
|
|
22
|
-
"fileHash": "
|
|
22
|
+
"fileHash": "ff5a7570",
|
|
23
23
|
"needsInterop": true
|
|
24
24
|
},
|
|
25
25
|
"react/jsx-runtime": {
|
|
26
26
|
"src": "../../../../node_modules/.pnpm/react@19.2.3/node_modules/react/jsx-runtime.js",
|
|
27
27
|
"file": "react_jsx-runtime.js",
|
|
28
|
-
"fileHash": "
|
|
28
|
+
"fileHash": "3beaa8db",
|
|
29
29
|
"needsInterop": true
|
|
30
30
|
},
|
|
31
31
|
"@openai/apps-sdk-ui/components/Avatar": {
|
|
32
32
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.1_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._90324f97b7190ccfdbe40a9e8bef3385/node_modules/@openai/apps-sdk-ui/dist/es/components/Avatar/index.js",
|
|
33
33
|
"file": "@openai_apps-sdk-ui_components_Avatar.js",
|
|
34
|
-
"fileHash": "
|
|
34
|
+
"fileHash": "45ef3fc1",
|
|
35
35
|
"needsInterop": false
|
|
36
36
|
},
|
|
37
37
|
"@openai/apps-sdk-ui/components/Button": {
|
|
38
38
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.1_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._90324f97b7190ccfdbe40a9e8bef3385/node_modules/@openai/apps-sdk-ui/dist/es/components/Button/index.js",
|
|
39
39
|
"file": "@openai_apps-sdk-ui_components_Button.js",
|
|
40
|
-
"fileHash": "
|
|
40
|
+
"fileHash": "45a08a33",
|
|
41
41
|
"needsInterop": false
|
|
42
42
|
},
|
|
43
43
|
"@openai/apps-sdk-ui/components/Checkbox": {
|
|
44
44
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.1_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._90324f97b7190ccfdbe40a9e8bef3385/node_modules/@openai/apps-sdk-ui/dist/es/components/Checkbox/index.js",
|
|
45
45
|
"file": "@openai_apps-sdk-ui_components_Checkbox.js",
|
|
46
|
-
"fileHash": "
|
|
46
|
+
"fileHash": "d5dd2181",
|
|
47
47
|
"needsInterop": false
|
|
48
48
|
},
|
|
49
49
|
"@openai/apps-sdk-ui/components/Icon": {
|
|
50
50
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.1_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._90324f97b7190ccfdbe40a9e8bef3385/node_modules/@openai/apps-sdk-ui/dist/es/components/Icon/index.js",
|
|
51
51
|
"file": "@openai_apps-sdk-ui_components_Icon.js",
|
|
52
|
-
"fileHash": "
|
|
52
|
+
"fileHash": "e1858071",
|
|
53
53
|
"needsInterop": false
|
|
54
54
|
},
|
|
55
55
|
"@openai/apps-sdk-ui/components/Input": {
|
|
56
56
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.1_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._90324f97b7190ccfdbe40a9e8bef3385/node_modules/@openai/apps-sdk-ui/dist/es/components/Input/index.js",
|
|
57
57
|
"file": "@openai_apps-sdk-ui_components_Input.js",
|
|
58
|
-
"fileHash": "
|
|
58
|
+
"fileHash": "336a28c8",
|
|
59
59
|
"needsInterop": false
|
|
60
60
|
},
|
|
61
61
|
"@openai/apps-sdk-ui/components/SegmentedControl": {
|
|
62
62
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.1_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._90324f97b7190ccfdbe40a9e8bef3385/node_modules/@openai/apps-sdk-ui/dist/es/components/SegmentedControl/index.js",
|
|
63
63
|
"file": "@openai_apps-sdk-ui_components_SegmentedControl.js",
|
|
64
|
-
"fileHash": "
|
|
64
|
+
"fileHash": "18dac619",
|
|
65
65
|
"needsInterop": false
|
|
66
66
|
},
|
|
67
67
|
"@openai/apps-sdk-ui/components/Select": {
|
|
68
68
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.1_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._90324f97b7190ccfdbe40a9e8bef3385/node_modules/@openai/apps-sdk-ui/dist/es/components/Select/index.js",
|
|
69
69
|
"file": "@openai_apps-sdk-ui_components_Select.js",
|
|
70
|
-
"fileHash": "
|
|
70
|
+
"fileHash": "bebc362b",
|
|
71
71
|
"needsInterop": false
|
|
72
72
|
},
|
|
73
73
|
"@openai/apps-sdk-ui/components/Textarea": {
|
|
74
74
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.1_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._90324f97b7190ccfdbe40a9e8bef3385/node_modules/@openai/apps-sdk-ui/dist/es/components/Textarea/index.js",
|
|
75
75
|
"file": "@openai_apps-sdk-ui_components_Textarea.js",
|
|
76
|
-
"fileHash": "
|
|
76
|
+
"fileHash": "d96e1cd8",
|
|
77
77
|
"needsInterop": false
|
|
78
78
|
},
|
|
79
79
|
"@openai/apps-sdk-ui/theme": {
|
|
80
80
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.1_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._90324f97b7190ccfdbe40a9e8bef3385/node_modules/@openai/apps-sdk-ui/dist/es/lib/theme.js",
|
|
81
81
|
"file": "@openai_apps-sdk-ui_theme.js",
|
|
82
|
-
"fileHash": "
|
|
82
|
+
"fileHash": "b92ead18",
|
|
83
83
|
"needsInterop": false
|
|
84
84
|
},
|
|
85
85
|
"clsx": {
|
|
86
86
|
"src": "../../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs",
|
|
87
87
|
"file": "clsx.js",
|
|
88
|
-
"fileHash": "
|
|
88
|
+
"fileHash": "5cf92f9f",
|
|
89
89
|
"needsInterop": false
|
|
90
90
|
},
|
|
91
91
|
"embla-carousel-react": {
|
|
92
92
|
"src": "../../../../node_modules/.pnpm/embla-carousel-react@8.6.0_react@19.2.3/node_modules/embla-carousel-react/esm/embla-carousel-react.esm.js",
|
|
93
93
|
"file": "embla-carousel-react.js",
|
|
94
|
-
"fileHash": "
|
|
94
|
+
"fileHash": "b5425b87",
|
|
95
95
|
"needsInterop": false
|
|
96
96
|
},
|
|
97
97
|
"embla-carousel-wheel-gestures": {
|
|
98
98
|
"src": "../../../../node_modules/.pnpm/embla-carousel-wheel-gestures@8.1.0_embla-carousel@8.6.0/node_modules/embla-carousel-wheel-gestures/dist/embla-carousel-wheel-gestures.esm.js",
|
|
99
99
|
"file": "embla-carousel-wheel-gestures.js",
|
|
100
|
-
"fileHash": "
|
|
100
|
+
"fileHash": "333b6f95",
|
|
101
101
|
"needsInterop": false
|
|
102
102
|
},
|
|
103
103
|
"mapbox-gl": {
|
|
104
104
|
"src": "../../../../node_modules/.pnpm/mapbox-gl@3.17.0/node_modules/mapbox-gl/dist/mapbox-gl.js",
|
|
105
105
|
"file": "mapbox-gl.js",
|
|
106
|
-
"fileHash": "
|
|
106
|
+
"fileHash": "4a0aa065",
|
|
107
107
|
"needsInterop": true
|
|
108
108
|
},
|
|
109
109
|
"react-dom/client": {
|
|
110
110
|
"src": "../../../../node_modules/.pnpm/react-dom@19.2.3_react@19.2.3/node_modules/react-dom/client.js",
|
|
111
111
|
"file": "react-dom_client.js",
|
|
112
|
-
"fileHash": "
|
|
112
|
+
"fileHash": "9ec14e81",
|
|
113
113
|
"needsInterop": true
|
|
114
114
|
},
|
|
115
115
|
"tailwind-merge": {
|
|
116
116
|
"src": "../../../../node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/bundle-mjs.mjs",
|
|
117
117
|
"file": "tailwind-merge.js",
|
|
118
|
-
"fileHash": "
|
|
118
|
+
"fileHash": "29cd9aec",
|
|
119
119
|
"needsInterop": false
|
|
120
120
|
}
|
|
121
121
|
},
|
|
@@ -123,12 +123,6 @@
|
|
|
123
123
|
"chunk-2DZGWGIP": {
|
|
124
124
|
"file": "chunk-2DZGWGIP.js"
|
|
125
125
|
},
|
|
126
|
-
"chunk-TSEQUROC": {
|
|
127
|
-
"file": "chunk-TSEQUROC.js"
|
|
128
|
-
},
|
|
129
|
-
"chunk-XZTIOEPG": {
|
|
130
|
-
"file": "chunk-XZTIOEPG.js"
|
|
131
|
-
},
|
|
132
126
|
"chunk-N6DVYEXK": {
|
|
133
127
|
"file": "chunk-N6DVYEXK.js"
|
|
134
128
|
},
|
|
@@ -144,6 +138,12 @@
|
|
|
144
138
|
"chunk-EGRHWZRV": {
|
|
145
139
|
"file": "chunk-EGRHWZRV.js"
|
|
146
140
|
},
|
|
141
|
+
"chunk-TSEQUROC": {
|
|
142
|
+
"file": "chunk-TSEQUROC.js"
|
|
143
|
+
},
|
|
144
|
+
"chunk-XZTIOEPG": {
|
|
145
|
+
"file": "chunk-XZTIOEPG.js"
|
|
146
|
+
},
|
|
147
147
|
"chunk-CNYJBM5F": {
|
|
148
148
|
"file": "chunk-CNYJBM5F.js"
|
|
149
149
|
},
|
package/template/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"4.0.16","results":[[":src/
|
|
1
|
+
{"version":"4.0.16","results":[[":src/resources/carousel-resource.test.tsx",{"duration":204.00628299999994,"failed":false}],[":src/components/album/albums.test.tsx",{"duration":354.172869,"failed":false}],[":src/resources/review-resource.test.tsx",{"duration":595.9101029999999,"failed":false}],[":src/components/map/map-view.test.tsx",{"duration":105.68071299999997,"failed":false}],[":src/components/map/place-inspector.test.tsx",{"duration":435.9108799999999,"failed":false}],[":src/components/album/fullscreen-viewer.test.tsx",{"duration":261.4708179999998,"failed":false}],[":src/components/map/place-list.test.tsx",{"duration":150.6326839999997,"failed":false}],[":src/components/map/place-card.test.tsx",{"duration":322.1624179999999,"failed":false}],[":src/components/map/place-carousel.test.tsx",{"duration":473.4562349999999,"failed":false}],[":src/components/album/album-carousel.test.tsx",{"duration":95.72953800000005,"failed":false}],[":src/components/carousel/carousel.test.tsx",{"duration":81.29822799999988,"failed":false}],[":src/resources/map-resource.test.tsx",{"duration":292.5989340000001,"failed":false}],[":src/resources/albums-resource.test.tsx",{"duration":276.86686799999984,"failed":false}],[":src/components/album/film-strip.test.tsx",{"duration":446.0441739999999,"failed":false}],[":src/components/album/album-card.test.tsx",{"duration":288.73790800000006,"failed":false}],[":src/components/carousel/card.test.tsx",{"duration":92.50694299999986,"failed":false}]]}
|
|
@@ -8,32 +8,10 @@
|
|
|
8
8
|
* - Default export: export default MyComponent
|
|
9
9
|
* - Named export: export const ReviewResource = ...
|
|
10
10
|
*/
|
|
11
|
+
import { createResourceExports } from 'sunpeak';
|
|
11
12
|
|
|
12
13
|
// Auto-discover all resource component files
|
|
13
14
|
const resourceModules = import.meta.glob('./*-resource.tsx', { eager: true });
|
|
14
15
|
|
|
15
|
-
// Build exports object from discovered files
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
for (const [path, module] of Object.entries(resourceModules)) {
|
|
19
|
-
// Extract key from path: './review-resource.tsx' -> 'review'
|
|
20
|
-
const match = path.match(/\.\/(.+)-resource\.tsx$/);
|
|
21
|
-
const key = match?.[1];
|
|
22
|
-
if (!key) continue;
|
|
23
|
-
|
|
24
|
-
// Convert to PascalCase and append 'Resource': 'review' -> 'ReviewResource'
|
|
25
|
-
const pascalKey = key.charAt(0).toUpperCase() + key.slice(1);
|
|
26
|
-
const exportName = `${pascalKey}Resource`;
|
|
27
|
-
|
|
28
|
-
const mod = module as Record<string, unknown>;
|
|
29
|
-
|
|
30
|
-
// Try default export first, then named export matching the expected name
|
|
31
|
-
const component = mod.default ?? mod[exportName];
|
|
32
|
-
|
|
33
|
-
// Accept functions (regular components) or objects (forwardRef/memo components)
|
|
34
|
-
if (component && (typeof component === 'function' || typeof component === 'object')) {
|
|
35
|
-
resources[exportName] = component as React.ComponentType;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export default resources;
|
|
16
|
+
// Build exports object from discovered files using library helper
|
|
17
|
+
export default createResourceExports(resourceModules);
|