sunpeak 0.5.17 → 0.5.19
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/README.md +2 -2
- package/bin/sunpeak.js +120 -8
- package/cli/build.mjs +199 -0
- package/cli/validate.mjs +245 -0
- package/package.json +2 -1
- package/template/README.md +45 -9
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Button.js +2 -2
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_SegmentedControl.js +1 -1
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Select.js +8 -8
- package/template/node_modules/.vite/deps/_metadata.json +26 -26
- package/template/node_modules/.vite/deps/{chunk-PRLY65A2.js → chunk-675LFNY2.js} +4 -4
- package/template/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/template/package.json +2 -2
- package/template/scripts/build-all.mjs +0 -117
- package/template/scripts/validate.mjs +0 -186
- /package/template/node_modules/.vite/deps/{chunk-PRLY65A2.js.map → chunk-675LFNY2.js.map} +0 -0
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
<a href="https://sunpeak.ai">
|
|
3
3
|
<picture>
|
|
4
|
-
<img alt="Sunpeak logo" src="https://
|
|
4
|
+
<img alt="Sunpeak logo" src="https://d10djik02wlf6x.cloudfront.net/sunpeak-github.svg">
|
|
5
5
|
</picture>
|
|
6
6
|
</a>
|
|
7
7
|
</div>
|
|
@@ -24,7 +24,7 @@ Quickstart, build, and test your ChatGPT App locally with OpenAI apps-sdk-ui Rea
|
|
|
24
24
|
<div align="center">
|
|
25
25
|
<a href="https://docs.sunpeak.ai/library/chatgpt-simulator">
|
|
26
26
|
<picture>
|
|
27
|
-
<img alt="ChatGPT Simulator" src="https://
|
|
27
|
+
<img alt="ChatGPT Simulator" src="https://d10djik02wlf6x.cloudfront.net/chatgpt-simulator.png">
|
|
28
28
|
</picture>
|
|
29
29
|
</a>
|
|
30
30
|
</div>
|
package/bin/sunpeak.js
CHANGED
|
@@ -4,8 +4,10 @@ import { existsSync, mkdirSync, cpSync, readFileSync, writeFileSync, renameSync
|
|
|
4
4
|
import { join, dirname, basename } from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { createInterface } from 'readline';
|
|
7
|
+
import { spawn } from 'child_process';
|
|
7
8
|
|
|
8
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const CLI_DIR = join(__dirname, '..', 'cli');
|
|
9
11
|
|
|
10
12
|
function prompt(question) {
|
|
11
13
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -17,6 +19,28 @@ function prompt(question) {
|
|
|
17
19
|
});
|
|
18
20
|
}
|
|
19
21
|
|
|
22
|
+
function runCommand(command, args = [], options = {}) {
|
|
23
|
+
const child = spawn(command, args, {
|
|
24
|
+
cwd: process.cwd(),
|
|
25
|
+
stdio: 'inherit',
|
|
26
|
+
env: { ...process.env, FORCE_COLOR: '1' },
|
|
27
|
+
...options,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
child.on('exit', (code) => {
|
|
31
|
+
process.exit(code || 0);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function checkPackageJson() {
|
|
36
|
+
const pkgPath = join(process.cwd(), 'package.json');
|
|
37
|
+
if (!existsSync(pkgPath)) {
|
|
38
|
+
console.error('Error: No package.json found in current directory.');
|
|
39
|
+
console.error('Make sure you are in a Sunpeak project directory.');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
20
44
|
async function init(projectName) {
|
|
21
45
|
if (!projectName) {
|
|
22
46
|
projectName = await prompt('☀️ 🏔️ Project name [my-app]: ');
|
|
@@ -82,7 +106,8 @@ async function init(projectName) {
|
|
|
82
106
|
Done! To get started:
|
|
83
107
|
|
|
84
108
|
cd ${projectName}
|
|
85
|
-
pnpm install
|
|
109
|
+
pnpm install
|
|
110
|
+
sunpeak dev
|
|
86
111
|
|
|
87
112
|
See README.md for more details.
|
|
88
113
|
`);
|
|
@@ -90,13 +115,100 @@ See README.md for more details.
|
|
|
90
115
|
|
|
91
116
|
const [, , command, ...args] = process.argv;
|
|
92
117
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
118
|
+
// Main CLI handler
|
|
119
|
+
(async () => {
|
|
120
|
+
// Commands that don't require a package.json
|
|
121
|
+
const standaloneCommands = ['new', 'help', undefined];
|
|
122
|
+
|
|
123
|
+
if (command && !standaloneCommands.includes(command)) {
|
|
124
|
+
checkPackageJson();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
switch (command) {
|
|
128
|
+
case 'new':
|
|
129
|
+
await init(args[0]);
|
|
130
|
+
break;
|
|
131
|
+
|
|
132
|
+
case 'dev':
|
|
133
|
+
runCommand('pnpm', ['dev', ...args]);
|
|
134
|
+
break;
|
|
135
|
+
|
|
136
|
+
case 'build':
|
|
137
|
+
{
|
|
138
|
+
const { build } = await import(join(CLI_DIR, 'build.mjs'));
|
|
139
|
+
await build(process.cwd());
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
|
|
143
|
+
case 'mcp':
|
|
144
|
+
case 'mcp:serve':
|
|
145
|
+
if (command === 'mcp:serve' || args[0] === 'serve' || args[0] === ':serve') {
|
|
146
|
+
runCommand('pnpm', ['mcp:serve', ...(command === 'mcp:serve' ? args : args.slice(1))]);
|
|
147
|
+
} else {
|
|
148
|
+
runCommand('pnpm', ['mcp', ...args]);
|
|
149
|
+
}
|
|
150
|
+
break;
|
|
151
|
+
|
|
152
|
+
case 'lint':
|
|
153
|
+
runCommand('pnpm', ['lint', ...args]);
|
|
154
|
+
break;
|
|
155
|
+
|
|
156
|
+
case 'typecheck':
|
|
157
|
+
runCommand('pnpm', ['typecheck', ...args]);
|
|
158
|
+
break;
|
|
159
|
+
|
|
160
|
+
case 'test':
|
|
161
|
+
runCommand('pnpm', ['test', ...args]);
|
|
162
|
+
break;
|
|
163
|
+
|
|
164
|
+
case 'format':
|
|
165
|
+
runCommand('pnpm', ['format', ...args]);
|
|
166
|
+
break;
|
|
167
|
+
|
|
168
|
+
case 'validate':
|
|
169
|
+
{
|
|
170
|
+
const { validate } = await import(join(CLI_DIR, 'validate.mjs'));
|
|
171
|
+
await validate(process.cwd());
|
|
172
|
+
}
|
|
173
|
+
break;
|
|
174
|
+
|
|
175
|
+
case 'help':
|
|
176
|
+
case undefined:
|
|
177
|
+
console.log(`
|
|
178
|
+
☀️ 🏔️ sunpeak - The MCP App SDK
|
|
179
|
+
|
|
180
|
+
Usage:
|
|
181
|
+
sunpeak <command> [options]
|
|
98
182
|
|
|
99
183
|
Commands:
|
|
100
|
-
new [name]
|
|
184
|
+
new [name] Create a new project from template
|
|
185
|
+
dev Start the development server
|
|
186
|
+
build Build all resources for production
|
|
187
|
+
mcp Run the MCP server with nodemon
|
|
188
|
+
mcp:serve Run the MCP server directly
|
|
189
|
+
lint Run ESLint to check code quality
|
|
190
|
+
typecheck Run TypeScript type checking
|
|
191
|
+
test Run tests with Vitest
|
|
192
|
+
format Format code with Prettier
|
|
193
|
+
validate Run full validation suite
|
|
194
|
+
help Show this help message
|
|
195
|
+
|
|
196
|
+
Examples:
|
|
197
|
+
sunpeak new my-app
|
|
198
|
+
sunpeak dev
|
|
199
|
+
sunpeak build
|
|
200
|
+
sunpeak mcp
|
|
201
|
+
|
|
202
|
+
For more information, visit: https://sunpeak.ai/
|
|
101
203
|
`);
|
|
102
|
-
|
|
204
|
+
break;
|
|
205
|
+
|
|
206
|
+
default:
|
|
207
|
+
console.error(`Unknown command: ${command}`);
|
|
208
|
+
console.error('Run "sunpeak help" to see available commands.');
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
})().catch((error) => {
|
|
212
|
+
console.error('Error:', error.message);
|
|
213
|
+
process.exit(1);
|
|
214
|
+
});
|
package/cli/build.mjs
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import { existsSync, rmSync, readdirSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Detect package manager for the project
|
|
8
|
+
*/
|
|
9
|
+
function detectPackageManager(projectRoot) {
|
|
10
|
+
if (existsSync(path.join(projectRoot, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
11
|
+
if (existsSync(path.join(projectRoot, 'yarn.lock'))) return 'yarn';
|
|
12
|
+
if (existsSync(path.join(projectRoot, 'package-lock.json'))) return 'npm';
|
|
13
|
+
return 'pnpm'; // default
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Build all resources for a Sunpeak project
|
|
18
|
+
* Runs in the context of a user's project directory
|
|
19
|
+
*/
|
|
20
|
+
export async function build(projectRoot = process.cwd()) {
|
|
21
|
+
const pm = detectPackageManager(projectRoot);
|
|
22
|
+
|
|
23
|
+
// Check for package.json first
|
|
24
|
+
const pkgJsonPath = path.join(projectRoot, 'package.json');
|
|
25
|
+
if (!existsSync(pkgJsonPath)) {
|
|
26
|
+
console.error('Error: No package.json found in current directory');
|
|
27
|
+
console.error('Make sure you are in a Sunpeak project directory');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const distDir = path.join(projectRoot, 'dist/chatgpt');
|
|
32
|
+
const buildDir = path.join(projectRoot, 'dist/build-output');
|
|
33
|
+
const tempDir = path.join(projectRoot, '.tmp');
|
|
34
|
+
const resourcesDir = path.join(projectRoot, 'src/components/resources');
|
|
35
|
+
const templateFile = path.join(projectRoot, 'src/index-resource.tsx');
|
|
36
|
+
const viteConfigFile = path.join(projectRoot, 'vite.config.build.ts');
|
|
37
|
+
|
|
38
|
+
// Validate project structure
|
|
39
|
+
if (!existsSync(resourcesDir)) {
|
|
40
|
+
console.error('Error: src/components/resources directory not found');
|
|
41
|
+
console.error('Expected location: ' + resourcesDir);
|
|
42
|
+
console.error('\nThe build command expects the standard Sunpeak project structure.');
|
|
43
|
+
console.error('If you have customized your project structure, you may need to use');
|
|
44
|
+
console.error('a custom build script instead of "sunpeak build".');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!existsSync(templateFile)) {
|
|
49
|
+
console.error('Error: src/index-resource.tsx not found');
|
|
50
|
+
console.error('Expected location: ' + templateFile);
|
|
51
|
+
console.error('\nThis file is the template entry point for building resources.');
|
|
52
|
+
console.error('If you have moved or renamed it, you may need to use a custom build script.');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!existsSync(viteConfigFile)) {
|
|
57
|
+
console.error('Error: vite.config.build.ts not found');
|
|
58
|
+
console.error('Expected location: ' + viteConfigFile);
|
|
59
|
+
console.error('\nThis Vite config is required for building resources.');
|
|
60
|
+
console.error('If you have renamed it, you may need to use a custom build script.');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Clean dist and temp directories
|
|
65
|
+
if (existsSync(distDir)) {
|
|
66
|
+
rmSync(distDir, { recursive: true });
|
|
67
|
+
}
|
|
68
|
+
if (existsSync(tempDir)) {
|
|
69
|
+
rmSync(tempDir, { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
mkdirSync(distDir, { recursive: true });
|
|
72
|
+
mkdirSync(tempDir, { recursive: true });
|
|
73
|
+
|
|
74
|
+
// Auto-discover all resources
|
|
75
|
+
const resourceFiles = readdirSync(resourcesDir)
|
|
76
|
+
.filter(file => file.endsWith('-resource.tsx'))
|
|
77
|
+
.map(file => {
|
|
78
|
+
// Extract kebab-case name: 'counter-resource.tsx' -> 'counter'
|
|
79
|
+
const kebabName = file.replace('-resource.tsx', '');
|
|
80
|
+
|
|
81
|
+
// Convert kebab-case to PascalCase: 'counter' -> 'Counter', 'my-widget' -> 'MyWidget'
|
|
82
|
+
const pascalName = kebabName
|
|
83
|
+
.split('-')
|
|
84
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
85
|
+
.join('');
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
componentName: `${pascalName}Resource`,
|
|
89
|
+
componentFile: file.replace('.tsx', ''),
|
|
90
|
+
entry: `.tmp/index-${kebabName}.tsx`,
|
|
91
|
+
output: `${kebabName}.js`,
|
|
92
|
+
buildOutDir: path.join(buildDir, kebabName),
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (resourceFiles.length === 0) {
|
|
97
|
+
console.error('Error: No resource files found in src/components/resources/');
|
|
98
|
+
console.error('Resource files should be named like: counter-resource.tsx');
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log('Building all resources...\n');
|
|
103
|
+
|
|
104
|
+
// Read and validate the template
|
|
105
|
+
const template = readFileSync(templateFile, 'utf-8');
|
|
106
|
+
|
|
107
|
+
// Verify template has required placeholders
|
|
108
|
+
if (!template.includes('// RESOURCE_IMPORT')) {
|
|
109
|
+
console.error('Error: src/index-resource.tsx is missing "// RESOURCE_IMPORT" placeholder');
|
|
110
|
+
console.error('\nThe template file must include this comment where the resource import should go.');
|
|
111
|
+
console.error('If you have customized this file, ensure it has the required placeholders.');
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!template.includes('// RESOURCE_MOUNT')) {
|
|
116
|
+
console.error('Error: src/index-resource.tsx is missing "// RESOURCE_MOUNT" placeholder');
|
|
117
|
+
console.error('\nThe template file must include this comment where the resource mount should go.');
|
|
118
|
+
console.error('If you have customized this file, ensure it has the required placeholders.');
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Build all resources (but don't copy yet)
|
|
123
|
+
resourceFiles.forEach(({ componentName, componentFile, entry, output, buildOutDir }, index) => {
|
|
124
|
+
console.log(`[${index + 1}/${resourceFiles.length}] Building ${output}...`);
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
// Create build directory if it doesn't exist
|
|
128
|
+
if (!existsSync(buildOutDir)) {
|
|
129
|
+
mkdirSync(buildOutDir, { recursive: true });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Create entry file from template in temp directory
|
|
133
|
+
const entryContent = template
|
|
134
|
+
.replace('// RESOURCE_IMPORT', `import { ${componentName} } from '../src/components/resources/${componentFile}';`)
|
|
135
|
+
.replace('// RESOURCE_MOUNT', `createRoot(root).render(<${componentName} />);`);
|
|
136
|
+
|
|
137
|
+
const entryPath = path.join(projectRoot, entry);
|
|
138
|
+
writeFileSync(entryPath, entryContent);
|
|
139
|
+
|
|
140
|
+
// Build with vite to build directory
|
|
141
|
+
const viteCommand = pm === 'npm' ? 'npx vite' : `${pm} exec vite`;
|
|
142
|
+
execSync(
|
|
143
|
+
`${viteCommand} build --config vite.config.build.ts`,
|
|
144
|
+
{
|
|
145
|
+
cwd: projectRoot,
|
|
146
|
+
stdio: 'inherit',
|
|
147
|
+
env: {
|
|
148
|
+
...process.env,
|
|
149
|
+
ENTRY_FILE: entry,
|
|
150
|
+
OUTPUT_FILE: output,
|
|
151
|
+
OUT_DIR: buildOutDir,
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error(`Failed to build ${output}`);
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Now copy all files from build-output to dist/chatgpt
|
|
162
|
+
console.log('\nCopying built files to dist/chatgpt...');
|
|
163
|
+
resourceFiles.forEach(({ output, buildOutDir }) => {
|
|
164
|
+
const builtFile = path.join(buildOutDir, output);
|
|
165
|
+
const destFile = path.join(distDir, output);
|
|
166
|
+
|
|
167
|
+
if (existsSync(builtFile)) {
|
|
168
|
+
copyFileSync(builtFile, destFile);
|
|
169
|
+
console.log(`✓ Copied ${output}`);
|
|
170
|
+
} else {
|
|
171
|
+
console.error(`Built file not found: ${builtFile}`);
|
|
172
|
+
if (existsSync(buildOutDir)) {
|
|
173
|
+
console.log(` Files in ${buildOutDir}:`, readdirSync(buildOutDir));
|
|
174
|
+
} else {
|
|
175
|
+
console.log(` Build directory doesn't exist: ${buildOutDir}`);
|
|
176
|
+
}
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Clean up temp and build directories
|
|
182
|
+
if (existsSync(tempDir)) {
|
|
183
|
+
rmSync(tempDir, { recursive: true });
|
|
184
|
+
}
|
|
185
|
+
if (existsSync(buildDir)) {
|
|
186
|
+
rmSync(buildDir, { recursive: true });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.log('\n✓ All resources built successfully!');
|
|
190
|
+
console.log(`\nBuilt files:`, readdirSync(distDir));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Allow running directly
|
|
194
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
195
|
+
build().catch(error => {
|
|
196
|
+
console.error(error);
|
|
197
|
+
process.exit(1);
|
|
198
|
+
});
|
|
199
|
+
}
|
package/cli/validate.mjs
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync, spawn } from 'child_process';
|
|
3
|
+
import { existsSync, readdirSync, readFileSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import http from 'http';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Validate a Sunpeak project by running all checks
|
|
9
|
+
* Runs in the context of a user's project directory
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Color codes for output
|
|
13
|
+
const colors = {
|
|
14
|
+
red: '\x1b[0;31m',
|
|
15
|
+
green: '\x1b[0;32m',
|
|
16
|
+
blue: '\x1b[0;34m',
|
|
17
|
+
yellow: '\x1b[1;33m',
|
|
18
|
+
reset: '\x1b[0m',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function printSuccess(text) {
|
|
22
|
+
console.log(`${colors.green}✓ ${text}${colors.reset}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function printError(text) {
|
|
26
|
+
console.log(`${colors.red}✗ ${text}${colors.reset}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function printWarning(text) {
|
|
30
|
+
console.log(`${colors.yellow}⚠ ${text}${colors.reset}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function detectPackageManager(projectRoot) {
|
|
34
|
+
if (existsSync(join(projectRoot, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
35
|
+
if (existsSync(join(projectRoot, 'yarn.lock'))) return 'yarn';
|
|
36
|
+
if (existsSync(join(projectRoot, 'package-lock.json'))) return 'npm';
|
|
37
|
+
return 'pnpm'; // default
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function hasScript(projectRoot, scriptName) {
|
|
41
|
+
try {
|
|
42
|
+
const pkgJson = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf-8'));
|
|
43
|
+
return !!pkgJson.scripts?.[scriptName];
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function runCommand(command, cwd, pm = 'pnpm') {
|
|
50
|
+
try {
|
|
51
|
+
// Replace pnpm with detected package manager
|
|
52
|
+
const actualCommand = command.replace(/^pnpm/, pm);
|
|
53
|
+
execSync(actualCommand, {
|
|
54
|
+
cwd,
|
|
55
|
+
stdio: 'inherit',
|
|
56
|
+
env: { ...process.env, FORCE_COLOR: '1' },
|
|
57
|
+
});
|
|
58
|
+
return true;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function waitForServer(port, timeout = 10000) {
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
const startTime = Date.now();
|
|
67
|
+
const checkServer = () => {
|
|
68
|
+
const req = http.get(`http://localhost:${port}`, () => {
|
|
69
|
+
resolve();
|
|
70
|
+
});
|
|
71
|
+
req.on('error', () => {
|
|
72
|
+
if (Date.now() - startTime > timeout) {
|
|
73
|
+
reject(new Error(`Server did not start within ${timeout}ms`));
|
|
74
|
+
} else {
|
|
75
|
+
setTimeout(checkServer, 500);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
req.end();
|
|
79
|
+
};
|
|
80
|
+
checkServer();
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function validate(projectRoot = process.cwd()) {
|
|
85
|
+
const pm = detectPackageManager(projectRoot);
|
|
86
|
+
|
|
87
|
+
// Check package.json exists
|
|
88
|
+
if (!existsSync(join(projectRoot, 'package.json'))) {
|
|
89
|
+
console.error('Error: No package.json found in current directory');
|
|
90
|
+
console.error('Make sure you are in a Sunpeak project directory');
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.log(`${colors.yellow}Starting validation for Sunpeak project...${colors.reset}`);
|
|
95
|
+
console.log(`Project root: ${projectRoot}`);
|
|
96
|
+
console.log(`Package manager: ${pm}\n`);
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
console.log(`Running: ${pm} install`);
|
|
100
|
+
if (!runCommand(`${pm} install`, projectRoot, pm)) {
|
|
101
|
+
throw new Error(`${pm} install failed`);
|
|
102
|
+
}
|
|
103
|
+
console.log()
|
|
104
|
+
printSuccess(`${pm} install`);
|
|
105
|
+
|
|
106
|
+
// Format (optional)
|
|
107
|
+
if (hasScript(projectRoot, 'format')) {
|
|
108
|
+
console.log(`\nRunning: ${pm} format`);
|
|
109
|
+
if (!runCommand(`${pm} format`, projectRoot, pm)) {
|
|
110
|
+
throw new Error(`${pm} format failed`);
|
|
111
|
+
}
|
|
112
|
+
printSuccess(`${pm} format`);
|
|
113
|
+
} else {
|
|
114
|
+
printWarning('Skipping format: no "format" script in package.json');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Lint (optional)
|
|
118
|
+
if (hasScript(projectRoot, 'lint')) {
|
|
119
|
+
console.log(`\nRunning: ${pm} lint`);
|
|
120
|
+
if (!runCommand(`${pm} lint`, projectRoot, pm)) {
|
|
121
|
+
throw new Error(`${pm} lint failed`);
|
|
122
|
+
}
|
|
123
|
+
printSuccess(`${pm} lint`);
|
|
124
|
+
} else {
|
|
125
|
+
printWarning('Skipping lint: no "lint" script in package.json');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Typecheck (optional)
|
|
129
|
+
if (hasScript(projectRoot, 'typecheck')) {
|
|
130
|
+
console.log(`\nRunning: ${pm} typecheck`);
|
|
131
|
+
if (!runCommand(`${pm} typecheck`, projectRoot, pm)) {
|
|
132
|
+
throw new Error(`${pm} typecheck failed`);
|
|
133
|
+
}
|
|
134
|
+
printSuccess(`${pm} typecheck`);
|
|
135
|
+
} else {
|
|
136
|
+
printWarning('Skipping typecheck: no "typecheck" script in package.json');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Test (optional)
|
|
140
|
+
if (hasScript(projectRoot, 'test')) {
|
|
141
|
+
console.log(`\nRunning: ${pm} test`);
|
|
142
|
+
if (!runCommand(`${pm} test`, projectRoot, pm)) {
|
|
143
|
+
throw new Error(`${pm} test failed`);
|
|
144
|
+
}
|
|
145
|
+
printSuccess(`${pm} test`);
|
|
146
|
+
} else {
|
|
147
|
+
printWarning('Skipping test: no "test" script in package.json');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.log(`\nRunning: sunpeak build`);
|
|
151
|
+
// Import and run build directly
|
|
152
|
+
const { build } = await import('./build.mjs');
|
|
153
|
+
await build(projectRoot);
|
|
154
|
+
|
|
155
|
+
const chatgptDir = join(projectRoot, 'dist', 'chatgpt');
|
|
156
|
+
|
|
157
|
+
if (!existsSync(chatgptDir)) {
|
|
158
|
+
printError('dist/chatgpt directory not found after build');
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const files = readdirSync(chatgptDir);
|
|
163
|
+
if (files.length === 0) {
|
|
164
|
+
printError('No files found in dist/chatgpt/');
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Verify all files are .js files
|
|
169
|
+
const nonJsFiles = files.filter(f => !f.endsWith('.js'));
|
|
170
|
+
if (nonJsFiles.length > 0) {
|
|
171
|
+
printError(`Unexpected non-JS files in ./dist/chatgpt/: ${nonJsFiles.join(', ')}`);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
console.log()
|
|
176
|
+
printSuccess('sunpeak build');
|
|
177
|
+
|
|
178
|
+
// MCP Server Check (optional)
|
|
179
|
+
if (hasScript(projectRoot, 'mcp:serve')) {
|
|
180
|
+
console.log(`\nRunning: ${pm} mcp:serve`);
|
|
181
|
+
const mcpProcess = spawn(pm, ['mcp:serve'], {
|
|
182
|
+
cwd: projectRoot,
|
|
183
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
184
|
+
env: { ...process.env, FORCE_COLOR: '1' },
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const mcpErrors = [];
|
|
188
|
+
|
|
189
|
+
mcpProcess.stderr.on('data', (data) => {
|
|
190
|
+
const message = data.toString();
|
|
191
|
+
if (message.includes('error') || message.includes('Error')) {
|
|
192
|
+
mcpErrors.push(message.trim());
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Store process for cleanup
|
|
197
|
+
process.on('exit', () => {
|
|
198
|
+
if (mcpProcess && !mcpProcess.killed) {
|
|
199
|
+
mcpProcess.kill();
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
console.log('\nWaiting for MCP server to start on port 6766...');
|
|
205
|
+
await waitForServer(6766, 10000);
|
|
206
|
+
|
|
207
|
+
// Give it a moment to ensure no immediate errors
|
|
208
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
209
|
+
|
|
210
|
+
if (mcpErrors.length > 0) {
|
|
211
|
+
printError('MCP server started but reported errors:');
|
|
212
|
+
mcpErrors.forEach(err => console.log(` ${err}`));
|
|
213
|
+
throw new Error('MCP server has errors');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
} catch (error) {
|
|
217
|
+
printError(`MCP server failed to start: ${error.message}`);
|
|
218
|
+
throw error;
|
|
219
|
+
} finally {
|
|
220
|
+
console.log('Stopping MCP server...');
|
|
221
|
+
mcpProcess.kill();
|
|
222
|
+
// Give it a moment to shut down
|
|
223
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
224
|
+
}
|
|
225
|
+
console.log()
|
|
226
|
+
printSuccess(`${pm} mcp\n`);
|
|
227
|
+
} else {
|
|
228
|
+
printWarning('Skipping MCP server check: no "mcp:serve" script in package.json\n');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
printSuccess('All systems GO!\n\n');
|
|
232
|
+
process.exit(0);
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.error(`\n${colors.red}Error: ${error.message}${colors.reset}\n`);
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Allow running directly
|
|
240
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
241
|
+
validate().catch(error => {
|
|
242
|
+
console.error(error);
|
|
243
|
+
process.exit(1);
|
|
244
|
+
});
|
|
245
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sunpeak",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.19",
|
|
4
4
|
"description": "The MCP App SDK. Quickstart, build, & test your ChatGPT App locally!",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"files": [
|
|
34
34
|
"dist",
|
|
35
35
|
"bin",
|
|
36
|
+
"cli",
|
|
36
37
|
"template",
|
|
37
38
|
"README.md"
|
|
38
39
|
],
|
package/template/README.md
CHANGED
|
@@ -7,12 +7,44 @@ For an initial overview of your new app and the sunpeak API, refer to the [docum
|
|
|
7
7
|
## Quickstart
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
pnpm
|
|
10
|
+
pnpm install
|
|
11
|
+
sunpeak dev
|
|
11
12
|
```
|
|
12
13
|
|
|
13
14
|
Edit the resource files in [./src/components/resources/](./src/components/resources/) to build your resource UI.
|
|
14
15
|
|
|
15
|
-
##
|
|
16
|
+
## CLI Commands
|
|
17
|
+
|
|
18
|
+
The `sunpeak` CLI provides convenient commands for common tasks:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
sunpeak dev # Start development server (vite)
|
|
22
|
+
sunpeak build # Build all resources for production
|
|
23
|
+
sunpeak mcp # Run MCP server with auto-reload
|
|
24
|
+
sunpeak validate # Run full validation suite
|
|
25
|
+
sunpeak test # Run tests
|
|
26
|
+
sunpeak typecheck # Run TypeScript checks
|
|
27
|
+
sunpeak lint # Run linting
|
|
28
|
+
sunpeak format # Format code
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Customization
|
|
32
|
+
|
|
33
|
+
**You can customize:**
|
|
34
|
+
|
|
35
|
+
- Package.json scripts (format, lint, typecheck, test are optional)
|
|
36
|
+
- Tooling configuration (ESLint, Prettier, TypeScript, Vite dev server)
|
|
37
|
+
- Component structure within `src/components/`
|
|
38
|
+
- Package manager (pnpm, npm, or yarn auto-detected)
|
|
39
|
+
|
|
40
|
+
**Do not customize (required by `sunpeak build`):**
|
|
41
|
+
|
|
42
|
+
- `src/components/resources/` - Resource files must be here
|
|
43
|
+
- `src/index-resource.tsx` - Build template (must have `// RESOURCE_IMPORT` and `// RESOURCE_MOUNT` comments)
|
|
44
|
+
- `vite.config.build.ts` - Build configuration
|
|
45
|
+
- Resource file naming: `*-resource.tsx` (e.g., `counter-resource.tsx`)
|
|
46
|
+
|
|
47
|
+
If you need to customize these paths, create a custom build script in package.json instead of using `sunpeak build`.
|
|
16
48
|
|
|
17
49
|
## Testing
|
|
18
50
|
|
|
@@ -21,7 +53,7 @@ Edit the resource files in [./src/components/resources/](./src/components/resour
|
|
|
21
53
|
Run all the checks with the following:
|
|
22
54
|
|
|
23
55
|
```bash
|
|
24
|
-
|
|
56
|
+
sunpeak validate
|
|
25
57
|
```
|
|
26
58
|
|
|
27
59
|
This will:
|
|
@@ -33,7 +65,7 @@ This will:
|
|
|
33
65
|
For manual QA of the UI, run:
|
|
34
66
|
|
|
35
67
|
```bash
|
|
36
|
-
|
|
68
|
+
sunpeak dev
|
|
37
69
|
```
|
|
38
70
|
|
|
39
71
|
### Testing in ChatGPT
|
|
@@ -42,7 +74,7 @@ Test your app directly in ChatGPT using the built-in MCP server:
|
|
|
42
74
|
|
|
43
75
|
```bash
|
|
44
76
|
# Start the MCP server (rebuilds and restarts on file changes).
|
|
45
|
-
|
|
77
|
+
sunpeak mcp
|
|
46
78
|
|
|
47
79
|
# In another terminal, run a tunnel. For example:
|
|
48
80
|
ngrok http 6766
|
|
@@ -59,13 +91,17 @@ When you make changes to the UI, refresh your app in ChatGPT after the MCP serve
|
|
|
59
91
|
Build your app for production:
|
|
60
92
|
|
|
61
93
|
```bash
|
|
62
|
-
|
|
94
|
+
sunpeak build
|
|
63
95
|
```
|
|
64
96
|
|
|
65
|
-
This creates optimized builds in
|
|
97
|
+
This creates optimized builds in `dist/chatgpt/`:
|
|
98
|
+
|
|
99
|
+
- `dist/chatgpt/counter.js`
|
|
100
|
+
- `dist/chatgpt/albums.js`
|
|
101
|
+
- `dist/chatgpt/carousel.js`
|
|
102
|
+
- _(One .js file per resource in src/components/resources/)_
|
|
66
103
|
|
|
67
|
-
-
|
|
68
|
-
- Host this file somewhere and reference it as a resource in your production MCP server.
|
|
104
|
+
Each file is a self-contained bundle with CSS inlined. Host these files and reference them as resources in your production MCP server.
|
|
69
105
|
|
|
70
106
|
## Resources
|
|
71
107
|
|
|
@@ -2,9 +2,9 @@ import {
|
|
|
2
2
|
Button,
|
|
3
3
|
ButtonLink,
|
|
4
4
|
CopyButton
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-XB525PXG.js";
|
|
5
|
+
} from "./chunk-675LFNY2.js";
|
|
7
6
|
import "./chunk-QPJAV452.js";
|
|
7
|
+
import "./chunk-XB525PXG.js";
|
|
8
8
|
import "./chunk-YOJ6QPGS.js";
|
|
9
9
|
import "./chunk-BAG6OO6S.js";
|
|
10
10
|
import "./chunk-EGRHWZRV.js";
|
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
handlePressableMouseEnter,
|
|
6
6
|
waitForAnimationFrame
|
|
7
7
|
} from "./chunk-BAG6OO6S.js";
|
|
8
|
+
import "./chunk-EGRHWZRV.js";
|
|
8
9
|
import {
|
|
9
10
|
dist_exports4 as dist_exports
|
|
10
11
|
} from "./chunk-SGWD4VEU.js";
|
|
11
12
|
import "./chunk-KFGKZMLK.js";
|
|
12
|
-
import "./chunk-EGRHWZRV.js";
|
|
13
13
|
import {
|
|
14
14
|
clsx_default
|
|
15
15
|
} from "./chunk-CNYJBM5F.js";
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Input
|
|
3
|
+
} from "./chunk-CQ3GYAYB.js";
|
|
1
4
|
import {
|
|
2
5
|
Button,
|
|
3
6
|
LoadingIndicator,
|
|
4
7
|
TransitionGroup
|
|
5
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-675LFNY2.js";
|
|
9
|
+
import {
|
|
10
|
+
o
|
|
11
|
+
} from "./chunk-QPJAV452.js";
|
|
6
12
|
import {
|
|
7
13
|
Check_default,
|
|
8
14
|
ChevronDownVector_default,
|
|
@@ -11,12 +17,6 @@ import {
|
|
|
11
17
|
Search_default,
|
|
12
18
|
X_default
|
|
13
19
|
} from "./chunk-XB525PXG.js";
|
|
14
|
-
import {
|
|
15
|
-
Input
|
|
16
|
-
} from "./chunk-CQ3GYAYB.js";
|
|
17
|
-
import {
|
|
18
|
-
o
|
|
19
|
-
} from "./chunk-QPJAV452.js";
|
|
20
20
|
import {
|
|
21
21
|
useTimeout
|
|
22
22
|
} from "./chunk-YOJ6QPGS.js";
|
|
@@ -26,13 +26,13 @@ import {
|
|
|
26
26
|
toCssVariables,
|
|
27
27
|
waitForAnimationFrame
|
|
28
28
|
} from "./chunk-BAG6OO6S.js";
|
|
29
|
+
import "./chunk-EGRHWZRV.js";
|
|
29
30
|
import {
|
|
30
31
|
dist_exports,
|
|
31
32
|
dist_exports3 as dist_exports2,
|
|
32
33
|
dist_exports5 as dist_exports3
|
|
33
34
|
} from "./chunk-SGWD4VEU.js";
|
|
34
35
|
import "./chunk-KFGKZMLK.js";
|
|
35
|
-
import "./chunk-EGRHWZRV.js";
|
|
36
36
|
import {
|
|
37
37
|
clsx_default
|
|
38
38
|
} from "./chunk-CNYJBM5F.js";
|
|
@@ -7,134 +7,134 @@
|
|
|
7
7
|
"react": {
|
|
8
8
|
"src": "../../../../node_modules/.pnpm/react@19.2.0/node_modules/react/index.js",
|
|
9
9
|
"file": "react.js",
|
|
10
|
-
"fileHash": "
|
|
10
|
+
"fileHash": "abb9cab2",
|
|
11
11
|
"needsInterop": true
|
|
12
12
|
},
|
|
13
13
|
"react-dom": {
|
|
14
14
|
"src": "../../../../node_modules/.pnpm/react-dom@19.2.0_react@19.2.0/node_modules/react-dom/index.js",
|
|
15
15
|
"file": "react-dom.js",
|
|
16
|
-
"fileHash": "
|
|
16
|
+
"fileHash": "8575f6ec",
|
|
17
17
|
"needsInterop": true
|
|
18
18
|
},
|
|
19
19
|
"react/jsx-dev-runtime": {
|
|
20
20
|
"src": "../../../../node_modules/.pnpm/react@19.2.0/node_modules/react/jsx-dev-runtime.js",
|
|
21
21
|
"file": "react_jsx-dev-runtime.js",
|
|
22
|
-
"fileHash": "
|
|
22
|
+
"fileHash": "9472cef2",
|
|
23
23
|
"needsInterop": true
|
|
24
24
|
},
|
|
25
25
|
"react/jsx-runtime": {
|
|
26
26
|
"src": "../../../../node_modules/.pnpm/react@19.2.0/node_modules/react/jsx-runtime.js",
|
|
27
27
|
"file": "react_jsx-runtime.js",
|
|
28
|
-
"fileHash": "
|
|
28
|
+
"fileHash": "08af77d4",
|
|
29
29
|
"needsInterop": true
|
|
30
30
|
},
|
|
31
31
|
"@openai/apps-sdk-ui/components/Button": {
|
|
32
32
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.0_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._60630c8dcc43ec213b3e346c9e26579b/node_modules/@openai/apps-sdk-ui/dist/es/components/Button/index.js",
|
|
33
33
|
"file": "@openai_apps-sdk-ui_components_Button.js",
|
|
34
|
-
"fileHash": "
|
|
34
|
+
"fileHash": "9937baa5",
|
|
35
35
|
"needsInterop": false
|
|
36
36
|
},
|
|
37
37
|
"@openai/apps-sdk-ui/components/Checkbox": {
|
|
38
38
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.0_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._60630c8dcc43ec213b3e346c9e26579b/node_modules/@openai/apps-sdk-ui/dist/es/components/Checkbox/index.js",
|
|
39
39
|
"file": "@openai_apps-sdk-ui_components_Checkbox.js",
|
|
40
|
-
"fileHash": "
|
|
40
|
+
"fileHash": "4cc1f6d9",
|
|
41
41
|
"needsInterop": false
|
|
42
42
|
},
|
|
43
43
|
"@openai/apps-sdk-ui/components/Icon": {
|
|
44
44
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.0_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._60630c8dcc43ec213b3e346c9e26579b/node_modules/@openai/apps-sdk-ui/dist/es/components/Icon/index.js",
|
|
45
45
|
"file": "@openai_apps-sdk-ui_components_Icon.js",
|
|
46
|
-
"fileHash": "
|
|
46
|
+
"fileHash": "7a6d731b",
|
|
47
47
|
"needsInterop": false
|
|
48
48
|
},
|
|
49
49
|
"@openai/apps-sdk-ui/components/Input": {
|
|
50
50
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.0_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._60630c8dcc43ec213b3e346c9e26579b/node_modules/@openai/apps-sdk-ui/dist/es/components/Input/index.js",
|
|
51
51
|
"file": "@openai_apps-sdk-ui_components_Input.js",
|
|
52
|
-
"fileHash": "
|
|
52
|
+
"fileHash": "35b40b58",
|
|
53
53
|
"needsInterop": false
|
|
54
54
|
},
|
|
55
55
|
"@openai/apps-sdk-ui/components/SegmentedControl": {
|
|
56
56
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.0_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._60630c8dcc43ec213b3e346c9e26579b/node_modules/@openai/apps-sdk-ui/dist/es/components/SegmentedControl/index.js",
|
|
57
57
|
"file": "@openai_apps-sdk-ui_components_SegmentedControl.js",
|
|
58
|
-
"fileHash": "
|
|
58
|
+
"fileHash": "62dffaa3",
|
|
59
59
|
"needsInterop": false
|
|
60
60
|
},
|
|
61
61
|
"@openai/apps-sdk-ui/components/Select": {
|
|
62
62
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.0_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._60630c8dcc43ec213b3e346c9e26579b/node_modules/@openai/apps-sdk-ui/dist/es/components/Select/index.js",
|
|
63
63
|
"file": "@openai_apps-sdk-ui_components_Select.js",
|
|
64
|
-
"fileHash": "
|
|
64
|
+
"fileHash": "28afd0cc",
|
|
65
65
|
"needsInterop": false
|
|
66
66
|
},
|
|
67
67
|
"@openai/apps-sdk-ui/components/Textarea": {
|
|
68
68
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.0_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._60630c8dcc43ec213b3e346c9e26579b/node_modules/@openai/apps-sdk-ui/dist/es/components/Textarea/index.js",
|
|
69
69
|
"file": "@openai_apps-sdk-ui_components_Textarea.js",
|
|
70
|
-
"fileHash": "
|
|
70
|
+
"fileHash": "cf25f4ed",
|
|
71
71
|
"needsInterop": false
|
|
72
72
|
},
|
|
73
73
|
"@openai/apps-sdk-ui/theme": {
|
|
74
74
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.0_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._60630c8dcc43ec213b3e346c9e26579b/node_modules/@openai/apps-sdk-ui/dist/es/lib/theme.js",
|
|
75
75
|
"file": "@openai_apps-sdk-ui_theme.js",
|
|
76
|
-
"fileHash": "
|
|
76
|
+
"fileHash": "bd38d415",
|
|
77
77
|
"needsInterop": false
|
|
78
78
|
},
|
|
79
79
|
"clsx": {
|
|
80
80
|
"src": "../../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs",
|
|
81
81
|
"file": "clsx.js",
|
|
82
|
-
"fileHash": "
|
|
82
|
+
"fileHash": "e128f89b",
|
|
83
83
|
"needsInterop": false
|
|
84
84
|
},
|
|
85
85
|
"embla-carousel-react": {
|
|
86
86
|
"src": "../../../../node_modules/.pnpm/embla-carousel-react@8.6.0_react@19.2.0/node_modules/embla-carousel-react/esm/embla-carousel-react.esm.js",
|
|
87
87
|
"file": "embla-carousel-react.js",
|
|
88
|
-
"fileHash": "
|
|
88
|
+
"fileHash": "9e2b9226",
|
|
89
89
|
"needsInterop": false
|
|
90
90
|
},
|
|
91
91
|
"embla-carousel-wheel-gestures": {
|
|
92
92
|
"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",
|
|
93
93
|
"file": "embla-carousel-wheel-gestures.js",
|
|
94
|
-
"fileHash": "
|
|
94
|
+
"fileHash": "6cc0c995",
|
|
95
95
|
"needsInterop": false
|
|
96
96
|
},
|
|
97
97
|
"react-dom/client": {
|
|
98
98
|
"src": "../../../../node_modules/.pnpm/react-dom@19.2.0_react@19.2.0/node_modules/react-dom/client.js",
|
|
99
99
|
"file": "react-dom_client.js",
|
|
100
|
-
"fileHash": "
|
|
100
|
+
"fileHash": "783cb9f4",
|
|
101
101
|
"needsInterop": true
|
|
102
102
|
},
|
|
103
103
|
"tailwind-merge": {
|
|
104
104
|
"src": "../../../../node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/bundle-mjs.mjs",
|
|
105
105
|
"file": "tailwind-merge.js",
|
|
106
|
-
"fileHash": "
|
|
106
|
+
"fileHash": "e1ba7ad3",
|
|
107
107
|
"needsInterop": false
|
|
108
108
|
}
|
|
109
109
|
},
|
|
110
110
|
"chunks": {
|
|
111
|
-
"chunk-PRLY65A2": {
|
|
112
|
-
"file": "chunk-PRLY65A2.js"
|
|
113
|
-
},
|
|
114
|
-
"chunk-XB525PXG": {
|
|
115
|
-
"file": "chunk-XB525PXG.js"
|
|
116
|
-
},
|
|
117
111
|
"chunk-CQ3GYAYB": {
|
|
118
112
|
"file": "chunk-CQ3GYAYB.js"
|
|
119
113
|
},
|
|
114
|
+
"chunk-675LFNY2": {
|
|
115
|
+
"file": "chunk-675LFNY2.js"
|
|
116
|
+
},
|
|
120
117
|
"chunk-QPJAV452": {
|
|
121
118
|
"file": "chunk-QPJAV452.js"
|
|
122
119
|
},
|
|
120
|
+
"chunk-XB525PXG": {
|
|
121
|
+
"file": "chunk-XB525PXG.js"
|
|
122
|
+
},
|
|
123
123
|
"chunk-YOJ6QPGS": {
|
|
124
124
|
"file": "chunk-YOJ6QPGS.js"
|
|
125
125
|
},
|
|
126
126
|
"chunk-BAG6OO6S": {
|
|
127
127
|
"file": "chunk-BAG6OO6S.js"
|
|
128
128
|
},
|
|
129
|
+
"chunk-EGRHWZRV": {
|
|
130
|
+
"file": "chunk-EGRHWZRV.js"
|
|
131
|
+
},
|
|
129
132
|
"chunk-SGWD4VEU": {
|
|
130
133
|
"file": "chunk-SGWD4VEU.js"
|
|
131
134
|
},
|
|
132
135
|
"chunk-KFGKZMLK": {
|
|
133
136
|
"file": "chunk-KFGKZMLK.js"
|
|
134
137
|
},
|
|
135
|
-
"chunk-EGRHWZRV": {
|
|
136
|
-
"file": "chunk-EGRHWZRV.js"
|
|
137
|
-
},
|
|
138
138
|
"chunk-CNYJBM5F": {
|
|
139
139
|
"file": "chunk-CNYJBM5F.js"
|
|
140
140
|
},
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
o
|
|
3
|
+
} from "./chunk-QPJAV452.js";
|
|
1
4
|
import {
|
|
2
5
|
Check_default,
|
|
3
6
|
Copy_default
|
|
4
7
|
} from "./chunk-XB525PXG.js";
|
|
5
|
-
import {
|
|
6
|
-
o
|
|
7
|
-
} from "./chunk-QPJAV452.js";
|
|
8
8
|
import {
|
|
9
9
|
useTimeout
|
|
10
10
|
} from "./chunk-YOJ6QPGS.js";
|
|
@@ -625,4 +625,4 @@ export {
|
|
|
625
625
|
ButtonLink,
|
|
626
626
|
CopyButton
|
|
627
627
|
};
|
|
628
|
-
//# sourceMappingURL=chunk-
|
|
628
|
+
//# sourceMappingURL=chunk-675LFNY2.js.map
|
package/template/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"4.0.13","results":[[":src/components/
|
|
1
|
+
{"version":"4.0.13","results":[[":src/components/resources/carousel-resource.test.tsx",{"duration":272.0362339999999,"failed":false}],[":src/components/album/albums.test.tsx",{"duration":303.67703000000006,"failed":false}],[":src/components/album/fullscreen-viewer.test.tsx",{"duration":280.3775370000003,"failed":false}],[":src/components/carousel/carousel.test.tsx",{"duration":87.16681699999981,"failed":false}],[":src/components/resources/counter-resource.test.tsx",{"duration":299.4044319999998,"failed":false}],[":src/components/resources/albums-resource.test.tsx",{"duration":289.73864500000013,"failed":false}],[":src/components/album/film-strip.test.tsx",{"duration":432.83026500000005,"failed":false}],[":src/components/album/album-card.test.tsx",{"duration":344.5009920000002,"failed":false}],[":src/components/card/card.test.tsx",{"duration":54.60665700000004,"failed":false}]]}
|
package/template/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"build": "
|
|
7
|
+
"build": "sunpeak build",
|
|
8
8
|
"dev": "vite --port ${PORT:-6767}",
|
|
9
9
|
"format": "prettier --write --list-different \"**/*.{ts,tsx,js,jsx,json,md}\"",
|
|
10
10
|
"mcp": "nodemon",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"lint": "eslint . --ext .ts,.tsx --fix",
|
|
13
13
|
"typecheck": "tsc --noEmit",
|
|
14
14
|
"test": "vitest run",
|
|
15
|
-
"validate": "
|
|
15
|
+
"validate": "sunpeak validate"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@openai/apps-sdk-ui": "^0.2.0",
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { execSync } from 'child_process';
|
|
3
|
-
import { existsSync, rmSync, readdirSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from 'fs';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { fileURLToPath } from 'url';
|
|
6
|
-
|
|
7
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
const distDir = path.join(__dirname, '../dist/chatgpt');
|
|
9
|
-
const buildDir = path.join(__dirname, '../dist/build-output');
|
|
10
|
-
const tempDir = path.join(__dirname, '../.tmp');
|
|
11
|
-
const resourcesDir = path.join(__dirname, '../src/components/resources');
|
|
12
|
-
const templateFile = path.join(__dirname, '../src/index-resource.tsx');
|
|
13
|
-
|
|
14
|
-
// Clean dist and temp directories
|
|
15
|
-
if (existsSync(distDir)) {
|
|
16
|
-
rmSync(distDir, { recursive: true });
|
|
17
|
-
}
|
|
18
|
-
if (existsSync(tempDir)) {
|
|
19
|
-
rmSync(tempDir, { recursive: true });
|
|
20
|
-
}
|
|
21
|
-
mkdirSync(distDir, { recursive: true });
|
|
22
|
-
mkdirSync(tempDir, { recursive: true });
|
|
23
|
-
|
|
24
|
-
// Auto-discover all resources
|
|
25
|
-
const resourceFiles = readdirSync(resourcesDir)
|
|
26
|
-
.filter(file => file.endsWith('-resource.tsx'))
|
|
27
|
-
.map(file => {
|
|
28
|
-
// Extract kebab-case name: 'counter-resource.tsx' -> 'counter'
|
|
29
|
-
const kebabName = file.replace('-resource.tsx', '');
|
|
30
|
-
|
|
31
|
-
// Convert kebab-case to PascalCase: 'counter' -> 'Counter', 'my-widget' -> 'MyWidget'
|
|
32
|
-
const pascalName = kebabName
|
|
33
|
-
.split('-')
|
|
34
|
-
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
35
|
-
.join('');
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
componentName: `${pascalName}Resource`,
|
|
39
|
-
componentFile: file.replace('.tsx', ''),
|
|
40
|
-
entry: `.tmp/index-${kebabName}.tsx`,
|
|
41
|
-
output: `${kebabName}.js`,
|
|
42
|
-
buildOutDir: path.join(buildDir, kebabName),
|
|
43
|
-
};
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
console.log('Building all resources...\n');
|
|
47
|
-
|
|
48
|
-
// Read the template
|
|
49
|
-
const template = readFileSync(templateFile, 'utf-8');
|
|
50
|
-
|
|
51
|
-
// Build all resources (but don't copy yet)
|
|
52
|
-
resourceFiles.forEach(({ componentName, componentFile, entry, output, buildOutDir }, index) => {
|
|
53
|
-
console.log(`[${index + 1}/${resourceFiles.length}] Building ${output}...`);
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
// Create build directory if it doesn't exist
|
|
57
|
-
if (!existsSync(buildOutDir)) {
|
|
58
|
-
mkdirSync(buildOutDir, { recursive: true });
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Create entry file from template in temp directory
|
|
62
|
-
const entryContent = template
|
|
63
|
-
.replace('// RESOURCE_IMPORT', `import { ${componentName} } from '../src/components/resources/${componentFile}';`)
|
|
64
|
-
.replace('// RESOURCE_MOUNT', `createRoot(root).render(<${componentName} />);`);
|
|
65
|
-
|
|
66
|
-
const entryPath = path.join(__dirname, '..', entry);
|
|
67
|
-
writeFileSync(entryPath, entryContent);
|
|
68
|
-
|
|
69
|
-
// Build with vite to build directory
|
|
70
|
-
execSync(
|
|
71
|
-
`vite build --config vite.config.build.ts`,
|
|
72
|
-
{
|
|
73
|
-
stdio: 'inherit',
|
|
74
|
-
env: {
|
|
75
|
-
...process.env,
|
|
76
|
-
ENTRY_FILE: entry,
|
|
77
|
-
OUTPUT_FILE: output,
|
|
78
|
-
OUT_DIR: buildOutDir,
|
|
79
|
-
},
|
|
80
|
-
}
|
|
81
|
-
);
|
|
82
|
-
} catch (error) {
|
|
83
|
-
console.error(`Failed to build ${output}`);
|
|
84
|
-
process.exit(1);
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// Now copy all files from build-output to dist/chatgpt
|
|
89
|
-
console.log('\nCopying built files to dist/chatgpt...');
|
|
90
|
-
resourceFiles.forEach(({ output, buildOutDir }) => {
|
|
91
|
-
const builtFile = path.join(buildOutDir, output);
|
|
92
|
-
const destFile = path.join(distDir, output);
|
|
93
|
-
|
|
94
|
-
if (existsSync(builtFile)) {
|
|
95
|
-
copyFileSync(builtFile, destFile);
|
|
96
|
-
console.log(`✓ Copied ${output}`);
|
|
97
|
-
} else {
|
|
98
|
-
console.error(`Built file not found: ${builtFile}`);
|
|
99
|
-
if (existsSync(buildOutDir)) {
|
|
100
|
-
console.log(` Files in ${buildOutDir}:`, readdirSync(buildOutDir));
|
|
101
|
-
} else {
|
|
102
|
-
console.log(` Build directory doesn't exist: ${buildOutDir}`);
|
|
103
|
-
}
|
|
104
|
-
process.exit(1);
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
// Clean up temp and build directories
|
|
109
|
-
if (existsSync(tempDir)) {
|
|
110
|
-
rmSync(tempDir, { recursive: true });
|
|
111
|
-
}
|
|
112
|
-
if (existsSync(buildDir)) {
|
|
113
|
-
rmSync(buildDir, { recursive: true });
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
console.log('\n✓ All resources built successfully!');
|
|
117
|
-
console.log(`\nBuilt files:`, readdirSync(distDir));
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Local testing script for Sunpeak project.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { execSync, spawn } from 'child_process';
|
|
8
|
-
import { existsSync, readdirSync } from 'fs';
|
|
9
|
-
import { fileURLToPath } from 'url';
|
|
10
|
-
import { dirname, join } from 'path';
|
|
11
|
-
import http from 'http';
|
|
12
|
-
|
|
13
|
-
// Color codes for output
|
|
14
|
-
const colors = {
|
|
15
|
-
red: '\x1b[0;31m',
|
|
16
|
-
green: '\x1b[0;32m',
|
|
17
|
-
blue: '\x1b[0;34m',
|
|
18
|
-
yellow: '\x1b[1;33m',
|
|
19
|
-
reset: '\x1b[0m',
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
// Get project root
|
|
23
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
24
|
-
const __dirname = dirname(__filename);
|
|
25
|
-
const PROJECT_ROOT = join(__dirname, '..');
|
|
26
|
-
|
|
27
|
-
function printSuccess(text) {
|
|
28
|
-
console.log(`${colors.green}✓ ${text}${colors.reset}`);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function printError(text) {
|
|
32
|
-
console.log(`${colors.red}✗ ${text}${colors.reset}`);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function runCommand(command, cwd) {
|
|
36
|
-
try {
|
|
37
|
-
execSync(command, {
|
|
38
|
-
cwd,
|
|
39
|
-
stdio: 'inherit',
|
|
40
|
-
env: { ...process.env, FORCE_COLOR: '1' },
|
|
41
|
-
});
|
|
42
|
-
return true;
|
|
43
|
-
} catch (error) {
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function waitForServer(port, timeout = 10000) {
|
|
49
|
-
return new Promise((resolve, reject) => {
|
|
50
|
-
const startTime = Date.now();
|
|
51
|
-
const checkServer = () => {
|
|
52
|
-
const req = http.get(`http://localhost:${port}`, () => {
|
|
53
|
-
resolve();
|
|
54
|
-
});
|
|
55
|
-
req.on('error', () => {
|
|
56
|
-
if (Date.now() - startTime > timeout) {
|
|
57
|
-
reject(new Error(`Server did not start within ${timeout}ms`));
|
|
58
|
-
} else {
|
|
59
|
-
setTimeout(checkServer, 500);
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
req.end();
|
|
63
|
-
};
|
|
64
|
-
checkServer();
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Main testing flow
|
|
69
|
-
console.log(`${colors.yellow}Starting local testing for Sunpeak project...${colors.reset}`);
|
|
70
|
-
console.log(`Project root: ${PROJECT_ROOT}\n`);
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
console.log('Running: pnpm install');
|
|
74
|
-
if (!runCommand('pnpm install', PROJECT_ROOT)) {
|
|
75
|
-
throw new Error('pnpm install failed');
|
|
76
|
-
}
|
|
77
|
-
console.log()
|
|
78
|
-
printSuccess('pnpm install');
|
|
79
|
-
|
|
80
|
-
console.log('\nRunning: pnpm format');
|
|
81
|
-
if (!runCommand('pnpm format', PROJECT_ROOT)) {
|
|
82
|
-
throw new Error('pnpm format failed');
|
|
83
|
-
}
|
|
84
|
-
printSuccess('pnpm format');
|
|
85
|
-
|
|
86
|
-
console.log('\nRunning: pnpm lint');
|
|
87
|
-
if (!runCommand('pnpm lint', PROJECT_ROOT)) {
|
|
88
|
-
throw new Error('pnpm lint failed');
|
|
89
|
-
}
|
|
90
|
-
printSuccess('pnpm lint');
|
|
91
|
-
|
|
92
|
-
console.log('\nRunning: pnpm typecheck');
|
|
93
|
-
if (!runCommand('pnpm typecheck', PROJECT_ROOT)) {
|
|
94
|
-
throw new Error('pnpm typecheck failed');
|
|
95
|
-
}
|
|
96
|
-
printSuccess('pnpm typecheck');
|
|
97
|
-
|
|
98
|
-
console.log('\nRunning: pnpm test');
|
|
99
|
-
if (!runCommand('pnpm test', PROJECT_ROOT)) {
|
|
100
|
-
throw new Error('pnpm test failed');
|
|
101
|
-
}
|
|
102
|
-
printSuccess('pnpm test');
|
|
103
|
-
|
|
104
|
-
console.log('\nRunning: pnpm build');
|
|
105
|
-
if (!runCommand('pnpm build', PROJECT_ROOT)) {
|
|
106
|
-
throw new Error('pnpm build failed');
|
|
107
|
-
}
|
|
108
|
-
const chatgptDir = join(PROJECT_ROOT, 'dist', 'chatgpt');
|
|
109
|
-
const expectedFiles = ['counter.js', 'albums.js', 'carousel.js'];
|
|
110
|
-
|
|
111
|
-
// Check all expected files exist
|
|
112
|
-
for (const file of expectedFiles) {
|
|
113
|
-
const filePath = join(chatgptDir, file);
|
|
114
|
-
if (!existsSync(filePath)) {
|
|
115
|
-
printError(`Missing expected file: ./dist/chatgpt/${file}`);
|
|
116
|
-
process.exit(1);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Verify only expected files are present
|
|
121
|
-
const files = readdirSync(chatgptDir);
|
|
122
|
-
const unexpectedFiles = files.filter(f => !expectedFiles.includes(f));
|
|
123
|
-
if (unexpectedFiles.length > 0) {
|
|
124
|
-
printError(`Unexpected files in ./dist/chatgpt/: ${unexpectedFiles.join(', ')}`);
|
|
125
|
-
printError(`Expected only: ${expectedFiles.join(', ')}`);
|
|
126
|
-
process.exit(1);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
console.log()
|
|
130
|
-
printSuccess('pnpm build');
|
|
131
|
-
|
|
132
|
-
// MCP Server Check
|
|
133
|
-
console.log('\nRunning: pnpm mcp:serve');
|
|
134
|
-
const mcpProcess = spawn('pnpm', ['mcp:serve'], {
|
|
135
|
-
cwd: PROJECT_ROOT,
|
|
136
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
137
|
-
env: { ...process.env, FORCE_COLOR: '1' },
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
const mcpErrors = [];
|
|
141
|
-
|
|
142
|
-
mcpProcess.stderr.on('data', (data) => {
|
|
143
|
-
const message = data.toString();
|
|
144
|
-
if (message.includes('error') || message.includes('Error')) {
|
|
145
|
-
mcpErrors.push(message.trim());
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
// Store process for cleanup
|
|
150
|
-
process.on('exit', () => {
|
|
151
|
-
if (mcpProcess && !mcpProcess.killed) {
|
|
152
|
-
mcpProcess.kill();
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
console.log('\nWaiting for MCP server to start on port 6766...');
|
|
158
|
-
await waitForServer(6766, 10000);
|
|
159
|
-
|
|
160
|
-
// Give it a moment to ensure no immediate errors
|
|
161
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
162
|
-
|
|
163
|
-
if (mcpErrors.length > 0) {
|
|
164
|
-
printError('MCP server started but reported errors:');
|
|
165
|
-
mcpErrors.forEach(err => console.log(` ${err}`));
|
|
166
|
-
throw new Error('MCP server has errors');
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
} catch (error) {
|
|
170
|
-
printError(`MCP server failed to start: ${error.message}`);
|
|
171
|
-
throw error;
|
|
172
|
-
} finally {
|
|
173
|
-
console.log('Stopping MCP server...');
|
|
174
|
-
mcpProcess.kill();
|
|
175
|
-
// Give it a moment to shut down
|
|
176
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
177
|
-
}
|
|
178
|
-
console.log()
|
|
179
|
-
printSuccess('pnpm mcp\n');
|
|
180
|
-
|
|
181
|
-
printSuccess('All systems GO!\n\n');
|
|
182
|
-
process.exit(0);
|
|
183
|
-
} catch (error) {
|
|
184
|
-
console.error(`\n${colors.red}Error: ${error.message}${colors.reset}\n`);
|
|
185
|
-
process.exit(1);
|
|
186
|
-
}
|
|
File without changes
|