@juuno-sdk/cli 1.0.5 → 1.0.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 +204 -20
- package/bin/cli.js +79 -184
- package/dist/assets/main-B8xiNI5_.js +1 -0
- package/dist/assets/main-COSZCfRq.css +1 -0
- package/dist/cli/src/auth/index.d.ts +51 -0
- package/dist/cli/src/auth/index.d.ts.map +1 -0
- package/dist/cli/src/auth/index.js +156 -0
- package/dist/cli/src/deploy/index.d.ts +12 -0
- package/dist/cli/src/deploy/index.d.ts.map +1 -0
- package/dist/cli/src/deploy/index.js +129 -0
- package/dist/cli/src/info/index.d.ts +12 -0
- package/dist/cli/src/info/index.d.ts.map +1 -0
- package/dist/cli/src/info/index.js +111 -0
- package/dist/cli/src/list/index.d.ts +22 -0
- package/dist/cli/src/list/index.d.ts.map +1 -0
- package/dist/cli/src/list/index.js +102 -0
- package/dist/cli/src/login/index.d.ts +12 -0
- package/dist/cli/src/login/index.d.ts.map +1 -0
- package/dist/cli/src/login/index.js +61 -0
- package/dist/cli/src/logout/index.d.ts +5 -0
- package/dist/cli/src/logout/index.d.ts.map +1 -0
- package/dist/cli/src/logout/index.js +26 -0
- package/dist/cli/src/simulator/index.d.ts +13 -0
- package/dist/cli/src/simulator/index.d.ts.map +1 -0
- package/dist/cli/src/simulator/index.js +136 -0
- package/dist/cli/src/whoami/index.d.ts +5 -0
- package/dist/cli/src/whoami/index.d.ts.map +1 -0
- package/dist/cli/src/whoami/index.js +24 -0
- package/dist/cli/tsconfig.build.tsbuildinfo +1 -0
- package/dist/index.html +2 -2
- package/package.json +8 -7
- package/dist/assets/main-BIR58A5i.css +0 -1
- package/dist/assets/main-BYDwbwR9.js +0 -1
- package/src/App.vue +0 -350
- package/src/deploy/index.ts +0 -202
- package/src/main.ts +0 -5
package/src/App.vue
DELETED
|
@@ -1,350 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* Juuno CLI Development Tool.
|
|
4
|
-
*
|
|
5
|
-
* Loads external apps and displays them in a split-screen view.
|
|
6
|
-
*/
|
|
7
|
-
export default {
|
|
8
|
-
name: 'JuunoCLI',
|
|
9
|
-
};
|
|
10
|
-
</script>
|
|
11
|
-
|
|
12
|
-
<script setup lang="ts">
|
|
13
|
-
import { ref, reactive, onMounted, shallowRef } from 'vue';
|
|
14
|
-
import type { Component } from 'vue';
|
|
15
|
-
import type { Scene } from '@juuno-sdk/app-sdk';
|
|
16
|
-
import { provideAppConfigContext } from '@juuno-sdk/app-sdk';
|
|
17
|
-
|
|
18
|
-
// Get external app URL from Vite define or fallback to default.
|
|
19
|
-
const externalAppUrl = ref(
|
|
20
|
-
(window as any).__JUUNO_EXTERNAL_APP_URL__ ||
|
|
21
|
-
'http://localhost:5002/index.js',
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
// Dynamically loaded components from external app (use shallowRef to avoid reactive overhead)
|
|
25
|
-
const PlayerComponent = shallowRef<Component | null>(null);
|
|
26
|
-
const ConfigComponent = shallowRef<Component | null>(null);
|
|
27
|
-
const isLoading = ref(true);
|
|
28
|
-
const error = ref<string | null>(null);
|
|
29
|
-
|
|
30
|
-
// Create scene (matching production pattern)
|
|
31
|
-
const scene = reactive({
|
|
32
|
-
idx: 'simulator-scene-1',
|
|
33
|
-
name: 'Simulator Test Scene',
|
|
34
|
-
app: null,
|
|
35
|
-
meta: {
|
|
36
|
-
title: 'Test App',
|
|
37
|
-
backgroundColor: '#2c3e50',
|
|
38
|
-
textColor: '#ecf0f1',
|
|
39
|
-
},
|
|
40
|
-
mediaAssets: [],
|
|
41
|
-
}) as Scene;
|
|
42
|
-
|
|
43
|
-
// Create mock service for saving scene data
|
|
44
|
-
const mockService = {
|
|
45
|
-
async save(updatedScene: Scene) {
|
|
46
|
-
console.log('Simulator: Scene updated', updatedScene);
|
|
47
|
-
// Sync changes back to the original scene object
|
|
48
|
-
Object.assign(scene, updatedScene);
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
// Provide context using production pattern (matches SceneAppConfig.vue)
|
|
53
|
-
// NOTE: Passing original scene directly for live-sync - config changes immediately
|
|
54
|
-
// reflect in player. Production clones the scene for draft/commit workflow.
|
|
55
|
-
const appConfigContext = provideAppConfigContext(mockService, {
|
|
56
|
-
scene: scene,
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// Load external app components
|
|
60
|
-
onMounted(async () => {
|
|
61
|
-
try {
|
|
62
|
-
isLoading.value = true;
|
|
63
|
-
error.value = null;
|
|
64
|
-
|
|
65
|
-
console.log(`Loading external app from: ${externalAppUrl.value}`);
|
|
66
|
-
|
|
67
|
-
// Load player component from import map
|
|
68
|
-
const playerModule = await import('external-app/player');
|
|
69
|
-
PlayerComponent.value = playerModule.default;
|
|
70
|
-
|
|
71
|
-
if (!PlayerComponent.value) {
|
|
72
|
-
throw new Error(
|
|
73
|
-
'External app must export a default component from /player',
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Load config component from import map (optional)
|
|
78
|
-
try {
|
|
79
|
-
const configModule = await import('external-app/config');
|
|
80
|
-
ConfigComponent.value = configModule.default || null;
|
|
81
|
-
} catch (configErr) {
|
|
82
|
-
console.warn('Config component not found or failed to load:', configErr);
|
|
83
|
-
ConfigComponent.value = null;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Load the scene into the app config context
|
|
87
|
-
appConfigContext.load(scene);
|
|
88
|
-
|
|
89
|
-
console.log('✅ External app loaded successfully');
|
|
90
|
-
isLoading.value = false;
|
|
91
|
-
} catch (err) {
|
|
92
|
-
console.error('Failed to load external app:', err);
|
|
93
|
-
error.value =
|
|
94
|
-
err instanceof Error ? err.message : 'Failed to load external app';
|
|
95
|
-
isLoading.value = false;
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
</script>
|
|
99
|
-
|
|
100
|
-
<template>
|
|
101
|
-
<div class="juuno-cli">
|
|
102
|
-
<header class="header">
|
|
103
|
-
<div class="header-content">
|
|
104
|
-
<h1>Juuno CLI</h1>
|
|
105
|
-
<div class="header-info">
|
|
106
|
-
<span class="app-url">{{ externalAppUrl }}</span>
|
|
107
|
-
</div>
|
|
108
|
-
</div>
|
|
109
|
-
</header>
|
|
110
|
-
|
|
111
|
-
<div v-if="isLoading" class="loading-state">
|
|
112
|
-
<div class="spinner"></div>
|
|
113
|
-
<p>Loading external app...</p>
|
|
114
|
-
<p class="url-info">{{ externalAppUrl }}</p>
|
|
115
|
-
</div>
|
|
116
|
-
|
|
117
|
-
<div v-else-if="error" class="error-state">
|
|
118
|
-
<div class="error-icon">⚠️</div>
|
|
119
|
-
<h2>Failed to Load App</h2>
|
|
120
|
-
<p class="error-message">{{ error }}</p>
|
|
121
|
-
<div class="error-details">
|
|
122
|
-
<p><strong>App URL:</strong> {{ externalAppUrl }}</p>
|
|
123
|
-
<p><strong>Common Issues:</strong></p>
|
|
124
|
-
<ul>
|
|
125
|
-
<li>App dev server not running</li>
|
|
126
|
-
<li>CORS not enabled on app dev server</li>
|
|
127
|
-
<li>
|
|
128
|
-
App doesn't export required components from /player and /config
|
|
129
|
-
</li>
|
|
130
|
-
<li>Incorrect URL provided</li>
|
|
131
|
-
<li>Missing exports field in package.json</li>
|
|
132
|
-
</ul>
|
|
133
|
-
</div>
|
|
134
|
-
</div>
|
|
135
|
-
|
|
136
|
-
<div v-else class="container">
|
|
137
|
-
<!-- Player View -->
|
|
138
|
-
<div class="player-section">
|
|
139
|
-
<h2>Player View</h2>
|
|
140
|
-
<div class="player-preview">
|
|
141
|
-
<component
|
|
142
|
-
:is="PlayerComponent"
|
|
143
|
-
v-if="PlayerComponent"
|
|
144
|
-
:scene="scene"
|
|
145
|
-
/>
|
|
146
|
-
<div v-else class="placeholder">No player component found</div>
|
|
147
|
-
</div>
|
|
148
|
-
</div>
|
|
149
|
-
|
|
150
|
-
<!-- Config View -->
|
|
151
|
-
<div class="config-section">
|
|
152
|
-
<h2>Configuration UI</h2>
|
|
153
|
-
<div class="config-panel">
|
|
154
|
-
<component :is="ConfigComponent" v-if="ConfigComponent" />
|
|
155
|
-
<div v-else class="placeholder">No config component found</div>
|
|
156
|
-
</div>
|
|
157
|
-
</div>
|
|
158
|
-
</div>
|
|
159
|
-
|
|
160
|
-
<footer class="footer">
|
|
161
|
-
<p>
|
|
162
|
-
<strong>Juuno CLI</strong> - Test your external app in a
|
|
163
|
-
production-like environment
|
|
164
|
-
</p>
|
|
165
|
-
</footer>
|
|
166
|
-
</div>
|
|
167
|
-
</template>
|
|
168
|
-
|
|
169
|
-
<style scoped>
|
|
170
|
-
* {
|
|
171
|
-
box-sizing: border-box;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
.juuno-cli {
|
|
175
|
-
min-height: 100vh;
|
|
176
|
-
display: flex;
|
|
177
|
-
flex-direction: column;
|
|
178
|
-
font-family:
|
|
179
|
-
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
|
|
180
|
-
Cantarell, sans-serif;
|
|
181
|
-
background-color: #f5f5f5;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
.header {
|
|
185
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
186
|
-
color: white;
|
|
187
|
-
padding: 1.5rem 2rem;
|
|
188
|
-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
.header-content {
|
|
192
|
-
max-width: 1600px;
|
|
193
|
-
margin: 0 auto;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
.header h1 {
|
|
197
|
-
margin: 0 0 0.5rem 0;
|
|
198
|
-
font-size: 1.75rem;
|
|
199
|
-
font-weight: 600;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
.header-info {
|
|
203
|
-
display: flex;
|
|
204
|
-
justify-content: space-between;
|
|
205
|
-
align-items: center;
|
|
206
|
-
gap: 1rem;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
.app-url {
|
|
210
|
-
font-size: 0.875rem;
|
|
211
|
-
opacity: 0.9;
|
|
212
|
-
font-family: 'Courier New', monospace;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
.loading-state,
|
|
216
|
-
.error-state {
|
|
217
|
-
flex: 1;
|
|
218
|
-
display: flex;
|
|
219
|
-
flex-direction: column;
|
|
220
|
-
align-items: center;
|
|
221
|
-
justify-content: center;
|
|
222
|
-
padding: 3rem;
|
|
223
|
-
text-align: center;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
.spinner {
|
|
227
|
-
width: 50px;
|
|
228
|
-
height: 50px;
|
|
229
|
-
border: 4px solid #f3f3f3;
|
|
230
|
-
border-top: 4px solid #667eea;
|
|
231
|
-
border-radius: 50%;
|
|
232
|
-
animation: spin 1s linear infinite;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
@keyframes spin {
|
|
236
|
-
0% {
|
|
237
|
-
transform: rotate(0deg);
|
|
238
|
-
}
|
|
239
|
-
100% {
|
|
240
|
-
transform: rotate(360deg);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
.url-info {
|
|
245
|
-
margin-top: 1rem;
|
|
246
|
-
font-size: 0.875rem;
|
|
247
|
-
color: #666;
|
|
248
|
-
font-family: 'Courier New', monospace;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
.error-icon {
|
|
252
|
-
font-size: 4rem;
|
|
253
|
-
margin-bottom: 1rem;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
.error-message {
|
|
257
|
-
color: #d32f2f;
|
|
258
|
-
font-weight: 500;
|
|
259
|
-
margin: 1rem 0;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
.error-details {
|
|
263
|
-
background: white;
|
|
264
|
-
border-radius: 8px;
|
|
265
|
-
padding: 1.5rem;
|
|
266
|
-
margin-top: 2rem;
|
|
267
|
-
text-align: left;
|
|
268
|
-
max-width: 600px;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
.error-details ul {
|
|
272
|
-
margin: 0.5rem 0 0 1.5rem;
|
|
273
|
-
padding: 0;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
.error-details li {
|
|
277
|
-
margin: 0.25rem 0;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
.container {
|
|
281
|
-
flex: 1;
|
|
282
|
-
display: grid;
|
|
283
|
-
grid-template-columns: 1fr 1fr;
|
|
284
|
-
gap: 2rem;
|
|
285
|
-
padding: 2rem;
|
|
286
|
-
max-width: 1600px;
|
|
287
|
-
margin: 0 auto;
|
|
288
|
-
width: 100%;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
.player-section,
|
|
292
|
-
.config-section {
|
|
293
|
-
background: white;
|
|
294
|
-
border-radius: 8px;
|
|
295
|
-
padding: 1.5rem;
|
|
296
|
-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
.player-section h2,
|
|
300
|
-
.config-section h2 {
|
|
301
|
-
margin: 0 0 1rem 0;
|
|
302
|
-
font-size: 1.25rem;
|
|
303
|
-
font-weight: 600;
|
|
304
|
-
color: #333;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
.player-preview {
|
|
308
|
-
aspect-ratio: 16 / 9;
|
|
309
|
-
background: #000;
|
|
310
|
-
border-radius: 4px;
|
|
311
|
-
overflow: hidden;
|
|
312
|
-
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
.config-panel {
|
|
316
|
-
background: #fafafa;
|
|
317
|
-
border-radius: 4px;
|
|
318
|
-
padding: 1rem;
|
|
319
|
-
min-height: 400px;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
.placeholder {
|
|
323
|
-
display: flex;
|
|
324
|
-
align-items: center;
|
|
325
|
-
justify-content: center;
|
|
326
|
-
height: 100%;
|
|
327
|
-
color: #999;
|
|
328
|
-
font-style: italic;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
.footer {
|
|
332
|
-
background: white;
|
|
333
|
-
padding: 1.5rem;
|
|
334
|
-
text-align: center;
|
|
335
|
-
border-top: 1px solid #e0e0e0;
|
|
336
|
-
color: #666;
|
|
337
|
-
font-size: 0.875rem;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
.footer p {
|
|
341
|
-
margin: 0;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/* Responsive layout */
|
|
345
|
-
@media (max-width: 1200px) {
|
|
346
|
-
.container {
|
|
347
|
-
grid-template-columns: 1fr;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
</style>
|
package/src/deploy/index.ts
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import { readFileSync, existsSync, readdirSync } from 'fs';
|
|
2
|
-
import { join, basename } from 'path';
|
|
3
|
-
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
|
|
4
|
-
import { lookup } from 'mime-types';
|
|
5
|
-
import inquirer from 'inquirer';
|
|
6
|
-
|
|
7
|
-
interface ExternalAppManifest {
|
|
8
|
-
id: string;
|
|
9
|
-
version: string;
|
|
10
|
-
name: string;
|
|
11
|
-
resources: Record<string, string[]>;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface DeployOptions {
|
|
15
|
-
buildDir: string;
|
|
16
|
-
apiKey?: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface S3Credentials {
|
|
20
|
-
s3Token: {
|
|
21
|
-
accessKeyId: string;
|
|
22
|
-
secretAccessKey: string;
|
|
23
|
-
sessionToken?: string;
|
|
24
|
-
};
|
|
25
|
-
region: string;
|
|
26
|
-
bucket: string;
|
|
27
|
-
namespace: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Exchange Juuno API key for temporary S3 credentials.
|
|
32
|
-
*/
|
|
33
|
-
async function exchangeApiKey(apiKey: string): Promise<S3Credentials> {
|
|
34
|
-
// TODO: Replace with actual backend endpoint when ready.
|
|
35
|
-
const apiUrl = process.env.JUUNO_API_URL || 'https://api.juuno.co';
|
|
36
|
-
|
|
37
|
-
try {
|
|
38
|
-
const response = await fetch(
|
|
39
|
-
`${apiUrl}/api/external-apps/auth/exchange-key`,
|
|
40
|
-
{
|
|
41
|
-
method: 'POST',
|
|
42
|
-
headers: { 'Content-Type': 'application/json' },
|
|
43
|
-
body: JSON.stringify({ apiKey }),
|
|
44
|
-
},
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
if (!response.ok) {
|
|
48
|
-
throw new Error(`Authentication failed: ${response.statusText}`);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return await response.json();
|
|
52
|
-
} catch (error) {
|
|
53
|
-
throw new Error(
|
|
54
|
-
`Failed to authenticate with Juuno API. The backend endpoint may not be ready yet. Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Upload a file to S3.
|
|
61
|
-
*/
|
|
62
|
-
async function uploadFile(
|
|
63
|
-
s3Client: S3Client,
|
|
64
|
-
bucket: string,
|
|
65
|
-
key: string,
|
|
66
|
-
filePath: string,
|
|
67
|
-
): Promise<void> {
|
|
68
|
-
const fileContent = readFileSync(filePath);
|
|
69
|
-
const contentType = lookup(filePath) || 'application/octet-stream';
|
|
70
|
-
|
|
71
|
-
const command = new PutObjectCommand({
|
|
72
|
-
Bucket: bucket,
|
|
73
|
-
Key: key,
|
|
74
|
-
Body: fileContent,
|
|
75
|
-
ContentType: contentType,
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
await s3Client.send(command);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Deploy external app to S3.
|
|
83
|
-
*/
|
|
84
|
-
export async function deployApp(options: DeployOptions): Promise<void> {
|
|
85
|
-
const buildDir = options.buildDir;
|
|
86
|
-
|
|
87
|
-
// 1. Validate build directory exists.
|
|
88
|
-
if (!existsSync(buildDir)) {
|
|
89
|
-
console.error(`❌ Build directory not found: ${buildDir}`);
|
|
90
|
-
console.error(' Run your build command first (e.g., npm run build)');
|
|
91
|
-
process.exit(1);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// 2. Load and validate manifest.
|
|
95
|
-
const manifestPath = join(buildDir, 'manifest.json');
|
|
96
|
-
if (!existsSync(manifestPath)) {
|
|
97
|
-
console.error(`❌ manifest.json not found in ${buildDir}`);
|
|
98
|
-
console.error(' Your build should generate a manifest.json file');
|
|
99
|
-
process.exit(1);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const manifest: ExternalAppManifest = JSON.parse(
|
|
103
|
-
readFileSync(manifestPath, 'utf-8'),
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
if (!manifest.id || !manifest.version || !manifest.name) {
|
|
107
|
-
console.error(
|
|
108
|
-
'❌ Invalid manifest.json - missing required fields (id, version, name)',
|
|
109
|
-
);
|
|
110
|
-
process.exit(1);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
console.log(`📦 Deploying ${manifest.name} v${manifest.version}...`);
|
|
114
|
-
console.log('');
|
|
115
|
-
|
|
116
|
-
// 3. Prompt for API key if not provided.
|
|
117
|
-
let apiKey = options.apiKey;
|
|
118
|
-
if (!apiKey) {
|
|
119
|
-
const answers = await inquirer.prompt([
|
|
120
|
-
{
|
|
121
|
-
type: 'password',
|
|
122
|
-
name: 'apiKey',
|
|
123
|
-
message: 'Enter your Juuno API key:',
|
|
124
|
-
validate: (input: string) => {
|
|
125
|
-
if (!input || input.trim().length === 0) {
|
|
126
|
-
return 'API key is required';
|
|
127
|
-
}
|
|
128
|
-
return true;
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
]);
|
|
132
|
-
apiKey = answers.apiKey;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// 4. Exchange API key for S3 credentials.
|
|
136
|
-
console.log('🔑 Authenticating...');
|
|
137
|
-
let credentials: S3Credentials;
|
|
138
|
-
try {
|
|
139
|
-
credentials = await exchangeApiKey(apiKey);
|
|
140
|
-
console.log(`✓ Authenticated as account: ${credentials.namespace}`);
|
|
141
|
-
console.log('');
|
|
142
|
-
} catch (error) {
|
|
143
|
-
console.error(
|
|
144
|
-
`❌ ${error instanceof Error ? error.message : String(error)}`,
|
|
145
|
-
);
|
|
146
|
-
process.exit(1);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// 5. Create S3 client with temporary credentials.
|
|
150
|
-
const s3Client = new S3Client({
|
|
151
|
-
region: credentials.region,
|
|
152
|
-
credentials: {
|
|
153
|
-
accessKeyId: credentials.s3Token.accessKeyId,
|
|
154
|
-
secretAccessKey: credentials.s3Token.secretAccessKey,
|
|
155
|
-
...(credentials.s3Token.sessionToken && {
|
|
156
|
-
sessionToken: credentials.s3Token.sessionToken,
|
|
157
|
-
}),
|
|
158
|
-
},
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
// 6. Upload all files from build directory.
|
|
162
|
-
const s3Path = `${credentials.namespace}/${manifest.id}/${manifest.version}`;
|
|
163
|
-
const files = readdirSync(buildDir).filter((file) =>
|
|
164
|
-
existsSync(join(buildDir, file)),
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
console.log(`☁️ Uploading to s3://${credentials.bucket}/${s3Path}/`);
|
|
168
|
-
console.log('');
|
|
169
|
-
|
|
170
|
-
for (const file of files) {
|
|
171
|
-
const filePath = join(buildDir, file);
|
|
172
|
-
const s3Key = `${s3Path}/${file}`;
|
|
173
|
-
|
|
174
|
-
process.stdout.write(` Uploading ${file}... `);
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
await uploadFile(s3Client, credentials.bucket, s3Key, filePath);
|
|
178
|
-
process.stdout.write('✓\n');
|
|
179
|
-
} catch (error) {
|
|
180
|
-
process.stdout.write('✗\n');
|
|
181
|
-
console.error(
|
|
182
|
-
` Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
183
|
-
);
|
|
184
|
-
process.exit(1);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// 7. Generate manifest URL and display success message.
|
|
189
|
-
const manifestUrl = `https://${credentials.bucket}.s3.${credentials.region}.amazonaws.com/${s3Path}/manifest.json`;
|
|
190
|
-
|
|
191
|
-
console.log('');
|
|
192
|
-
console.log('✅ Deployment successful!');
|
|
193
|
-
console.log('');
|
|
194
|
-
console.log('📋 Manifest URL:');
|
|
195
|
-
console.log(` ${manifestUrl}`);
|
|
196
|
-
console.log('');
|
|
197
|
-
console.log('→ Next steps:');
|
|
198
|
-
console.log(' 1. Go to your Juuno account');
|
|
199
|
-
console.log(' 2. Navigate to SDK > Custom Apps');
|
|
200
|
-
console.log(' 3. Add a new custom app with the manifest URL above');
|
|
201
|
-
console.log('');
|
|
202
|
-
}
|