sunpeak 0.3.5 → 0.3.7

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 CHANGED
@@ -28,7 +28,7 @@ Build and test your ChatGPT App UI locally with OpenAI apps-sdk-ui React compone
28
28
  Requirements: Node (20+), pnpm (10+)
29
29
 
30
30
  ```bash
31
- pnpm dlx sunpeak init
31
+ pnpm dlx sunpeak new
32
32
  ```
33
33
 
34
34
  ### Existing Projects
@@ -99,6 +99,7 @@ For development quickstart on this package, see [DEVELOPMENT.md](./DEVELOPMENT.m
99
99
  ## Resources
100
100
 
101
101
  - [ChatGPT Apps SDK Design Guidelines](https://developers.openai.com/apps-sdk/concepts/design-guidelines)
102
- - [ChatGPT Apps SDK UI Documentation](https://developers.openai.com/apps-sdk/build/chatgpt-ui)
102
+ - [ChatGPT Apps SDK Documentation - UI](https://developers.openai.com/apps-sdk/build/chatgpt-ui)
103
103
  - [ChatGPT Apps SDK window.openai Reference](https://developers.openai.com/apps-sdk/build/mcp-server#understand-the-windowopenai-widget-runtime)
104
104
  - [ChatGPT Apps SDK Examples](https://github.com/openai/openai-apps-sdk-examples)
105
+ - [ChatGPT Apps SDK UI Documentation](https://openai.github.io/apps-sdk-ui/)
package/bin/sunpeak.js CHANGED
@@ -80,13 +80,13 @@ See README.md for more details.
80
80
 
81
81
  const [,, command, ...args] = process.argv;
82
82
 
83
- if (command === 'init') {
83
+ if (command === 'new') {
84
84
  init(args[0]);
85
85
  } else {
86
86
  console.log(`
87
87
  sunpeak - ChatGPT Apps UI SDK
88
88
 
89
89
  Commands:
90
- init [name] Create a new project from template
90
+ new [name] Create a new project from template
91
91
  `);
92
92
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sunpeak",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "The ChatGPT Apps UI SDK. Build and test your ChatGPT App UI locally with OpenAI apps-sdk-ui components.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -66,6 +66,7 @@
66
66
  "zod": "^3.23.8"
67
67
  },
68
68
  "devDependencies": {
69
+ "@playwright/test": "^1.56.1",
69
70
  "@tailwindcss/vite": "^4.1.17",
70
71
  "@testing-library/jest-dom": "^6.9.1",
71
72
  "@testing-library/react": "^16.3.0",
@@ -105,6 +106,8 @@
105
106
  "dev": "pnpm --filter my-sunpeak-app dev",
106
107
  "lint": "eslint . --ext .ts,.tsx --fix",
107
108
  "typecheck": "tsc --noEmit",
108
- "test": "vitest run"
109
+ "test": "vitest run",
110
+ "test:e2e": "playwright test",
111
+ "validate": "node scripts/validate.mjs"
109
112
  }
110
113
  }
@@ -30,13 +30,20 @@ dist/ # Build output (generated)
30
30
 
31
31
  ### Testing Locally
32
32
 
33
- Run the following scripts, and manually QA the UI from the dev server:
33
+ Run all the checks with the following:
34
+
35
+ ```bash
36
+ pnpm validate
37
+ ```
38
+
39
+ This will:
40
+ - Run linting, typechecking, and unit tests
41
+ - Build your app
42
+ - Verify that build outputs are created correctly
43
+
44
+ For manual QA of the UI, run:
34
45
 
35
46
  ```bash
36
- pnpm lint
37
- pnpm typecheck
38
- pnpm test
39
- pnpm build
40
47
  pnpm dev
41
48
  ```
42
49
 
@@ -5,11 +5,12 @@
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "vite build --config vite.config.build.ts",
8
- "dev": "vite",
8
+ "dev": "vite --port ${PORT:-6767}",
9
9
  "mcp": "tsx mcp/server.ts",
10
10
  "lint": "eslint . --ext .ts,.tsx --fix",
11
11
  "typecheck": "tsc --noEmit",
12
- "test": "vitest run"
12
+ "test": "vitest run",
13
+ "validate": "node scripts/validate.mjs"
13
14
  },
14
15
  "dependencies": {
15
16
  "@openai/apps-sdk-ui": "^0.2.0",
@@ -0,0 +1,171 @@
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 lint');
81
+ if (!runCommand('pnpm lint', PROJECT_ROOT)) {
82
+ throw new Error('pnpm lint failed');
83
+ }
84
+ printSuccess('pnpm lint');
85
+
86
+ console.log('\nRunning: pnpm typecheck');
87
+ if (!runCommand('pnpm typecheck', PROJECT_ROOT)) {
88
+ throw new Error('pnpm typecheck failed');
89
+ }
90
+ printSuccess('pnpm typecheck');
91
+
92
+ console.log('\nRunning: pnpm test');
93
+ if (!runCommand('pnpm test', PROJECT_ROOT)) {
94
+ throw new Error('pnpm test failed');
95
+ }
96
+ printSuccess('pnpm test');
97
+
98
+ console.log('\nRunning: pnpm build');
99
+ if (!runCommand('pnpm build', PROJECT_ROOT)) {
100
+ throw new Error('pnpm build failed');
101
+ }
102
+ const chatgptDir = join(PROJECT_ROOT, 'dist', 'chatgpt');
103
+ const builtFile = join(chatgptDir, 'index.js');
104
+ if (!existsSync(builtFile)) {
105
+ printError('Missing expected file: ./dist/chatgpt/index.js');
106
+ process.exit(1);
107
+ }
108
+ const files = readdirSync(chatgptDir);
109
+ if (files.length !== 1 || files[0] !== 'index.js') {
110
+ printError(`Unexpected files in ./dist/chatgpt/: ${files.join(', ')}`);
111
+ printError('Expected only: index.js');
112
+ process.exit(1);
113
+ }
114
+ console.log()
115
+ printSuccess('pnpm build');
116
+
117
+ // MCP Server Check
118
+ console.log('\nRunning: pnpm mcp');
119
+ const mcpProcess = spawn('pnpm', ['mcp'], {
120
+ cwd: PROJECT_ROOT,
121
+ stdio: ['ignore', 'pipe', 'pipe'],
122
+ env: { ...process.env, FORCE_COLOR: '1' },
123
+ });
124
+
125
+ const mcpErrors = [];
126
+
127
+ mcpProcess.stderr.on('data', (data) => {
128
+ const message = data.toString();
129
+ if (message.includes('error') || message.includes('Error')) {
130
+ mcpErrors.push(message.trim());
131
+ }
132
+ });
133
+
134
+ // Store process for cleanup
135
+ process.on('exit', () => {
136
+ if (mcpProcess && !mcpProcess.killed) {
137
+ mcpProcess.kill();
138
+ }
139
+ });
140
+
141
+ try {
142
+ console.log('\nWaiting for MCP server to start on port 6766...');
143
+ await waitForServer(6766, 10000);
144
+
145
+ // Give it a moment to ensure no immediate errors
146
+ await new Promise(resolve => setTimeout(resolve, 1000));
147
+
148
+ if (mcpErrors.length > 0) {
149
+ printError('MCP server started but reported errors:');
150
+ mcpErrors.forEach(err => console.log(` ${err}`));
151
+ throw new Error('MCP server has errors');
152
+ }
153
+
154
+ } catch (error) {
155
+ printError(`MCP server failed to start: ${error.message}`);
156
+ throw error;
157
+ } finally {
158
+ console.log('Stopping MCP server...');
159
+ mcpProcess.kill();
160
+ // Give it a moment to shut down
161
+ await new Promise(resolve => setTimeout(resolve, 1000));
162
+ }
163
+ console.log()
164
+ printSuccess('pnpm mcp\n');
165
+
166
+ printSuccess('All systems GO!\n\n');
167
+ process.exit(0);
168
+ } catch (error) {
169
+ console.error(`\n${colors.red}Error: ${error.message}${colors.reset}\n`);
170
+ process.exit(1);
171
+ }
@@ -1,4 +1,5 @@
1
1
  import './styles/globals.css';
2
+ import 'sunpeak/style.css';
2
3
 
3
4
  import { useWidgetProps } from 'sunpeak';
4
5
  import { OpenAICarousel, OpenAICard } from './components';
@@ -1,6 +1,5 @@
1
1
  @import "tailwindcss";
2
2
  @import "@openai/apps-sdk-ui/css";
3
- @import "sunpeak/style.css";
4
3
 
5
4
  /* Required for Tailwind to find class references in Apps SDK UI components. */
6
5
  @source "../node_modules/@openai/apps-sdk-ui";
@@ -22,12 +22,7 @@
22
22
  "declarationMap": true,
23
23
  "sourceMap": true,
24
24
 
25
- "baseUrl": ".",
26
- "paths": {
27
- "sunpeak": ["../src/index.ts"],
28
- "sunpeak/*": ["../src/*"],
29
- "~/*": ["../src/*"]
30
- }
25
+ "baseUrl": "."
31
26
  },
32
27
  "include": ["src", "dev"],
33
28
  "references": [{ "path": "./tsconfig.node.json" }]
@@ -4,6 +4,10 @@ import path from 'path';
4
4
  import tailwindcss from '@tailwindcss/vite';
5
5
  import { readFileSync, writeFileSync, unlinkSync, existsSync } from 'fs';
6
6
 
7
+ // Check if we're in the sunpeak workspace (directory is named "template")
8
+ const isTemplate = path.basename(__dirname) === 'template';
9
+ const parentSrc = path.resolve(__dirname, '../src');
10
+
7
11
  // Plugin to inline CSS into the JS bundle
8
12
  function inlineCssPlugin() {
9
13
  return {
@@ -35,8 +39,11 @@ export default defineConfig({
35
39
  },
36
40
  resolve: {
37
41
  alias: {
38
- 'sunpeak': path.resolve(__dirname, '../src'),
39
- '~': path.resolve(__dirname, '../src'),
42
+ // In workspace dev mode, use local sunpeak source
43
+ ...(isTemplate && {
44
+ 'sunpeak': parentSrc,
45
+ '~': parentSrc,
46
+ }),
40
47
  },
41
48
  conditions: ['style', 'import', 'module', 'browser', 'default'],
42
49
  },
@@ -1,6 +1,10 @@
1
1
  import { defineConfig } from 'vitest/config';
2
2
  import path from 'path';
3
3
 
4
+ // Check if we're in the sunpeak workspace (directory is named "template")
5
+ const isTemplate = path.basename(__dirname) === 'template';
6
+ const parentSrc = path.resolve(__dirname, '../src');
7
+
4
8
  export default defineConfig({
5
9
  test: {
6
10
  globals: true,
@@ -9,8 +13,11 @@ export default defineConfig({
9
13
  },
10
14
  resolve: {
11
15
  alias: {
12
- 'sunpeak': path.resolve(__dirname, '../src'),
13
- '~': path.resolve(__dirname, '../src'),
16
+ // In workspace dev mode, use local sunpeak source
17
+ ...(isTemplate && {
18
+ 'sunpeak': parentSrc,
19
+ '~': parentSrc,
20
+ }),
14
21
  },
15
22
  },
16
23
  });