create-brightsy-component-lib 0.1.0
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 +201 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1105 -0
- package/dist/index.js.map +1 -0
- package/package.json +38 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
/**
|
|
4
|
+
* create-brightsy-component-lib
|
|
5
|
+
*
|
|
6
|
+
* CLI tool to scaffold and deploy Brightsy component library projects.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx create-brightsy-component-lib my-components
|
|
10
|
+
* npx create-brightsy-component-lib my-components --template app
|
|
11
|
+
* npx create-brightsy-component-lib login
|
|
12
|
+
* npx create-brightsy-component-lib deploy
|
|
13
|
+
*/
|
|
14
|
+
import { program } from 'commander';
|
|
15
|
+
import prompts from 'prompts';
|
|
16
|
+
import chalk from 'chalk';
|
|
17
|
+
import fs from 'fs';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import { fileURLToPath } from 'url';
|
|
20
|
+
import { execSync } from 'child_process';
|
|
21
|
+
import { createServer } from 'http';
|
|
22
|
+
import { randomBytes, createHash } from 'crypto';
|
|
23
|
+
import { URL } from 'url';
|
|
24
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
25
|
+
const __dirname = path.dirname(__filename);
|
|
26
|
+
// Config file location
|
|
27
|
+
const CONFIG_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '', '.brightsy');
|
|
28
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
29
|
+
// Load saved config
|
|
30
|
+
function loadConfig() {
|
|
31
|
+
try {
|
|
32
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
33
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
// Ignore errors
|
|
38
|
+
}
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
// Save config
|
|
42
|
+
function saveConfig(config) {
|
|
43
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
44
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
47
|
+
// Set restrictive permissions
|
|
48
|
+
fs.chmodSync(CONFIG_FILE, 0o600);
|
|
49
|
+
}
|
|
50
|
+
// Generate PKCE code verifier and challenge
|
|
51
|
+
function generatePKCE() {
|
|
52
|
+
const verifier = randomBytes(32).toString('base64url');
|
|
53
|
+
const challenge = createHash('sha256').update(verifier).digest('base64url');
|
|
54
|
+
return { verifier, challenge };
|
|
55
|
+
}
|
|
56
|
+
// OAuth login flow
|
|
57
|
+
async function login(options) {
|
|
58
|
+
console.log(BANNER);
|
|
59
|
+
console.log(chalk.cyan('Authenticating with Brightsy...\n'));
|
|
60
|
+
const endpoint = options.endpoint || 'https://brightsy.ai';
|
|
61
|
+
const clientId = 'brightsy-cli';
|
|
62
|
+
const redirectPort = 8976;
|
|
63
|
+
const redirectUri = `http://localhost:${redirectPort}/callback`;
|
|
64
|
+
const { verifier, challenge } = generatePKCE();
|
|
65
|
+
const state = randomBytes(16).toString('hex');
|
|
66
|
+
// Build authorization URL
|
|
67
|
+
const authUrl = new URL(`${endpoint}/oauth/authorize`);
|
|
68
|
+
authUrl.searchParams.set('client_id', clientId);
|
|
69
|
+
authUrl.searchParams.set('redirect_uri', redirectUri);
|
|
70
|
+
authUrl.searchParams.set('response_type', 'code');
|
|
71
|
+
authUrl.searchParams.set('scope', 'openid profile files libraries');
|
|
72
|
+
authUrl.searchParams.set('code_challenge', challenge);
|
|
73
|
+
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
74
|
+
authUrl.searchParams.set('state', state);
|
|
75
|
+
console.log(chalk.cyan('Opening browser for authentication...'));
|
|
76
|
+
console.log(chalk.dim(`If the browser doesn't open, visit:\n${authUrl.toString()}\n`));
|
|
77
|
+
// Open browser
|
|
78
|
+
const openCommand = process.platform === 'darwin' ? 'open' :
|
|
79
|
+
process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
80
|
+
try {
|
|
81
|
+
execSync(`${openCommand} "${authUrl.toString()}"`, { stdio: 'ignore' });
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
// Browser open failed, user will need to copy URL manually
|
|
85
|
+
}
|
|
86
|
+
// Start local server to receive callback
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
const server = createServer(async (req, res) => {
|
|
89
|
+
const url = new URL(req.url || '', `http://localhost:${redirectPort}`);
|
|
90
|
+
if (url.pathname === '/callback') {
|
|
91
|
+
const code = url.searchParams.get('code');
|
|
92
|
+
const returnedState = url.searchParams.get('state');
|
|
93
|
+
const error = url.searchParams.get('error');
|
|
94
|
+
if (error) {
|
|
95
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
96
|
+
res.end(`<html><body><h1>Authentication Failed</h1><p>${error}</p><script>window.close()</script></body></html>`);
|
|
97
|
+
server.close();
|
|
98
|
+
reject(new Error(error));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (returnedState !== state) {
|
|
102
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
103
|
+
res.end(`<html><body><h1>Authentication Failed</h1><p>State mismatch</p><script>window.close()</script></body></html>`);
|
|
104
|
+
server.close();
|
|
105
|
+
reject(new Error('State mismatch - possible CSRF attack'));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (!code) {
|
|
109
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
110
|
+
res.end(`<html><body><h1>Authentication Failed</h1><p>No code received</p><script>window.close()</script></body></html>`);
|
|
111
|
+
server.close();
|
|
112
|
+
reject(new Error('No authorization code received'));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
// Exchange code for tokens
|
|
117
|
+
const tokenResponse = await fetch(`${endpoint}/oauth/token`, {
|
|
118
|
+
method: 'POST',
|
|
119
|
+
headers: { 'Content-Type': 'application/json' },
|
|
120
|
+
body: JSON.stringify({
|
|
121
|
+
grant_type: 'authorization_code',
|
|
122
|
+
client_id: clientId,
|
|
123
|
+
code,
|
|
124
|
+
redirect_uri: redirectUri,
|
|
125
|
+
code_verifier: verifier,
|
|
126
|
+
})
|
|
127
|
+
});
|
|
128
|
+
if (!tokenResponse.ok) {
|
|
129
|
+
const error = await tokenResponse.json().catch(() => ({}));
|
|
130
|
+
throw new Error(error.error_description || error.error || 'Token exchange failed');
|
|
131
|
+
}
|
|
132
|
+
const tokens = await tokenResponse.json();
|
|
133
|
+
// Get user info to find account
|
|
134
|
+
const userResponse = await fetch(`${endpoint}/oauth/userinfo`, {
|
|
135
|
+
headers: { 'Authorization': `Bearer ${tokens.access_token}` }
|
|
136
|
+
});
|
|
137
|
+
let accountId = '';
|
|
138
|
+
let accountSlug = '';
|
|
139
|
+
if (userResponse.ok) {
|
|
140
|
+
const userInfo = await userResponse.json();
|
|
141
|
+
// If user has accounts, let them select one
|
|
142
|
+
if (userInfo.accounts && userInfo.accounts.length > 0) {
|
|
143
|
+
if (userInfo.accounts.length === 1) {
|
|
144
|
+
accountId = userInfo.accounts[0].id;
|
|
145
|
+
accountSlug = userInfo.accounts[0].slug;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
// Multiple accounts - will prompt after
|
|
149
|
+
console.log(chalk.yellow('\nMultiple accounts found. Please select one:'));
|
|
150
|
+
const { selectedAccount } = await prompts({
|
|
151
|
+
type: 'select',
|
|
152
|
+
name: 'selectedAccount',
|
|
153
|
+
message: 'Select account:',
|
|
154
|
+
choices: userInfo.accounts.map((acc) => ({
|
|
155
|
+
title: acc.name || acc.slug,
|
|
156
|
+
value: acc,
|
|
157
|
+
}))
|
|
158
|
+
});
|
|
159
|
+
accountId = selectedAccount.id;
|
|
160
|
+
accountSlug = selectedAccount.slug;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Save config
|
|
165
|
+
const config = {
|
|
166
|
+
access_token: tokens.access_token,
|
|
167
|
+
refresh_token: tokens.refresh_token,
|
|
168
|
+
account_id: accountId,
|
|
169
|
+
account_slug: accountSlug,
|
|
170
|
+
endpoint,
|
|
171
|
+
expires_at: Date.now() + (tokens.expires_in * 1000),
|
|
172
|
+
};
|
|
173
|
+
saveConfig(config);
|
|
174
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
175
|
+
res.end(`
|
|
176
|
+
<html>
|
|
177
|
+
<body style="font-family: system-ui; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0;">
|
|
178
|
+
<div style="text-align: center;">
|
|
179
|
+
<h1 style="color: #3b82f6;">Authentication Successful!</h1>
|
|
180
|
+
<p>You can close this window and return to the terminal.</p>
|
|
181
|
+
</div>
|
|
182
|
+
<script>setTimeout(() => window.close(), 2000)</script>
|
|
183
|
+
</body>
|
|
184
|
+
</html>
|
|
185
|
+
`);
|
|
186
|
+
server.close();
|
|
187
|
+
console.log(chalk.green('\n✓ Authentication successful!'));
|
|
188
|
+
console.log(` Account: ${chalk.bold(accountSlug || accountId)}`);
|
|
189
|
+
console.log(` Config saved to: ${chalk.dim(CONFIG_FILE)}`);
|
|
190
|
+
console.log();
|
|
191
|
+
resolve();
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
195
|
+
res.end(`<html><body><h1>Authentication Failed</h1><p>${error.message}</p></body></html>`);
|
|
196
|
+
server.close();
|
|
197
|
+
reject(error);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
res.writeHead(404);
|
|
202
|
+
res.end('Not found');
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
server.listen(redirectPort, () => {
|
|
206
|
+
console.log(chalk.dim(`Waiting for authentication callback on port ${redirectPort}...`));
|
|
207
|
+
});
|
|
208
|
+
// Timeout after 5 minutes
|
|
209
|
+
setTimeout(() => {
|
|
210
|
+
server.close();
|
|
211
|
+
reject(new Error('Authentication timed out'));
|
|
212
|
+
}, 5 * 60 * 1000);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
// Logout
|
|
216
|
+
function logout() {
|
|
217
|
+
console.log(BANNER);
|
|
218
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
219
|
+
fs.unlinkSync(CONFIG_FILE);
|
|
220
|
+
console.log(chalk.green('✓ Logged out successfully'));
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
console.log(chalk.yellow('Not logged in'));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// Check if logged in and token is valid
|
|
227
|
+
async function ensureLoggedIn(options) {
|
|
228
|
+
const config = loadConfig();
|
|
229
|
+
if (!config.access_token) {
|
|
230
|
+
console.log(chalk.yellow('Not logged in. Starting authentication...\n'));
|
|
231
|
+
await login(options);
|
|
232
|
+
return loadConfig();
|
|
233
|
+
}
|
|
234
|
+
// Check if token is expired
|
|
235
|
+
if (config.expires_at && Date.now() > config.expires_at) {
|
|
236
|
+
if (config.refresh_token) {
|
|
237
|
+
console.log(chalk.dim('Token expired, refreshing...'));
|
|
238
|
+
try {
|
|
239
|
+
const endpoint = config.endpoint || options.endpoint || 'https://brightsy.ai';
|
|
240
|
+
const response = await fetch(`${endpoint}/oauth/token`, {
|
|
241
|
+
method: 'POST',
|
|
242
|
+
headers: { 'Content-Type': 'application/json' },
|
|
243
|
+
body: JSON.stringify({
|
|
244
|
+
grant_type: 'refresh_token',
|
|
245
|
+
client_id: 'brightsy-cli',
|
|
246
|
+
refresh_token: config.refresh_token,
|
|
247
|
+
})
|
|
248
|
+
});
|
|
249
|
+
if (response.ok) {
|
|
250
|
+
const tokens = await response.json();
|
|
251
|
+
config.access_token = tokens.access_token;
|
|
252
|
+
if (tokens.refresh_token) {
|
|
253
|
+
config.refresh_token = tokens.refresh_token;
|
|
254
|
+
}
|
|
255
|
+
config.expires_at = Date.now() + (tokens.expires_in * 1000);
|
|
256
|
+
saveConfig(config);
|
|
257
|
+
console.log(chalk.green('✓ Token refreshed'));
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
throw new Error('Refresh failed');
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch (e) {
|
|
264
|
+
console.log(chalk.yellow('Session expired. Please log in again.\n'));
|
|
265
|
+
await login(options);
|
|
266
|
+
return loadConfig();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
console.log(chalk.yellow('Session expired. Please log in again.\n'));
|
|
271
|
+
await login(options);
|
|
272
|
+
return loadConfig();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return config;
|
|
276
|
+
}
|
|
277
|
+
// ASCII art banner
|
|
278
|
+
const BANNER = `
|
|
279
|
+
${chalk.blue('╔═══════════════════════════════════════════════╗')}
|
|
280
|
+
${chalk.blue('║')} ${chalk.bold.cyan('Brightsy Component Library Generator')} ${chalk.blue('║')}
|
|
281
|
+
${chalk.blue('╚═══════════════════════════════════════════════╝')}
|
|
282
|
+
`;
|
|
283
|
+
// Template files for "components" template
|
|
284
|
+
const COMPONENTS_TEMPLATE = {
|
|
285
|
+
'package.json': (config) => JSON.stringify({
|
|
286
|
+
name: config.projectName,
|
|
287
|
+
version: '1.0.0',
|
|
288
|
+
description: config.description,
|
|
289
|
+
type: 'module',
|
|
290
|
+
main: './dist/index.js',
|
|
291
|
+
module: './dist/index.js',
|
|
292
|
+
types: './dist/index.d.ts',
|
|
293
|
+
exports: {
|
|
294
|
+
'.': {
|
|
295
|
+
types: './dist/index.d.ts',
|
|
296
|
+
import: './dist/index.js'
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
scripts: {
|
|
300
|
+
dev: 'vite --port 5180',
|
|
301
|
+
build: 'vite build',
|
|
302
|
+
preview: 'vite preview',
|
|
303
|
+
typecheck: 'tsc --noEmit'
|
|
304
|
+
},
|
|
305
|
+
dependencies: {
|
|
306
|
+
react: '^18.2.0',
|
|
307
|
+
'react-dom': '^18.2.0'
|
|
308
|
+
},
|
|
309
|
+
devDependencies: {
|
|
310
|
+
'@brightsy/component-dev-kit': '^0.1.0',
|
|
311
|
+
'@types/react': '^18.2.0',
|
|
312
|
+
'@types/react-dom': '^18.2.0',
|
|
313
|
+
'@vitejs/plugin-react': '^4.3.4',
|
|
314
|
+
typescript: '^5.6.3',
|
|
315
|
+
vite: '^6.0.1'
|
|
316
|
+
},
|
|
317
|
+
peerDependencies: {
|
|
318
|
+
'@brightsy/page-builder': '*',
|
|
319
|
+
'@brightsy/client': '*'
|
|
320
|
+
},
|
|
321
|
+
author: config.author,
|
|
322
|
+
license: 'MIT'
|
|
323
|
+
}, null, 2),
|
|
324
|
+
'tsconfig.json': () => JSON.stringify({
|
|
325
|
+
compilerOptions: {
|
|
326
|
+
target: 'ES2020',
|
|
327
|
+
useDefineForClassFields: true,
|
|
328
|
+
lib: ['ES2020', 'DOM', 'DOM.Iterable'],
|
|
329
|
+
module: 'ESNext',
|
|
330
|
+
skipLibCheck: true,
|
|
331
|
+
moduleResolution: 'bundler',
|
|
332
|
+
allowImportingTsExtensions: true,
|
|
333
|
+
resolveJsonModule: true,
|
|
334
|
+
isolatedModules: true,
|
|
335
|
+
noEmit: true,
|
|
336
|
+
jsx: 'react-jsx',
|
|
337
|
+
strict: true,
|
|
338
|
+
noUnusedLocals: true,
|
|
339
|
+
noUnusedParameters: true,
|
|
340
|
+
noFallthroughCasesInSwitch: true,
|
|
341
|
+
esModuleInterop: true
|
|
342
|
+
},
|
|
343
|
+
include: ['src'],
|
|
344
|
+
references: [{ path: './tsconfig.node.json' }]
|
|
345
|
+
}, null, 2),
|
|
346
|
+
'tsconfig.node.json': () => JSON.stringify({
|
|
347
|
+
compilerOptions: {
|
|
348
|
+
composite: true,
|
|
349
|
+
skipLibCheck: true,
|
|
350
|
+
module: 'ESNext',
|
|
351
|
+
moduleResolution: 'bundler',
|
|
352
|
+
allowSyntheticDefaultImports: true,
|
|
353
|
+
strict: true
|
|
354
|
+
},
|
|
355
|
+
include: ['vite.config.ts']
|
|
356
|
+
}, null, 2),
|
|
357
|
+
'vite.config.ts': (config) => `import { defineConfig } from 'vite';
|
|
358
|
+
import react from '@vitejs/plugin-react';
|
|
359
|
+
import { brightsyComponentLibrary } from '@brightsy/component-dev-kit/vite';
|
|
360
|
+
import path from 'path';
|
|
361
|
+
|
|
362
|
+
export default defineConfig({
|
|
363
|
+
plugins: [
|
|
364
|
+
react({
|
|
365
|
+
jsxRuntime: 'classic',
|
|
366
|
+
jsxImportSource: undefined,
|
|
367
|
+
}),
|
|
368
|
+
...brightsyComponentLibrary({
|
|
369
|
+
name: '${config.projectName}',
|
|
370
|
+
}),
|
|
371
|
+
],
|
|
372
|
+
build: {
|
|
373
|
+
outDir: 'dist',
|
|
374
|
+
emptyOutDir: true,
|
|
375
|
+
cssCodeSplit: false,
|
|
376
|
+
lib: {
|
|
377
|
+
entry: path.resolve(__dirname, 'src/index.tsx'),
|
|
378
|
+
formats: ['es'],
|
|
379
|
+
fileName: () => 'index.js',
|
|
380
|
+
},
|
|
381
|
+
rollupOptions: {
|
|
382
|
+
external: ['react', 'react-dom'],
|
|
383
|
+
output: {
|
|
384
|
+
globals: {
|
|
385
|
+
react: 'React',
|
|
386
|
+
'react-dom': 'ReactDOM',
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
sourcemap: true,
|
|
391
|
+
},
|
|
392
|
+
server: {
|
|
393
|
+
port: 5180,
|
|
394
|
+
cors: true,
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
`,
|
|
398
|
+
'.gitignore': () => `node_modules
|
|
399
|
+
dist
|
|
400
|
+
.DS_Store
|
|
401
|
+
*.local
|
|
402
|
+
`,
|
|
403
|
+
'src/index.tsx': (config) => `/**
|
|
404
|
+
* ${config.projectName}
|
|
405
|
+
*
|
|
406
|
+
* ${config.description}
|
|
407
|
+
*/
|
|
408
|
+
|
|
409
|
+
import { createComponentExports } from '@brightsy/component-dev-kit';
|
|
410
|
+
import { HelloWorld, helloWorldConfig, helloWorldLLMInstruction } from './components/HelloWorld';
|
|
411
|
+
|
|
412
|
+
// Create the library export
|
|
413
|
+
const library = createComponentExports(
|
|
414
|
+
// Components
|
|
415
|
+
{
|
|
416
|
+
HelloWorld,
|
|
417
|
+
},
|
|
418
|
+
// Configs
|
|
419
|
+
{
|
|
420
|
+
HelloWorld: helloWorldConfig,
|
|
421
|
+
},
|
|
422
|
+
// LLM Instructions
|
|
423
|
+
{
|
|
424
|
+
HelloWorld: helloWorldLLMInstruction,
|
|
425
|
+
}
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
// Default export: components for dynamic import
|
|
429
|
+
export default library.default;
|
|
430
|
+
|
|
431
|
+
// Named exports for static usage
|
|
432
|
+
export const componentConfigs = library.componentConfigs;
|
|
433
|
+
export const componentLLMInstructions = library.componentLLMInstructions;
|
|
434
|
+
|
|
435
|
+
// Re-export individual components
|
|
436
|
+
export { HelloWorld } from './components/HelloWorld';
|
|
437
|
+
`,
|
|
438
|
+
'src/components/HelloWorld/HelloWorld.tsx': () => `/**
|
|
439
|
+
* HelloWorld Component
|
|
440
|
+
*
|
|
441
|
+
* A simple example component to get you started.
|
|
442
|
+
*/
|
|
443
|
+
|
|
444
|
+
import React from 'react';
|
|
445
|
+
|
|
446
|
+
export interface HelloWorldProps {
|
|
447
|
+
/** The name to greet */
|
|
448
|
+
name: string;
|
|
449
|
+
/** Custom greeting message */
|
|
450
|
+
greeting?: string;
|
|
451
|
+
/** Text color */
|
|
452
|
+
color?: string;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
export const HelloWorld: React.FC<HelloWorldProps> = ({
|
|
456
|
+
name,
|
|
457
|
+
greeting = 'Hello',
|
|
458
|
+
color = 'var(--color-text, #111827)',
|
|
459
|
+
}) => {
|
|
460
|
+
return (
|
|
461
|
+
<div
|
|
462
|
+
style={{
|
|
463
|
+
padding: 'var(--spacing-md, 16px)',
|
|
464
|
+
borderRadius: 'var(--border-radius-md, 8px)',
|
|
465
|
+
backgroundColor: 'var(--color-surface, #ffffff)',
|
|
466
|
+
border: '1px solid var(--color-border, #e5e7eb)',
|
|
467
|
+
fontFamily: 'var(--font-family-primary, system-ui)',
|
|
468
|
+
}}
|
|
469
|
+
>
|
|
470
|
+
<h2
|
|
471
|
+
style={{
|
|
472
|
+
margin: 0,
|
|
473
|
+
color,
|
|
474
|
+
fontSize: 'var(--font-size-xl, 24px)',
|
|
475
|
+
fontWeight: 'var(--font-weight-semibold, 600)',
|
|
476
|
+
}}
|
|
477
|
+
>
|
|
478
|
+
{greeting}, {name}!
|
|
479
|
+
</h2>
|
|
480
|
+
<p
|
|
481
|
+
style={{
|
|
482
|
+
margin: 'var(--spacing-sm, 12px) 0 0',
|
|
483
|
+
color: 'var(--color-text-muted, #6b7280)',
|
|
484
|
+
}}
|
|
485
|
+
>
|
|
486
|
+
Welcome to your Brightsy component library.
|
|
487
|
+
</p>
|
|
488
|
+
</div>
|
|
489
|
+
);
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
export default HelloWorld;
|
|
493
|
+
`,
|
|
494
|
+
'src/components/HelloWorld/HelloWorld.config.ts': () => `/**
|
|
495
|
+
* HelloWorld Puck Configuration
|
|
496
|
+
*/
|
|
497
|
+
|
|
498
|
+
import type { BrightsyComponentConfig } from '@brightsy/component-dev-kit';
|
|
499
|
+
import type { HelloWorldProps } from './HelloWorld';
|
|
500
|
+
|
|
501
|
+
export const helloWorldConfig: BrightsyComponentConfig<HelloWorldProps> = {
|
|
502
|
+
label: 'Hello World',
|
|
503
|
+
category: 'Getting Started',
|
|
504
|
+
icon: '👋',
|
|
505
|
+
|
|
506
|
+
fields: {
|
|
507
|
+
name: {
|
|
508
|
+
type: 'text',
|
|
509
|
+
label: 'Name',
|
|
510
|
+
description: 'The name to greet',
|
|
511
|
+
required: true,
|
|
512
|
+
},
|
|
513
|
+
greeting: {
|
|
514
|
+
type: 'text',
|
|
515
|
+
label: 'Greeting',
|
|
516
|
+
description: 'Custom greeting message',
|
|
517
|
+
placeholder: 'Hello',
|
|
518
|
+
},
|
|
519
|
+
color: {
|
|
520
|
+
type: 'text',
|
|
521
|
+
label: 'Text Color',
|
|
522
|
+
description: 'CSS color for the greeting',
|
|
523
|
+
placeholder: '#111827',
|
|
524
|
+
},
|
|
525
|
+
},
|
|
526
|
+
|
|
527
|
+
defaultProps: {
|
|
528
|
+
name: 'World',
|
|
529
|
+
greeting: 'Hello',
|
|
530
|
+
color: 'var(--color-text, #111827)',
|
|
531
|
+
},
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
export default helloWorldConfig;
|
|
535
|
+
`,
|
|
536
|
+
'src/components/HelloWorld/HelloWorld.llm.ts': () => `/**
|
|
537
|
+
* HelloWorld LLM Instructions
|
|
538
|
+
*/
|
|
539
|
+
|
|
540
|
+
import type { LLMInstruction } from '@brightsy/component-dev-kit';
|
|
541
|
+
|
|
542
|
+
export const helloWorldLLMInstruction: LLMInstruction = \`
|
|
543
|
+
HelloWorld: A simple greeting component.
|
|
544
|
+
|
|
545
|
+
Use this as a template for creating your own components.
|
|
546
|
+
|
|
547
|
+
Props:
|
|
548
|
+
- name (string, required): The name to greet
|
|
549
|
+
- greeting (string, default: "Hello"): Custom greeting message
|
|
550
|
+
- color (string): CSS color for the text
|
|
551
|
+
|
|
552
|
+
Example:
|
|
553
|
+
{
|
|
554
|
+
"type": "HelloWorld",
|
|
555
|
+
"props": {
|
|
556
|
+
"name": "Developer",
|
|
557
|
+
"greeting": "Welcome",
|
|
558
|
+
"color": "#3b82f6"
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
\`;
|
|
562
|
+
|
|
563
|
+
export default helloWorldLLMInstruction;
|
|
564
|
+
`,
|
|
565
|
+
'src/components/HelloWorld/index.ts': () => `export { HelloWorld, type HelloWorldProps } from './HelloWorld';
|
|
566
|
+
export { helloWorldConfig } from './HelloWorld.config';
|
|
567
|
+
export { helloWorldLLMInstruction } from './HelloWorld.llm';
|
|
568
|
+
`,
|
|
569
|
+
'README.md': (config) => `# ${config.projectName}
|
|
570
|
+
|
|
571
|
+
${config.description}
|
|
572
|
+
|
|
573
|
+
## Quick Start
|
|
574
|
+
|
|
575
|
+
\`\`\`bash
|
|
576
|
+
# Install dependencies
|
|
577
|
+
npm install
|
|
578
|
+
|
|
579
|
+
# Start development server
|
|
580
|
+
npm run dev
|
|
581
|
+
|
|
582
|
+
# Build for production
|
|
583
|
+
npm run build
|
|
584
|
+
\`\`\`
|
|
585
|
+
|
|
586
|
+
## Creating Components
|
|
587
|
+
|
|
588
|
+
Each component should have three files:
|
|
589
|
+
|
|
590
|
+
1. \`Component.tsx\` - The React component
|
|
591
|
+
2. \`Component.config.ts\` - Puck editor configuration
|
|
592
|
+
3. \`Component.llm.ts\` - LLM instructions for AI assistance
|
|
593
|
+
|
|
594
|
+
See \`src/components/HelloWorld\` for an example.
|
|
595
|
+
|
|
596
|
+
## Deploying to Brightsy
|
|
597
|
+
|
|
598
|
+
1. Build your library: \`npm run build\`
|
|
599
|
+
2. Host the \`dist/\` folder on a CDN
|
|
600
|
+
3. Register the library in Brightsy:
|
|
601
|
+
- Via the Brightsy dashboard
|
|
602
|
+
- Via MCP tools
|
|
603
|
+
- Via the BrightsyClient API
|
|
604
|
+
|
|
605
|
+
## Theme Variables
|
|
606
|
+
|
|
607
|
+
Use CSS variables for consistent theming:
|
|
608
|
+
|
|
609
|
+
\`\`\`css
|
|
610
|
+
/* Colors */
|
|
611
|
+
--color-primary
|
|
612
|
+
--color-text
|
|
613
|
+
--color-text-muted
|
|
614
|
+
--color-surface
|
|
615
|
+
--color-border
|
|
616
|
+
|
|
617
|
+
/* Spacing */
|
|
618
|
+
--spacing-xs, --spacing-sm, --spacing-md, --spacing-lg, --spacing-xl
|
|
619
|
+
|
|
620
|
+
/* Typography */
|
|
621
|
+
--font-family-primary
|
|
622
|
+
--font-size-sm, --font-size-md, --font-size-lg
|
|
623
|
+
|
|
624
|
+
/* Other */
|
|
625
|
+
--border-radius-sm, --border-radius-md, --border-radius-lg
|
|
626
|
+
\`\`\`
|
|
627
|
+
|
|
628
|
+
## License
|
|
629
|
+
|
|
630
|
+
MIT
|
|
631
|
+
`,
|
|
632
|
+
};
|
|
633
|
+
// Template files for "app" template (single application component)
|
|
634
|
+
const APP_TEMPLATE = {
|
|
635
|
+
...COMPONENTS_TEMPLATE,
|
|
636
|
+
'src/index.tsx': (config) => `/**
|
|
637
|
+
* ${config.projectName}
|
|
638
|
+
*
|
|
639
|
+
* ${config.description}
|
|
640
|
+
*
|
|
641
|
+
* This is a custom application built on Brightsy.
|
|
642
|
+
* The App component is the entire application - it handles routing, state, etc.
|
|
643
|
+
*/
|
|
644
|
+
|
|
645
|
+
import { createComponentExports } from '@brightsy/component-dev-kit';
|
|
646
|
+
import { App, appConfig, appLLMInstruction } from './App';
|
|
647
|
+
|
|
648
|
+
const library = createComponentExports(
|
|
649
|
+
{ App },
|
|
650
|
+
{ App: appConfig },
|
|
651
|
+
{ App: appLLMInstruction }
|
|
652
|
+
);
|
|
653
|
+
|
|
654
|
+
export default library.default;
|
|
655
|
+
export const componentConfigs = library.componentConfigs;
|
|
656
|
+
export const componentLLMInstructions = library.componentLLMInstructions;
|
|
657
|
+
export { App } from './App';
|
|
658
|
+
`,
|
|
659
|
+
'src/App/App.tsx': (config) => `/**
|
|
660
|
+
* Main Application Component
|
|
661
|
+
*
|
|
662
|
+
* This component IS the entire application.
|
|
663
|
+
* It handles its own routing, state, and data fetching.
|
|
664
|
+
*/
|
|
665
|
+
|
|
666
|
+
import React, { useState } from 'react';
|
|
667
|
+
|
|
668
|
+
export interface AppProps {
|
|
669
|
+
/** Application title */
|
|
670
|
+
title: string;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
export const App: React.FC<AppProps> = ({ title }) => {
|
|
674
|
+
const [count, setCount] = useState(0);
|
|
675
|
+
|
|
676
|
+
return (
|
|
677
|
+
<div
|
|
678
|
+
style={{
|
|
679
|
+
minHeight: '100vh',
|
|
680
|
+
fontFamily: 'var(--font-family-primary, system-ui)',
|
|
681
|
+
backgroundColor: 'var(--color-surface, #f9fafb)',
|
|
682
|
+
}}
|
|
683
|
+
>
|
|
684
|
+
{/* Header */}
|
|
685
|
+
<header
|
|
686
|
+
style={{
|
|
687
|
+
padding: 'var(--spacing-md, 16px)',
|
|
688
|
+
backgroundColor: 'var(--color-primary, #3b82f6)',
|
|
689
|
+
color: 'var(--color-primary-foreground, #ffffff)',
|
|
690
|
+
}}
|
|
691
|
+
>
|
|
692
|
+
<h1 style={{ margin: 0, fontSize: 'var(--font-size-xl, 24px)' }}>
|
|
693
|
+
{title}
|
|
694
|
+
</h1>
|
|
695
|
+
</header>
|
|
696
|
+
|
|
697
|
+
{/* Main Content */}
|
|
698
|
+
<main
|
|
699
|
+
style={{
|
|
700
|
+
padding: 'var(--spacing-lg, 24px)',
|
|
701
|
+
maxWidth: '800px',
|
|
702
|
+
margin: '0 auto',
|
|
703
|
+
}}
|
|
704
|
+
>
|
|
705
|
+
<div
|
|
706
|
+
style={{
|
|
707
|
+
padding: 'var(--spacing-lg, 24px)',
|
|
708
|
+
backgroundColor: 'var(--color-surface, #ffffff)',
|
|
709
|
+
borderRadius: 'var(--border-radius-lg, 12px)',
|
|
710
|
+
border: '1px solid var(--color-border, #e5e7eb)',
|
|
711
|
+
textAlign: 'center',
|
|
712
|
+
}}
|
|
713
|
+
>
|
|
714
|
+
<h2 style={{ marginTop: 0 }}>Welcome to your Brightsy App</h2>
|
|
715
|
+
<p style={{ color: 'var(--color-text-muted, #6b7280)' }}>
|
|
716
|
+
This is a custom application built on Brightsy.
|
|
717
|
+
</p>
|
|
718
|
+
|
|
719
|
+
<div style={{ marginTop: 'var(--spacing-lg, 24px)' }}>
|
|
720
|
+
<p>Count: {count}</p>
|
|
721
|
+
<button
|
|
722
|
+
onClick={() => setCount(c => c + 1)}
|
|
723
|
+
style={{
|
|
724
|
+
padding: 'var(--spacing-sm, 12px) var(--spacing-md, 16px)',
|
|
725
|
+
backgroundColor: 'var(--color-primary, #3b82f6)',
|
|
726
|
+
color: 'var(--color-primary-foreground, #ffffff)',
|
|
727
|
+
border: 'none',
|
|
728
|
+
borderRadius: 'var(--border-radius-md, 8px)',
|
|
729
|
+
cursor: 'pointer',
|
|
730
|
+
fontSize: 'var(--font-size-sm, 14px)',
|
|
731
|
+
}}
|
|
732
|
+
>
|
|
733
|
+
Increment
|
|
734
|
+
</button>
|
|
735
|
+
</div>
|
|
736
|
+
</div>
|
|
737
|
+
</main>
|
|
738
|
+
</div>
|
|
739
|
+
);
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
export default App;
|
|
743
|
+
`,
|
|
744
|
+
'src/App/App.config.ts': (config) => `/**
|
|
745
|
+
* App Configuration
|
|
746
|
+
*/
|
|
747
|
+
|
|
748
|
+
import type { BrightsyComponentConfig } from '@brightsy/component-dev-kit';
|
|
749
|
+
import type { AppProps } from './App';
|
|
750
|
+
|
|
751
|
+
export const appConfig: BrightsyComponentConfig<AppProps> = {
|
|
752
|
+
label: '${config.projectName} App',
|
|
753
|
+
category: 'Applications',
|
|
754
|
+
icon: '📱',
|
|
755
|
+
|
|
756
|
+
fields: {
|
|
757
|
+
title: {
|
|
758
|
+
type: 'text',
|
|
759
|
+
label: 'App Title',
|
|
760
|
+
description: 'Title shown in the header',
|
|
761
|
+
required: true,
|
|
762
|
+
},
|
|
763
|
+
},
|
|
764
|
+
|
|
765
|
+
defaultProps: {
|
|
766
|
+
title: '${config.projectName}',
|
|
767
|
+
},
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
export default appConfig;
|
|
771
|
+
`,
|
|
772
|
+
'src/App/App.llm.ts': () => `/**
|
|
773
|
+
* App LLM Instructions
|
|
774
|
+
*/
|
|
775
|
+
|
|
776
|
+
import type { LLMInstruction } from '@brightsy/component-dev-kit';
|
|
777
|
+
|
|
778
|
+
export const appLLMInstruction: LLMInstruction = \`
|
|
779
|
+
App: A custom application component.
|
|
780
|
+
|
|
781
|
+
This is not a composable component - it's an entire application
|
|
782
|
+
that handles its own routing, state, and data fetching.
|
|
783
|
+
|
|
784
|
+
Props:
|
|
785
|
+
- title (string): Application title
|
|
786
|
+
|
|
787
|
+
Deploy on a Brightsy site in "app mode" to use this component
|
|
788
|
+
as the entire site content.
|
|
789
|
+
\`;
|
|
790
|
+
|
|
791
|
+
export default appLLMInstruction;
|
|
792
|
+
`,
|
|
793
|
+
'src/App/index.ts': () => `export { App, type AppProps } from './App';
|
|
794
|
+
export { appConfig } from './App.config';
|
|
795
|
+
export { appLLMInstruction } from './App.llm';
|
|
796
|
+
`,
|
|
797
|
+
};
|
|
798
|
+
// Main scaffolding function
|
|
799
|
+
async function scaffold(projectName, options) {
|
|
800
|
+
console.log(BANNER);
|
|
801
|
+
// Validate project name
|
|
802
|
+
if (fs.existsSync(projectName)) {
|
|
803
|
+
console.error(chalk.red(`Error: Directory "${projectName}" already exists.`));
|
|
804
|
+
process.exit(1);
|
|
805
|
+
}
|
|
806
|
+
// Prompt for configuration
|
|
807
|
+
const response = await prompts([
|
|
808
|
+
{
|
|
809
|
+
type: options.template ? null : 'select',
|
|
810
|
+
name: 'template',
|
|
811
|
+
message: 'Choose a template:',
|
|
812
|
+
choices: [
|
|
813
|
+
{ title: 'Component Library', value: 'components', description: 'Multiple components for page builder' },
|
|
814
|
+
{ title: 'Custom Application', value: 'app', description: 'Single app component' },
|
|
815
|
+
],
|
|
816
|
+
initial: 0,
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
type: 'text',
|
|
820
|
+
name: 'description',
|
|
821
|
+
message: 'Project description:',
|
|
822
|
+
initial: 'A Brightsy component library',
|
|
823
|
+
},
|
|
824
|
+
{
|
|
825
|
+
type: 'text',
|
|
826
|
+
name: 'author',
|
|
827
|
+
message: 'Author:',
|
|
828
|
+
initial: '',
|
|
829
|
+
},
|
|
830
|
+
]);
|
|
831
|
+
const config = {
|
|
832
|
+
projectName,
|
|
833
|
+
template: (options.template || response.template),
|
|
834
|
+
description: response.description,
|
|
835
|
+
author: response.author,
|
|
836
|
+
};
|
|
837
|
+
console.log();
|
|
838
|
+
console.log(chalk.cyan(`Creating ${config.template === 'app' ? 'application' : 'component library'}: ${projectName}`));
|
|
839
|
+
console.log();
|
|
840
|
+
// Select template
|
|
841
|
+
const template = config.template === 'app' ? APP_TEMPLATE : COMPONENTS_TEMPLATE;
|
|
842
|
+
// Create project directory
|
|
843
|
+
fs.mkdirSync(projectName, { recursive: true });
|
|
844
|
+
// Create subdirectories
|
|
845
|
+
if (config.template === 'app') {
|
|
846
|
+
fs.mkdirSync(path.join(projectName, 'src/App'), { recursive: true });
|
|
847
|
+
}
|
|
848
|
+
else {
|
|
849
|
+
fs.mkdirSync(path.join(projectName, 'src/components/HelloWorld'), { recursive: true });
|
|
850
|
+
}
|
|
851
|
+
// Write template files
|
|
852
|
+
for (const [filePath, generator] of Object.entries(template)) {
|
|
853
|
+
const fullPath = path.join(projectName, filePath);
|
|
854
|
+
const dir = path.dirname(fullPath);
|
|
855
|
+
if (!fs.existsSync(dir)) {
|
|
856
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
857
|
+
}
|
|
858
|
+
const content = typeof generator === 'function' ? generator(config) : generator;
|
|
859
|
+
fs.writeFileSync(fullPath, content);
|
|
860
|
+
console.log(chalk.green(' ✓'), filePath);
|
|
861
|
+
}
|
|
862
|
+
console.log();
|
|
863
|
+
console.log(chalk.bold.green('Done!'));
|
|
864
|
+
console.log();
|
|
865
|
+
console.log('Next steps:');
|
|
866
|
+
console.log(chalk.cyan(` cd ${projectName}`));
|
|
867
|
+
console.log(chalk.cyan(' npm install'));
|
|
868
|
+
console.log(chalk.cyan(' npm run dev'));
|
|
869
|
+
console.log();
|
|
870
|
+
console.log('For more info, see:');
|
|
871
|
+
console.log(chalk.blue(' https://docs.brightsy.ai/component-libraries'));
|
|
872
|
+
}
|
|
873
|
+
async function deploy(options) {
|
|
874
|
+
console.log(BANNER);
|
|
875
|
+
console.log(chalk.cyan('Deploying component library to Brightsy...\n'));
|
|
876
|
+
// Ensure user is logged in
|
|
877
|
+
const authConfig = await ensureLoggedIn({ endpoint: options.endpoint });
|
|
878
|
+
if (!authConfig.access_token || !authConfig.account_id) {
|
|
879
|
+
console.error(chalk.red('Error: Authentication failed. Please run: brightsy login'));
|
|
880
|
+
process.exit(1);
|
|
881
|
+
}
|
|
882
|
+
// Check if we're in a valid project directory
|
|
883
|
+
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
884
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
885
|
+
console.error(chalk.red('Error: No package.json found. Run this command from your project root.'));
|
|
886
|
+
process.exit(1);
|
|
887
|
+
}
|
|
888
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
889
|
+
const projectName = packageJson.name;
|
|
890
|
+
// Check if dist/index.js exists, if not, build first
|
|
891
|
+
const distPath = path.join(process.cwd(), 'dist', 'index.js');
|
|
892
|
+
if (!fs.existsSync(distPath)) {
|
|
893
|
+
console.log(chalk.yellow('No build found. Building project first...\n'));
|
|
894
|
+
try {
|
|
895
|
+
execSync('npm run build', { stdio: 'inherit' });
|
|
896
|
+
console.log();
|
|
897
|
+
}
|
|
898
|
+
catch (error) {
|
|
899
|
+
console.error(chalk.red('Build failed. Please fix the errors and try again.'));
|
|
900
|
+
process.exit(1);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
if (!fs.existsSync(distPath)) {
|
|
904
|
+
console.error(chalk.red('Error: dist/index.js not found after build.'));
|
|
905
|
+
process.exit(1);
|
|
906
|
+
}
|
|
907
|
+
// Prompt for missing config
|
|
908
|
+
const prompts_needed = [];
|
|
909
|
+
if (!options.envName) {
|
|
910
|
+
prompts_needed.push({
|
|
911
|
+
type: 'select',
|
|
912
|
+
name: 'envName',
|
|
913
|
+
message: 'Environment:',
|
|
914
|
+
choices: [
|
|
915
|
+
{ title: 'Production', value: 'production' },
|
|
916
|
+
{ title: 'Development', value: 'development' },
|
|
917
|
+
{ title: 'Staging', value: 'staging' },
|
|
918
|
+
]
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
if (!options.version) {
|
|
922
|
+
prompts_needed.push({
|
|
923
|
+
type: 'text',
|
|
924
|
+
name: 'version',
|
|
925
|
+
message: 'Version:',
|
|
926
|
+
initial: packageJson.version || '1.0.0'
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
const answers = prompts_needed.length > 0 ? await prompts(prompts_needed) : {};
|
|
930
|
+
const deployConfig = {
|
|
931
|
+
endpoint: authConfig.endpoint || options.endpoint || 'https://brightsy.ai',
|
|
932
|
+
accountId: authConfig.account_id,
|
|
933
|
+
accessToken: authConfig.access_token,
|
|
934
|
+
libId: options.libId,
|
|
935
|
+
envName: options.envName || answers.envName || 'production',
|
|
936
|
+
version: options.version || answers.version || packageJson.version || '1.0.0',
|
|
937
|
+
};
|
|
938
|
+
const isProduction = deployConfig.envName === 'production';
|
|
939
|
+
const uploadPath = `libs/${projectName}/${deployConfig.version}`;
|
|
940
|
+
console.log(chalk.cyan('\nDeployment Configuration:'));
|
|
941
|
+
console.log(` Account: ${chalk.bold(authConfig.account_slug || authConfig.account_id)}`);
|
|
942
|
+
console.log(` Project: ${chalk.bold(projectName)}`);
|
|
943
|
+
console.log(` Version: ${chalk.bold(deployConfig.version)}`);
|
|
944
|
+
console.log(` Environment: ${chalk.bold(deployConfig.envName)}`);
|
|
945
|
+
console.log(` Upload Path: ${chalk.bold(uploadPath)}`);
|
|
946
|
+
console.log();
|
|
947
|
+
try {
|
|
948
|
+
// Step 1: Get upload URL
|
|
949
|
+
console.log(chalk.cyan('Step 1/3: Getting upload URL...'));
|
|
950
|
+
const uploadUrlResponse = await fetch(`${deployConfig.endpoint}/api/v1beta/${deployConfig.accountId}/files/upload-url`, {
|
|
951
|
+
method: 'POST',
|
|
952
|
+
headers: {
|
|
953
|
+
'Authorization': `Bearer ${deployConfig.accessToken}`,
|
|
954
|
+
'Content-Type': 'application/json'
|
|
955
|
+
},
|
|
956
|
+
body: JSON.stringify({
|
|
957
|
+
path: uploadPath,
|
|
958
|
+
filename: 'index.js'
|
|
959
|
+
})
|
|
960
|
+
});
|
|
961
|
+
if (!uploadUrlResponse.ok) {
|
|
962
|
+
const error = await uploadUrlResponse.json().catch(() => ({}));
|
|
963
|
+
throw new Error(error.message || `HTTP ${uploadUrlResponse.status}`);
|
|
964
|
+
}
|
|
965
|
+
const { uploadUrl, fileUrl } = await uploadUrlResponse.json();
|
|
966
|
+
console.log(chalk.green(' ✓ Upload URL obtained'));
|
|
967
|
+
// Step 2: Upload the file
|
|
968
|
+
console.log(chalk.cyan('Step 2/3: Uploading library...'));
|
|
969
|
+
const fileContent = fs.readFileSync(distPath);
|
|
970
|
+
const uploadResponse = await fetch(uploadUrl, {
|
|
971
|
+
method: 'PUT',
|
|
972
|
+
body: fileContent,
|
|
973
|
+
headers: {
|
|
974
|
+
'Content-Type': 'application/javascript'
|
|
975
|
+
}
|
|
976
|
+
});
|
|
977
|
+
if (!uploadResponse.ok) {
|
|
978
|
+
throw new Error(`Upload failed: HTTP ${uploadResponse.status}`);
|
|
979
|
+
}
|
|
980
|
+
console.log(chalk.green(' ✓ Library uploaded'));
|
|
981
|
+
console.log(chalk.dim(` URL: ${fileUrl}`));
|
|
982
|
+
// Step 3: Register/update library environment (if libId provided)
|
|
983
|
+
if (deployConfig.libId) {
|
|
984
|
+
console.log(chalk.cyan('Step 3/3: Registering library environment...'));
|
|
985
|
+
// Try to create/update the environment
|
|
986
|
+
const envResponse = await fetch(`${deployConfig.endpoint}/api/v1beta/${deployConfig.accountId}/libraries/${deployConfig.libId}/environments`, {
|
|
987
|
+
method: 'POST',
|
|
988
|
+
headers: {
|
|
989
|
+
'Authorization': `Bearer ${deployConfig.accessToken}`,
|
|
990
|
+
'Content-Type': 'application/json'
|
|
991
|
+
},
|
|
992
|
+
body: JSON.stringify({
|
|
993
|
+
name: deployConfig.envName,
|
|
994
|
+
module_url: fileUrl,
|
|
995
|
+
is_production: isProduction
|
|
996
|
+
})
|
|
997
|
+
});
|
|
998
|
+
if (envResponse.ok) {
|
|
999
|
+
console.log(chalk.green(' ✓ Library environment registered'));
|
|
1000
|
+
}
|
|
1001
|
+
else {
|
|
1002
|
+
const error = await envResponse.json().catch(() => ({}));
|
|
1003
|
+
console.log(chalk.yellow(` ⚠ Could not register environment: ${error.message || 'Unknown error'}`));
|
|
1004
|
+
console.log(chalk.yellow(' You may need to register it manually via MCP or the dashboard.'));
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
else {
|
|
1008
|
+
console.log(chalk.dim('Step 3/3: Skipped (no --lib-id provided)'));
|
|
1009
|
+
console.log(chalk.yellow('\nTo complete registration, use MCP tools:'));
|
|
1010
|
+
console.log(chalk.dim(` create_lib_env --lib_id <your-lib-id> --name ${deployConfig.envName} --module_url ${fileUrl} --is_production ${isProduction}`));
|
|
1011
|
+
}
|
|
1012
|
+
console.log();
|
|
1013
|
+
console.log(chalk.bold.green('Deployment complete!'));
|
|
1014
|
+
console.log();
|
|
1015
|
+
console.log('Library URL:');
|
|
1016
|
+
console.log(chalk.blue(` ${fileUrl}`));
|
|
1017
|
+
console.log();
|
|
1018
|
+
if (!deployConfig.libId) {
|
|
1019
|
+
console.log('Next steps:');
|
|
1020
|
+
console.log(chalk.cyan(' 1. Register the library in Brightsy (if not already):'));
|
|
1021
|
+
console.log(chalk.dim(' MCP: create_lib --name "Your Library Name"'));
|
|
1022
|
+
console.log();
|
|
1023
|
+
console.log(chalk.cyan(' 2. Add this environment to the library:'));
|
|
1024
|
+
console.log(chalk.dim(` MCP: create_lib_env --lib_id <lib-uuid> --name ${deployConfig.envName} --module_url ${fileUrl}`));
|
|
1025
|
+
console.log();
|
|
1026
|
+
console.log(chalk.cyan(' 3. Assign the library to a page type:'));
|
|
1027
|
+
console.log(chalk.dim(' MCP: assign_lib_to_page_type --page_type_id <type-uuid> --lib_id <lib-uuid>'));
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
catch (error) {
|
|
1031
|
+
console.error(chalk.red(`\nDeployment failed: ${error.message}`));
|
|
1032
|
+
process.exit(1);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
// CLI setup
|
|
1036
|
+
program
|
|
1037
|
+
.name('create-brightsy-component-lib')
|
|
1038
|
+
.description('Scaffold and deploy Brightsy component libraries')
|
|
1039
|
+
.version('0.1.0');
|
|
1040
|
+
// Create command (default)
|
|
1041
|
+
program
|
|
1042
|
+
.command('create <project-name>', { isDefault: true })
|
|
1043
|
+
.description('Scaffold a new Brightsy component library')
|
|
1044
|
+
.option('-t, --template <type>', 'Template type (components, app)')
|
|
1045
|
+
.action(scaffold);
|
|
1046
|
+
// Login command
|
|
1047
|
+
program
|
|
1048
|
+
.command('login')
|
|
1049
|
+
.description('Authenticate with Brightsy using OAuth')
|
|
1050
|
+
.option('-e, --endpoint <url>', 'API endpoint (default: https://brightsy.ai)')
|
|
1051
|
+
.action((options) => {
|
|
1052
|
+
login({ endpoint: options.endpoint }).catch((error) => {
|
|
1053
|
+
console.error(chalk.red(`\nLogin failed: ${error.message}`));
|
|
1054
|
+
process.exit(1);
|
|
1055
|
+
});
|
|
1056
|
+
});
|
|
1057
|
+
// Logout command
|
|
1058
|
+
program
|
|
1059
|
+
.command('logout')
|
|
1060
|
+
.description('Log out of Brightsy')
|
|
1061
|
+
.action(logout);
|
|
1062
|
+
// Whoami command
|
|
1063
|
+
program
|
|
1064
|
+
.command('whoami')
|
|
1065
|
+
.description('Show current authenticated account')
|
|
1066
|
+
.action(() => {
|
|
1067
|
+
console.log(BANNER);
|
|
1068
|
+
const config = loadConfig();
|
|
1069
|
+
if (config.access_token) {
|
|
1070
|
+
console.log(chalk.green('Logged in as:'));
|
|
1071
|
+
console.log(` Account: ${chalk.bold(config.account_slug || config.account_id || 'Unknown')}`);
|
|
1072
|
+
console.log(` Endpoint: ${chalk.dim(config.endpoint || 'https://brightsy.ai')}`);
|
|
1073
|
+
if (config.expires_at) {
|
|
1074
|
+
const expiresIn = Math.round((config.expires_at - Date.now()) / 1000 / 60);
|
|
1075
|
+
if (expiresIn > 0) {
|
|
1076
|
+
console.log(` Token expires in: ${chalk.dim(expiresIn + ' minutes')}`);
|
|
1077
|
+
}
|
|
1078
|
+
else {
|
|
1079
|
+
console.log(` Token: ${chalk.yellow('Expired (will refresh on next command)')}`);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
else {
|
|
1084
|
+
console.log(chalk.yellow('Not logged in.'));
|
|
1085
|
+
console.log(chalk.dim('Run: create-brightsy-component-lib login'));
|
|
1086
|
+
}
|
|
1087
|
+
});
|
|
1088
|
+
// Deploy command
|
|
1089
|
+
program
|
|
1090
|
+
.command('deploy')
|
|
1091
|
+
.description('Deploy the library to Brightsy file storage')
|
|
1092
|
+
.option('-e, --endpoint <url>', 'API endpoint (default: https://brightsy.ai)')
|
|
1093
|
+
.option('-l, --lib-id <id>', 'Library ID to register environment for')
|
|
1094
|
+
.option('-n, --env-name <name>', 'Environment name (production, development, staging)')
|
|
1095
|
+
.option('-v, --version <version>', 'Version to deploy (default: from package.json)')
|
|
1096
|
+
.action((options) => {
|
|
1097
|
+
deploy({
|
|
1098
|
+
endpoint: options.endpoint,
|
|
1099
|
+
libId: options.libId,
|
|
1100
|
+
envName: options.envName,
|
|
1101
|
+
version: options.version
|
|
1102
|
+
});
|
|
1103
|
+
});
|
|
1104
|
+
program.parse();
|
|
1105
|
+
//# sourceMappingURL=index.js.map
|