sunpeak 0.7.10 → 0.8.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 +9 -11
- package/bin/commands/deploy.mjs +18 -9
- package/bin/commands/login.mjs +73 -55
- package/bin/commands/logout.mjs +26 -12
- package/bin/commands/pull.mjs +60 -39
- package/bin/commands/push.mjs +73 -49
- package/bin/commands/upgrade.mjs +203 -0
- package/bin/sunpeak.js +62 -31
- package/dist/chatgpt/chatgpt-simulator.d.ts +2 -1
- package/dist/index.cjs +15 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +15 -12
- package/dist/index.js.map +1 -1
- package/dist/mcp/entry.cjs +41 -9
- package/dist/mcp/entry.cjs.map +1 -1
- package/dist/mcp/entry.js +42 -10
- package/dist/mcp/entry.js.map +1 -1
- package/dist/mcp/index.cjs +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/server-B9YgCQdS.cjs +5067 -0
- package/dist/server-B9YgCQdS.cjs.map +1 -0
- package/dist/server-DVmTC-SF.js +5068 -0
- package/dist/server-DVmTC-SF.js.map +1 -0
- package/dist/style.css +1 -1
- package/dist/types/simulation.d.ts +1 -1
- package/package.json +17 -17
- package/template/.sunpeak/dev.tsx +78 -15
- package/template/.sunpeak/vite-env.d.ts +1 -0
- package/template/README.md +6 -6
- package/template/dist/albums.js +4 -4
- package/template/dist/albums.json +1 -1
- package/template/dist/carousel.js +8 -8
- package/template/dist/carousel.json +1 -1
- package/template/dist/counter.js +4 -4
- package/template/dist/counter.json +1 -1
- package/template/dist/map.js +4 -4
- package/template/dist/map.json +1 -1
- package/template/node_modules/.bin/tsx +2 -2
- package/template/node_modules/.bin/vite +2 -2
- package/template/node_modules/.bin/vitest +2 -2
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Avatar.js +7 -7
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Avatar.js.map +1 -1
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Button.js +6 -6
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Checkbox.js +6 -6
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Checkbox.js.map +1 -1
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Icon.js +3 -3
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Input.js +3 -3
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_SegmentedControl.js +9 -9
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_SegmentedControl.js.map +1 -1
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Select.js +33 -33
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Select.js.map +1 -1
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Textarea.js +7 -7
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Textarea.js.map +1 -1
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_theme.js +2 -2
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_theme.js.map +1 -1
- package/template/node_modules/.vite/deps/_metadata.json +60 -60
- package/template/node_modules/.vite/deps/{chunk-CQ3GYAYB.js → chunk-2DZGWGIP.js} +5 -5
- package/template/node_modules/.vite/deps/{chunk-CQ3GYAYB.js.map → chunk-2DZGWGIP.js.map} +1 -1
- package/template/node_modules/.vite/deps/{chunk-4TLBUCVB.js → chunk-BUOVMFCD.js} +6 -6
- package/template/node_modules/.vite/deps/{chunk-4TLBUCVB.js.map → chunk-BUOVMFCD.js.map} +2 -2
- package/template/node_modules/.vite/deps/{chunk-BAG6OO6S.js → chunk-DYQDWJMS.js} +5 -5
- package/template/node_modules/.vite/deps/{chunk-BAG6OO6S.js.map → chunk-DYQDWJMS.js.map} +1 -1
- package/template/node_modules/.vite/deps/{chunk-YOJ6QPGS.js → chunk-JAGHY6H6.js} +3 -3
- package/template/node_modules/.vite/deps/{chunk-YOJ6QPGS.js.map → chunk-JAGHY6H6.js.map} +1 -1
- package/template/node_modules/.vite/deps/{chunk-PTVT3RFX.js → chunk-JGVISENQ.js} +6 -6
- package/template/node_modules/.vite/deps/{chunk-PTVT3RFX.js.map → chunk-JGVISENQ.js.map} +1 -1
- package/template/node_modules/.vite/deps/{chunk-LR7NKCX5.js → chunk-SPYXUHEY.js} +44 -44
- package/template/node_modules/.vite/deps/{chunk-LR7NKCX5.js.map → chunk-SPYXUHEY.js.map} +1 -1
- package/template/node_modules/.vite/deps/{chunk-SGWD4VEU.js → chunk-TSEQUROC.js} +113 -107
- package/template/node_modules/.vite/deps/chunk-TSEQUROC.js.map +7 -0
- package/template/node_modules/.vite/deps/{chunk-XB525PXG.js → chunk-UM3ZGDFR.js} +747 -747
- package/template/node_modules/.vite/deps/{chunk-XB525PXG.js.map → chunk-UM3ZGDFR.js.map} +1 -1
- package/template/node_modules/.vite/deps/{chunk-KFGKZMLK.js → chunk-XZTIOEPG.js} +7 -7
- package/template/node_modules/.vite/deps/{chunk-KFGKZMLK.js.map → chunk-XZTIOEPG.js.map} +2 -2
- package/template/node_modules/.vite/deps/embla-carousel-react.js +3 -3
- package/template/node_modules/.vite/deps/embla-carousel-react.js.map +1 -1
- package/template/node_modules/.vite/deps/react-dom.js +2 -2
- package/template/node_modules/.vite/deps/react-dom_client.js +11 -11
- package/template/node_modules/.vite/deps/react-dom_client.js.map +2 -2
- package/template/node_modules/.vite/deps/react.js +1 -1
- package/template/node_modules/.vite/deps/react_jsx-dev-runtime.js +5 -5
- package/template/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +1 -1
- package/template/node_modules/.vite/deps/react_jsx-runtime.js +2 -2
- package/template/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/template/package.json +11 -11
- package/template/src/components/map/map-view.test.tsx +1 -1
- package/template/src/components/map/map-view.tsx +1 -1
- package/template/src/components/map/map.tsx +1 -1
- package/template/src/components/map/place-card.test.tsx +1 -1
- package/template/src/components/map/place-card.tsx +1 -1
- package/template/src/components/map/place-carousel.test.tsx +1 -1
- package/template/src/components/map/place-carousel.tsx +1 -1
- package/template/src/components/map/place-inspector.test.tsx +1 -1
- package/template/src/components/map/place-inspector.tsx +1 -1
- package/template/src/components/map/place-list.test.tsx +1 -1
- package/template/src/components/map/place-list.tsx +1 -1
- package/template/src/components/map/types.ts +18 -0
- package/template/src/resources/index.ts +39 -4
- package/template/src/simulations/albums-show-simulation.json +131 -0
- package/template/src/simulations/carousel-show-simulation.json +68 -0
- package/template/src/simulations/counter-show-simulation.json +20 -0
- package/template/src/simulations/index.ts +17 -12
- package/template/src/simulations/map-show-simulation.json +123 -0
- package/template/src/vite-env.d.ts +1 -0
- package/template/tsconfig.json +1 -1
- package/dist/server-BOYwNazb.cjs +0 -930
- package/dist/server-BOYwNazb.cjs.map +0 -1
- package/dist/server-C6vMGV6H.js +0 -931
- package/dist/server-C6vMGV6H.js.map +0 -1
- package/template/node_modules/.vite/deps/chunk-SGWD4VEU.js.map +0 -7
- package/template/src/simulations/albums-simulation.ts +0 -147
- package/template/src/simulations/carousel-simulation.ts +0 -84
- package/template/src/simulations/counter-simulation.ts +0 -34
- package/template/src/simulations/map-simulation.ts +0 -154
package/bin/commands/push.mjs
CHANGED
|
@@ -11,7 +11,7 @@ const CREDENTIALS_FILE = join(CREDENTIALS_DIR, 'credentials.json');
|
|
|
11
11
|
/**
|
|
12
12
|
* Load credentials from disk
|
|
13
13
|
*/
|
|
14
|
-
function
|
|
14
|
+
function loadCredentialsImpl() {
|
|
15
15
|
if (!existsSync(CREDENTIALS_FILE)) {
|
|
16
16
|
return null;
|
|
17
17
|
}
|
|
@@ -25,7 +25,7 @@ function loadCredentials() {
|
|
|
25
25
|
/**
|
|
26
26
|
* Get the current git repository name in owner/repo format
|
|
27
27
|
*/
|
|
28
|
-
function
|
|
28
|
+
function getGitRepoNameImpl() {
|
|
29
29
|
try {
|
|
30
30
|
// Try to get the remote URL first
|
|
31
31
|
const remoteUrl = execSync('git remote get-url origin 2>/dev/null', { encoding: 'utf-8' }).trim();
|
|
@@ -44,16 +44,33 @@ function getGitRepoName() {
|
|
|
44
44
|
return null;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Default dependencies (real implementations)
|
|
49
|
+
*/
|
|
50
|
+
export const defaultDeps = {
|
|
51
|
+
fetch: globalThis.fetch,
|
|
52
|
+
loadCredentials: loadCredentialsImpl,
|
|
53
|
+
getGitRepoName: getGitRepoNameImpl,
|
|
54
|
+
existsSync,
|
|
55
|
+
readFileSync,
|
|
56
|
+
readdirSync,
|
|
57
|
+
console,
|
|
58
|
+
process,
|
|
59
|
+
apiUrl: SUNPEAK_API_URL,
|
|
60
|
+
};
|
|
61
|
+
|
|
47
62
|
/**
|
|
48
63
|
* Find all resources in a directory
|
|
49
64
|
* Returns array of { name, jsPath, metaPath, meta }
|
|
50
65
|
*/
|
|
51
|
-
export function findResources(distDir) {
|
|
52
|
-
|
|
66
|
+
export function findResources(distDir, deps = defaultDeps) {
|
|
67
|
+
const d = { ...defaultDeps, ...deps };
|
|
68
|
+
|
|
69
|
+
if (!d.existsSync(distDir)) {
|
|
53
70
|
return [];
|
|
54
71
|
}
|
|
55
72
|
|
|
56
|
-
const files = readdirSync(distDir);
|
|
73
|
+
const files = d.readdirSync(distDir);
|
|
57
74
|
const jsFiles = files.filter((f) => f.endsWith('.js'));
|
|
58
75
|
const jsonFiles = new Set(files.filter((f) => f.endsWith('.json')));
|
|
59
76
|
|
|
@@ -70,9 +87,9 @@ export function findResources(distDir) {
|
|
|
70
87
|
|
|
71
88
|
let meta = null;
|
|
72
89
|
try {
|
|
73
|
-
meta = JSON.parse(readFileSync(metaPath, 'utf-8'));
|
|
90
|
+
meta = JSON.parse(d.readFileSync(metaPath, 'utf-8'));
|
|
74
91
|
} catch {
|
|
75
|
-
console.warn(`Warning: Could not parse ${name}.json`);
|
|
92
|
+
d.console.warn(`Warning: Could not parse ${name}.json`);
|
|
76
93
|
}
|
|
77
94
|
|
|
78
95
|
return { name, jsPath, metaPath, meta };
|
|
@@ -83,10 +100,12 @@ export function findResources(distDir) {
|
|
|
83
100
|
* Build a resource from a specific JS file path
|
|
84
101
|
* Returns { name, jsPath, metaPath, meta }
|
|
85
102
|
*/
|
|
86
|
-
function buildResourceFromFile(jsPath) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
103
|
+
function buildResourceFromFile(jsPath, deps = defaultDeps) {
|
|
104
|
+
const d = { ...defaultDeps, ...deps };
|
|
105
|
+
|
|
106
|
+
if (!d.existsSync(jsPath)) {
|
|
107
|
+
d.console.error(`Error: File not found: ${jsPath}`);
|
|
108
|
+
d.process.exit(1);
|
|
90
109
|
}
|
|
91
110
|
|
|
92
111
|
// Extract name from filename (remove .js extension)
|
|
@@ -98,11 +117,11 @@ function buildResourceFromFile(jsPath) {
|
|
|
98
117
|
const metaPath = join(dir, `${name}.json`);
|
|
99
118
|
|
|
100
119
|
let meta = null;
|
|
101
|
-
if (existsSync(metaPath)) {
|
|
120
|
+
if (d.existsSync(metaPath)) {
|
|
102
121
|
try {
|
|
103
|
-
meta = JSON.parse(readFileSync(metaPath, 'utf-8'));
|
|
122
|
+
meta = JSON.parse(d.readFileSync(metaPath, 'utf-8'));
|
|
104
123
|
} catch {
|
|
105
|
-
console.warn(`Warning: Could not parse ${name}.json`);
|
|
124
|
+
d.console.warn(`Warning: Could not parse ${name}.json`);
|
|
106
125
|
}
|
|
107
126
|
}
|
|
108
127
|
|
|
@@ -112,12 +131,14 @@ function buildResourceFromFile(jsPath) {
|
|
|
112
131
|
/**
|
|
113
132
|
* Push a single resource to the API
|
|
114
133
|
*/
|
|
115
|
-
async function pushResource(resource, repository, tags, accessToken) {
|
|
134
|
+
async function pushResource(resource, repository, tags, accessToken, deps = defaultDeps) {
|
|
135
|
+
const d = { ...defaultDeps, ...deps };
|
|
136
|
+
|
|
116
137
|
if (!resource.meta?.uri) {
|
|
117
138
|
throw new Error('Resource is missing URI. Run "sunpeak build" to generate URIs.');
|
|
118
139
|
}
|
|
119
140
|
|
|
120
|
-
const jsContent = readFileSync(resource.jsPath);
|
|
141
|
+
const jsContent = d.readFileSync(resource.jsPath);
|
|
121
142
|
const jsBlob = new Blob([jsContent], { type: 'application/javascript' });
|
|
122
143
|
|
|
123
144
|
// Build form data
|
|
@@ -168,7 +189,7 @@ async function pushResource(resource, repository, tags, accessToken) {
|
|
|
168
189
|
});
|
|
169
190
|
}
|
|
170
191
|
|
|
171
|
-
const response = await fetch(`${
|
|
192
|
+
const response = await d.fetch(`${d.apiUrl}/api/v1/resources`, {
|
|
172
193
|
method: 'POST',
|
|
173
194
|
headers: {
|
|
174
195
|
Authorization: `Bearer ${accessToken}`,
|
|
@@ -195,11 +216,14 @@ async function pushResource(resource, repository, tags, accessToken) {
|
|
|
195
216
|
* @param {string} options.repository - Repository name (optional, defaults to git repo name)
|
|
196
217
|
* @param {string} options.file - Path to a specific resource JS file (optional)
|
|
197
218
|
* @param {string[]} options.tags - Tags to assign to the pushed resources (optional)
|
|
219
|
+
* @param {Object} deps - Dependencies (for testing). Uses defaultDeps if not provided.
|
|
198
220
|
*/
|
|
199
|
-
export async function push(projectRoot = process.cwd(), options = {}) {
|
|
221
|
+
export async function push(projectRoot = process.cwd(), options = {}, deps = defaultDeps) {
|
|
222
|
+
const d = { ...defaultDeps, ...deps };
|
|
223
|
+
|
|
200
224
|
// Handle help flag
|
|
201
225
|
if (options.help) {
|
|
202
|
-
console.log(`
|
|
226
|
+
d.console.log(`
|
|
203
227
|
sunpeak push - Push resources to the Sunpeak repository
|
|
204
228
|
|
|
205
229
|
Usage:
|
|
@@ -219,76 +243,76 @@ Examples:
|
|
|
219
243
|
sunpeak push dist/carousel.js Push a single resource
|
|
220
244
|
sunpeak push -r myorg/my-app Push to "myorg/my-app" repository
|
|
221
245
|
sunpeak push -t v1.0.0 Push with a version tag
|
|
222
|
-
sunpeak push -t v1.0.0 -t
|
|
246
|
+
sunpeak push -t v1.0.0 -t prod Push with multiple tags
|
|
223
247
|
`);
|
|
224
248
|
return;
|
|
225
249
|
}
|
|
226
250
|
|
|
227
251
|
// Check credentials
|
|
228
|
-
const credentials = loadCredentials();
|
|
252
|
+
const credentials = d.loadCredentials();
|
|
229
253
|
if (!credentials?.access_token) {
|
|
230
|
-
console.error('Error: Not logged in. Run "sunpeak login" first.');
|
|
231
|
-
process.exit(1);
|
|
254
|
+
d.console.error('Error: Not logged in. Run "sunpeak login" first.');
|
|
255
|
+
d.process.exit(1);
|
|
232
256
|
}
|
|
233
257
|
|
|
234
258
|
// Determine repository name (owner/repo format)
|
|
235
|
-
const repository = options.repository || getGitRepoName();
|
|
259
|
+
const repository = options.repository || d.getGitRepoName();
|
|
236
260
|
if (!repository) {
|
|
237
|
-
console.error('Error: Could not determine repository name.');
|
|
238
|
-
console.error('Please provide a repository name: sunpeak push --repository <owner/repo>');
|
|
239
|
-
console.error('Or run this command from within a git repository with a remote origin.');
|
|
240
|
-
process.exit(1);
|
|
261
|
+
d.console.error('Error: Could not determine repository name.');
|
|
262
|
+
d.console.error('Please provide a repository name: sunpeak push --repository <owner/repo>');
|
|
263
|
+
d.console.error('Or run this command from within a git repository with a remote origin.');
|
|
264
|
+
d.process.exit(1);
|
|
241
265
|
}
|
|
242
266
|
|
|
243
267
|
// Find resources - either a specific file or all from dist directory
|
|
244
268
|
let resources;
|
|
245
269
|
if (options.file) {
|
|
246
270
|
// Push a single specific resource
|
|
247
|
-
resources = [buildResourceFromFile(options.file)];
|
|
271
|
+
resources = [buildResourceFromFile(options.file, d)];
|
|
248
272
|
} else {
|
|
249
273
|
// Default: find all resources in dist directory
|
|
250
274
|
const distDir = join(projectRoot, 'dist');
|
|
251
|
-
if (!existsSync(distDir)) {
|
|
252
|
-
console.error(`Error: dist/ directory not found`);
|
|
253
|
-
console.error('Run "sunpeak build" first to build your resources.');
|
|
254
|
-
process.exit(1);
|
|
275
|
+
if (!d.existsSync(distDir)) {
|
|
276
|
+
d.console.error(`Error: dist/ directory not found`);
|
|
277
|
+
d.console.error('Run "sunpeak build" first to build your resources.');
|
|
278
|
+
d.process.exit(1);
|
|
255
279
|
}
|
|
256
280
|
|
|
257
|
-
resources = findResources(distDir);
|
|
281
|
+
resources = findResources(distDir, d);
|
|
258
282
|
if (resources.length === 0) {
|
|
259
|
-
console.error(`Error: No resources found in dist/`);
|
|
260
|
-
console.error('Run "sunpeak build" first to build your resources.');
|
|
261
|
-
process.exit(1);
|
|
283
|
+
d.console.error(`Error: No resources found in dist/`);
|
|
284
|
+
d.console.error('Run "sunpeak build" first to build your resources.');
|
|
285
|
+
d.process.exit(1);
|
|
262
286
|
}
|
|
263
287
|
}
|
|
264
288
|
|
|
265
|
-
console.log(`Pushing ${resources.length} resource(s) to repository "${repository}"...`);
|
|
289
|
+
d.console.log(`Pushing ${resources.length} resource(s) to repository "${repository}"...`);
|
|
266
290
|
if (options.tags && options.tags.length > 0) {
|
|
267
|
-
console.log(`Tags: ${options.tags.join(', ')}`);
|
|
291
|
+
d.console.log(`Tags: ${options.tags.join(', ')}`);
|
|
268
292
|
}
|
|
269
|
-
console.log();
|
|
293
|
+
d.console.log();
|
|
270
294
|
|
|
271
295
|
// Push each resource
|
|
272
296
|
let successCount = 0;
|
|
273
297
|
for (const resource of resources) {
|
|
274
298
|
try {
|
|
275
|
-
const result = await pushResource(resource, repository, options.tags, credentials.access_token);
|
|
276
|
-
console.log(`✓ Pushed ${resource.name} (id: ${result.id})`);
|
|
299
|
+
const result = await pushResource(resource, repository, options.tags, credentials.access_token, d);
|
|
300
|
+
d.console.log(`✓ Pushed ${resource.name} (id: ${result.id})`);
|
|
277
301
|
if (result.tags?.length > 0) {
|
|
278
|
-
console.log(` Tags: ${result.tags.join(', ')}`);
|
|
302
|
+
d.console.log(` Tags: ${result.tags.join(', ')}`);
|
|
279
303
|
}
|
|
280
304
|
successCount++;
|
|
281
305
|
} catch (error) {
|
|
282
|
-
console.error(`✗ Failed to push ${resource.name}: ${error.message}`);
|
|
306
|
+
d.console.error(`✗ Failed to push ${resource.name}: ${error.message}`);
|
|
283
307
|
}
|
|
284
308
|
}
|
|
285
309
|
|
|
286
|
-
console.log();
|
|
310
|
+
d.console.log();
|
|
287
311
|
if (successCount === resources.length) {
|
|
288
|
-
console.log(`✓ Successfully pushed ${successCount} resource(s).`);
|
|
312
|
+
d.console.log(`✓ Successfully pushed ${successCount} resource(s).`);
|
|
289
313
|
} else {
|
|
290
|
-
console.log(`Pushed ${successCount}/${resources.length} resource(s).`);
|
|
291
|
-
process.exit(1);
|
|
314
|
+
d.console.log(`Pushed ${successCount}/${resources.length} resource(s).`);
|
|
315
|
+
d.process.exit(1);
|
|
292
316
|
}
|
|
293
317
|
}
|
|
294
318
|
|
|
@@ -327,7 +351,7 @@ Examples:
|
|
|
327
351
|
sunpeak push dist/carousel.js Push a single resource
|
|
328
352
|
sunpeak push -r myorg/my-app Push to "myorg/my-app" repository
|
|
329
353
|
sunpeak push -t v1.0.0 Push with a version tag
|
|
330
|
-
sunpeak push -t v1.0.0 -t
|
|
354
|
+
sunpeak push -t v1.0.0 -t prod Push with multiple tags
|
|
331
355
|
`);
|
|
332
356
|
process.exit(0);
|
|
333
357
|
} else if (!arg.startsWith('-')) {
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync, spawn } from 'child_process';
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
import { join, dirname } from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get the current installed version from package.json
|
|
11
|
+
*/
|
|
12
|
+
function getCurrentVersionImpl() {
|
|
13
|
+
const pkgPath = join(__dirname, '..', '..', 'package.json');
|
|
14
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
15
|
+
return pkg.version;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Fetch the latest version from npm registry
|
|
20
|
+
*/
|
|
21
|
+
async function fetchLatestVersionImpl(packageName = 'sunpeak') {
|
|
22
|
+
const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
throw new Error(`Failed to fetch latest version: ${response.status}`);
|
|
25
|
+
}
|
|
26
|
+
const data = await response.json();
|
|
27
|
+
return data.version;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Compare two semver versions
|
|
32
|
+
* Returns: -1 if a < b, 0 if a === b, 1 if a > b
|
|
33
|
+
*/
|
|
34
|
+
function compareVersions(a, b) {
|
|
35
|
+
const partsA = a.split('.').map(Number);
|
|
36
|
+
const partsB = b.split('.').map(Number);
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < 3; i++) {
|
|
39
|
+
const numA = partsA[i] || 0;
|
|
40
|
+
const numB = partsB[i] || 0;
|
|
41
|
+
if (numA < numB) return -1;
|
|
42
|
+
if (numA > numB) return 1;
|
|
43
|
+
}
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Detect which package manager is being used
|
|
49
|
+
*/
|
|
50
|
+
function detectPackageManagerImpl() {
|
|
51
|
+
// Check npm_config_user_agent first (set by npm/pnpm/yarn when running scripts)
|
|
52
|
+
const userAgent = process.env.npm_config_user_agent || '';
|
|
53
|
+
if (userAgent.includes('pnpm')) return 'pnpm';
|
|
54
|
+
if (userAgent.includes('yarn')) return 'yarn';
|
|
55
|
+
if (userAgent.includes('npm')) return 'npm';
|
|
56
|
+
|
|
57
|
+
// Fallback: check if commands exist
|
|
58
|
+
try {
|
|
59
|
+
execSync('pnpm --version', { stdio: 'ignore' });
|
|
60
|
+
return 'pnpm';
|
|
61
|
+
} catch {
|
|
62
|
+
// pnpm not available
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
execSync('yarn --version', { stdio: 'ignore' });
|
|
67
|
+
return 'yarn';
|
|
68
|
+
} catch {
|
|
69
|
+
// yarn not available
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return 'npm';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Run the upgrade command
|
|
77
|
+
*/
|
|
78
|
+
function runUpgradeImpl(packageManager, packageName = 'sunpeak') {
|
|
79
|
+
const commands = {
|
|
80
|
+
npm: ['npm', ['install', '-g', packageName]],
|
|
81
|
+
pnpm: ['pnpm', ['add', '-g', packageName]],
|
|
82
|
+
yarn: ['yarn', ['global', 'add', packageName]],
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const [cmd, args] = commands[packageManager];
|
|
86
|
+
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
const child = spawn(cmd, args, {
|
|
89
|
+
stdio: 'inherit',
|
|
90
|
+
shell: process.platform === 'win32',
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
child.on('close', (code) => {
|
|
94
|
+
if (code === 0) {
|
|
95
|
+
resolve();
|
|
96
|
+
} else {
|
|
97
|
+
reject(new Error(`Upgrade failed with exit code ${code}`));
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
child.on('error', (err) => {
|
|
102
|
+
reject(err);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Default dependencies (real implementations)
|
|
109
|
+
*/
|
|
110
|
+
export const defaultDeps = {
|
|
111
|
+
getCurrentVersion: getCurrentVersionImpl,
|
|
112
|
+
fetchLatestVersion: fetchLatestVersionImpl,
|
|
113
|
+
detectPackageManager: detectPackageManagerImpl,
|
|
114
|
+
runUpgrade: runUpgradeImpl,
|
|
115
|
+
console,
|
|
116
|
+
process,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Main upgrade command
|
|
121
|
+
* @param {Object} options - Command options
|
|
122
|
+
* @param {Object} deps - Dependencies (for testing). Uses defaultDeps if not provided.
|
|
123
|
+
*/
|
|
124
|
+
export async function upgrade(options = {}, deps = defaultDeps) {
|
|
125
|
+
const d = { ...defaultDeps, ...deps };
|
|
126
|
+
|
|
127
|
+
// Show help if requested
|
|
128
|
+
if (options.help) {
|
|
129
|
+
d.console.log(`
|
|
130
|
+
sunpeak upgrade - Upgrade sunpeak to the latest version
|
|
131
|
+
|
|
132
|
+
Usage:
|
|
133
|
+
sunpeak upgrade [options]
|
|
134
|
+
|
|
135
|
+
Options:
|
|
136
|
+
--check, -c Check for updates without installing
|
|
137
|
+
--help, -h Show this help message
|
|
138
|
+
|
|
139
|
+
Examples:
|
|
140
|
+
sunpeak upgrade # Upgrade to latest version
|
|
141
|
+
sunpeak upgrade --check # Check if updates are available
|
|
142
|
+
`);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const currentVersion = d.getCurrentVersion();
|
|
147
|
+
d.console.log(`Current version: ${currentVersion}`);
|
|
148
|
+
d.console.log('Checking for updates...');
|
|
149
|
+
|
|
150
|
+
let latestVersion;
|
|
151
|
+
try {
|
|
152
|
+
latestVersion = await d.fetchLatestVersion();
|
|
153
|
+
} catch (error) {
|
|
154
|
+
d.console.error(`Error checking for updates: ${error.message}`);
|
|
155
|
+
d.process.exit(1);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const comparison = compareVersions(currentVersion, latestVersion);
|
|
160
|
+
|
|
161
|
+
if (comparison >= 0) {
|
|
162
|
+
d.console.log(`\n✓ You are already on the latest version (${currentVersion})`);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
d.console.log(`\nNew version available: ${latestVersion}`);
|
|
167
|
+
|
|
168
|
+
// If --check flag, just report and exit
|
|
169
|
+
if (options.check) {
|
|
170
|
+
d.console.log(`\nRun "sunpeak upgrade" to upgrade.`);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const packageManager = d.detectPackageManager();
|
|
175
|
+
d.console.log(`\nUpgrading using ${packageManager}...`);
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
await d.runUpgrade(packageManager);
|
|
179
|
+
d.console.log(`\n✓ Successfully upgraded to sunpeak@${latestVersion}`);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
d.console.error(`\nError upgrading: ${error.message}`);
|
|
182
|
+
d.console.log(`\nYou can manually upgrade by running:`);
|
|
183
|
+
d.console.log(` ${packageManager} ${packageManager === 'yarn' ? 'global add' : packageManager === 'pnpm' ? 'add -g' : 'install -g'} sunpeak`);
|
|
184
|
+
d.process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Export for testing
|
|
189
|
+
export { compareVersions };
|
|
190
|
+
|
|
191
|
+
// Allow running directly
|
|
192
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
193
|
+
const args = process.argv.slice(2);
|
|
194
|
+
const options = {
|
|
195
|
+
check: args.includes('--check') || args.includes('-c'),
|
|
196
|
+
help: args.includes('--help') || args.includes('-h'),
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
upgrade(options).catch((error) => {
|
|
200
|
+
console.error('Error:', error.message);
|
|
201
|
+
process.exit(1);
|
|
202
|
+
});
|
|
203
|
+
}
|
package/bin/sunpeak.js
CHANGED
|
@@ -57,10 +57,10 @@ function parseResourcesInput(input) {
|
|
|
57
57
|
function updateIndexFiles(targetDir, selectedResources) {
|
|
58
58
|
// Map resource names to their component/export names
|
|
59
59
|
const resourceMap = {
|
|
60
|
-
albums: { component: 'album', resourceClass: 'AlbumsResource'
|
|
61
|
-
carousel: { component: 'carousel', resourceClass: 'CarouselResource'
|
|
62
|
-
counter: { component: null, resourceClass: 'CounterResource'
|
|
63
|
-
map: { component: 'map', resourceClass: 'MapResource'
|
|
60
|
+
albums: { component: 'album', resourceClass: 'AlbumsResource' },
|
|
61
|
+
carousel: { component: 'carousel', resourceClass: 'CarouselResource' },
|
|
62
|
+
counter: { component: null, resourceClass: 'CounterResource' },
|
|
63
|
+
map: { component: 'map', resourceClass: 'MapResource' },
|
|
64
64
|
};
|
|
65
65
|
|
|
66
66
|
// Update components/index.ts
|
|
@@ -73,31 +73,47 @@ function updateIndexFiles(targetDir, selectedResources) {
|
|
|
73
73
|
.join('\n');
|
|
74
74
|
writeFileSync(componentsIndexPath, componentExports + '\n');
|
|
75
75
|
|
|
76
|
-
// Update resources/index.ts
|
|
76
|
+
// Update resources/index.ts - must have default export for dev.tsx
|
|
77
77
|
const resourcesIndexPath = join(targetDir, 'src', 'resources', 'index.ts');
|
|
78
|
-
const
|
|
79
|
-
.map((r) => `
|
|
78
|
+
const resourceImports = selectedResources
|
|
79
|
+
.map((r) => `import { ${resourceMap[r].resourceClass} } from './${r}-resource';`)
|
|
80
80
|
.join('\n');
|
|
81
|
-
|
|
81
|
+
const resourceExportsObject = selectedResources
|
|
82
|
+
.map((r) => ` ${resourceMap[r].resourceClass},`)
|
|
83
|
+
.join('\n');
|
|
84
|
+
const resourcesContent = `${resourceImports}
|
|
85
|
+
|
|
86
|
+
export default {
|
|
87
|
+
${resourceExportsObject}
|
|
88
|
+
};
|
|
89
|
+
`;
|
|
90
|
+
writeFileSync(resourcesIndexPath, resourcesContent);
|
|
82
91
|
|
|
83
|
-
// Update simulations/index.ts
|
|
92
|
+
// Update simulations/index.ts - uses auto-discovery for JSON simulation files
|
|
84
93
|
const simulationsIndexPath = join(targetDir, 'src', 'simulations', 'index.ts');
|
|
85
|
-
const simulationImports = selectedResources
|
|
86
|
-
.map((r) => `import { ${r}Simulation } from './${r}-simulation.js';`)
|
|
87
|
-
.join('\n');
|
|
88
|
-
const simulationExports = selectedResources.map((r) => ` ${r}: ${r}Simulation,`).join('\n');
|
|
89
94
|
const simulationsContent = `/**
|
|
90
95
|
* Server-safe simulation configurations
|
|
91
96
|
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
97
|
+
* Auto-discovers all *-simulation.json files in this directory.
|
|
98
|
+
* File naming: {resource}-{tool}-simulation.json (e.g., albums-show-simulation.json)
|
|
99
|
+
*
|
|
100
|
+
* This file can be safely imported in Node.js contexts (like MCP servers)
|
|
101
|
+
* without causing issues with CSS imports or React components.
|
|
94
102
|
*/
|
|
95
103
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
104
|
+
// Auto-discover all simulation JSON files
|
|
105
|
+
const simulationModules = import.meta.glob('./*-simulation.json', { eager: true });
|
|
106
|
+
|
|
107
|
+
// Build SIMULATIONS object from discovered files
|
|
108
|
+
// Key is the full name without -simulation.json suffix (e.g., 'albums-show')
|
|
109
|
+
export const SIMULATIONS = Object.fromEntries(
|
|
110
|
+
Object.entries(simulationModules).map(([path, module]) => {
|
|
111
|
+
// Extract simulation key from path: './albums-show-simulation.json' -> 'albums-show'
|
|
112
|
+
const match = path.match(/\\.\\/(.+)-simulation\\.json$/);
|
|
113
|
+
const key = match?.[1] ?? path;
|
|
114
|
+
return [key, (module as { default: unknown }).default];
|
|
115
|
+
})
|
|
116
|
+
) as Record<string, unknown>;
|
|
101
117
|
`;
|
|
102
118
|
writeFileSync(simulationsIndexPath, simulationsContent);
|
|
103
119
|
}
|
|
@@ -163,12 +179,17 @@ async function init(projectName, resourcesArg) {
|
|
|
163
179
|
const excludedResources = VALID_RESOURCES.filter((r) => !selectedResources.includes(r));
|
|
164
180
|
|
|
165
181
|
for (const resource of excludedResources) {
|
|
166
|
-
// Skip resource files
|
|
167
|
-
if (
|
|
182
|
+
// Skip resource files (tsx, test, and json metadata)
|
|
183
|
+
if (
|
|
184
|
+
name === `${resource}-resource.tsx` ||
|
|
185
|
+
name === `${resource}-resource.test.tsx` ||
|
|
186
|
+
name === `${resource}-resource.json`
|
|
187
|
+
) {
|
|
168
188
|
return false;
|
|
169
189
|
}
|
|
170
|
-
// Skip simulation files
|
|
171
|
-
|
|
190
|
+
// Skip simulation JSON files that start with the resource name
|
|
191
|
+
// e.g., albums-show-simulation.json, albums-edit-simulation.json
|
|
192
|
+
if (name.startsWith(`${resource}-`) && name.endsWith('-simulation.json')) {
|
|
172
193
|
return false;
|
|
173
194
|
}
|
|
174
195
|
// Skip component directories (map resource name to component dir name)
|
|
@@ -217,13 +238,13 @@ Done! To get started:
|
|
|
217
238
|
|
|
218
239
|
cd ${projectName}
|
|
219
240
|
pnpm install
|
|
220
|
-
|
|
241
|
+
sunpeak dev
|
|
221
242
|
|
|
222
243
|
That's it! Your project commands:
|
|
223
244
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
245
|
+
sunpeak dev # Start development server
|
|
246
|
+
sunpeak build # Build for production
|
|
247
|
+
sunpeak mcp # Start MCP server
|
|
227
248
|
pnpm test # Run tests
|
|
228
249
|
|
|
229
250
|
See README.md for more details.
|
|
@@ -291,6 +312,7 @@ function parseResourceArgs(args) {
|
|
|
291
312
|
'push',
|
|
292
313
|
'pull',
|
|
293
314
|
'deploy',
|
|
315
|
+
'upgrade',
|
|
294
316
|
'help',
|
|
295
317
|
undefined,
|
|
296
318
|
];
|
|
@@ -360,6 +382,17 @@ function parseResourceArgs(args) {
|
|
|
360
382
|
}
|
|
361
383
|
break;
|
|
362
384
|
|
|
385
|
+
case 'upgrade':
|
|
386
|
+
{
|
|
387
|
+
const { upgrade } = await import(join(COMMANDS_DIR, 'upgrade.mjs'));
|
|
388
|
+
const options = {
|
|
389
|
+
check: args.includes('--check') || args.includes('-c'),
|
|
390
|
+
help: args.includes('--help') || args.includes('-h'),
|
|
391
|
+
};
|
|
392
|
+
await upgrade(options);
|
|
393
|
+
}
|
|
394
|
+
break;
|
|
395
|
+
|
|
363
396
|
case 'help':
|
|
364
397
|
case undefined:
|
|
365
398
|
console.log(`
|
|
@@ -373,9 +406,6 @@ Usage:
|
|
|
373
406
|
Example: npx sunpeak new my-app "albums,carousel"
|
|
374
407
|
|
|
375
408
|
Inside your project, use npm scripts:
|
|
376
|
-
pnpm dev Start development server
|
|
377
|
-
pnpm build Build for production
|
|
378
|
-
pnpm mcp Start MCP server
|
|
379
409
|
pnpm test Run tests
|
|
380
410
|
|
|
381
411
|
Direct CLI commands (when sunpeak is installed):
|
|
@@ -388,6 +418,7 @@ Direct CLI commands (when sunpeak is installed):
|
|
|
388
418
|
sunpeak push Push resources to repository
|
|
389
419
|
sunpeak pull Pull resources from repository
|
|
390
420
|
sunpeak deploy Push resources with "prod" tag
|
|
421
|
+
sunpeak upgrade Upgrade sunpeak to latest version
|
|
391
422
|
sunpeak --version Show version number
|
|
392
423
|
|
|
393
424
|
For more information, visit: https://sunpeak.ai/
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Simulation } from '../types/simulation';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
+
type SimulationWithResource = Simulation & Required<Pick<Simulation, 'resource'>>;
|
|
3
4
|
interface ChatGPTSimulatorProps {
|
|
4
5
|
children?: React.ReactNode;
|
|
5
|
-
simulations?:
|
|
6
|
+
simulations?: SimulationWithResource[];
|
|
6
7
|
appName?: string;
|
|
7
8
|
appIcon?: string;
|
|
8
9
|
}
|