luxlabs 1.0.21 → 1.0.24
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 +16 -21
- package/commands/ab-tests.js +14 -11
- package/commands/agents.js +11 -11
- package/commands/data.js +19 -17
- package/commands/deploy.js +145 -82
- package/commands/flows.js +152 -133
- package/commands/interface/init.js +36 -35
- package/commands/interface.js +135 -10
- package/commands/knowledge.js +3 -3
- package/commands/list.js +6 -24
- package/commands/login.js +31 -26
- package/commands/logout.js +13 -4
- package/commands/logs.js +17 -66
- package/commands/project.js +74 -47
- package/commands/secrets.js +1 -1
- package/commands/servers.js +9 -113
- package/commands/storage.js +1 -1
- package/commands/tools.js +4 -4
- package/commands/validate-data-lux.js +5 -2
- package/commands/voice-agents.js +22 -18
- package/lib/config.js +235 -83
- package/lib/helpers.js +6 -4
- package/lux.js +4 -94
- package/package.json +6 -1
- package/templates/interface-boilerplate/components/auth/sign-in-form.tsx +41 -34
- package/templates/interface-boilerplate/components/auth/sign-up-form.tsx +41 -34
- package/templates/interface-boilerplate/components/providers/posthog-provider.tsx +41 -26
- package/templates/interface-boilerplate/gitignore.template +4 -0
- package/templates/interface-boilerplate/lib/auth.config.ts +3 -2
- package/templates/interface-boilerplate/lib/knowledge.ts +2 -2
- package/templates/interface-boilerplate/middleware.ts +14 -3
- package/templates/interface-boilerplate/next-env.d.ts +6 -0
- package/templates/interface-boilerplate/package-lock.json +432 -8
- package/commands/dev.js +0 -578
- package/commands/init.js +0 -126
- package/commands/link.js +0 -127
- package/commands/up.js +0 -211
package/commands/logout.js
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
|
-
const { loadConfig,
|
|
2
|
+
const { loadConfig, loadUnifiedConfig, saveUnifiedConfig } = require('../lib/config');
|
|
3
3
|
|
|
4
4
|
async function logout() {
|
|
5
5
|
const config = loadConfig();
|
|
6
6
|
|
|
7
|
-
if (!config || !config.
|
|
7
|
+
if (!config || !config.apiKey) {
|
|
8
8
|
console.log(chalk.yellow('Not currently logged in.'));
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
const orgId = config.orgId;
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
// Clear the current org's credentials from unified config
|
|
15
|
+
const unified = loadUnifiedConfig();
|
|
16
|
+
if (unified.orgs && unified.orgs[orgId]) {
|
|
17
|
+
delete unified.orgs[orgId].apiKey;
|
|
18
|
+
}
|
|
19
|
+
unified.currentOrg = null;
|
|
20
|
+
unified.currentProject = null;
|
|
21
|
+
saveUnifiedConfig(unified);
|
|
22
|
+
|
|
23
|
+
console.log(chalk.green(`Successfully logged out from ${config.orgName || orgId}`));
|
|
15
24
|
}
|
|
16
25
|
|
|
17
26
|
module.exports = {
|
package/commands/logs.js
CHANGED
|
@@ -11,39 +11,34 @@ async function logs(options) {
|
|
|
11
11
|
// Check authentication
|
|
12
12
|
if (!isAuthenticated()) {
|
|
13
13
|
console.log(
|
|
14
|
-
chalk.red('
|
|
14
|
+
chalk.red('Not authenticated. Run'),
|
|
15
15
|
chalk.white('lux login'),
|
|
16
16
|
chalk.red('first.')
|
|
17
17
|
);
|
|
18
18
|
process.exit(1);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
if (!
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
21
|
+
// Use provided interface ID or fall back to local config
|
|
22
|
+
let interfaceId = options.id;
|
|
23
|
+
if (!interfaceId) {
|
|
24
|
+
const interfaceConfig = loadInterfaceConfig();
|
|
25
|
+
if (!interfaceConfig || !interfaceConfig.id) {
|
|
26
|
+
console.log(
|
|
27
|
+
chalk.red('No interface found. Provide an interface ID or run from a project directory.')
|
|
28
|
+
);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
interfaceId = interfaceConfig.id;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
const apiUrl = getApiUrl();
|
|
33
|
-
const interfaceId = interfaceConfig.id;
|
|
34
35
|
const logType = options.type || 'runtime';
|
|
35
36
|
|
|
36
37
|
try {
|
|
37
|
-
|
|
38
|
-
// Follow mode - poll for new logs
|
|
39
|
-
await followLogs(interfaceId, apiUrl, logType);
|
|
40
|
-
} else {
|
|
41
|
-
// One-time fetch
|
|
42
|
-
await fetchLogs(interfaceId, apiUrl, logType);
|
|
43
|
-
}
|
|
38
|
+
await fetchLogs(interfaceId, apiUrl, logType);
|
|
44
39
|
} catch (error) {
|
|
45
40
|
console.error(
|
|
46
|
-
chalk.red('\
|
|
41
|
+
chalk.red('\nError:'),
|
|
47
42
|
error.response?.data?.error || error.message
|
|
48
43
|
);
|
|
49
44
|
|
|
@@ -75,50 +70,6 @@ async function fetchLogs(interfaceId, apiUrl, logType) {
|
|
|
75
70
|
}
|
|
76
71
|
}
|
|
77
72
|
|
|
78
|
-
async function followLogs(interfaceId, apiUrl, logType) {
|
|
79
|
-
console.log(
|
|
80
|
-
chalk.cyan(`\n📋 Following ${logType} logs... (Ctrl+C to stop)\n`)
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
let lastTimestamp = null;
|
|
84
|
-
|
|
85
|
-
while (true) {
|
|
86
|
-
try {
|
|
87
|
-
const endpoint =
|
|
88
|
-
logType === 'build'
|
|
89
|
-
? `/api/interfaces/${interfaceId}/build-logs`
|
|
90
|
-
: `/api/interfaces/${interfaceId}/runtime-logs`;
|
|
91
|
-
|
|
92
|
-
const params = lastTimestamp ? { since: lastTimestamp } : {};
|
|
93
|
-
|
|
94
|
-
const { data } = await axios.get(`${apiUrl}${endpoint}`, {
|
|
95
|
-
headers: getAuthHeaders(),
|
|
96
|
-
params,
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
if (data.logs && data.logs.length > 0) {
|
|
100
|
-
if (logType === 'build') {
|
|
101
|
-
displayBuildLogs(data.logs);
|
|
102
|
-
} else {
|
|
103
|
-
displayRuntimeLogs(data.logs);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Update last timestamp
|
|
107
|
-
const lastLog = data.logs[data.logs.length - 1];
|
|
108
|
-
if (lastLog.timestamp) {
|
|
109
|
-
lastTimestamp = lastLog.timestamp;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Wait 2 seconds before next poll
|
|
114
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
115
|
-
} catch (error) {
|
|
116
|
-
console.error(chalk.red('Error fetching logs:'), error.message);
|
|
117
|
-
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
73
|
function displayBuildLogs(logs) {
|
|
123
74
|
if (!logs || logs.length === 0) {
|
|
124
75
|
console.log(chalk.dim('No logs available yet.'));
|
|
@@ -158,15 +109,15 @@ function displayRuntimeLogs(logs) {
|
|
|
158
109
|
|
|
159
110
|
switch (level) {
|
|
160
111
|
case 'error':
|
|
161
|
-
prefix = '
|
|
112
|
+
prefix = '[ERROR] ';
|
|
162
113
|
color = chalk.red;
|
|
163
114
|
break;
|
|
164
115
|
case 'warn':
|
|
165
|
-
prefix = '
|
|
116
|
+
prefix = '[WARN] ';
|
|
166
117
|
color = chalk.yellow;
|
|
167
118
|
break;
|
|
168
119
|
case 'info':
|
|
169
|
-
prefix = '
|
|
120
|
+
prefix = '[INFO] ';
|
|
170
121
|
color = chalk.cyan;
|
|
171
122
|
break;
|
|
172
123
|
default:
|
package/commands/project.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const { execSync } = require('child_process');
|
|
3
|
+
const { execSync, execFileSync } = require('child_process');
|
|
4
4
|
const axios = require('axios');
|
|
5
5
|
const ora = require('ora');
|
|
6
6
|
const chalk = require('chalk');
|
|
@@ -10,15 +10,19 @@ const {
|
|
|
10
10
|
isAuthenticated,
|
|
11
11
|
getOrgId,
|
|
12
12
|
getProjectId,
|
|
13
|
+
getShortId,
|
|
13
14
|
LUX_STUDIO_DIR,
|
|
15
|
+
FOLDER_NAMES,
|
|
14
16
|
} = require('../lib/config');
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* Get the project directory path
|
|
18
|
-
* ~/.lux-studio/{
|
|
20
|
+
* ~/.lux-studio/{shortOrgId}/p/{shortProjectId}/
|
|
19
21
|
*/
|
|
20
22
|
function getProjectDir(orgId, projectId) {
|
|
21
|
-
|
|
23
|
+
const shortOrgId = getShortId(orgId);
|
|
24
|
+
const shortProjectId = getShortId(projectId);
|
|
25
|
+
return path.join(LUX_STUDIO_DIR, shortOrgId, FOLDER_NAMES.projects, shortProjectId);
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
/**
|
|
@@ -115,39 +119,42 @@ async function deployProject(projectId) {
|
|
|
115
119
|
console.log(chalk.dim(`Repository: LuxUserProjects/${repoName}\n`));
|
|
116
120
|
|
|
117
121
|
// Step 0: Build check - verify all interfaces build successfully (if any exist)
|
|
118
|
-
|
|
119
|
-
|
|
122
|
+
// Use short folder name for interfaces directory
|
|
123
|
+
const interfacesDir = path.join(projectDir, FOLDER_NAMES.interfaces);
|
|
124
|
+
let shortInterfaceIds = [];
|
|
120
125
|
|
|
121
126
|
if (fs.existsSync(interfacesDir)) {
|
|
122
|
-
|
|
127
|
+
// Directory entries are short IDs after migration
|
|
128
|
+
shortInterfaceIds = fs.readdirSync(interfacesDir).filter(f => {
|
|
123
129
|
const stat = fs.statSync(path.join(interfacesDir, f));
|
|
124
130
|
return stat.isDirectory();
|
|
125
131
|
});
|
|
126
132
|
|
|
127
|
-
if (
|
|
128
|
-
console.log(chalk.dim(`Found ${
|
|
133
|
+
if (shortInterfaceIds.length > 0) {
|
|
134
|
+
console.log(chalk.dim(`Found ${shortInterfaceIds.length} interface(s) to build\n`));
|
|
129
135
|
|
|
130
|
-
for (const
|
|
131
|
-
|
|
136
|
+
for (const shortInterfaceId of shortInterfaceIds) {
|
|
137
|
+
// Path uses short IDs
|
|
138
|
+
const repoDir = path.join(interfacesDir, shortInterfaceId, 'repo');
|
|
132
139
|
const packageJsonPath = path.join(repoDir, 'package.json');
|
|
133
140
|
|
|
134
141
|
if (fs.existsSync(packageJsonPath)) {
|
|
135
|
-
const buildSpinner = ora(`Building interface ${
|
|
142
|
+
const buildSpinner = ora(`Building interface ${shortInterfaceId}...`).start();
|
|
136
143
|
|
|
137
144
|
try {
|
|
138
145
|
// Install dependencies if node_modules doesn't exist
|
|
139
146
|
const nodeModulesPath = path.join(repoDir, 'node_modules');
|
|
140
147
|
if (!fs.existsSync(nodeModulesPath)) {
|
|
141
|
-
buildSpinner.text = `Installing dependencies for ${
|
|
148
|
+
buildSpinner.text = `Installing dependencies for ${shortInterfaceId}...`;
|
|
142
149
|
execSync('npm install', { cwd: repoDir, stdio: 'pipe', timeout: 300000 });
|
|
143
150
|
}
|
|
144
151
|
|
|
145
152
|
// Run build
|
|
146
|
-
buildSpinner.text = `Building interface ${
|
|
153
|
+
buildSpinner.text = `Building interface ${shortInterfaceId}...`;
|
|
147
154
|
execSync('npm run build', { cwd: repoDir, stdio: 'pipe', timeout: 300000 });
|
|
148
|
-
buildSpinner.succeed(`Interface ${
|
|
155
|
+
buildSpinner.succeed(`Interface ${shortInterfaceId} built successfully`);
|
|
149
156
|
} catch (error) {
|
|
150
|
-
buildSpinner.fail(`Interface ${
|
|
157
|
+
buildSpinner.fail(`Interface ${shortInterfaceId} build failed`);
|
|
151
158
|
|
|
152
159
|
console.log(chalk.red('\n═══════════════════════════════════════════════════════════════'));
|
|
153
160
|
console.log(chalk.red(' BUILD FAILED'));
|
|
@@ -164,7 +171,7 @@ async function deployProject(projectId) {
|
|
|
164
171
|
}
|
|
165
172
|
|
|
166
173
|
console.log(chalk.dim('\n───────────────────────────────────────────────────────────────'));
|
|
167
|
-
console.log(chalk.cyan('\
|
|
174
|
+
console.log(chalk.cyan('\nTo fix this issue, please copy the build error above and'));
|
|
168
175
|
console.log(chalk.cyan(' share it with your coding agent (Claude) for assistance.\n'));
|
|
169
176
|
console.log(chalk.dim(' Interface directory:'));
|
|
170
177
|
console.log(chalk.dim(` ${repoDir}\n`));
|
|
@@ -230,11 +237,6 @@ async function deployProject(projectId) {
|
|
|
230
237
|
// Create/update .gitignore
|
|
231
238
|
createGitignore(projectDir);
|
|
232
239
|
|
|
233
|
-
// Always configure git user (required for commits)
|
|
234
|
-
// Use jason@uselux.ai so Vercel recognizes the commit author as a team member
|
|
235
|
-
execSync('git config user.email "jason@uselux.ai"', { cwd: projectDir, stdio: 'pipe' });
|
|
236
|
-
execSync('git config user.name "Lux Deploy"', { cwd: projectDir, stdio: 'pipe' });
|
|
237
|
-
|
|
238
240
|
gitSpinner.succeed('Git repository ready');
|
|
239
241
|
} catch (error) {
|
|
240
242
|
gitSpinner.fail('Failed to prepare git repository');
|
|
@@ -273,21 +275,31 @@ async function deployProject(projectId) {
|
|
|
273
275
|
// Check if there are staged changes
|
|
274
276
|
const stagedDiff = execSync('git diff --cached --name-only', { cwd: projectDir, encoding: 'utf-8' });
|
|
275
277
|
|
|
278
|
+
// Use --author flag to set commit author without persisting to repo config
|
|
279
|
+
// Escape special characters for shell double quotes (\ " $ ` and newlines)
|
|
280
|
+
const escapedMessage = commitMessage
|
|
281
|
+
.replace(/\\/g, '\\\\')
|
|
282
|
+
.replace(/"/g, '\\"')
|
|
283
|
+
.replace(/\$/g, '\\$')
|
|
284
|
+
.replace(/`/g, '\\`')
|
|
285
|
+
.replace(/[\r\n]/g, ' ');
|
|
286
|
+
const authorArg = '--author="Lux Deploy <jason@uselux.ai>"';
|
|
287
|
+
|
|
276
288
|
if (stagedDiff.trim() === '' && hasCommits) {
|
|
277
289
|
// No staged changes and already has commits
|
|
278
290
|
commitSpinner.succeed('No changes to commit');
|
|
279
291
|
} else if (!hasCommits) {
|
|
280
292
|
// First commit - use --allow-empty if needed
|
|
281
293
|
try {
|
|
282
|
-
execSync(`git commit -m "${
|
|
294
|
+
execSync(`git commit ${authorArg} -m "${escapedMessage}"`, { cwd: projectDir, stdio: 'pipe' });
|
|
283
295
|
commitSpinner.succeed('Created initial commit');
|
|
284
296
|
} catch {
|
|
285
|
-
execSync(`git commit --allow-empty -m "${
|
|
297
|
+
execSync(`git commit ${authorArg} --allow-empty -m "${escapedMessage}"`, { cwd: projectDir, stdio: 'pipe' });
|
|
286
298
|
commitSpinner.succeed('Created initial commit');
|
|
287
299
|
}
|
|
288
300
|
} else {
|
|
289
301
|
// Has staged changes
|
|
290
|
-
execSync(`git commit -m "${
|
|
302
|
+
execSync(`git commit ${authorArg} -m "${escapedMessage}"`, { cwd: projectDir, stdio: 'pipe' });
|
|
291
303
|
commitSpinner.succeed('Commit created');
|
|
292
304
|
}
|
|
293
305
|
} catch (error) {
|
|
@@ -311,21 +323,25 @@ async function deployProject(projectId) {
|
|
|
311
323
|
const authUrl = repoUrl.replace('https://github.com/', `https://x-access-token:${token}@github.com/`);
|
|
312
324
|
|
|
313
325
|
// Set remote (or update if exists)
|
|
326
|
+
// Use execFileSync to avoid shell injection with URLs
|
|
314
327
|
try {
|
|
315
|
-
|
|
328
|
+
execFileSync('git', ['remote', 'add', 'origin', authUrl], { cwd: projectDir, stdio: 'pipe' });
|
|
316
329
|
} catch {
|
|
317
330
|
// Remote might already exist, update it
|
|
318
|
-
|
|
331
|
+
execFileSync('git', ['remote', 'set-url', 'origin', authUrl], { cwd: projectDir, stdio: 'pipe' });
|
|
319
332
|
}
|
|
320
333
|
|
|
321
334
|
// Always ensure we're on main branch before pushing
|
|
322
335
|
execSync('git branch -M main', { cwd: projectDir, stdio: 'pipe' });
|
|
323
336
|
|
|
324
|
-
// Force push to main
|
|
325
|
-
|
|
337
|
+
// Force push to main (use -c for one-time config to avoid persisting to repo)
|
|
338
|
+
// Include user config so git has valid committer identity for any merge commits
|
|
339
|
+
// Note: http.postBuffer only applies to HTTPS remotes (which we use with token auth)
|
|
340
|
+
execSync('git -c http.postBuffer=524288000 -c user.name="Lux Deploy" -c user.email="jason@uselux.ai" push -f origin main', { cwd: projectDir, stdio: 'pipe', timeout: 120000 });
|
|
326
341
|
|
|
327
342
|
// Clear the token from remote URL after push (security)
|
|
328
|
-
|
|
343
|
+
// Use execFileSync to avoid shell injection with URLs
|
|
344
|
+
execFileSync('git', ['remote', 'set-url', 'origin', repoUrl], { cwd: projectDir, stdio: 'pipe' });
|
|
329
345
|
|
|
330
346
|
pushSpinner.succeed('Pushed to GitHub');
|
|
331
347
|
} catch (error) {
|
|
@@ -333,8 +349,9 @@ async function deployProject(projectId) {
|
|
|
333
349
|
console.error(chalk.red('\nError:'), error.message);
|
|
334
350
|
|
|
335
351
|
// Clear token from URL even on failure
|
|
352
|
+
// Use execFileSync to avoid shell injection with URLs
|
|
336
353
|
try {
|
|
337
|
-
|
|
354
|
+
execFileSync('git', ['remote', 'set-url', 'origin', repoUrl], { cwd: projectDir, stdio: 'pipe' });
|
|
338
355
|
} catch {
|
|
339
356
|
// Ignore
|
|
340
357
|
}
|
|
@@ -369,7 +386,8 @@ async function deployProject(projectId) {
|
|
|
369
386
|
vercelSpinner.succeed(`Interface ${iface.interfaceId.substring(0, 8)} deployment triggered`);
|
|
370
387
|
|
|
371
388
|
// Sync local custom domains to Vercel after successful deployment
|
|
372
|
-
const
|
|
389
|
+
const shortInterfaceIdForMeta = getShortId(iface.interfaceId);
|
|
390
|
+
const interfaceMetadataPath = path.join(interfacesDir, shortInterfaceIdForMeta, 'metadata.json');
|
|
373
391
|
if (fs.existsSync(interfaceMetadataPath)) {
|
|
374
392
|
try {
|
|
375
393
|
const metadata = JSON.parse(fs.readFileSync(interfaceMetadataPath, 'utf-8'));
|
|
@@ -448,11 +466,11 @@ async function deployProject(projectId) {
|
|
|
448
466
|
|
|
449
467
|
for (const result of deploymentResults) {
|
|
450
468
|
if (result.success) {
|
|
451
|
-
console.log(chalk.green(
|
|
469
|
+
console.log(chalk.green(`${result.interfaceId.substring(0, 8)}`));
|
|
452
470
|
console.log(chalk.dim(` URL: ${result.url}`));
|
|
453
471
|
console.log(chalk.dim(` Deployment ID: ${result.deploymentId}`));
|
|
454
472
|
} else {
|
|
455
|
-
console.log(chalk.red(
|
|
473
|
+
console.log(chalk.red(`${result.interfaceId.substring(0, 8)}`));
|
|
456
474
|
console.log(chalk.dim(` Error: ${result.error}`));
|
|
457
475
|
}
|
|
458
476
|
console.log('');
|
|
@@ -460,7 +478,7 @@ async function deployProject(projectId) {
|
|
|
460
478
|
|
|
461
479
|
if (interfaceHasErrors) {
|
|
462
480
|
console.log(chalk.dim('───────────────────────────────────────────────────────────────'));
|
|
463
|
-
console.log(chalk.cyan('\
|
|
481
|
+
console.log(chalk.cyan('\nTo debug failed deployments, check the Vercel dashboard:'));
|
|
464
482
|
console.log(chalk.dim(' https://vercel.com/dashboard\n'));
|
|
465
483
|
console.log(chalk.dim('───────────────────────────────────────────────────────────────\n'));
|
|
466
484
|
}
|
|
@@ -475,24 +493,33 @@ async function deployProject(projectId) {
|
|
|
475
493
|
}
|
|
476
494
|
|
|
477
495
|
// Step 7: Deploy flows (if any exist)
|
|
478
|
-
|
|
496
|
+
// Use short folder name for flows directory
|
|
497
|
+
const flowsDir = path.join(projectDir, FOLDER_NAMES.flows);
|
|
479
498
|
if (fs.existsSync(flowsDir)) {
|
|
480
|
-
|
|
499
|
+
// Each flow is in its own folder: f/{shortFlowId}/draft.json
|
|
500
|
+
const flowDirs = fs.readdirSync(flowsDir).filter(f => {
|
|
501
|
+
const stat = fs.statSync(path.join(flowsDir, f));
|
|
502
|
+
return stat.isDirectory();
|
|
503
|
+
});
|
|
481
504
|
|
|
482
|
-
if (
|
|
483
|
-
console.log(chalk.dim(`\nDeploying ${
|
|
505
|
+
if (flowDirs.length > 0) {
|
|
506
|
+
console.log(chalk.dim(`\nDeploying ${flowDirs.length} flow(s)...\n`));
|
|
484
507
|
|
|
485
508
|
const flowResults = [];
|
|
486
509
|
let flowHasErrors = false;
|
|
487
510
|
|
|
488
|
-
for (const
|
|
489
|
-
const
|
|
490
|
-
const flowSpinner = ora(`Deploying flow ${flowId.substring(0, 8)}...`).start();
|
|
511
|
+
for (const shortFlowId of flowDirs) {
|
|
512
|
+
const flowSpinner = ora(`Deploying flow ${shortFlowId}...`).start();
|
|
491
513
|
|
|
492
514
|
try {
|
|
493
|
-
// Read flow data
|
|
494
|
-
const flowPath = path.join(flowsDir,
|
|
515
|
+
// Read flow data from draft.json in the flow's folder
|
|
516
|
+
const flowPath = path.join(flowsDir, shortFlowId, 'draft.json');
|
|
517
|
+
if (!fs.existsSync(flowPath)) {
|
|
518
|
+
flowSpinner.warn(`Flow ${shortFlowId} has no draft.json, skipping`);
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
495
521
|
const flowData = JSON.parse(fs.readFileSync(flowPath, 'utf-8'));
|
|
522
|
+
const flowId = flowData.id || shortFlowId; // Use ID from flow data if available
|
|
496
523
|
|
|
497
524
|
// Deploy to cloud API using /publish endpoint
|
|
498
525
|
// The publish endpoint supports CLI auth and will create the flow if it doesn't exist
|
|
@@ -529,10 +556,10 @@ async function deployProject(projectId) {
|
|
|
529
556
|
fs.writeFileSync(flowPath, JSON.stringify(flowData, null, 2));
|
|
530
557
|
} catch (error) {
|
|
531
558
|
flowHasErrors = true;
|
|
532
|
-
flowSpinner.fail(`Flow ${
|
|
559
|
+
flowSpinner.fail(`Flow ${shortFlowId} deployment failed`);
|
|
533
560
|
const errorData = error.response?.data;
|
|
534
561
|
flowResults.push({
|
|
535
|
-
flowId,
|
|
562
|
+
flowId: shortFlowId,
|
|
536
563
|
success: false,
|
|
537
564
|
error: errorData?.error || error.message,
|
|
538
565
|
});
|
|
@@ -549,12 +576,12 @@ async function deployProject(projectId) {
|
|
|
549
576
|
|
|
550
577
|
for (const result of flowResults) {
|
|
551
578
|
if (result.success) {
|
|
552
|
-
console.log(chalk.green(
|
|
579
|
+
console.log(chalk.green(`${result.name || result.flowId.substring(0, 8)}`));
|
|
553
580
|
if (result.webhookUrl) {
|
|
554
581
|
console.log(chalk.dim(` Webhook: ${result.webhookUrl}`));
|
|
555
582
|
}
|
|
556
583
|
} else {
|
|
557
|
-
console.log(chalk.red(
|
|
584
|
+
console.log(chalk.red(`${result.flowId.substring(0, 8)}`));
|
|
558
585
|
console.log(chalk.dim(` Error: ${result.error}`));
|
|
559
586
|
}
|
|
560
587
|
}
|
package/commands/secrets.js
CHANGED
package/commands/servers.js
CHANGED
|
@@ -83,7 +83,7 @@ async function listServers() {
|
|
|
83
83
|
return;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
console.log(chalk.cyan('\
|
|
86
|
+
console.log(chalk.cyan('\nRunning Dev Servers\n'));
|
|
87
87
|
|
|
88
88
|
for (const server of registry.servers) {
|
|
89
89
|
console.log(chalk.white(` ${server.appName || server.appId}`));
|
|
@@ -139,9 +139,8 @@ async function getLogs(appNameOrId, options = {}) {
|
|
|
139
139
|
|
|
140
140
|
// Read and display logs
|
|
141
141
|
const lines = options.lines || 50;
|
|
142
|
-
const follow = options.follow || false;
|
|
143
142
|
|
|
144
|
-
console.log(chalk.cyan(`\
|
|
143
|
+
console.log(chalk.cyan(`\nLogs for ${server.appName || server.appId}`));
|
|
145
144
|
console.log(chalk.dim(` Port: ${server.port} | Log file: ${server.logFile}\n`));
|
|
146
145
|
console.log(chalk.dim('─'.repeat(60)));
|
|
147
146
|
|
|
@@ -155,50 +154,18 @@ async function getLogs(appNameOrId, options = {}) {
|
|
|
155
154
|
});
|
|
156
155
|
|
|
157
156
|
console.log(chalk.dim('─'.repeat(60)));
|
|
158
|
-
|
|
159
|
-
if (follow) {
|
|
160
|
-
console.log(chalk.dim('\nWatching for new logs... (Ctrl+C to stop)\n'));
|
|
161
|
-
|
|
162
|
-
// Watch the file for changes
|
|
163
|
-
let lastSize = fs.statSync(server.logFile).size;
|
|
164
|
-
|
|
165
|
-
const watcher = fs.watchFile(server.logFile, { interval: 500 }, (curr, prev) => {
|
|
166
|
-
if (curr.size > lastSize) {
|
|
167
|
-
// Read new content
|
|
168
|
-
const fd = fs.openSync(server.logFile, 'r');
|
|
169
|
-
const buffer = Buffer.alloc(curr.size - lastSize);
|
|
170
|
-
fs.readSync(fd, buffer, 0, buffer.length, lastSize);
|
|
171
|
-
fs.closeSync(fd);
|
|
172
|
-
|
|
173
|
-
const newContent = buffer.toString('utf8');
|
|
174
|
-
newContent.split('\n').filter(l => l.trim()).forEach(line => {
|
|
175
|
-
printLogLine(line);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
lastSize = curr.size;
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
// Handle Ctrl+C
|
|
183
|
-
process.on('SIGINT', () => {
|
|
184
|
-
fs.unwatchFile(server.logFile);
|
|
185
|
-
console.log(chalk.dim('\n\nStopped watching logs.\n'));
|
|
186
|
-
process.exit(0);
|
|
187
|
-
});
|
|
188
|
-
} else {
|
|
189
|
-
console.log(chalk.dim(`\nShowing last ${displayLines.length} lines. Use -f to follow.\n`));
|
|
190
|
-
}
|
|
157
|
+
console.log(chalk.dim(`\nShowing last ${displayLines.length} lines. Use --lines <number> to adjust.\n`));
|
|
191
158
|
}
|
|
192
159
|
|
|
193
160
|
/**
|
|
194
161
|
* Print a log line with appropriate coloring
|
|
195
162
|
*/
|
|
196
163
|
function printLogLine(line) {
|
|
197
|
-
if (line.includes('error') || line.includes('Error') || line.includes('ERROR')
|
|
164
|
+
if (line.includes('error') || line.includes('Error') || line.includes('ERROR')) {
|
|
198
165
|
console.log(chalk.red(line));
|
|
199
|
-
} else if (line.includes('warn') || line.includes('Warn') || line.includes('WARN')
|
|
166
|
+
} else if (line.includes('warn') || line.includes('Warn') || line.includes('WARN')) {
|
|
200
167
|
console.log(chalk.yellow(line));
|
|
201
|
-
} else if (line.includes('
|
|
168
|
+
} else if (line.includes('Ready') || line.includes('success')) {
|
|
202
169
|
console.log(chalk.green(line));
|
|
203
170
|
} else if (line.includes('[stderr]')) {
|
|
204
171
|
console.log(chalk.yellow(line));
|
|
@@ -284,7 +251,7 @@ async function restartServer(appNameOrId) {
|
|
|
284
251
|
const signalFile = path.join(signalDir, `${server.appId}.signal`);
|
|
285
252
|
fs.writeFileSync(signalFile, Date.now().toString());
|
|
286
253
|
|
|
287
|
-
console.log(chalk.green(`\
|
|
254
|
+
console.log(chalk.green(`\nRestart signal sent for ${server.appName || server.appId}`));
|
|
288
255
|
console.log(chalk.dim(` The server will restart momentarily.\n`));
|
|
289
256
|
}
|
|
290
257
|
|
|
@@ -352,9 +319,8 @@ async function getBrowserConsoleLogs(appNameOrId, options = {}) {
|
|
|
352
319
|
}
|
|
353
320
|
|
|
354
321
|
const lines = options.lines || 50;
|
|
355
|
-
const follow = options.follow || false;
|
|
356
322
|
|
|
357
|
-
console.log(chalk.cyan(`\
|
|
323
|
+
console.log(chalk.cyan(`\nBrowser Console Logs for ${server.appName || server.appId}`));
|
|
358
324
|
console.log(chalk.dim(` Port: ${server.port} | Log file: ${consoleLogFile}\n`));
|
|
359
325
|
console.log(chalk.dim('─'.repeat(60)));
|
|
360
326
|
|
|
@@ -368,36 +334,7 @@ async function getBrowserConsoleLogs(appNameOrId, options = {}) {
|
|
|
368
334
|
});
|
|
369
335
|
|
|
370
336
|
console.log(chalk.dim('─'.repeat(60)));
|
|
371
|
-
|
|
372
|
-
if (follow) {
|
|
373
|
-
console.log(chalk.dim('\nWatching for new logs... (Ctrl+C to stop)\n'));
|
|
374
|
-
|
|
375
|
-
let lastSize = fs.statSync(consoleLogFile).size;
|
|
376
|
-
|
|
377
|
-
fs.watchFile(consoleLogFile, { interval: 500 }, (curr) => {
|
|
378
|
-
if (curr.size > lastSize) {
|
|
379
|
-
const fd = fs.openSync(consoleLogFile, 'r');
|
|
380
|
-
const buffer = Buffer.alloc(curr.size - lastSize);
|
|
381
|
-
fs.readSync(fd, buffer, 0, buffer.length, lastSize);
|
|
382
|
-
fs.closeSync(fd);
|
|
383
|
-
|
|
384
|
-
const newContent = buffer.toString('utf8');
|
|
385
|
-
newContent.split('\n').filter(l => l.trim()).forEach(line => {
|
|
386
|
-
printConsoleLine(line);
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
lastSize = curr.size;
|
|
390
|
-
}
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
process.on('SIGINT', () => {
|
|
394
|
-
fs.unwatchFile(consoleLogFile);
|
|
395
|
-
console.log(chalk.dim('\n\nStopped watching logs.\n'));
|
|
396
|
-
process.exit(0);
|
|
397
|
-
});
|
|
398
|
-
} else {
|
|
399
|
-
console.log(chalk.dim(`\nShowing last ${displayLines.length} lines. Use -f to follow.\n`));
|
|
400
|
-
}
|
|
337
|
+
console.log(chalk.dim(`\nShowing last ${displayLines.length} lines. Use --lines <number> to adjust.\n`));
|
|
401
338
|
}
|
|
402
339
|
|
|
403
340
|
/**
|
|
@@ -418,49 +355,8 @@ function printConsoleLine(line) {
|
|
|
418
355
|
}
|
|
419
356
|
}
|
|
420
357
|
|
|
421
|
-
/**
|
|
422
|
-
* Handle the logs command
|
|
423
|
-
*/
|
|
424
|
-
async function handleLogs(args = [], options = {}) {
|
|
425
|
-
if (args.length === 0) {
|
|
426
|
-
// No app specified - show help or list servers
|
|
427
|
-
const registry = await loadRegistry(true);
|
|
428
|
-
|
|
429
|
-
if (!registry.servers || registry.servers.length === 0) {
|
|
430
|
-
console.log(chalk.yellow('\nNo dev servers are currently running.'));
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
if (registry.servers.length === 1) {
|
|
435
|
-
// Only one server, show its logs
|
|
436
|
-
if (options.console) {
|
|
437
|
-
await getBrowserConsoleLogs(registry.servers[0].appId, options);
|
|
438
|
-
} else {
|
|
439
|
-
await getLogs(registry.servers[0].appId, options);
|
|
440
|
-
}
|
|
441
|
-
return;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
// Multiple servers - ask user to specify
|
|
445
|
-
console.log(chalk.cyan('\nMultiple servers running. Specify which app:\n'));
|
|
446
|
-
registry.servers.forEach(s => {
|
|
447
|
-
console.log(chalk.white(` lux logs ${s.appName || s.appId}`));
|
|
448
|
-
});
|
|
449
|
-
console.log('');
|
|
450
|
-
return;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
const appNameOrId = args[0];
|
|
454
|
-
if (options.console) {
|
|
455
|
-
await getBrowserConsoleLogs(appNameOrId, options);
|
|
456
|
-
} else {
|
|
457
|
-
await getLogs(appNameOrId, options);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
358
|
module.exports = {
|
|
462
359
|
handleServers,
|
|
463
|
-
handleLogs,
|
|
464
360
|
listServers,
|
|
465
361
|
getLogs,
|
|
466
362
|
getBrowserConsoleLogs,
|
package/commands/storage.js
CHANGED