@boltic/cli 1.0.40 ā 1.0.42
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 +64 -15
- package/api/serverless.js +150 -5
- package/cli.js +69 -34
- package/commands/env.js +11 -2
- package/commands/integration.js +11 -2
- package/commands/mcp.js +9 -2
- package/commands/serverless.js +1085 -271
- package/helper/verbose.js +62 -0
- package/package.json +1 -1
package/commands/serverless.js
CHANGED
|
@@ -38,7 +38,11 @@ import {
|
|
|
38
38
|
pullServerless,
|
|
39
39
|
publishServerless,
|
|
40
40
|
updateServerless,
|
|
41
|
+
getServerlessBuilds,
|
|
42
|
+
getServerlessLogs,
|
|
43
|
+
getBuildLogs,
|
|
41
44
|
} from "../api/serverless.js";
|
|
45
|
+
import { setVerboseMode } from "../helper/verbose.js";
|
|
42
46
|
|
|
43
47
|
// Define commands and their descriptions
|
|
44
48
|
const commands = {
|
|
@@ -58,10 +62,6 @@ const commands = {
|
|
|
58
62
|
description: "Test a serverless function locally",
|
|
59
63
|
action: handleTest,
|
|
60
64
|
},
|
|
61
|
-
help: {
|
|
62
|
-
description: "Show help for serverless commands",
|
|
63
|
-
action: showHelp,
|
|
64
|
-
},
|
|
65
65
|
list: {
|
|
66
66
|
description: "List all serverless functions",
|
|
67
67
|
action: handleList,
|
|
@@ -70,6 +70,22 @@ const commands = {
|
|
|
70
70
|
description: "Show status of a serverless function",
|
|
71
71
|
action: handleStatus,
|
|
72
72
|
},
|
|
73
|
+
builds: {
|
|
74
|
+
description: "List builds for a serverless function",
|
|
75
|
+
action: handleBuilds,
|
|
76
|
+
},
|
|
77
|
+
logs: {
|
|
78
|
+
description: "Show logs for a serverless function",
|
|
79
|
+
action: handleLogs,
|
|
80
|
+
},
|
|
81
|
+
"build logs": {
|
|
82
|
+
description: "Show logs for a specific build",
|
|
83
|
+
action: handleBuildLogs,
|
|
84
|
+
},
|
|
85
|
+
help: {
|
|
86
|
+
description: "Show help for serverless commands",
|
|
87
|
+
action: showHelp,
|
|
88
|
+
},
|
|
73
89
|
};
|
|
74
90
|
|
|
75
91
|
// Serverless type choices for dropdown
|
|
@@ -381,7 +397,7 @@ async function handleCodeTypeCreate(name, language, version, targetDir) {
|
|
|
381
397
|
}
|
|
382
398
|
|
|
383
399
|
/**
|
|
384
|
-
* Handle git type serverless creation - creates
|
|
400
|
+
* Handle git type serverless creation - creates serverless on server and clones the repo
|
|
385
401
|
*/
|
|
386
402
|
async function handleGitTypeCreate(name, language, version, targetDir) {
|
|
387
403
|
console.log(chalk.cyan("\nš Creating git-based serverless project..."));
|
|
@@ -442,7 +458,6 @@ async function handleGitTypeCreate(name, language, version, targetDir) {
|
|
|
442
458
|
}
|
|
443
459
|
|
|
444
460
|
// Extract serverless ID and git info from response
|
|
445
|
-
// Response structure: { ID, Links: { Git: { Repository: { SshURL, HtmlURL, CloneURL, ... } } } }
|
|
446
461
|
const serverlessId = response.ID || response.data?.ID || response._id;
|
|
447
462
|
const gitRepo =
|
|
448
463
|
response.Links?.Git?.Repository ||
|
|
@@ -451,8 +466,41 @@ async function handleGitTypeCreate(name, language, version, targetDir) {
|
|
|
451
466
|
const gitHttpUrl = gitRepo?.HtmlURL || "";
|
|
452
467
|
const gitCloneUrl = gitRepo?.CloneURL || "";
|
|
453
468
|
|
|
454
|
-
//
|
|
455
|
-
|
|
469
|
+
// Remove the empty directory created earlier - we'll clone into it
|
|
470
|
+
try {
|
|
471
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
472
|
+
} catch {
|
|
473
|
+
// Ignore cleanup errors
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Clone the repo from server (which has the server-generated boltic.yaml)
|
|
477
|
+
let cloneSuccess = false;
|
|
478
|
+
if (gitSshUrl) {
|
|
479
|
+
console.log(chalk.cyan("\nš„ Cloning git repository..."));
|
|
480
|
+
try {
|
|
481
|
+
// Clone the repo
|
|
482
|
+
execSync(`git clone ${gitSshUrl} "${targetDir}"`, {
|
|
483
|
+
stdio: "pipe",
|
|
484
|
+
timeout: 30000,
|
|
485
|
+
});
|
|
486
|
+
cloneSuccess = true;
|
|
487
|
+
console.log(chalk.green("ā
Repository cloned successfully!"));
|
|
488
|
+
} catch (err) {
|
|
489
|
+
console.log(
|
|
490
|
+
chalk.yellow(
|
|
491
|
+
"ā ļø Could not clone repository. You may not have SSH access yet."
|
|
492
|
+
)
|
|
493
|
+
);
|
|
494
|
+
cloneSuccess = false;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// If clone failed, create directory with minimal setup
|
|
499
|
+
if (!cloneSuccess) {
|
|
500
|
+
try {
|
|
501
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
502
|
+
// Create a minimal boltic.yaml as fallback
|
|
503
|
+
const bolticYamlContent = `app: "${name}"
|
|
456
504
|
region: "asia-south1"
|
|
457
505
|
handler: "${HANDLER_MAPPING[language]}"
|
|
458
506
|
language: "${language}/${version}"
|
|
@@ -460,19 +508,7 @@ language: "${language}/${version}"
|
|
|
460
508
|
serverlessConfig:
|
|
461
509
|
serverlessId: "${serverlessId}"
|
|
462
510
|
Name: "${name}"
|
|
463
|
-
Description: ""
|
|
464
511
|
Runtime: "git"
|
|
465
|
-
# Environment variables for your serverless function
|
|
466
|
-
# To add env variables, replace {} with key-value pairs like:
|
|
467
|
-
# Env:
|
|
468
|
-
# API_KEY: "your-api-key"
|
|
469
|
-
#TO add port map, replace {} with port map like:
|
|
470
|
-
# PortMap:
|
|
471
|
-
# - Name: "port"
|
|
472
|
-
# Port: "8080"
|
|
473
|
-
# Protocol: "http"/"https"
|
|
474
|
-
Env: {}
|
|
475
|
-
PortMap: {}
|
|
476
512
|
Scaling:
|
|
477
513
|
AutoStop: false
|
|
478
514
|
Min: 1
|
|
@@ -483,61 +519,23 @@ serverlessConfig:
|
|
|
483
519
|
MemoryMB: 128
|
|
484
520
|
MemoryMaxMB: 128
|
|
485
521
|
Timeout: 60
|
|
486
|
-
Validations: null
|
|
487
|
-
|
|
488
|
-
build:
|
|
489
|
-
builtin: dockerfile
|
|
490
|
-
ignorefile: .gitignore
|
|
491
522
|
`;
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
bolticYamlContent
|
|
497
|
-
);
|
|
498
|
-
} catch (err) {
|
|
499
|
-
console.error(chalk.red(`\nā Failed to create boltic.yaml`));
|
|
500
|
-
console.error(chalk.red(`Error: ${err.message}`));
|
|
501
|
-
return;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
// Check if user has git access by trying ls-remote
|
|
505
|
-
let hasGitAccess = false;
|
|
506
|
-
if (gitSshUrl) {
|
|
507
|
-
console.log(chalk.cyan("\nš Checking git repository access..."));
|
|
508
|
-
try {
|
|
523
|
+
fs.writeFileSync(
|
|
524
|
+
path.join(targetDir, "boltic.yaml"),
|
|
525
|
+
bolticYamlContent
|
|
526
|
+
);
|
|
509
527
|
// Initialize git repo
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
cwd: targetDir,
|
|
518
|
-
stdio: "pipe",
|
|
519
|
-
timeout: 15000,
|
|
520
|
-
});
|
|
521
|
-
hasGitAccess = true;
|
|
522
|
-
} catch (err) {
|
|
523
|
-
hasGitAccess = false;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
// If user has access, create main branch
|
|
528
|
-
if (hasGitAccess) {
|
|
529
|
-
try {
|
|
530
|
-
console.log(chalk.cyan("š§ Setting up git branch..."));
|
|
531
|
-
// Create main branch
|
|
532
|
-
execSync(`git checkout -b main`, { cwd: targetDir, stdio: "pipe" });
|
|
533
|
-
console.log(chalk.green("ā Created main branch"));
|
|
528
|
+
if (gitSshUrl) {
|
|
529
|
+
execSync(`git init`, { cwd: targetDir, stdio: "pipe" });
|
|
530
|
+
execSync(`git remote add origin ${gitSshUrl}`, {
|
|
531
|
+
cwd: targetDir,
|
|
532
|
+
stdio: "pipe",
|
|
533
|
+
});
|
|
534
|
+
}
|
|
534
535
|
} catch (err) {
|
|
535
|
-
|
|
536
|
-
console.
|
|
537
|
-
|
|
538
|
-
"ā ļø Could not auto-setup git branch. You can set it up manually."
|
|
539
|
-
)
|
|
540
|
-
);
|
|
536
|
+
console.error(chalk.red(`\nā Failed to create project directory`));
|
|
537
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
538
|
+
return;
|
|
541
539
|
}
|
|
542
540
|
}
|
|
543
541
|
|
|
@@ -573,11 +571,10 @@ build:
|
|
|
573
571
|
}
|
|
574
572
|
console.log();
|
|
575
573
|
|
|
576
|
-
if (
|
|
574
|
+
if (cloneSuccess) {
|
|
577
575
|
console.log(
|
|
578
|
-
chalk.green("ā
|
|
576
|
+
chalk.green("ā
Repository cloned with server configuration!")
|
|
579
577
|
);
|
|
580
|
-
console.log(chalk.green("ā
Main branch created!"));
|
|
581
578
|
console.log();
|
|
582
579
|
console.log(
|
|
583
580
|
chalk.yellow("š Next steps - Add your code and push:")
|
|
@@ -585,11 +582,13 @@ build:
|
|
|
585
582
|
console.log(chalk.dim(" 1. Add your server code to this folder"));
|
|
586
583
|
console.log(chalk.dim(" 2. Commit and push:"));
|
|
587
584
|
console.log(chalk.white(` git add .`));
|
|
588
|
-
console.log(
|
|
589
|
-
|
|
585
|
+
console.log(
|
|
586
|
+
chalk.white(` git commit -m "Add application code"`)
|
|
587
|
+
);
|
|
588
|
+
console.log(chalk.white(` git push origin main`));
|
|
590
589
|
} else {
|
|
591
590
|
console.log(
|
|
592
|
-
chalk.
|
|
591
|
+
chalk.yellow("ā ļø Could not clone repository automatically.")
|
|
593
592
|
);
|
|
594
593
|
console.log(
|
|
595
594
|
chalk.yellow(
|
|
@@ -598,14 +597,20 @@ build:
|
|
|
598
597
|
);
|
|
599
598
|
console.log();
|
|
600
599
|
console.log(
|
|
601
|
-
chalk.yellow("š Once you have access,
|
|
600
|
+
chalk.yellow("š Once you have access, sync with remote:")
|
|
601
|
+
);
|
|
602
|
+
console.log(chalk.dim(" 1. Pull the server config first:"));
|
|
603
|
+
console.log(
|
|
604
|
+
chalk.white(
|
|
605
|
+
` git pull origin main --allow-unrelated-histories`
|
|
606
|
+
)
|
|
602
607
|
);
|
|
603
|
-
console.log(chalk.dim("
|
|
604
|
-
console.log(chalk.dim(" 2. Run:"));
|
|
605
|
-
console.log(chalk.white(` git checkout -b main`));
|
|
608
|
+
console.log(chalk.dim(" 2. Add your code and push:"));
|
|
606
609
|
console.log(chalk.white(` git add .`));
|
|
607
|
-
console.log(
|
|
608
|
-
|
|
610
|
+
console.log(
|
|
611
|
+
chalk.white(` git commit -m "Add application code"`)
|
|
612
|
+
);
|
|
613
|
+
console.log(chalk.white(` git push origin main`));
|
|
609
614
|
}
|
|
610
615
|
} else {
|
|
611
616
|
console.log();
|
|
@@ -830,6 +835,15 @@ async function handlePublish(args = []) {
|
|
|
830
835
|
const languageBase = parseLanguageFromConfig(language);
|
|
831
836
|
const runtime = serverlessConfig?.Runtime || "code";
|
|
832
837
|
let code = null;
|
|
838
|
+
if (runtime === "git") {
|
|
839
|
+
console.log(
|
|
840
|
+
chalk.red("\nš Git type serverless does not support publish")
|
|
841
|
+
);
|
|
842
|
+
console.log(
|
|
843
|
+
chalk.yellow("Please publish using git push origin main")
|
|
844
|
+
);
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
833
847
|
|
|
834
848
|
if (runtime === "code") {
|
|
835
849
|
code = readHandlerFile(directory, languageBase, config);
|
|
@@ -1540,132 +1554,184 @@ async function handlePull(args) {
|
|
|
1540
1554
|
function showHelp() {
|
|
1541
1555
|
console.log(chalk.cyan("\nServerless Commands:\n"));
|
|
1542
1556
|
Object.entries(commands).forEach(([cmd, details]) => {
|
|
1543
|
-
console.log(chalk.bold(` ${cmd}`) +
|
|
1557
|
+
console.log(chalk.bold(` ${cmd.padEnd(12)}`) + details.description);
|
|
1544
1558
|
});
|
|
1545
1559
|
|
|
1546
|
-
console.log(chalk.cyan("\
|
|
1560
|
+
console.log(chalk.cyan("\nGlobal Options:\n"));
|
|
1561
|
+
console.log(
|
|
1562
|
+
chalk.bold(" --help, -h".padEnd(20)) + "Show help for a command"
|
|
1563
|
+
);
|
|
1564
|
+
|
|
1565
|
+
console.log(chalk.cyan("\nCreate Options:\n"));
|
|
1547
1566
|
console.log(
|
|
1548
|
-
chalk.bold(" --type, -t") +
|
|
1549
|
-
|
|
1550
|
-
"Serverless type: blueprint, git, or container (prompts if not provided)"
|
|
1567
|
+
chalk.bold(" --type, -t".padEnd(20)) +
|
|
1568
|
+
"Serverless type: blueprint, git, or container"
|
|
1551
1569
|
);
|
|
1552
1570
|
console.log(
|
|
1553
|
-
chalk.bold(" --name, -n") +
|
|
1554
|
-
|
|
1555
|
-
"Name of the serverless function (required, prompts if not provided)"
|
|
1571
|
+
chalk.bold(" --name, -n".padEnd(20)) +
|
|
1572
|
+
"Name of the serverless function"
|
|
1556
1573
|
);
|
|
1557
1574
|
console.log(
|
|
1558
|
-
chalk.bold(" --language, -l") +
|
|
1559
|
-
|
|
1560
|
-
"Programming language: nodejs, python, golang, java (prompts if not provided)"
|
|
1575
|
+
chalk.bold(" --language, -l".padEnd(20)) +
|
|
1576
|
+
"Programming language: nodejs, python, golang, java"
|
|
1561
1577
|
);
|
|
1562
1578
|
console.log(
|
|
1563
|
-
chalk.bold(" --directory, -d") +
|
|
1564
|
-
|
|
1565
|
-
"Directory where to create the project (default: current directory)"
|
|
1579
|
+
chalk.bold(" --directory, -d".padEnd(20)) +
|
|
1580
|
+
"Directory for the project (default: current)"
|
|
1566
1581
|
);
|
|
1567
1582
|
|
|
1568
|
-
console.log(chalk.cyan("\nTest
|
|
1583
|
+
console.log(chalk.cyan("\nTest Options:\n"));
|
|
1569
1584
|
console.log(
|
|
1570
|
-
chalk.bold(" --port, -p") +
|
|
1571
|
-
chalk.dim(" ") +
|
|
1585
|
+
chalk.bold(" --port, -p".padEnd(20)) +
|
|
1572
1586
|
"Port to run the server on (default: 8080)"
|
|
1573
1587
|
);
|
|
1574
1588
|
console.log(
|
|
1575
|
-
chalk.bold(" --language, -l") +
|
|
1576
|
-
|
|
1577
|
-
"Language (nodejs, python, golang, java) - auto-detected if not specified"
|
|
1589
|
+
chalk.bold(" --language, -l".padEnd(20)) +
|
|
1590
|
+
"Language (auto-detected if not specified)"
|
|
1578
1591
|
);
|
|
1579
1592
|
console.log(
|
|
1580
|
-
chalk.bold(" --directory, -d") +
|
|
1581
|
-
|
|
1582
|
-
"Base directory of the project (default: current directory)"
|
|
1593
|
+
chalk.bold(" --directory, -d".padEnd(20)) +
|
|
1594
|
+
"Base directory of the project"
|
|
1583
1595
|
);
|
|
1584
1596
|
|
|
1585
|
-
console.log(chalk.cyan("\nPublish
|
|
1597
|
+
console.log(chalk.cyan("\nPublish Options:\n"));
|
|
1586
1598
|
console.log(
|
|
1587
|
-
chalk.bold(" --directory, -d") +
|
|
1588
|
-
|
|
1589
|
-
"Directory of the serverless project (default: current directory)"
|
|
1599
|
+
chalk.bold(" --directory, -d".padEnd(20)) +
|
|
1600
|
+
"Directory of the serverless project"
|
|
1590
1601
|
);
|
|
1591
1602
|
|
|
1592
|
-
console.log(chalk.cyan("\nStatus
|
|
1603
|
+
console.log(chalk.cyan("\nStatus Options:\n"));
|
|
1604
|
+
console.log(
|
|
1605
|
+
chalk.bold(" --name, -n".padEnd(20)) +
|
|
1606
|
+
"Name of the serverless function"
|
|
1607
|
+
);
|
|
1593
1608
|
console.log(
|
|
1594
|
-
chalk.bold(" --
|
|
1595
|
-
|
|
1596
|
-
"Name of the serverless function (prompts if not provided)"
|
|
1609
|
+
chalk.bold(" --watch, -w".padEnd(20)) +
|
|
1610
|
+
"Poll until status is running, failed, or degraded"
|
|
1597
1611
|
);
|
|
1598
1612
|
|
|
1599
|
-
console.log(chalk.cyan("\
|
|
1613
|
+
console.log(chalk.cyan("\nBuilds Options:\n"));
|
|
1600
1614
|
console.log(
|
|
1601
|
-
chalk.
|
|
1602
|
-
"
|
|
1603
|
-
)
|
|
1615
|
+
chalk.bold(" --name, -n".padEnd(20)) +
|
|
1616
|
+
"Name of the serverless function"
|
|
1604
1617
|
);
|
|
1605
|
-
|
|
1606
|
-
console.log(chalk.
|
|
1618
|
+
|
|
1619
|
+
console.log(chalk.cyan("\nLogs Options:\n"));
|
|
1607
1620
|
console.log(
|
|
1608
|
-
"
|
|
1621
|
+
chalk.bold(" --name, -n".padEnd(20)) +
|
|
1622
|
+
"Name of the serverless function"
|
|
1609
1623
|
);
|
|
1610
1624
|
console.log(
|
|
1611
|
-
chalk.
|
|
1612
|
-
" # Create git-based serverless (add your code, then publish)"
|
|
1613
|
-
)
|
|
1625
|
+
chalk.bold(" --follow, -f".padEnd(20)) + "Follow logs in real-time"
|
|
1614
1626
|
);
|
|
1615
1627
|
console.log(
|
|
1616
|
-
"
|
|
1628
|
+
chalk.bold(" --lines, -l".padEnd(20)) +
|
|
1629
|
+
"Number of lines to show (default: 100)"
|
|
1630
|
+
);
|
|
1631
|
+
|
|
1632
|
+
console.log(chalk.cyan("\nBuild Logs Options:\n"));
|
|
1633
|
+
console.log(
|
|
1634
|
+
chalk.bold(" --name, -n".padEnd(20)) +
|
|
1635
|
+
"Name of the serverless function"
|
|
1617
1636
|
);
|
|
1618
|
-
console.log(chalk.dim(" # Create container-based serverless"));
|
|
1619
1637
|
console.log(
|
|
1620
|
-
"
|
|
1638
|
+
chalk.bold(" --build, -b".padEnd(20)) +
|
|
1639
|
+
"Build ID (prompts if not provided)"
|
|
1621
1640
|
);
|
|
1622
|
-
|
|
1641
|
+
|
|
1642
|
+
console.log(chalk.cyan("\nExamples:\n"));
|
|
1643
|
+
|
|
1644
|
+
console.log(chalk.dim(" # Create a blueprint serverless"));
|
|
1623
1645
|
console.log(
|
|
1624
|
-
" boltic serverless create
|
|
1646
|
+
" boltic serverless create -t blueprint -n my-api -l nodejs\n"
|
|
1625
1647
|
);
|
|
1626
1648
|
|
|
1627
|
-
console.log(chalk.
|
|
1628
|
-
console.log(
|
|
1629
|
-
console.log(" boltic serverless test\n");
|
|
1630
|
-
console.log(chalk.dim(" # Specify port"));
|
|
1631
|
-
console.log(" boltic serverless test --port 3000\n");
|
|
1649
|
+
console.log(chalk.dim(" # Test locally on port 3000"));
|
|
1650
|
+
console.log(" boltic serverless test -p 3000\n");
|
|
1632
1651
|
|
|
1633
|
-
console.log(chalk.cyan("\nPublish Examples:\n"));
|
|
1634
1652
|
console.log(chalk.dim(" # Publish from current directory"));
|
|
1635
1653
|
console.log(" boltic serverless publish\n");
|
|
1636
|
-
console.log(chalk.dim(" # Publish from specific directory"));
|
|
1637
|
-
console.log(" boltic serverless publish -d ./my-function\n");
|
|
1638
1654
|
|
|
1639
|
-
console.log(chalk.cyan("\nList Examples:\n"));
|
|
1640
1655
|
console.log(chalk.dim(" # List all serverless functions"));
|
|
1641
1656
|
console.log(" boltic serverless list\n");
|
|
1642
1657
|
|
|
1643
|
-
console.log(chalk.
|
|
1644
|
-
console.log(
|
|
1645
|
-
|
|
1646
|
-
console.log(chalk.dim(" #
|
|
1647
|
-
console.log(" boltic serverless
|
|
1658
|
+
console.log(chalk.dim(" # Check status with polling"));
|
|
1659
|
+
console.log(" boltic serverless status -n my-function --watch\n");
|
|
1660
|
+
|
|
1661
|
+
console.log(chalk.dim(" # View builds for a serverless"));
|
|
1662
|
+
console.log(" boltic serverless builds -n my-function\n");
|
|
1663
|
+
|
|
1664
|
+
console.log(chalk.dim(" # View runtime logs"));
|
|
1665
|
+
console.log(" boltic serverless logs -n my-function -f\n");
|
|
1666
|
+
|
|
1667
|
+
console.log(chalk.dim(" # View build logs"));
|
|
1668
|
+
console.log(" boltic serverless build logs -n my-function\n");
|
|
1648
1669
|
}
|
|
1649
1670
|
|
|
1650
1671
|
// Execute the serverless command
|
|
1651
1672
|
const execute = async (args) => {
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1673
|
+
let subCommand = args[0];
|
|
1674
|
+
let argsToPass = args.slice(1);
|
|
1675
|
+
|
|
1676
|
+
// Handle help flags
|
|
1677
|
+
if (
|
|
1678
|
+
!subCommand ||
|
|
1679
|
+
subCommand === "--help" ||
|
|
1680
|
+
subCommand === "-h" ||
|
|
1681
|
+
args.includes("--help") ||
|
|
1682
|
+
args.includes("-h")
|
|
1683
|
+
) {
|
|
1655
1684
|
showHelp();
|
|
1656
1685
|
return;
|
|
1657
1686
|
}
|
|
1658
1687
|
|
|
1688
|
+
// Handle two-word commands like "build logs"
|
|
1689
|
+
if (subCommand === "build" && args[1] === "logs") {
|
|
1690
|
+
subCommand = "build logs";
|
|
1691
|
+
argsToPass = args.slice(2);
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1659
1694
|
if (!commands[subCommand]) {
|
|
1660
|
-
console.log(chalk.red(
|
|
1695
|
+
console.log(chalk.red(`Unknown serverless command: "${subCommand}"\n`));
|
|
1661
1696
|
showHelp();
|
|
1662
1697
|
return;
|
|
1663
1698
|
}
|
|
1664
1699
|
|
|
1665
1700
|
const commandObj = commands[subCommand];
|
|
1666
|
-
await commandObj.action(
|
|
1701
|
+
await commandObj.action(argsToPass);
|
|
1667
1702
|
};
|
|
1668
1703
|
|
|
1704
|
+
/**
|
|
1705
|
+
* Get the URL for a serverless function
|
|
1706
|
+
*/
|
|
1707
|
+
function getServerlessUrl(serverless) {
|
|
1708
|
+
const appDomain = serverless.AppDomain?.[0];
|
|
1709
|
+
if (appDomain) {
|
|
1710
|
+
return `https://${appDomain.DomainName}.${appDomain.BaseUrl || "serverless.boltic.app"}`;
|
|
1711
|
+
}
|
|
1712
|
+
return null;
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
/**
|
|
1716
|
+
* Get status color for display
|
|
1717
|
+
*/
|
|
1718
|
+
function getStatusColor(status) {
|
|
1719
|
+
switch (status) {
|
|
1720
|
+
case "running":
|
|
1721
|
+
return chalk.green;
|
|
1722
|
+
case "draft":
|
|
1723
|
+
case "building":
|
|
1724
|
+
case "pending":
|
|
1725
|
+
return chalk.yellow;
|
|
1726
|
+
case "stopped":
|
|
1727
|
+
case "failed":
|
|
1728
|
+
case "degraded":
|
|
1729
|
+
return chalk.red;
|
|
1730
|
+
default:
|
|
1731
|
+
return chalk.gray;
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1669
1735
|
async function handleList(args = []) {
|
|
1670
1736
|
try {
|
|
1671
1737
|
const { apiUrl, token, accountId, session } = await getCurrentEnv();
|
|
@@ -1711,9 +1777,10 @@ async function handleList(args = []) {
|
|
|
1711
1777
|
: "š";
|
|
1712
1778
|
const language = serverless.Config?.CodeOpts?.Language;
|
|
1713
1779
|
const status = serverless.Status;
|
|
1780
|
+
const url = getServerlessUrl(serverless);
|
|
1714
1781
|
|
|
1715
1782
|
return {
|
|
1716
|
-
name: `${serverless.Config.Name}: ${typeIcon} ${runtime} |
|
|
1783
|
+
name: `${serverless.Config.Name}: ${typeIcon} ${runtime} | ${status}${language ? ` | ${language}` : ""}${url ? ` | ${url}` : ""}`,
|
|
1717
1784
|
value: serverless,
|
|
1718
1785
|
};
|
|
1719
1786
|
});
|
|
@@ -1731,44 +1798,7 @@ async function handleList(args = []) {
|
|
|
1731
1798
|
|
|
1732
1799
|
// Show details of selected serverless
|
|
1733
1800
|
if (selected) {
|
|
1734
|
-
|
|
1735
|
-
const typeIcon =
|
|
1736
|
-
runtime === "git"
|
|
1737
|
-
? "š¦"
|
|
1738
|
-
: runtime === "container"
|
|
1739
|
-
? "š³"
|
|
1740
|
-
: "š";
|
|
1741
|
-
|
|
1742
|
-
console.log("\n" + chalk.cyan("ā".repeat(60)));
|
|
1743
|
-
console.log(chalk.bold("\nš Selected Serverless Details:\n"));
|
|
1744
|
-
console.log(
|
|
1745
|
-
chalk.cyan(" Name: ") + chalk.white(selected.Config.Name)
|
|
1746
|
-
);
|
|
1747
|
-
console.log(chalk.cyan(" ID: ") + chalk.white(selected.ID));
|
|
1748
|
-
console.log(
|
|
1749
|
-
chalk.cyan(" Type: ") + chalk.white(`${typeIcon} ${runtime}`)
|
|
1750
|
-
);
|
|
1751
|
-
console.log(
|
|
1752
|
-
chalk.cyan(" Status: ") + chalk.white(selected.Status)
|
|
1753
|
-
);
|
|
1754
|
-
if (selected.Config?.CodeOpts?.Language) {
|
|
1755
|
-
console.log(
|
|
1756
|
-
chalk.cyan(" Language: ") +
|
|
1757
|
-
chalk.white(selected.Config.CodeOpts.Language)
|
|
1758
|
-
);
|
|
1759
|
-
}
|
|
1760
|
-
if (selected.Config?.ContainerOpts?.Image) {
|
|
1761
|
-
console.log(
|
|
1762
|
-
chalk.cyan(" Image: ") +
|
|
1763
|
-
chalk.white(selected.Config.ContainerOpts.Image)
|
|
1764
|
-
);
|
|
1765
|
-
}
|
|
1766
|
-
console.log(chalk.cyan("ā".repeat(60)));
|
|
1767
|
-
console.log(
|
|
1768
|
-
chalk.dim(
|
|
1769
|
-
"\nUse 'boltic serverless pull' to pull this serverless locally."
|
|
1770
|
-
)
|
|
1771
|
-
);
|
|
1801
|
+
displayServerlessDetails(selected);
|
|
1772
1802
|
}
|
|
1773
1803
|
} catch (error) {
|
|
1774
1804
|
if (
|
|
@@ -1785,143 +1815,927 @@ async function handleList(args = []) {
|
|
|
1785
1815
|
}
|
|
1786
1816
|
}
|
|
1787
1817
|
|
|
1818
|
+
/**
|
|
1819
|
+
* Display detailed information about a serverless function
|
|
1820
|
+
*/
|
|
1821
|
+
function displayServerlessDetails(serverless) {
|
|
1822
|
+
const runtime = serverless.Config?.Runtime || "code";
|
|
1823
|
+
const typeIcon =
|
|
1824
|
+
runtime === "git" ? "š¦" : runtime === "container" ? "š³" : "š";
|
|
1825
|
+
const status = serverless.Status;
|
|
1826
|
+
const statusColor = getStatusColor(status);
|
|
1827
|
+
const url = getServerlessUrl(serverless);
|
|
1828
|
+
|
|
1829
|
+
console.log("\n" + chalk.cyan("ā".repeat(60)));
|
|
1830
|
+
console.log(chalk.bold("\nš Serverless Details\n"));
|
|
1831
|
+
console.log(chalk.cyan(" Name: ") + chalk.white(serverless.Config.Name));
|
|
1832
|
+
console.log(chalk.cyan(" ID: ") + chalk.white(serverless.ID));
|
|
1833
|
+
console.log(
|
|
1834
|
+
chalk.cyan(" Type: ") + chalk.white(`${typeIcon} ${runtime}`)
|
|
1835
|
+
);
|
|
1836
|
+
console.log(chalk.cyan(" Status: ") + statusColor(status));
|
|
1837
|
+
|
|
1838
|
+
if (url) {
|
|
1839
|
+
console.log(chalk.cyan(" URL: ") + chalk.white.bold(url));
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
if (serverless.Config?.CodeOpts?.Language) {
|
|
1843
|
+
console.log(
|
|
1844
|
+
chalk.cyan(" Language: ") +
|
|
1845
|
+
chalk.white(serverless.Config.CodeOpts.Language)
|
|
1846
|
+
);
|
|
1847
|
+
}
|
|
1848
|
+
if (serverless.Config?.ContainerOpts?.Image) {
|
|
1849
|
+
console.log(
|
|
1850
|
+
chalk.cyan(" Image: ") +
|
|
1851
|
+
chalk.white(serverless.Config.ContainerOpts.Image)
|
|
1852
|
+
);
|
|
1853
|
+
}
|
|
1854
|
+
if (serverless.Config?.Resources) {
|
|
1855
|
+
console.log(
|
|
1856
|
+
chalk.cyan(" Resources: ") +
|
|
1857
|
+
chalk.white(
|
|
1858
|
+
`CPU: ${serverless.Config.Resources.CPU}, Memory: ${serverless.Config.Resources.MemoryMB}MB`
|
|
1859
|
+
)
|
|
1860
|
+
);
|
|
1861
|
+
}
|
|
1862
|
+
if (serverless.Config?.Scaling) {
|
|
1863
|
+
console.log(
|
|
1864
|
+
chalk.cyan(" Scaling: ") +
|
|
1865
|
+
chalk.white(
|
|
1866
|
+
`Min: ${serverless.Config.Scaling.Min}, Max: ${serverless.Config.Scaling.Max}`
|
|
1867
|
+
)
|
|
1868
|
+
);
|
|
1869
|
+
}
|
|
1870
|
+
if (serverless.RegionID) {
|
|
1871
|
+
console.log(
|
|
1872
|
+
chalk.cyan(" Region: ") + chalk.white(serverless.RegionID)
|
|
1873
|
+
);
|
|
1874
|
+
}
|
|
1875
|
+
if (serverless.CreatedAt) {
|
|
1876
|
+
console.log(
|
|
1877
|
+
chalk.cyan(" Created: ") +
|
|
1878
|
+
chalk.white(new Date(serverless.CreatedAt).toLocaleString())
|
|
1879
|
+
);
|
|
1880
|
+
}
|
|
1881
|
+
if (serverless.UpdatedAt) {
|
|
1882
|
+
console.log(
|
|
1883
|
+
chalk.cyan(" Updated: ") +
|
|
1884
|
+
chalk.white(new Date(serverless.UpdatedAt).toLocaleString())
|
|
1885
|
+
);
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
console.log();
|
|
1889
|
+
console.log(chalk.cyan("ā".repeat(60)));
|
|
1890
|
+
console.log(
|
|
1891
|
+
chalk.dim(
|
|
1892
|
+
"\nTip: Use 'boltic serverless status -n <name> --watch' to poll for status changes."
|
|
1893
|
+
)
|
|
1894
|
+
);
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
/**
|
|
1898
|
+
* Parse status command arguments
|
|
1899
|
+
*/
|
|
1900
|
+
function parseStatusArgs(args) {
|
|
1901
|
+
const parsed = {
|
|
1902
|
+
name: null,
|
|
1903
|
+
watch: false,
|
|
1904
|
+
verbose: false,
|
|
1905
|
+
timeout: -1, // -1 means infinite
|
|
1906
|
+
};
|
|
1907
|
+
|
|
1908
|
+
for (let i = 0; i < args.length; i++) {
|
|
1909
|
+
const arg = args[i];
|
|
1910
|
+
const nextArg = args[i + 1];
|
|
1911
|
+
|
|
1912
|
+
if ((arg === "--name" || arg === "-n") && nextArg) {
|
|
1913
|
+
parsed.name = nextArg;
|
|
1914
|
+
i++;
|
|
1915
|
+
} else if (arg === "--watch" || arg === "-w") {
|
|
1916
|
+
parsed.watch = true;
|
|
1917
|
+
} else if (arg === "--verbose" || arg === "-v") {
|
|
1918
|
+
parsed.verbose = true;
|
|
1919
|
+
} else if ((arg === "--timeout" || arg === "-t") && nextArg) {
|
|
1920
|
+
parsed.timeout = parseInt(nextArg, 10);
|
|
1921
|
+
i++;
|
|
1922
|
+
} else if (!arg.startsWith("-") && !parsed.name) {
|
|
1923
|
+
// Accept positional argument as name
|
|
1924
|
+
parsed.name = arg;
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
return parsed;
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1788
1931
|
/**
|
|
1789
1932
|
* Handle the status command - show status of a serverless function
|
|
1790
1933
|
*/
|
|
1791
1934
|
async function handleStatus(args = []) {
|
|
1792
1935
|
try {
|
|
1793
|
-
|
|
1794
|
-
let name =
|
|
1795
|
-
const nameIndex = args.indexOf("--name");
|
|
1796
|
-
const shortNameIndex = args.indexOf("-n");
|
|
1936
|
+
const parsedArgs = parseStatusArgs(args);
|
|
1937
|
+
let { name, watch, verbose, timeout } = parsedArgs;
|
|
1797
1938
|
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
name = args[shortNameIndex + 1];
|
|
1939
|
+
// Enable verbose mode if requested
|
|
1940
|
+
if (verbose) {
|
|
1941
|
+
setVerboseMode(true);
|
|
1802
1942
|
}
|
|
1803
1943
|
|
|
1804
|
-
|
|
1944
|
+
const { apiUrl, token, accountId, session } = await getCurrentEnv();
|
|
1945
|
+
|
|
1946
|
+
// If name not provided, show list selector
|
|
1805
1947
|
if (!name) {
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1948
|
+
console.log(chalk.cyan("\nš Fetching serverless functions...\n"));
|
|
1949
|
+
|
|
1950
|
+
const allServerless = await listAllServerless(
|
|
1951
|
+
apiUrl,
|
|
1952
|
+
token,
|
|
1953
|
+
accountId,
|
|
1954
|
+
session
|
|
1955
|
+
);
|
|
1956
|
+
|
|
1957
|
+
if (!allServerless || !Array.isArray(allServerless)) {
|
|
1958
|
+
console.error(
|
|
1959
|
+
chalk.red(
|
|
1960
|
+
"\nā Failed to fetch serverless: Invalid response format"
|
|
1961
|
+
)
|
|
1962
|
+
);
|
|
1963
|
+
return;
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
if (allServerless.length === 0) {
|
|
1967
|
+
console.log(chalk.yellow("No serverless functions found."));
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
// Build choices for the list
|
|
1972
|
+
const choices = allServerless.map((serverless) => {
|
|
1973
|
+
const runtime = serverless.Config?.Runtime || "code";
|
|
1974
|
+
const typeIcon =
|
|
1975
|
+
runtime === "git"
|
|
1976
|
+
? "š¦"
|
|
1977
|
+
: runtime === "container"
|
|
1978
|
+
? "š³"
|
|
1979
|
+
: "š";
|
|
1980
|
+
const status = serverless.Status;
|
|
1981
|
+
const statusColor = getStatusColor(status);
|
|
1982
|
+
|
|
1983
|
+
return {
|
|
1984
|
+
name: `${serverless.Config.Name} | ${typeIcon} ${runtime} | ${statusColor(status)}`,
|
|
1985
|
+
value: serverless,
|
|
1986
|
+
};
|
|
1987
|
+
});
|
|
1988
|
+
|
|
1989
|
+
const selected = await search({
|
|
1990
|
+
message: "Select a serverless function:",
|
|
1991
|
+
source: async (term) => {
|
|
1992
|
+
if (!term) return choices;
|
|
1993
|
+
return choices.filter((choice) =>
|
|
1994
|
+
choice.name.toLowerCase().includes(term.toLowerCase())
|
|
1995
|
+
);
|
|
1813
1996
|
},
|
|
1814
1997
|
});
|
|
1998
|
+
|
|
1999
|
+
if (!selected) {
|
|
2000
|
+
return;
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
// Display status directly since we have the full object
|
|
2004
|
+
displayServerlessDetails(selected);
|
|
2005
|
+
|
|
2006
|
+
// If watch mode, continue polling
|
|
2007
|
+
if (watch) {
|
|
2008
|
+
name = selected.Config.Name;
|
|
2009
|
+
} else {
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
1815
2012
|
}
|
|
1816
2013
|
|
|
1817
|
-
|
|
2014
|
+
// If not in watch mode (and name was provided), just fetch and display once
|
|
2015
|
+
if (!watch) {
|
|
2016
|
+
console.log(chalk.cyan(`\nš Fetching status for "${name}"...\n`));
|
|
1818
2017
|
|
|
1819
|
-
|
|
2018
|
+
// First find the serverless by name to get the ID
|
|
2019
|
+
const result = await listAllServerless(
|
|
2020
|
+
apiUrl,
|
|
2021
|
+
token,
|
|
2022
|
+
accountId,
|
|
2023
|
+
session,
|
|
2024
|
+
name
|
|
2025
|
+
);
|
|
1820
2026
|
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
2027
|
+
if (!result || !Array.isArray(result) || !result[0]) {
|
|
2028
|
+
console.error(
|
|
2029
|
+
chalk.red(`\nā Serverless "${name}" not found.`)
|
|
2030
|
+
);
|
|
2031
|
+
console.log(
|
|
2032
|
+
chalk.yellow(
|
|
2033
|
+
"\nUse 'boltic serverless list' to see all serverless functions."
|
|
2034
|
+
)
|
|
2035
|
+
);
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
1829
2038
|
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
2039
|
+
// Use pullServerless to get the full details with accurate status
|
|
2040
|
+
const serverlessId = result[0].ParentID || result[0].ID;
|
|
2041
|
+
const serverless = await pullServerless(
|
|
2042
|
+
apiUrl,
|
|
2043
|
+
token,
|
|
2044
|
+
accountId,
|
|
2045
|
+
session,
|
|
2046
|
+
serverlessId
|
|
1835
2047
|
);
|
|
2048
|
+
|
|
2049
|
+
if (!serverless) {
|
|
2050
|
+
console.error(
|
|
2051
|
+
chalk.red("\nā Failed to fetch serverless details")
|
|
2052
|
+
);
|
|
2053
|
+
return;
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
displayServerlessDetails(serverless);
|
|
1836
2057
|
return;
|
|
1837
2058
|
}
|
|
1838
2059
|
|
|
1839
|
-
//
|
|
1840
|
-
|
|
2060
|
+
// Watch mode - poll for status changes
|
|
2061
|
+
console.log(chalk.cyan(`\nšļø Watching status for "${name}"...\n`));
|
|
2062
|
+
const timeoutMsg = timeout > 0 ? ` (timeout: ${timeout}s)` : "";
|
|
2063
|
+
console.log(chalk.dim(`Press Ctrl+C to stop watching.${timeoutMsg}\n`));
|
|
1841
2064
|
|
|
1842
|
-
|
|
2065
|
+
// First, get the serverless ID
|
|
2066
|
+
const initialResult = await listAllServerless(
|
|
2067
|
+
apiUrl,
|
|
2068
|
+
token,
|
|
2069
|
+
accountId,
|
|
2070
|
+
session,
|
|
2071
|
+
name
|
|
2072
|
+
);
|
|
2073
|
+
|
|
2074
|
+
if (
|
|
2075
|
+
!initialResult ||
|
|
2076
|
+
!Array.isArray(initialResult) ||
|
|
2077
|
+
!initialResult[0]
|
|
2078
|
+
) {
|
|
1843
2079
|
console.error(chalk.red(`\nā Serverless "${name}" not found.`));
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
2080
|
+
return;
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
const serverlessId = initialResult[0].ParentID || initialResult[0].ID;
|
|
2084
|
+
const terminalStates = ["running", "failed", "degraded", "suspended"];
|
|
2085
|
+
let lastStatus = null;
|
|
2086
|
+
let iteration = 0;
|
|
2087
|
+
const startTime = Date.now();
|
|
2088
|
+
|
|
2089
|
+
while (true) {
|
|
2090
|
+
iteration++;
|
|
2091
|
+
|
|
2092
|
+
// Check timeout (-1 means infinite)
|
|
2093
|
+
if (timeout > 0) {
|
|
2094
|
+
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
2095
|
+
if (elapsed >= timeout) {
|
|
2096
|
+
console.log(
|
|
2097
|
+
chalk.yellow(
|
|
2098
|
+
`\n\nā±ļø Timeout reached after ${timeout} seconds.`
|
|
2099
|
+
)
|
|
2100
|
+
);
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
// Use pullServerless for accurate status
|
|
2106
|
+
const serverless = await pullServerless(
|
|
2107
|
+
apiUrl,
|
|
2108
|
+
token,
|
|
2109
|
+
accountId,
|
|
2110
|
+
session,
|
|
2111
|
+
serverlessId
|
|
1848
2112
|
);
|
|
2113
|
+
|
|
2114
|
+
if (!serverless) {
|
|
2115
|
+
console.error(
|
|
2116
|
+
chalk.red(`\nā Failed to fetch serverless status.`)
|
|
2117
|
+
);
|
|
2118
|
+
return;
|
|
2119
|
+
}
|
|
2120
|
+
const status = serverless.Status;
|
|
2121
|
+
const statusColor = getStatusColor(status);
|
|
2122
|
+
const url = getServerlessUrl(serverless);
|
|
2123
|
+
|
|
2124
|
+
// Show status update
|
|
2125
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
2126
|
+
if (status !== lastStatus) {
|
|
2127
|
+
console.log(
|
|
2128
|
+
chalk.dim(`[${timestamp}]`) +
|
|
2129
|
+
` Status: ${statusColor(status)}` +
|
|
2130
|
+
(url ? chalk.dim(` | ${url}`) : "")
|
|
2131
|
+
);
|
|
2132
|
+
lastStatus = status;
|
|
2133
|
+
} else if (iteration % 3 === 0) {
|
|
2134
|
+
// Show a dot every 3 iterations to indicate it's still polling
|
|
2135
|
+
process.stdout.write(chalk.dim("."));
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
// Check if we've reached a terminal state
|
|
2139
|
+
if (terminalStates.includes(status)) {
|
|
2140
|
+
console.log();
|
|
2141
|
+
displayServerlessDetails(serverless);
|
|
2142
|
+
console.log(
|
|
2143
|
+
chalk.green(`\nā Reached terminal state: ${status}`)
|
|
2144
|
+
);
|
|
2145
|
+
return;
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
// Wait before next poll
|
|
2149
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
2150
|
+
}
|
|
2151
|
+
} catch (error) {
|
|
2152
|
+
if (
|
|
2153
|
+
error.message &&
|
|
2154
|
+
error.message.includes("User force closed the prompt")
|
|
2155
|
+
) {
|
|
2156
|
+
console.log(chalk.yellow("\nā ļø Operation cancelled by user"));
|
|
1849
2157
|
return;
|
|
1850
2158
|
}
|
|
2159
|
+
console.error(
|
|
2160
|
+
chalk.red("\nā An error occurred:"),
|
|
2161
|
+
error.message || "Unknown error"
|
|
2162
|
+
);
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
/**
|
|
2167
|
+
* Helper to select a serverless function interactively
|
|
2168
|
+
*/
|
|
2169
|
+
async function selectServerless(
|
|
2170
|
+
apiUrl,
|
|
2171
|
+
token,
|
|
2172
|
+
accountId,
|
|
2173
|
+
session,
|
|
2174
|
+
message = "Select a serverless function:"
|
|
2175
|
+
) {
|
|
2176
|
+
const allServerless = await listAllServerless(
|
|
2177
|
+
apiUrl,
|
|
2178
|
+
token,
|
|
2179
|
+
accountId,
|
|
2180
|
+
session
|
|
2181
|
+
);
|
|
2182
|
+
|
|
2183
|
+
if (!allServerless || !Array.isArray(allServerless)) {
|
|
2184
|
+
throw new Error("Failed to fetch serverless: Invalid response format");
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
if (allServerless.length === 0) {
|
|
2188
|
+
console.log(chalk.yellow("No serverless functions found."));
|
|
2189
|
+
return null;
|
|
2190
|
+
}
|
|
1851
2191
|
|
|
1852
|
-
|
|
2192
|
+
const choices = allServerless.map((serverless) => {
|
|
1853
2193
|
const runtime = serverless.Config?.Runtime || "code";
|
|
1854
2194
|
const typeIcon =
|
|
1855
2195
|
runtime === "git" ? "š¦" : runtime === "container" ? "š³" : "š";
|
|
1856
2196
|
const status = serverless.Status;
|
|
1857
|
-
const statusColor =
|
|
1858
|
-
status === "running"
|
|
1859
|
-
? chalk.green
|
|
1860
|
-
: status === "draft"
|
|
1861
|
-
? chalk.yellow
|
|
1862
|
-
: status === "stopped"
|
|
1863
|
-
? chalk.red
|
|
1864
|
-
: chalk.gray;
|
|
1865
2197
|
|
|
1866
|
-
|
|
1867
|
-
|
|
2198
|
+
return {
|
|
2199
|
+
name: `${serverless.Config.Name} | ${typeIcon} ${runtime} | ${status}`,
|
|
2200
|
+
value: serverless,
|
|
2201
|
+
};
|
|
2202
|
+
});
|
|
2203
|
+
|
|
2204
|
+
return await search({
|
|
2205
|
+
message,
|
|
2206
|
+
source: async (term) => {
|
|
2207
|
+
if (!term) return choices;
|
|
2208
|
+
return choices.filter((choice) =>
|
|
2209
|
+
choice.name.toLowerCase().includes(term.toLowerCase())
|
|
2210
|
+
);
|
|
2211
|
+
},
|
|
2212
|
+
});
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
/**
|
|
2216
|
+
* Handle the builds command - list builds for a serverless function
|
|
2217
|
+
*/
|
|
2218
|
+
async function handleBuilds(args = []) {
|
|
2219
|
+
try {
|
|
2220
|
+
// Parse name from args (supports --name, -n, or positional)
|
|
2221
|
+
let name = null;
|
|
2222
|
+
for (let i = 0; i < args.length; i++) {
|
|
2223
|
+
const arg = args[i];
|
|
2224
|
+
if ((arg === "--name" || arg === "-n") && args[i + 1]) {
|
|
2225
|
+
name = args[i + 1];
|
|
2226
|
+
break;
|
|
2227
|
+
} else if (!arg.startsWith("-") && !name) {
|
|
2228
|
+
name = arg;
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
const { apiUrl, token, accountId, session } = await getCurrentEnv();
|
|
2233
|
+
|
|
2234
|
+
let serverless;
|
|
2235
|
+
|
|
2236
|
+
// If name not provided, show selector
|
|
2237
|
+
if (!name) {
|
|
2238
|
+
console.log(chalk.cyan("\nš Select a serverless function...\n"));
|
|
2239
|
+
serverless = await selectServerless(
|
|
2240
|
+
apiUrl,
|
|
2241
|
+
token,
|
|
2242
|
+
accountId,
|
|
2243
|
+
session,
|
|
2244
|
+
"Select serverless to view builds:"
|
|
2245
|
+
);
|
|
2246
|
+
|
|
2247
|
+
if (!serverless) {
|
|
2248
|
+
return;
|
|
2249
|
+
}
|
|
2250
|
+
} else {
|
|
2251
|
+
// Fetch by name
|
|
2252
|
+
const result = await listAllServerless(
|
|
2253
|
+
apiUrl,
|
|
2254
|
+
token,
|
|
2255
|
+
accountId,
|
|
2256
|
+
session,
|
|
2257
|
+
name
|
|
2258
|
+
);
|
|
2259
|
+
|
|
2260
|
+
if (!result || !Array.isArray(result) || !result[0]) {
|
|
2261
|
+
console.error(
|
|
2262
|
+
chalk.red(`\nā Serverless "${name}" not found.`)
|
|
2263
|
+
);
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2266
|
+
serverless = result[0];
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
// Check if serverless is container type - builds are not available
|
|
2270
|
+
const runtime = serverless.Config?.Runtime || "code";
|
|
2271
|
+
if (runtime === "container") {
|
|
2272
|
+
console.log(
|
|
2273
|
+
chalk.yellow(
|
|
2274
|
+
`\nā ļø Builds are not available for container-type serverless functions.`
|
|
2275
|
+
)
|
|
2276
|
+
);
|
|
2277
|
+
console.log(
|
|
2278
|
+
chalk.dim(
|
|
2279
|
+
` Container images are built externally and pulled directly.`
|
|
2280
|
+
)
|
|
2281
|
+
);
|
|
2282
|
+
console.log(
|
|
2283
|
+
chalk.dim(
|
|
2284
|
+
`\n To view runtime logs, use: boltic serverless logs ${serverless.Config.Name}`
|
|
2285
|
+
)
|
|
2286
|
+
);
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
2289
|
+
|
|
1868
2290
|
console.log(
|
|
1869
|
-
chalk.cyan(
|
|
2291
|
+
chalk.cyan(
|
|
2292
|
+
`\nšØ Fetching builds for "${serverless.Config.Name}"...\n`
|
|
2293
|
+
)
|
|
2294
|
+
);
|
|
2295
|
+
|
|
2296
|
+
const buildsData = await getServerlessBuilds(
|
|
2297
|
+
apiUrl,
|
|
2298
|
+
token,
|
|
2299
|
+
accountId,
|
|
2300
|
+
session,
|
|
2301
|
+
serverless.ID
|
|
1870
2302
|
);
|
|
1871
|
-
|
|
2303
|
+
|
|
2304
|
+
if (!buildsData || !buildsData.data || buildsData.data.length === 0) {
|
|
2305
|
+
console.log(chalk.yellow("No builds found for this serverless."));
|
|
2306
|
+
return;
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
const builds = buildsData.data;
|
|
2310
|
+
|
|
2311
|
+
console.log(chalk.green(`Found ${builds.length} build(s):\n`));
|
|
2312
|
+
console.log(chalk.cyan("ā".repeat(100)));
|
|
1872
2313
|
console.log(
|
|
1873
|
-
chalk.
|
|
2314
|
+
chalk.bold(" # ") +
|
|
2315
|
+
chalk.bold("Status".padEnd(12)) +
|
|
2316
|
+
chalk.bold("Version".padEnd(10)) +
|
|
2317
|
+
chalk.bold("Created".padEnd(22)) +
|
|
2318
|
+
chalk.bold("Build ID")
|
|
1874
2319
|
);
|
|
1875
|
-
console.log(chalk.cyan("
|
|
2320
|
+
console.log(chalk.cyan("ā".repeat(100)));
|
|
2321
|
+
|
|
2322
|
+
builds.forEach((build, index) => {
|
|
2323
|
+
const status =
|
|
2324
|
+
build.StatusHistory?.slice(-1)[0]?.Status ||
|
|
2325
|
+
build.Status ||
|
|
2326
|
+
"unknown";
|
|
2327
|
+
const statusColor = getStatusColor(status);
|
|
2328
|
+
const createdAt = build.CreatedAt
|
|
2329
|
+
? new Date(build.CreatedAt).toLocaleString()
|
|
2330
|
+
: "N/A";
|
|
2331
|
+
const version = build.Version || "N/A";
|
|
1876
2332
|
|
|
1877
|
-
if (serverless.Config?.CodeOpts?.Language) {
|
|
1878
2333
|
console.log(
|
|
1879
|
-
chalk.
|
|
1880
|
-
|
|
2334
|
+
chalk.dim(` ${String(index + 1).padStart(2)} `) +
|
|
2335
|
+
statusColor(status.padEnd(12)) +
|
|
2336
|
+
`v${String(version).padEnd(9)}` +
|
|
2337
|
+
createdAt.padEnd(22) +
|
|
2338
|
+
build.ID
|
|
1881
2339
|
);
|
|
2340
|
+
|
|
2341
|
+
// Show status history for recent builds (first 3)
|
|
2342
|
+
if (
|
|
2343
|
+
index < 3 &&
|
|
2344
|
+
build.StatusHistory &&
|
|
2345
|
+
build.StatusHistory.length > 1
|
|
2346
|
+
) {
|
|
2347
|
+
const history = build.StatusHistory.map((h) => {
|
|
2348
|
+
const ts = h.Timestamp
|
|
2349
|
+
? new Date(h.Timestamp).toLocaleTimeString()
|
|
2350
|
+
: "";
|
|
2351
|
+
return `${h.Status}${ts ? ` (${ts})` : ""}`;
|
|
2352
|
+
}).join(" ā ");
|
|
2353
|
+
console.log(chalk.dim(` āā ${history}`));
|
|
2354
|
+
}
|
|
2355
|
+
});
|
|
2356
|
+
|
|
2357
|
+
console.log(chalk.cyan("ā".repeat(100)));
|
|
2358
|
+
console.log(
|
|
2359
|
+
chalk.dim(
|
|
2360
|
+
"\nTip: Use 'boltic serverless build logs -n <name>' to view logs for a build."
|
|
2361
|
+
)
|
|
2362
|
+
);
|
|
2363
|
+
} catch (error) {
|
|
2364
|
+
if (
|
|
2365
|
+
error.message &&
|
|
2366
|
+
error.message.includes("User force closed the prompt")
|
|
2367
|
+
) {
|
|
2368
|
+
console.log(chalk.yellow("\nā ļø Operation cancelled by user"));
|
|
2369
|
+
return;
|
|
1882
2370
|
}
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
2371
|
+
console.error(
|
|
2372
|
+
chalk.red("\nā An error occurred:"),
|
|
2373
|
+
error.message || "Unknown error"
|
|
2374
|
+
);
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
/**
|
|
2379
|
+
* Handle the logs command - show logs for a serverless function
|
|
2380
|
+
*/
|
|
2381
|
+
async function handleLogs(args = []) {
|
|
2382
|
+
try {
|
|
2383
|
+
// Parse args (supports --name, -n, or positional)
|
|
2384
|
+
let name = null;
|
|
2385
|
+
let follow = false;
|
|
2386
|
+
let lines = 100;
|
|
2387
|
+
|
|
2388
|
+
for (let i = 0; i < args.length; i++) {
|
|
2389
|
+
const arg = args[i];
|
|
2390
|
+
const nextArg = args[i + 1];
|
|
2391
|
+
|
|
2392
|
+
if ((arg === "--name" || arg === "-n") && nextArg) {
|
|
2393
|
+
name = nextArg;
|
|
2394
|
+
i++;
|
|
2395
|
+
} else if (arg === "--follow" || arg === "-f") {
|
|
2396
|
+
follow = true;
|
|
2397
|
+
} else if ((arg === "--lines" || arg === "-l") && nextArg) {
|
|
2398
|
+
lines = parseInt(nextArg, 10) || 100;
|
|
2399
|
+
i++;
|
|
2400
|
+
} else if (!arg.startsWith("-") && !name) {
|
|
2401
|
+
// Accept positional argument as name
|
|
2402
|
+
name = arg;
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
const { apiUrl, token, accountId, session } = await getCurrentEnv();
|
|
2407
|
+
|
|
2408
|
+
let serverless;
|
|
2409
|
+
|
|
2410
|
+
// If name not provided, show selector
|
|
2411
|
+
if (!name) {
|
|
2412
|
+
console.log(chalk.cyan("\nš Select a serverless function...\n"));
|
|
2413
|
+
serverless = await selectServerless(
|
|
2414
|
+
apiUrl,
|
|
2415
|
+
token,
|
|
2416
|
+
accountId,
|
|
2417
|
+
session,
|
|
2418
|
+
"Select serverless to view logs:"
|
|
1887
2419
|
);
|
|
2420
|
+
|
|
2421
|
+
if (!serverless) {
|
|
2422
|
+
return;
|
|
2423
|
+
}
|
|
2424
|
+
} else {
|
|
2425
|
+
// Fetch by name
|
|
2426
|
+
const result = await listAllServerless(
|
|
2427
|
+
apiUrl,
|
|
2428
|
+
token,
|
|
2429
|
+
accountId,
|
|
2430
|
+
session,
|
|
2431
|
+
name
|
|
2432
|
+
);
|
|
2433
|
+
|
|
2434
|
+
if (!result || !Array.isArray(result) || !result[0]) {
|
|
2435
|
+
console.error(
|
|
2436
|
+
chalk.red(`\nā Serverless "${name}" not found.`)
|
|
2437
|
+
);
|
|
2438
|
+
return;
|
|
2439
|
+
}
|
|
2440
|
+
serverless = result[0];
|
|
1888
2441
|
}
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
2442
|
+
|
|
2443
|
+
console.log(
|
|
2444
|
+
chalk.cyan(
|
|
2445
|
+
`\nš Fetching logs for "${serverless.Config.Name}"...\n`
|
|
2446
|
+
)
|
|
2447
|
+
);
|
|
2448
|
+
|
|
2449
|
+
if (follow) {
|
|
2450
|
+
console.log(chalk.dim("Following logs... Press Ctrl+C to stop.\n"));
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
const fetchAndDisplayLogs = async (timestampEnd = null) => {
|
|
2454
|
+
const now = Math.floor(Date.now() / 1000);
|
|
2455
|
+
const logsData = await getServerlessLogs(
|
|
2456
|
+
apiUrl,
|
|
2457
|
+
token,
|
|
2458
|
+
accountId,
|
|
2459
|
+
session,
|
|
2460
|
+
serverless.ID,
|
|
2461
|
+
{
|
|
2462
|
+
limit: lines,
|
|
2463
|
+
timestampEnd: timestampEnd || now,
|
|
2464
|
+
timestampStart: (timestampEnd || now) - 24 * 60 * 60,
|
|
2465
|
+
}
|
|
1895
2466
|
);
|
|
2467
|
+
|
|
2468
|
+
if (!logsData || !logsData.data || logsData.data.length === 0) {
|
|
2469
|
+
if (!follow) {
|
|
2470
|
+
console.log(
|
|
2471
|
+
chalk.yellow("No logs found for this serverless.")
|
|
2472
|
+
);
|
|
2473
|
+
}
|
|
2474
|
+
return timestampEnd;
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
const logs = logsData.data;
|
|
2478
|
+
let latestTimestamp = timestampEnd;
|
|
2479
|
+
|
|
2480
|
+
logs.forEach((log) => {
|
|
2481
|
+
// Timestamp is unix epoch in seconds
|
|
2482
|
+
const timestamp = log.Timestamp
|
|
2483
|
+
? new Date(log.Timestamp * 1000).toLocaleTimeString()
|
|
2484
|
+
: "";
|
|
2485
|
+
const severity = log.Severity || "INFO";
|
|
2486
|
+
const severityColor =
|
|
2487
|
+
severity === "ERROR"
|
|
2488
|
+
? chalk.red
|
|
2489
|
+
: severity === "WARNING" || severity === "WARN"
|
|
2490
|
+
? chalk.yellow
|
|
2491
|
+
: severity === "DEBUG"
|
|
2492
|
+
? chalk.blue
|
|
2493
|
+
: chalk.gray;
|
|
2494
|
+
|
|
2495
|
+
// Parse the Log field which may contain JSON
|
|
2496
|
+
let message = "";
|
|
2497
|
+
if (log.Log) {
|
|
2498
|
+
try {
|
|
2499
|
+
const parsed = JSON.parse(log.Log);
|
|
2500
|
+
message = parsed.msg || parsed.message || log.Log;
|
|
2501
|
+
} catch {
|
|
2502
|
+
// Not JSON, use as-is
|
|
2503
|
+
message = log.Log;
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
console.log(
|
|
2508
|
+
chalk.dim(`[${timestamp}]`) +
|
|
2509
|
+
` ${severityColor(severity.padEnd(7))} ${message}`
|
|
2510
|
+
);
|
|
2511
|
+
|
|
2512
|
+
if (
|
|
2513
|
+
log.Timestamp &&
|
|
2514
|
+
(!latestTimestamp || log.Timestamp > latestTimestamp)
|
|
2515
|
+
) {
|
|
2516
|
+
latestTimestamp = log.Timestamp;
|
|
2517
|
+
}
|
|
2518
|
+
});
|
|
2519
|
+
|
|
2520
|
+
return latestTimestamp;
|
|
2521
|
+
};
|
|
2522
|
+
|
|
2523
|
+
let lastTimestamp = await fetchAndDisplayLogs();
|
|
2524
|
+
|
|
2525
|
+
if (follow) {
|
|
2526
|
+
while (true) {
|
|
2527
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
2528
|
+
lastTimestamp = await fetchAndDisplayLogs(lastTimestamp);
|
|
2529
|
+
}
|
|
1896
2530
|
}
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
2531
|
+
} catch (error) {
|
|
2532
|
+
if (
|
|
2533
|
+
error.message &&
|
|
2534
|
+
error.message.includes("User force closed the prompt")
|
|
2535
|
+
) {
|
|
2536
|
+
console.log(chalk.yellow("\nā ļø Operation cancelled by user"));
|
|
2537
|
+
return;
|
|
2538
|
+
}
|
|
2539
|
+
console.error(
|
|
2540
|
+
chalk.red("\nā An error occurred:"),
|
|
2541
|
+
error.message || "Unknown error"
|
|
2542
|
+
);
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
/**
|
|
2547
|
+
* Handle the "build logs" command - show logs for a specific build
|
|
2548
|
+
*/
|
|
2549
|
+
async function handleBuildLogs(args = []) {
|
|
2550
|
+
try {
|
|
2551
|
+
// Parse args (supports --name, -n, or positional)
|
|
2552
|
+
let name = null;
|
|
2553
|
+
let buildId = null;
|
|
2554
|
+
|
|
2555
|
+
for (let i = 0; i < args.length; i++) {
|
|
2556
|
+
const arg = args[i];
|
|
2557
|
+
const nextArg = args[i + 1];
|
|
2558
|
+
|
|
2559
|
+
if ((arg === "--name" || arg === "-n") && nextArg) {
|
|
2560
|
+
name = nextArg;
|
|
2561
|
+
i++;
|
|
2562
|
+
} else if ((arg === "--build" || arg === "-b") && nextArg) {
|
|
2563
|
+
buildId = nextArg;
|
|
2564
|
+
i++;
|
|
2565
|
+
} else if (!arg.startsWith("-") && !name) {
|
|
2566
|
+
// Accept positional argument as name
|
|
2567
|
+
name = arg;
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
const { apiUrl, token, accountId, session } = await getCurrentEnv();
|
|
2572
|
+
|
|
2573
|
+
let serverless;
|
|
2574
|
+
|
|
2575
|
+
// If name not provided, show selector
|
|
2576
|
+
if (!name) {
|
|
2577
|
+
console.log(chalk.cyan("\nš Select a serverless function...\n"));
|
|
2578
|
+
serverless = await selectServerless(
|
|
2579
|
+
apiUrl,
|
|
2580
|
+
token,
|
|
2581
|
+
accountId,
|
|
2582
|
+
session,
|
|
2583
|
+
"Select serverless to view build logs:"
|
|
2584
|
+
);
|
|
2585
|
+
|
|
2586
|
+
if (!serverless) {
|
|
2587
|
+
return;
|
|
2588
|
+
}
|
|
2589
|
+
} else {
|
|
2590
|
+
// Fetch by name
|
|
2591
|
+
const result = await listAllServerless(
|
|
2592
|
+
apiUrl,
|
|
2593
|
+
token,
|
|
2594
|
+
accountId,
|
|
2595
|
+
session,
|
|
2596
|
+
name
|
|
1903
2597
|
);
|
|
2598
|
+
|
|
2599
|
+
if (!result || !Array.isArray(result) || !result[0]) {
|
|
2600
|
+
console.error(
|
|
2601
|
+
chalk.red(`\nā Serverless "${name}" not found.`)
|
|
2602
|
+
);
|
|
2603
|
+
return;
|
|
2604
|
+
}
|
|
2605
|
+
serverless = result[0];
|
|
1904
2606
|
}
|
|
1905
|
-
|
|
2607
|
+
|
|
2608
|
+
// Check if serverless is container type - build logs are not available
|
|
2609
|
+
const runtime = serverless.Config?.Runtime || "code";
|
|
2610
|
+
if (runtime === "container") {
|
|
2611
|
+
console.log(
|
|
2612
|
+
chalk.yellow(
|
|
2613
|
+
`\nā ļø Build logs are not available for container-type serverless functions.`
|
|
2614
|
+
)
|
|
2615
|
+
);
|
|
1906
2616
|
console.log(
|
|
1907
|
-
chalk.
|
|
2617
|
+
chalk.dim(
|
|
2618
|
+
` Container images are built externally and pulled directly.`
|
|
2619
|
+
)
|
|
1908
2620
|
);
|
|
1909
|
-
}
|
|
1910
|
-
if (serverless.CreatedAt) {
|
|
1911
2621
|
console.log(
|
|
1912
|
-
chalk.
|
|
1913
|
-
|
|
2622
|
+
chalk.dim(
|
|
2623
|
+
`\n To view runtime logs, use: boltic serverless logs ${serverless.Config.Name}`
|
|
2624
|
+
)
|
|
1914
2625
|
);
|
|
2626
|
+
return;
|
|
1915
2627
|
}
|
|
1916
|
-
|
|
2628
|
+
|
|
2629
|
+
// If build ID not provided, fetch builds and let user select
|
|
2630
|
+
if (!buildId) {
|
|
1917
2631
|
console.log(
|
|
1918
|
-
chalk.cyan(
|
|
1919
|
-
|
|
2632
|
+
chalk.cyan(
|
|
2633
|
+
`\nšØ Fetching builds for "${serverless.Config.Name}"...\n`
|
|
2634
|
+
)
|
|
2635
|
+
);
|
|
2636
|
+
|
|
2637
|
+
const buildsData = await getServerlessBuilds(
|
|
2638
|
+
apiUrl,
|
|
2639
|
+
token,
|
|
2640
|
+
accountId,
|
|
2641
|
+
session,
|
|
2642
|
+
serverless.ID
|
|
1920
2643
|
);
|
|
2644
|
+
|
|
2645
|
+
if (
|
|
2646
|
+
!buildsData ||
|
|
2647
|
+
!buildsData.data ||
|
|
2648
|
+
buildsData.data.length === 0
|
|
2649
|
+
) {
|
|
2650
|
+
console.log(
|
|
2651
|
+
chalk.yellow("No builds found for this serverless.")
|
|
2652
|
+
);
|
|
2653
|
+
return;
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
const builds = buildsData.data;
|
|
2657
|
+
|
|
2658
|
+
const buildChoices = builds.map((build, index) => {
|
|
2659
|
+
const status =
|
|
2660
|
+
build.StatusHistory?.slice(-1)[0]?.Status ||
|
|
2661
|
+
build.Status ||
|
|
2662
|
+
"unknown";
|
|
2663
|
+
const statusColor = getStatusColor(status);
|
|
2664
|
+
const createdAt = build.CreatedAt
|
|
2665
|
+
? new Date(build.CreatedAt).toLocaleString()
|
|
2666
|
+
: "N/A";
|
|
2667
|
+
|
|
2668
|
+
return {
|
|
2669
|
+
name: `#${index + 1} | ${statusColor(status)} | ${createdAt} | ${build.ID.substring(0, 8)}...`,
|
|
2670
|
+
value: build,
|
|
2671
|
+
};
|
|
2672
|
+
});
|
|
2673
|
+
|
|
2674
|
+
const selectedBuild = await search({
|
|
2675
|
+
message: "Select a build to view logs:",
|
|
2676
|
+
source: async (term) => {
|
|
2677
|
+
if (!term) return buildChoices;
|
|
2678
|
+
return buildChoices.filter((choice) =>
|
|
2679
|
+
choice.name.toLowerCase().includes(term.toLowerCase())
|
|
2680
|
+
);
|
|
2681
|
+
},
|
|
2682
|
+
});
|
|
2683
|
+
|
|
2684
|
+
if (!selectedBuild) {
|
|
2685
|
+
return;
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
buildId = selectedBuild.ID;
|
|
1921
2689
|
}
|
|
1922
2690
|
|
|
1923
|
-
console.log();
|
|
1924
|
-
|
|
2691
|
+
console.log(chalk.cyan(`\nš Fetching build logs...\n`));
|
|
2692
|
+
|
|
2693
|
+
const logsData = await getBuildLogs(
|
|
2694
|
+
apiUrl,
|
|
2695
|
+
token,
|
|
2696
|
+
accountId,
|
|
2697
|
+
session,
|
|
2698
|
+
serverless.ID,
|
|
2699
|
+
buildId
|
|
2700
|
+
);
|
|
2701
|
+
|
|
2702
|
+
if (!logsData || !logsData.data) {
|
|
2703
|
+
console.log(chalk.yellow("No logs found for this build."));
|
|
2704
|
+
return;
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
console.log(chalk.cyan("ā".repeat(80)));
|
|
2708
|
+
console.log(chalk.bold("Build Logs:\n"));
|
|
2709
|
+
|
|
2710
|
+
// Handle different log formats
|
|
2711
|
+
const logs = Array.isArray(logsData.data)
|
|
2712
|
+
? logsData.data
|
|
2713
|
+
: [logsData.data];
|
|
2714
|
+
|
|
2715
|
+
logs.forEach((log) => {
|
|
2716
|
+
if (typeof log === "string") {
|
|
2717
|
+
console.log(log);
|
|
2718
|
+
} else if (log.Log) {
|
|
2719
|
+
// Log field contains the actual log content (may include ANSI colors)
|
|
2720
|
+
// Output directly to preserve color codes
|
|
2721
|
+
process.stdout.write(log.Log);
|
|
2722
|
+
if (!log.Log.endsWith("\n")) {
|
|
2723
|
+
process.stdout.write("\n");
|
|
2724
|
+
}
|
|
2725
|
+
} else if (log.Message || log.message) {
|
|
2726
|
+
const timestamp = log.Timestamp
|
|
2727
|
+
? new Date(log.Timestamp * 1000).toLocaleTimeString()
|
|
2728
|
+
: "";
|
|
2729
|
+
console.log(
|
|
2730
|
+
chalk.dim(`[${timestamp}]`) +
|
|
2731
|
+
` ${log.Message || log.message}`
|
|
2732
|
+
);
|
|
2733
|
+
} else {
|
|
2734
|
+
console.log(JSON.stringify(log, null, 2));
|
|
2735
|
+
}
|
|
2736
|
+
});
|
|
2737
|
+
|
|
2738
|
+
console.log("\n" + chalk.cyan("ā".repeat(80)));
|
|
1925
2739
|
} catch (error) {
|
|
1926
2740
|
if (
|
|
1927
2741
|
error.message &&
|