sunpeak 0.10.7 → 0.12.1
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 +117 -20
- package/bin/commands/build.mjs +1 -11
- package/bin/commands/deploy.mjs +7 -1
- package/bin/commands/dev.mjs +175 -12
- package/bin/commands/new.mjs +211 -0
- package/bin/commands/pull.mjs +1 -1
- package/bin/commands/push.mjs +36 -13
- package/bin/lib/patterns.mjs +25 -0
- package/bin/sunpeak.js +15 -232
- package/dist/chatgpt/index.cjs +1 -1
- package/dist/chatgpt/index.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +2 -2
- package/dist/mcp/entry.cjs +1 -1
- package/dist/mcp/entry.js +1 -1
- package/dist/mcp/favicon.d.ts +2 -0
- package/dist/mcp/index.cjs +3 -1
- package/dist/mcp/index.cjs.map +1 -1
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +3 -1
- package/dist/{server-BLKltt88.js → server-BI9Y531R.js} +31 -3
- package/dist/{server-BLKltt88.js.map → server-BI9Y531R.js.map} +1 -1
- package/dist/{server-D_oRdZjX.cjs → server-CcLDAGBE.cjs} +31 -3
- package/dist/{server-D_oRdZjX.cjs.map → server-CcLDAGBE.cjs.map} +1 -1
- package/dist/{simulator-url-B6DZi3vV.cjs → simulator-url-CYMOGoB1.cjs} +5 -5
- package/dist/{simulator-url-B6DZi3vV.cjs.map → simulator-url-CYMOGoB1.cjs.map} +1 -1
- package/dist/{simulator-url-izFV6mji.js → simulator-url-DG79-dU3.js} +4 -4
- package/dist/{simulator-url-izFV6mji.js.map → simulator-url-DG79-dU3.js.map} +1 -1
- package/package.json +1 -1
- package/template/.sunpeak/dev.tsx +4 -4
- package/template/README.md +25 -27
- package/template/_gitignore +4 -0
- package/template/dist/albums/albums.json +1 -1
- package/template/dist/carousel/carousel.json +1 -1
- package/template/dist/map/map.json +1 -1
- package/template/dist/review/review.json +1 -1
- package/template/index.html +0 -1
- package/template/node_modules/.vite/deps/_metadata.json +22 -22
- package/template/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/@openai_apps-sdk-ui_components_Avatar.js +96 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/@openai_apps-sdk-ui_components_Avatar.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/@openai_apps-sdk-ui_components_Button.js +625 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/@openai_apps-sdk-ui_components_Button.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/@openai_apps-sdk-ui_components_Checkbox.js +33 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/@openai_apps-sdk-ui_components_Checkbox.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/@openai_apps-sdk-ui_components_Icon.js +1498 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/@openai_apps-sdk-ui_components_Icon.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/@openai_apps-sdk-ui_components_Input.js +13 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/@openai_apps-sdk-ui_components_Input.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/@openai_apps-sdk-ui_components_SegmentedControl.js +103 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/@openai_apps-sdk-ui_components_SegmentedControl.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/@openai_apps-sdk-ui_components_Select.js +3680 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/@openai_apps-sdk-ui_components_Select.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/@openai_apps-sdk-ui_components_Textarea.js +95 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/@openai_apps-sdk-ui_components_Textarea.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/@openai_apps-sdk-ui_theme.js +45 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/@openai_apps-sdk-ui_theme.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-2UDYPUBJ.js +15201 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-2UDYPUBJ.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-6QVG4F2X.js +93 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-6QVG4F2X.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-BUOVMFCD.js +1004 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-BUOVMFCD.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-CNYJBM5F.js +21 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-CNYJBM5F.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-EGRHWZRV.js +1 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-EGRHWZRV.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-ILHRZGIS.js +46 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-ILHRZGIS.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-JAGHY6H6.js +231 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-JAGHY6H6.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-JGVISENQ.js +292 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-JGVISENQ.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-P5LK4A7U.js +112 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-P5LK4A7U.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-QPJAV452.js +13 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-QPJAV452.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-RYYR2YMB.js +111 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-RYYR2YMB.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-UM3ZGDFR.js +4480 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-UM3ZGDFR.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-XZTIOEPG.js +280 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/chunk-XZTIOEPG.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/clsx.js +10 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/clsx.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/embla-carousel-react.js +1712 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/embla-carousel-react.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/embla-carousel-wheel-gestures.js +589 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/embla-carousel-wheel-gestures.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/mapbox-gl.js +32835 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/mapbox-gl.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/package.json +3 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/react-dom.js +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/react-dom.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/react-dom_client.js +20217 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/react-dom_client.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/react.js +6 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/react.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/react_jsx-dev-runtime.js +278 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/react_jsx-dev-runtime.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/react_jsx-runtime.js +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/react_jsx-runtime.js.map +7 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/tailwind-merge.js +3095 -0
- package/template/node_modules/.vite-mcp/deps_temp_992accd8/tailwind-merge.js.map +7 -0
- package/template/package.json +0 -1
- package/template/vitest.config.ts +1 -1
- package/bin/commands/mcp.mjs +0 -244
- package/template/public/favicon.ico +0 -0
- package/template/src/resources/albums/albums-show-simulation.json +0 -131
- package/template/src/resources/carousel/carousel-show-simulation.json +0 -68
- package/template/src/resources/map/map-show-simulation.json +0 -123
- package/template/src/resources/review/review-diff-simulation.json +0 -80
- package/template/src/resources/review/review-post-simulation.json +0 -56
- package/template/src/resources/review/review-purchase-simulation.json +0 -88
- /package/template/{src/test → tests}/setup.ts +0 -0
- /package/template/{dist → tests/simulations}/albums/albums-show-simulation.json +0 -0
- /package/template/{dist → tests/simulations}/carousel/carousel-show-simulation.json +0 -0
- /package/template/{dist → tests/simulations}/map/map-show-simulation.json +0 -0
- /package/template/{dist → tests/simulations}/review/review-diff-simulation.json +0 -0
- /package/template/{dist → tests/simulations}/review/review-post-simulation.json +0 -0
- /package/template/{dist → tests/simulations}/review/review-purchase-simulation.json +0 -0
package/README.md
CHANGED
|
@@ -17,13 +17,14 @@
|
|
|
17
17
|
|
|
18
18
|
The ChatGPT App framework.
|
|
19
19
|
|
|
20
|
-
Quickstart, build, test, and ship your ChatGPT App
|
|
20
|
+
Quickstart, build, test, and ship your ChatGPT App—locally!
|
|
21
21
|
|
|
22
22
|
[Demo (Hosted)](https://sunpeak.ai/#simulator) ~
|
|
23
23
|
[Demo (Video)](https://d10djik02wlf6x.cloudfront.net/sunpeak-demo-prod.mp4) ~
|
|
24
24
|
[Discord (NEW)](https://discord.gg/FB2QNXqRnw) ~
|
|
25
25
|
[Documentation](https://docs.sunpeak.ai/) ~
|
|
26
|
-
[GitHub](https://github.com/Sunpeak-AI/sunpeak)
|
|
26
|
+
[GitHub](https://github.com/Sunpeak-AI/sunpeak) ~
|
|
27
|
+
[Resource Repository](https://app.sunpeak.ai/)
|
|
27
28
|
|
|
28
29
|
<div align="center">
|
|
29
30
|
<a href="https://docs.sunpeak.ai/library/chatgpt-simulator">
|
|
@@ -35,8 +36,6 @@ Quickstart, build, test, and ship your ChatGPT App locally!
|
|
|
35
36
|
|
|
36
37
|
## Quickstart
|
|
37
38
|
|
|
38
|
-
### New Projects
|
|
39
|
-
|
|
40
39
|
Requirements: Node (20+), pnpm (10+)
|
|
41
40
|
|
|
42
41
|
```bash
|
|
@@ -48,29 +47,69 @@ To add sunpeak to an existing project, refer to the [documentation](https://docs
|
|
|
48
47
|
|
|
49
48
|
## Overview
|
|
50
49
|
|
|
51
|
-
sunpeak is an npm package
|
|
50
|
+
sunpeak is an npm package that helps you build ChatGPT Apps (MCP Resources) while keeping your MCP server client-agnostic. sunpeak consists of:
|
|
51
|
+
|
|
52
|
+
### The `sunpeak` library (`./src`)
|
|
53
|
+
|
|
54
|
+
1. Runtime APIs: Strongly typed APIs for interacting with the ChatGPT runtime, **architected to support future platforms** (Gemini, Claude, etc.).
|
|
55
|
+
2. ChatGPT simulator: React component replicating ChatGPT's runtime to **test Apps locally and automatically**.
|
|
56
|
+
3. MCP server: Serve Resources with mock data to the real ChatGPT with HMR (**no more cache issues or 5-click manual refreshes**).
|
|
57
|
+
|
|
58
|
+
### The `sunpeak` framework (`./template`)
|
|
59
|
+
|
|
60
|
+
Next.js for ChatGPT Apps. Using a Review page as an example, sunpeak projects look like:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
my-app/
|
|
64
|
+
├── src/resources/
|
|
65
|
+
│ └── review/
|
|
66
|
+
│ ├── review-resource.tsx # Review UI component.
|
|
67
|
+
│ └── review-resource.json # Review UI MCP metadata.
|
|
68
|
+
├── tests/simulations/
|
|
69
|
+
│ └── review/
|
|
70
|
+
│ ├── review-{scenario1}-simulation.json # Mock state for testing.
|
|
71
|
+
│ └── review-{scenario2}-simulation.json # Mock state for testing.
|
|
72
|
+
└── package.json
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
1. Project scaffold: Complete development setup with the sunpeak library.
|
|
76
|
+
2. UI components: Production-ready components following ChatGPT design guidelines and using OpenAI apps-sdk-ui React components.
|
|
77
|
+
3. Convention over configuration:
|
|
78
|
+
1. Create UIs by creating a `-resource.tsx` component file ([example](#resource-component)) and `-resource.json` MCP metadata file ([example](#resource-mcp-metadata)).
|
|
79
|
+
2. Create test state (simulations) for local dev, ChatGPT dev, automated testing, and demos by creating a `-simulation.json` file. ([example](#simulation))
|
|
52
80
|
|
|
53
|
-
|
|
54
|
-
1. Runtime APIs - Strongly typed, multi-platform APIs for interacting with the ChatGPT runtime, architected to support future platforms (Gemini, Claude).
|
|
55
|
-
2. ChatGPT simulator - React component replicating ChatGPT's runtime.
|
|
56
|
-
3. MCP server - Mock data MCP server for testing local UIs (resources) in the real ChatGPT.
|
|
57
|
-
2. **The `sunpeak` framework** (`./template`). Next.js for ChatGPT Apps. This templated npm package includes:
|
|
58
|
-
1. Project scaffold - Complete development setup with build, test, and mcp tooling, including the sunpeak library.
|
|
59
|
-
2. UI components - Production-ready components following ChatGPT design guidelines and using OpenAI apps-sdk-ui React components.
|
|
60
|
-
3. Convention over configuration - Create UIs (resources) by simply creating a `src/resources/NAME/NAME-resource.tsx` React file and `src/resources/NAME/NAME-resource.json` metadata file.
|
|
61
|
-
3. **The `sunpeak` CLI** (`./bin`). Commands for managing ChatGPT Apps. Includes a client for the [sunpeak Resource Repository](https://app.sunpeak.ai/) (ECR for ChatGPT Apps). The repository helps you & your CI/CD decouple your App from your client-agnostic MCP server:
|
|
62
|
-
1. Tag your app builds with version numbers and environment names (like `v1.0.0` and `prod`)
|
|
63
|
-
2. `push` built Apps to a central location
|
|
64
|
-
3. `pull` built Apps to be run in different environments
|
|
81
|
+
### The `sunpeak` CLI (`./bin`)
|
|
65
82
|
|
|
66
|
-
|
|
83
|
+
Commands for managing ChatGPT Apps. Includes a client for the [sunpeak Resource Repository](https://app.sunpeak.ai/). The repository helps you & your CI/CD decouple your App from your client-agnostic MCP server while also providing a hosted runtime to collaborate, demo, and share your ChatGPT Apps.
|
|
67
84
|
|
|
68
|
-
|
|
85
|
+
Think Docker Hub for ChatGPT Apps:
|
|
86
|
+
|
|
87
|
+
<div align="center">
|
|
88
|
+
<a href="https://docs.sunpeak.ai/library/chatgpt-simulator">
|
|
89
|
+
<picture>
|
|
90
|
+
<img alt="ChatGPT Resource Repository" src="https://d10djik02wlf6x.cloudfront.net/blog/storybook-for-chatgpt-apps.png">
|
|
91
|
+
</picture>
|
|
92
|
+
</a>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
1. Tag your app builds with version numbers and environment names (like `v1.0.0` and `prod`)
|
|
96
|
+
2. `push` built Apps to a central location
|
|
97
|
+
3. `pull` built Apps to be run in different environments, like your production MCP server.
|
|
98
|
+
4. Share your fully-functional demo Apps with teammates, prospects, and strangers!
|
|
99
|
+
|
|
100
|
+
## Examples
|
|
101
|
+
|
|
102
|
+
Example sunpeak resource & simulation files for an MCP Resource called "Review".
|
|
103
|
+
|
|
104
|
+
### Resource Component
|
|
105
|
+
|
|
106
|
+
React component defining a UI (MCP Resource) in your ChatGPT App.
|
|
69
107
|
|
|
70
108
|
```tsx
|
|
109
|
+
// src/resources/review-resource.tsx
|
|
71
110
|
import { Card } from './components';
|
|
72
111
|
|
|
73
|
-
export function
|
|
112
|
+
export function ReviewResource() {
|
|
74
113
|
return (
|
|
75
114
|
<Card
|
|
76
115
|
image="https://images.unsplash.com/photo-1520950237264-dfe336995c34"
|
|
@@ -86,6 +125,64 @@ export function MCPResource() {
|
|
|
86
125
|
}
|
|
87
126
|
```
|
|
88
127
|
|
|
128
|
+
### Resource MCP Metadata
|
|
129
|
+
|
|
130
|
+
MCP metadata for your UI. Version your resource metadata alongside the resource itself.
|
|
131
|
+
|
|
132
|
+
This is just an official [MCP Resource object](https://modelcontextprotocol.io/specification/2025-11-25/server/resources#resource).
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
// src/resources/review-resource.json
|
|
137
|
+
"name": "review",
|
|
138
|
+
"title": "Review",
|
|
139
|
+
"description": "Visualize and review a proposed set of changes or actions",
|
|
140
|
+
"mimeType": "text/html+skybridge",
|
|
141
|
+
"_meta": {
|
|
142
|
+
"openai/widgetDomain": "https://sunpeak.ai",
|
|
143
|
+
"openai/widgetCSP": {
|
|
144
|
+
"resource_domains": ["https://cdn.openai.com"]
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Simulation
|
|
151
|
+
|
|
152
|
+
`sunpeak` object. Testing a ChatGPT App require setting a lot of state: state in your backend (accessed via MCP tool), the stored widget runtime, and ChatGPT itself.
|
|
153
|
+
|
|
154
|
+
Simulation files let you define key App states for development, automated testing, and demo purposes.
|
|
155
|
+
|
|
156
|
+
Simulation files contain an [official MCP Tool object](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool) and an [official MCP CallToolResult object](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#structured-content). ChatGPT state (like light/dark mode) is not set on the simulation, but rather on the sunpeak `ChatGPTSimulator` itself via UI, props, or URL params.
|
|
157
|
+
|
|
158
|
+
```json
|
|
159
|
+
{
|
|
160
|
+
// tests/simulations/review-diff-simulation.json
|
|
161
|
+
"userMessage": "Refactor the authentication module", // Simulator styling.
|
|
162
|
+
"tool": {
|
|
163
|
+
// Official MCP Tool object.
|
|
164
|
+
"name": "diff-review",
|
|
165
|
+
"description": "Show a review dialog for a proposed code diff",
|
|
166
|
+
"inputSchema": { "type": "object", "properties": {}, "additionalProperties": false },
|
|
167
|
+
"title": "Diff Review",
|
|
168
|
+
"annotations": { "readOnlyHint": false },
|
|
169
|
+
"_meta": {
|
|
170
|
+
"openai/toolInvocation/invoking": "Preparing changes",
|
|
171
|
+
"openai/toolInvocation/invoked": "Changes ready for review",
|
|
172
|
+
"openai/widgetAccessible": true,
|
|
173
|
+
"openai/resultCanProduceWidget": true
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
"callToolResult": {
|
|
177
|
+
// Official MCP CallToolResult object.
|
|
178
|
+
"structuredContent": {
|
|
179
|
+
// ...
|
|
180
|
+
},
|
|
181
|
+
"_meta": {}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
89
186
|
## Resources
|
|
90
187
|
|
|
91
188
|
- [ChatGPT Apps SDK Design Guidelines](https://developers.openai.com/apps-sdk/concepts/design-guidelines)
|
package/bin/commands/build.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import react from '@vitejs/plugin-react';
|
|
|
4
4
|
import tailwindcss from '@tailwindcss/vite';
|
|
5
5
|
import { existsSync, rmSync, readdirSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, unlinkSync } from 'fs';
|
|
6
6
|
import path from 'path';
|
|
7
|
-
import { toPascalCase
|
|
7
|
+
import { toPascalCase } from '../lib/patterns.mjs';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Build all resources for a Sunpeak project
|
|
@@ -241,16 +241,6 @@ export async function build(projectRoot = process.cwd()) {
|
|
|
241
241
|
process.exit(1);
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
-
// Copy affiliated simulation files (matching {resource}-*-simulation.json pattern)
|
|
245
|
-
const simulationFiles = readdirSync(resourceDir)
|
|
246
|
-
.filter(file => isSimulationFile(file, kebabName));
|
|
247
|
-
|
|
248
|
-
for (const simFile of simulationFiles) {
|
|
249
|
-
const srcSim = path.join(resourceDir, simFile);
|
|
250
|
-
const destSim = path.join(distOutDir, simFile);
|
|
251
|
-
copyFileSync(srcSim, destSim);
|
|
252
|
-
console.log(`✓ Copied ${kebabName}/${simFile}`);
|
|
253
|
-
}
|
|
254
244
|
}
|
|
255
245
|
|
|
256
246
|
// Clean up temp and build directories
|
package/bin/commands/deploy.mjs
CHANGED
|
@@ -28,6 +28,7 @@ Usage:
|
|
|
28
28
|
Options:
|
|
29
29
|
-r, --repository <owner/repo> Repository name (defaults to git remote origin)
|
|
30
30
|
-t, --tag <name> Additional tag(s) to assign (always includes "prod")
|
|
31
|
+
--no-simulations Skip pushing simulations, only push resources
|
|
31
32
|
-h, --help Show this help message
|
|
32
33
|
|
|
33
34
|
Arguments:
|
|
@@ -40,6 +41,7 @@ Examples:
|
|
|
40
41
|
sunpeak deploy dist/carousel Deploy a single resource
|
|
41
42
|
sunpeak deploy -r myorg/my-app Deploy to "myorg/my-app" repository
|
|
42
43
|
sunpeak deploy -t v1.0 Deploy with "prod" and "v1.0" tags
|
|
44
|
+
sunpeak deploy --no-simulations Deploy without simulations
|
|
43
45
|
|
|
44
46
|
This command is equivalent to: sunpeak push --tag prod
|
|
45
47
|
`);
|
|
@@ -75,7 +77,7 @@ This command is equivalent to: sunpeak push --tag prod
|
|
|
75
77
|
/**
|
|
76
78
|
* Parse command line arguments
|
|
77
79
|
*/
|
|
78
|
-
function parseArgs(args) {
|
|
80
|
+
export function parseArgs(args) {
|
|
79
81
|
const options = { tags: [] };
|
|
80
82
|
let i = 0;
|
|
81
83
|
|
|
@@ -86,6 +88,8 @@ function parseArgs(args) {
|
|
|
86
88
|
options.repository = args[++i];
|
|
87
89
|
} else if (arg === '--tag' || arg === '-t') {
|
|
88
90
|
options.tags.push(args[++i]);
|
|
91
|
+
} else if (arg === '--no-simulations') {
|
|
92
|
+
options.noSimulations = true;
|
|
89
93
|
} else if (arg === '--help' || arg === '-h') {
|
|
90
94
|
console.log(`
|
|
91
95
|
sunpeak deploy - Deploy resources to production (push with "prod" tag)
|
|
@@ -96,6 +100,7 @@ Usage:
|
|
|
96
100
|
Options:
|
|
97
101
|
-r, --repository <owner/repo> Repository name (defaults to git remote origin)
|
|
98
102
|
-t, --tag <name> Additional tag(s) to assign (always includes "prod")
|
|
103
|
+
--no-simulations Skip pushing simulations, only push resources
|
|
99
104
|
-h, --help Show this help message
|
|
100
105
|
|
|
101
106
|
Arguments:
|
|
@@ -108,6 +113,7 @@ Examples:
|
|
|
108
113
|
sunpeak deploy dist/carousel Deploy a single resource
|
|
109
114
|
sunpeak deploy -r myorg/my-app Deploy to "myorg/my-app" repository
|
|
110
115
|
sunpeak deploy -t v1.0 Deploy with "prod" and "v1.0" tags
|
|
116
|
+
sunpeak deploy --no-simulations Deploy without simulations
|
|
111
117
|
|
|
112
118
|
This command is equivalent to: sunpeak push --tag prod
|
|
113
119
|
`);
|
package/bin/commands/dev.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
const { existsSync, readFileSync } = fs;
|
|
5
|
+
const { join, resolve, basename, dirname } = path;
|
|
4
6
|
import { createRequire } from 'module';
|
|
5
7
|
import { pathToFileURL } from 'url';
|
|
6
8
|
|
|
@@ -81,16 +83,51 @@ export async function dev(projectRoot = process.cwd(), args = []) {
|
|
|
81
83
|
port = parseInt(args[portArgIndex + 1]);
|
|
82
84
|
}
|
|
83
85
|
|
|
86
|
+
// Parse --no-begging flag
|
|
87
|
+
const noBegging = args.includes('--no-begging');
|
|
88
|
+
|
|
84
89
|
console.log(`Starting Vite dev server on port ${port}...`);
|
|
85
90
|
|
|
86
91
|
// Check if we're in the sunpeak workspace (directory is named "template")
|
|
87
92
|
const isTemplate = basename(projectRoot) === 'template';
|
|
88
93
|
const parentSrc = resolve(projectRoot, '../src');
|
|
89
94
|
|
|
95
|
+
// Import sunpeak modules (MCP server and discovery utilities)
|
|
96
|
+
let sunpeakMcp, sunpeak;
|
|
97
|
+
if (isTemplate) {
|
|
98
|
+
// In workspace dev mode, import from local dist folder
|
|
99
|
+
sunpeakMcp = await import(pathToFileURL(resolve(projectRoot, '../dist/mcp/index.js')).href);
|
|
100
|
+
sunpeak = await import(pathToFileURL(resolve(projectRoot, '../dist/index.js')).href);
|
|
101
|
+
} else {
|
|
102
|
+
// Import from installed sunpeak package
|
|
103
|
+
const sunpeakBase = require.resolve('sunpeak').replace(/dist\/index\.(c)?js$/, '');
|
|
104
|
+
sunpeakMcp = await import(pathToFileURL(join(sunpeakBase, 'dist/mcp/index.js')).href);
|
|
105
|
+
sunpeak = await import(pathToFileURL(join(sunpeakBase, 'dist/index.js')).href);
|
|
106
|
+
}
|
|
107
|
+
const { FAVICON_BUFFER: faviconBuffer, runMCPServer } = sunpeakMcp;
|
|
108
|
+
const { findResourceDirs, findSimulationFiles } = sunpeak;
|
|
109
|
+
|
|
110
|
+
// Vite plugin to serve the sunpeak favicon
|
|
111
|
+
const sunpeakFaviconPlugin = () => ({
|
|
112
|
+
name: 'sunpeak-favicon',
|
|
113
|
+
configureServer(server) {
|
|
114
|
+
server.middlewares.use((req, res, next) => {
|
|
115
|
+
if (req.url === '/favicon.ico') {
|
|
116
|
+
res.setHeader('Content-Type', 'image/png');
|
|
117
|
+
res.setHeader('Content-Length', faviconBuffer.length);
|
|
118
|
+
res.setHeader('Cache-Control', 'public, max-age=86400');
|
|
119
|
+
res.end(faviconBuffer);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
next();
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
90
127
|
// Create and start Vite dev server programmatically
|
|
91
128
|
const server = await createServer({
|
|
92
129
|
root: projectRoot,
|
|
93
|
-
plugins: [react(), tailwindcss()],
|
|
130
|
+
plugins: [react(), tailwindcss(), sunpeakFaviconPlugin()],
|
|
94
131
|
resolve: {
|
|
95
132
|
alias: {
|
|
96
133
|
// In workspace dev mode, use local sunpeak source
|
|
@@ -109,16 +146,142 @@ export async function dev(projectRoot = process.cwd(), args = []) {
|
|
|
109
146
|
server.printUrls();
|
|
110
147
|
server.bindCLIShortcuts({ print: true });
|
|
111
148
|
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
149
|
+
// Print star-begging message unless --no-begging is set
|
|
150
|
+
if (!noBegging) {
|
|
151
|
+
// #FFB800 in 24-bit ANSI color
|
|
152
|
+
console.log('\n\n\x1b[38;2;255;184;0m\u2b50\ufe0f \u2192 \u2764\ufe0f https://github.com/Sunpeak-AI/sunpeak\x1b[0m\n');
|
|
153
|
+
}
|
|
117
154
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
});
|
|
155
|
+
// Discover simulations using sunpeak's discovery utilities
|
|
156
|
+
const resourcesDir = join(projectRoot, 'src/resources');
|
|
157
|
+
const simulationsDir = join(projectRoot, 'tests/simulations');
|
|
158
|
+
const resourceDirs = findResourceDirs(resourcesDir, (key) => `${key}-resource.json`, fs);
|
|
159
|
+
|
|
160
|
+
const simulations = [];
|
|
161
|
+
for (const { key: resourceKey, dir: resourceDir, resourcePath } of resourceDirs) {
|
|
162
|
+
const resource = JSON.parse(readFileSync(resourcePath, 'utf-8'));
|
|
163
|
+
const resourceSimDir = join(simulationsDir, resourceKey);
|
|
164
|
+
const simulationFiles = findSimulationFiles(resourceSimDir, resourceKey, fs);
|
|
165
|
+
|
|
166
|
+
for (const { filename, path: simPath } of simulationFiles) {
|
|
167
|
+
const simulationKey = filename.replace(/-simulation\.json$/, '');
|
|
168
|
+
const simulation = JSON.parse(readFileSync(simPath, 'utf-8'));
|
|
169
|
+
|
|
170
|
+
simulations.push({
|
|
171
|
+
...simulation,
|
|
172
|
+
name: simulationKey,
|
|
173
|
+
distPath: join(projectRoot, `dist/${resourceKey}/${resourceKey}.js`),
|
|
174
|
+
srcPath: `/src/resources/${resourceKey}/${resourceKey}-resource.tsx`,
|
|
175
|
+
resource,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Start MCP server with its own Vite instance
|
|
181
|
+
if (simulations.length > 0) {
|
|
182
|
+
console.log(`\nStarting MCP server with ${simulations.length} simulation(s)...`);
|
|
183
|
+
|
|
184
|
+
// Virtual entry module plugin for MCP
|
|
185
|
+
const sunpeakEntryPlugin = () => ({
|
|
186
|
+
name: 'sunpeak-entry',
|
|
187
|
+
resolveId(id) {
|
|
188
|
+
if (id.startsWith('virtual:sunpeak-entry')) {
|
|
189
|
+
return id;
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
load(id) {
|
|
193
|
+
if (id.startsWith('virtual:sunpeak-entry')) {
|
|
194
|
+
const url = new URL(id.replace('virtual:sunpeak-entry', 'http://x'));
|
|
195
|
+
const srcPath = url.searchParams.get('src');
|
|
196
|
+
const componentName = url.searchParams.get('component');
|
|
197
|
+
|
|
198
|
+
if (!srcPath || !componentName) {
|
|
199
|
+
return 'console.error("Missing src or component param");';
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return `
|
|
203
|
+
import { createElement } from 'react';
|
|
204
|
+
import { createRoot } from 'react-dom/client';
|
|
205
|
+
import * as ResourceModule from '${srcPath}';
|
|
206
|
+
|
|
207
|
+
const Component = ResourceModule.default || ResourceModule['${componentName}'];
|
|
208
|
+
if (!Component) {
|
|
209
|
+
document.getElementById('root').innerHTML = '<pre style="color:red;padding:16px">Component not found: ${componentName}\\nExports: ' + Object.keys(ResourceModule).join(', ') + '</pre>';
|
|
210
|
+
} else {
|
|
211
|
+
createRoot(document.getElementById('root')).render(createElement(Component));
|
|
212
|
+
}
|
|
213
|
+
`;
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Create Vite dev server in middleware mode for MCP
|
|
219
|
+
// Use separate cache directory to avoid conflicts with main dev server
|
|
220
|
+
const mcpViteServer = await createServer({
|
|
221
|
+
root: projectRoot,
|
|
222
|
+
cacheDir: 'node_modules/.vite-mcp',
|
|
223
|
+
plugins: [react(), tailwindcss(), sunpeakEntryPlugin()],
|
|
224
|
+
resolve: {
|
|
225
|
+
alias: {
|
|
226
|
+
...(isTemplate && {
|
|
227
|
+
sunpeak: parentSrc,
|
|
228
|
+
}),
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
server: {
|
|
232
|
+
middlewareMode: true,
|
|
233
|
+
allowedHosts: true,
|
|
234
|
+
watch: {
|
|
235
|
+
// Only watch files that affect the UI bundle (not JSON, tests, etc.)
|
|
236
|
+
// MCP resources reload on next tool call, not on file change
|
|
237
|
+
ignored: (filePath) => {
|
|
238
|
+
if (!filePath.includes('.')) return false; // Watch directories
|
|
239
|
+
if (/\.(tsx?|css)$/.test(filePath)) {
|
|
240
|
+
return /\.(test|spec)\.tsx?$/.test(filePath); // Ignore tests
|
|
241
|
+
}
|
|
242
|
+
return true; // Ignore everything else
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
optimizeDeps: {
|
|
247
|
+
include: ['react', 'react-dom/client'],
|
|
248
|
+
},
|
|
249
|
+
appType: 'custom',
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
253
|
+
runMCPServer({
|
|
254
|
+
name: pkg.name || 'Sunpeak',
|
|
255
|
+
version: pkg.version || '0.1.0',
|
|
256
|
+
simulations,
|
|
257
|
+
port: 6766,
|
|
258
|
+
viteServer: mcpViteServer,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Handle signals - close both servers
|
|
262
|
+
process.on('SIGINT', async () => {
|
|
263
|
+
await mcpViteServer.close();
|
|
264
|
+
await server.close();
|
|
265
|
+
process.exit(0);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
process.on('SIGTERM', async () => {
|
|
269
|
+
await mcpViteServer.close();
|
|
270
|
+
await server.close();
|
|
271
|
+
process.exit(0);
|
|
272
|
+
});
|
|
273
|
+
} else {
|
|
274
|
+
// No simulations - just handle signals for the dev server
|
|
275
|
+
process.on('SIGINT', async () => {
|
|
276
|
+
await server.close();
|
|
277
|
+
process.exit(0);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
process.on('SIGTERM', async () => {
|
|
281
|
+
await server.close();
|
|
282
|
+
process.exit(0);
|
|
283
|
+
});
|
|
284
|
+
}
|
|
122
285
|
}
|
|
123
286
|
|
|
124
287
|
// Allow running directly
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdirSync, cpSync, readFileSync, writeFileSync, renameSync } from 'fs';
|
|
3
|
+
import { join, dirname, basename } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { createInterface } from 'readline';
|
|
6
|
+
import { discoverResources } from '../lib/patterns.mjs';
|
|
7
|
+
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Default prompt implementation using readline
|
|
12
|
+
* @param {string} question
|
|
13
|
+
* @returns {Promise<string>}
|
|
14
|
+
*/
|
|
15
|
+
function defaultPrompt(question) {
|
|
16
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
rl.question(question, (answer) => {
|
|
19
|
+
rl.close();
|
|
20
|
+
resolve(answer.trim());
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Default dependencies (real implementations)
|
|
27
|
+
*/
|
|
28
|
+
export const defaultDeps = {
|
|
29
|
+
discoverResources,
|
|
30
|
+
existsSync,
|
|
31
|
+
mkdirSync,
|
|
32
|
+
cpSync,
|
|
33
|
+
readFileSync,
|
|
34
|
+
writeFileSync,
|
|
35
|
+
renameSync,
|
|
36
|
+
prompt: defaultPrompt,
|
|
37
|
+
console,
|
|
38
|
+
process,
|
|
39
|
+
cwd: () => process.cwd(),
|
|
40
|
+
templateDir: join(__dirname, '..', '..', 'template'),
|
|
41
|
+
rootPkgPath: join(__dirname, '..', '..', 'package.json'),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Parse and validate resources input
|
|
46
|
+
* @param {string} input - Comma or space separated resource names
|
|
47
|
+
* @param {string[]} validResources - List of valid resource names
|
|
48
|
+
* @param {Object} deps - Dependencies for testing
|
|
49
|
+
* @returns {string[]} - Validated and deduplicated resource names
|
|
50
|
+
*/
|
|
51
|
+
export function parseResourcesInput(input, validResources, deps = defaultDeps) {
|
|
52
|
+
const d = { ...defaultDeps, ...deps };
|
|
53
|
+
|
|
54
|
+
// If no input, return all resources
|
|
55
|
+
if (!input || input.trim() === '') {
|
|
56
|
+
return validResources;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Split by comma or space and trim
|
|
60
|
+
const tokens = input
|
|
61
|
+
.toLowerCase()
|
|
62
|
+
.split(/[,\s]+/)
|
|
63
|
+
.map((s) => s.trim())
|
|
64
|
+
.filter((s) => s.length > 0);
|
|
65
|
+
|
|
66
|
+
// Validate tokens
|
|
67
|
+
const invalid = tokens.filter((t) => !validResources.includes(t));
|
|
68
|
+
if (invalid.length > 0) {
|
|
69
|
+
d.console.error(`Error: Invalid resource(s): ${invalid.join(', ')}`);
|
|
70
|
+
d.console.error(`Valid resources are: ${validResources.join(', ')}`);
|
|
71
|
+
d.process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Remove duplicates
|
|
75
|
+
return [...new Set(tokens)];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Create a new Sunpeak project
|
|
80
|
+
* @param {string} projectName - Name of the project directory
|
|
81
|
+
* @param {string} resourcesArg - Optional comma/space separated resources to include
|
|
82
|
+
* @param {Object} deps - Dependencies for testing
|
|
83
|
+
*/
|
|
84
|
+
export async function init(projectName, resourcesArg, deps = defaultDeps) {
|
|
85
|
+
const d = { ...defaultDeps, ...deps };
|
|
86
|
+
|
|
87
|
+
// Discover available resources from template
|
|
88
|
+
const availableResources = d.discoverResources();
|
|
89
|
+
if (availableResources.length === 0) {
|
|
90
|
+
d.console.error('Error: No resources found in template/src/resources/');
|
|
91
|
+
d.process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!projectName) {
|
|
95
|
+
projectName = await d.prompt('☀️ 🏔️ Project name [my-app]: ');
|
|
96
|
+
if (!projectName) {
|
|
97
|
+
projectName = 'my-app';
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (projectName === 'template') {
|
|
102
|
+
d.console.error('Error: "template" is a reserved name. Please choose another name.');
|
|
103
|
+
d.process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Use resources from args or ask for them
|
|
107
|
+
let resourcesInput;
|
|
108
|
+
if (resourcesArg) {
|
|
109
|
+
resourcesInput = resourcesArg;
|
|
110
|
+
d.console.log(`☀️ 🏔️ Resources: ${resourcesArg}`);
|
|
111
|
+
} else {
|
|
112
|
+
resourcesInput = await d.prompt(
|
|
113
|
+
`☀️ 🏔️ Resources (UIs) to include [${availableResources.join(', ')}]: `
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
const selectedResources = parseResourcesInput(resourcesInput, availableResources, d);
|
|
117
|
+
|
|
118
|
+
const targetDir = join(d.cwd(), projectName);
|
|
119
|
+
|
|
120
|
+
if (d.existsSync(targetDir)) {
|
|
121
|
+
d.console.error(`Error: Directory "${projectName}" already exists`);
|
|
122
|
+
d.process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
d.console.log(`☀️ 🏔️ Creating ${projectName}...`);
|
|
126
|
+
|
|
127
|
+
d.mkdirSync(targetDir, { recursive: true });
|
|
128
|
+
|
|
129
|
+
// Filter resource directories based on selection
|
|
130
|
+
const excludedResources = availableResources.filter((r) => !selectedResources.includes(r));
|
|
131
|
+
|
|
132
|
+
d.cpSync(d.templateDir, targetDir, {
|
|
133
|
+
recursive: true,
|
|
134
|
+
filter: (src) => {
|
|
135
|
+
const name = basename(src);
|
|
136
|
+
|
|
137
|
+
// Skip node_modules and lock file
|
|
138
|
+
if (name === 'node_modules' || name === 'pnpm-lock.yaml') {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (const resource of excludedResources) {
|
|
143
|
+
// Skip entire resource directory: src/resources/{resource}/
|
|
144
|
+
if (src.includes('/resources/') && name === resource) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
// Skip simulation directories for excluded resources: tests/simulations/{resource}/
|
|
148
|
+
if (src.includes('/tests/simulations/') && name === resource) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
// Skip e2e test files for excluded resources
|
|
152
|
+
if (src.includes('/tests/e2e/') && name === `${resource}.spec.ts`) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return true;
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Rename underscore-prefixed files to dotfiles
|
|
162
|
+
const dotfiles = ['_gitignore', '_prettierignore', '_prettierrc'];
|
|
163
|
+
for (const file of dotfiles) {
|
|
164
|
+
const srcPath = join(targetDir, file);
|
|
165
|
+
const destPath = join(targetDir, file.replace(/^_/, '.'));
|
|
166
|
+
if (d.existsSync(srcPath)) {
|
|
167
|
+
d.renameSync(srcPath, destPath);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Read sunpeak version from root package.json
|
|
172
|
+
const rootPkg = JSON.parse(d.readFileSync(d.rootPkgPath, 'utf-8'));
|
|
173
|
+
const sunpeakVersion = `^${rootPkg.version}`;
|
|
174
|
+
|
|
175
|
+
// Update project package.json
|
|
176
|
+
const pkgPath = join(targetDir, 'package.json');
|
|
177
|
+
const pkg = JSON.parse(d.readFileSync(pkgPath, 'utf-8'));
|
|
178
|
+
pkg.name = projectName;
|
|
179
|
+
|
|
180
|
+
// Replace workspace:* with actual version
|
|
181
|
+
if (pkg.dependencies?.sunpeak === 'workspace:*') {
|
|
182
|
+
pkg.dependencies.sunpeak = sunpeakVersion;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
d.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
186
|
+
|
|
187
|
+
d.console.log(`
|
|
188
|
+
Done! To get started:
|
|
189
|
+
|
|
190
|
+
cd ${projectName}
|
|
191
|
+
pnpm install
|
|
192
|
+
sunpeak dev
|
|
193
|
+
|
|
194
|
+
That's it! Your project commands:
|
|
195
|
+
|
|
196
|
+
sunpeak dev # Start dev server + MCP endpoint
|
|
197
|
+
sunpeak build # Build for production
|
|
198
|
+
pnpm test # Run tests
|
|
199
|
+
|
|
200
|
+
See README.md for more details.
|
|
201
|
+
`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Allow running directly
|
|
205
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
206
|
+
const [projectName, resources] = process.argv.slice(2);
|
|
207
|
+
init(projectName, resources).catch((error) => {
|
|
208
|
+
console.error('Error:', error.message);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
});
|
|
211
|
+
}
|