bindler 1.2.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +943 -639
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
2
8
|
|
|
3
9
|
// src/cli.ts
|
|
4
10
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
11
|
+
import chalk30 from "chalk";
|
|
6
12
|
|
|
7
13
|
// src/commands/new.ts
|
|
8
|
-
import { existsSync as
|
|
14
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
9
15
|
import { basename as basename2 } from "path";
|
|
10
16
|
import inquirer from "inquirer";
|
|
11
|
-
import
|
|
17
|
+
import chalk2 from "chalk";
|
|
12
18
|
|
|
13
19
|
// src/lib/config.ts
|
|
14
20
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync } from "fs";
|
|
@@ -694,9 +700,175 @@ function mergeYamlWithProject(project, yamlConfig) {
|
|
|
694
700
|
};
|
|
695
701
|
}
|
|
696
702
|
|
|
703
|
+
// src/lib/validation.ts
|
|
704
|
+
import { existsSync as existsSync5, accessSync, constants } from "fs";
|
|
705
|
+
import { homedir as homedir2 } from "os";
|
|
706
|
+
import { join as join5 } from "path";
|
|
707
|
+
import chalk from "chalk";
|
|
708
|
+
var MACOS_PROTECTED_PATHS = [
|
|
709
|
+
join5(homedir2(), "Desktop"),
|
|
710
|
+
join5(homedir2(), "Documents"),
|
|
711
|
+
join5(homedir2(), "Downloads")
|
|
712
|
+
];
|
|
713
|
+
function isProtectedPath(path) {
|
|
714
|
+
if (process.platform !== "darwin") return false;
|
|
715
|
+
const normalizedPath = path.replace(/\/+$/, "");
|
|
716
|
+
return MACOS_PROTECTED_PATHS.some(
|
|
717
|
+
(protected_) => normalizedPath === protected_ || normalizedPath.startsWith(protected_ + "/")
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
function checkPortAvailable(port) {
|
|
721
|
+
const result = execCommandSafe(`lsof -i :${port} -P -n 2>/dev/null | grep LISTEN | head -1`);
|
|
722
|
+
if (!result.success || !result.output) {
|
|
723
|
+
return { available: true };
|
|
724
|
+
}
|
|
725
|
+
const parts = result.output.trim().split(/\s+/);
|
|
726
|
+
const processName = parts[0] || "unknown";
|
|
727
|
+
return { available: false, usedBy: processName };
|
|
728
|
+
}
|
|
729
|
+
function validateProject(project) {
|
|
730
|
+
const errors = [];
|
|
731
|
+
const warnings = [];
|
|
732
|
+
if (!existsSync5(project.path)) {
|
|
733
|
+
errors.push(`Path does not exist: ${project.path}`);
|
|
734
|
+
}
|
|
735
|
+
if (isProtectedPath(project.path)) {
|
|
736
|
+
errors.push(
|
|
737
|
+
`Path is in a macOS protected folder (${project.path}). Nginx cannot access Desktop/Documents/Downloads without Full Disk Access. Move your project to ~/projects or grant nginx Full Disk Access in System Settings.`
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
if (project.type === "npm" && project.port) {
|
|
741
|
+
const portCheck = checkPortAvailable(project.port);
|
|
742
|
+
if (!portCheck.available) {
|
|
743
|
+
warnings.push(`Port ${project.port} is in use by ${portCheck.usedBy}`);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
if (!project.hostname.match(/^[a-zA-Z0-9][a-zA-Z0-9.-]*[a-zA-Z0-9]$/)) {
|
|
747
|
+
warnings.push(`Hostname "${project.hostname}" may be invalid`);
|
|
748
|
+
}
|
|
749
|
+
if (project.type === "npm" && !project.start) {
|
|
750
|
+
const pkgPath = join5(project.path, "package.json");
|
|
751
|
+
if (existsSync5(pkgPath)) {
|
|
752
|
+
try {
|
|
753
|
+
const pkg = JSON.parse(__require("fs").readFileSync(pkgPath, "utf-8"));
|
|
754
|
+
if (!pkg.scripts?.start) {
|
|
755
|
+
warnings.push('No start command specified and package.json has no "start" script');
|
|
756
|
+
}
|
|
757
|
+
} catch {
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return {
|
|
762
|
+
valid: errors.length === 0,
|
|
763
|
+
errors,
|
|
764
|
+
warnings
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
function validateConfig(config) {
|
|
768
|
+
const errors = [];
|
|
769
|
+
const warnings = [];
|
|
770
|
+
const listenPort = parseInt(config.defaults.nginxListen.split(":").pop() || "80", 10);
|
|
771
|
+
if (listenPort === 80 || listenPort === 443) {
|
|
772
|
+
if (process.getuid && process.getuid() !== 0) {
|
|
773
|
+
warnings.push(
|
|
774
|
+
`Nginx is configured to listen on port ${listenPort}, which requires root/sudo. Run 'sudo bindler apply' or use a higher port like 8080.`
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
const nginxPath = config.defaults.nginxManagedPath;
|
|
779
|
+
const nginxDir = nginxPath.substring(0, nginxPath.lastIndexOf("/"));
|
|
780
|
+
if (!existsSync5(nginxDir)) {
|
|
781
|
+
errors.push(`Nginx config directory does not exist: ${nginxDir}`);
|
|
782
|
+
}
|
|
783
|
+
for (const project of config.projects) {
|
|
784
|
+
if (project.enabled === false) continue;
|
|
785
|
+
const projectResult = validateProject(project);
|
|
786
|
+
for (const err of projectResult.errors) {
|
|
787
|
+
errors.push(`[${project.name}] ${err}`);
|
|
788
|
+
}
|
|
789
|
+
for (const warn of projectResult.warnings) {
|
|
790
|
+
warnings.push(`[${project.name}] ${warn}`);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
const hostnames = config.projects.filter((p) => p.enabled !== false).map((p) => p.hostname);
|
|
794
|
+
const duplicates = hostnames.filter((h, i) => hostnames.indexOf(h) !== i);
|
|
795
|
+
if (duplicates.length > 0) {
|
|
796
|
+
const uniqueDupes = [...new Set(duplicates)];
|
|
797
|
+
for (const dupe of uniqueDupes) {
|
|
798
|
+
const projects = config.projects.filter((p) => p.hostname === dupe).map((p) => p.name);
|
|
799
|
+
if (!projects.every((p, i, arr) => {
|
|
800
|
+
const proj = config.projects.find((x) => x.name === p);
|
|
801
|
+
const firstProj = config.projects.find((x) => x.name === arr[0]);
|
|
802
|
+
return proj?.basePath !== firstProj?.basePath || i === 0;
|
|
803
|
+
})) {
|
|
804
|
+
warnings.push(`Hostname "${dupe}" is shared by: ${projects.join(", ")} (ensure they have different base paths)`);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
const ports = config.projects.filter((p) => p.enabled !== false && p.type === "npm" && p.port).map((p) => ({ name: p.name, port: p.port }));
|
|
809
|
+
const portMap = /* @__PURE__ */ new Map();
|
|
810
|
+
for (const { name, port } of ports) {
|
|
811
|
+
const existing = portMap.get(port) || [];
|
|
812
|
+
existing.push(name);
|
|
813
|
+
portMap.set(port, existing);
|
|
814
|
+
}
|
|
815
|
+
for (const [port, names] of portMap) {
|
|
816
|
+
if (names.length > 1) {
|
|
817
|
+
errors.push(`Port ${port} is used by multiple projects: ${names.join(", ")}`);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return {
|
|
821
|
+
valid: errors.length === 0,
|
|
822
|
+
errors,
|
|
823
|
+
warnings
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
function printValidationResult(result) {
|
|
827
|
+
if (result.errors.length > 0) {
|
|
828
|
+
console.log(chalk.red("\n\u2717 Validation errors:"));
|
|
829
|
+
for (const err of result.errors) {
|
|
830
|
+
console.log(chalk.red(` \u2022 ${err}`));
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
if (result.warnings.length > 0) {
|
|
834
|
+
console.log(chalk.yellow("\n\u26A0 Warnings:"));
|
|
835
|
+
for (const warn of result.warnings) {
|
|
836
|
+
console.log(chalk.yellow(` \u2022 ${warn}`));
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
if (result.valid && result.warnings.length === 0) {
|
|
840
|
+
console.log(chalk.green("\u2713 All checks passed"));
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
function runPreflightChecks(config) {
|
|
844
|
+
const result = validateConfig(config);
|
|
845
|
+
const nginxCheck = execCommandSafe("which nginx");
|
|
846
|
+
if (!nginxCheck.success) {
|
|
847
|
+
result.errors.push("Nginx is not installed. Run: brew install nginx");
|
|
848
|
+
}
|
|
849
|
+
const nginxRunning = execCommandSafe("pgrep nginx");
|
|
850
|
+
if (!nginxRunning.success) {
|
|
851
|
+
result.warnings.push("Nginx is not running. After apply, start it with: brew services start nginx");
|
|
852
|
+
}
|
|
853
|
+
const localProjects = config.projects.filter((p) => p.local && p.enabled !== false);
|
|
854
|
+
if (localProjects.length > 0) {
|
|
855
|
+
const hostsResult = execCommandSafe("cat /etc/hosts");
|
|
856
|
+
if (hostsResult.success) {
|
|
857
|
+
for (const project of localProjects) {
|
|
858
|
+
if (!hostsResult.output.includes(project.hostname)) {
|
|
859
|
+
result.warnings.push(
|
|
860
|
+
`Hostname "${project.hostname}" not in /etc/hosts. Add: 127.0.0.1 ${project.hostname}`
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
return result;
|
|
867
|
+
}
|
|
868
|
+
|
|
697
869
|
// src/commands/new.ts
|
|
698
870
|
async function newCommand(options) {
|
|
699
|
-
console.log(
|
|
871
|
+
console.log(chalk2.dim("Checking prerequisites...\n"));
|
|
700
872
|
const issues = [];
|
|
701
873
|
if (!isNginxInstalled()) {
|
|
702
874
|
issues.push("nginx is not installed. Install: brew install nginx (macOS) or apt install nginx (Linux)");
|
|
@@ -705,11 +877,11 @@ async function newCommand(options) {
|
|
|
705
877
|
issues.push("PM2 is not installed. Install: npm install -g pm2");
|
|
706
878
|
}
|
|
707
879
|
if (issues.length > 0) {
|
|
708
|
-
console.log(
|
|
880
|
+
console.log(chalk2.red("Missing prerequisites:\n"));
|
|
709
881
|
for (const issue of issues) {
|
|
710
|
-
console.log(
|
|
882
|
+
console.log(chalk2.red(` \u2717 ${issue}`));
|
|
711
883
|
}
|
|
712
|
-
console.log(
|
|
884
|
+
console.log(chalk2.dim("\nRun `bindler doctor` for full diagnostics."));
|
|
713
885
|
const { proceed } = await inquirer.prompt([
|
|
714
886
|
{
|
|
715
887
|
type: "confirm",
|
|
@@ -723,17 +895,17 @@ async function newCommand(options) {
|
|
|
723
895
|
}
|
|
724
896
|
console.log("");
|
|
725
897
|
} else {
|
|
726
|
-
console.log(
|
|
898
|
+
console.log(chalk2.green("\u2713 Prerequisites OK\n"));
|
|
727
899
|
}
|
|
728
900
|
const defaults = getDefaults();
|
|
729
901
|
let project = {};
|
|
730
902
|
const cwd = process.cwd();
|
|
731
903
|
const cwdName = basename2(cwd);
|
|
732
904
|
const initialPath = options.path || cwd;
|
|
733
|
-
const yamlConfig =
|
|
905
|
+
const yamlConfig = existsSync6(initialPath) ? readBindlerYaml(initialPath) : null;
|
|
734
906
|
let yamlDefaults = {};
|
|
735
907
|
if (yamlConfig) {
|
|
736
|
-
console.log(
|
|
908
|
+
console.log(chalk2.cyan("Found bindler.yaml - using as defaults\n"));
|
|
737
909
|
yamlDefaults = yamlToProject(yamlConfig, initialPath);
|
|
738
910
|
}
|
|
739
911
|
if (!options.name) {
|
|
@@ -767,7 +939,7 @@ async function newCommand(options) {
|
|
|
767
939
|
name: "type",
|
|
768
940
|
message: "Project type:",
|
|
769
941
|
choices: (answers2) => {
|
|
770
|
-
const detected =
|
|
942
|
+
const detected = existsSync6(answers2.path) ? detectProjectType(answers2.path) : "static";
|
|
771
943
|
return [
|
|
772
944
|
{ name: `npm (Node.js app)${detected === "npm" ? " - detected" : ""}`, value: "npm" },
|
|
773
945
|
{ name: `static (HTML/CSS/JS)${detected === "static" ? " - detected" : ""}`, value: "static" }
|
|
@@ -775,7 +947,7 @@ async function newCommand(options) {
|
|
|
775
947
|
},
|
|
776
948
|
default: (answers2) => {
|
|
777
949
|
if (yamlDefaults.type) return yamlDefaults.type;
|
|
778
|
-
return
|
|
950
|
+
return existsSync6(answers2.path) ? detectProjectType(answers2.path) : "static";
|
|
779
951
|
}
|
|
780
952
|
},
|
|
781
953
|
{
|
|
@@ -808,7 +980,7 @@ async function newCommand(options) {
|
|
|
808
980
|
if (yamlDefaults.security) project.security = yamlDefaults.security;
|
|
809
981
|
if (yamlDefaults.environments) project.environments = yamlDefaults.environments;
|
|
810
982
|
if (answers.type === "npm") {
|
|
811
|
-
const scripts =
|
|
983
|
+
const scripts = existsSync6(answers.path) ? getPackageJsonScripts(answers.path) : [];
|
|
812
984
|
const suggestedPort = yamlDefaults.port || findAvailablePort();
|
|
813
985
|
const npmAnswers = await inquirer.prompt([
|
|
814
986
|
{
|
|
@@ -862,7 +1034,7 @@ async function newCommand(options) {
|
|
|
862
1034
|
}
|
|
863
1035
|
} else {
|
|
864
1036
|
if (!options.hostname) {
|
|
865
|
-
console.error(
|
|
1037
|
+
console.error(chalk2.red("Error: --hostname is required"));
|
|
866
1038
|
process.exit(1);
|
|
867
1039
|
}
|
|
868
1040
|
project.name = options.name;
|
|
@@ -882,14 +1054,14 @@ async function newCommand(options) {
|
|
|
882
1054
|
}
|
|
883
1055
|
}
|
|
884
1056
|
if (!validateProjectName(project.name)) {
|
|
885
|
-
console.error(
|
|
1057
|
+
console.error(chalk2.red("Error: Invalid project name"));
|
|
886
1058
|
process.exit(1);
|
|
887
1059
|
}
|
|
888
1060
|
if (!validateHostname(project.hostname)) {
|
|
889
|
-
console.error(
|
|
1061
|
+
console.error(chalk2.red("Error: Invalid hostname"));
|
|
890
1062
|
process.exit(1);
|
|
891
1063
|
}
|
|
892
|
-
if (!
|
|
1064
|
+
if (!existsSync6(project.path)) {
|
|
893
1065
|
const createDir = options.name ? true : (await inquirer.prompt([
|
|
894
1066
|
{
|
|
895
1067
|
type: "confirm",
|
|
@@ -900,51 +1072,72 @@ async function newCommand(options) {
|
|
|
900
1072
|
])).create;
|
|
901
1073
|
if (createDir) {
|
|
902
1074
|
mkdirSync3(project.path, { recursive: true });
|
|
903
|
-
console.log(
|
|
1075
|
+
console.log(chalk2.green(`Created directory: ${project.path}`));
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
const validationResult = validateProject(project);
|
|
1079
|
+
if (!validationResult.valid || validationResult.warnings.length > 0) {
|
|
1080
|
+
console.log("");
|
|
1081
|
+
printValidationResult(validationResult);
|
|
1082
|
+
if (!validationResult.valid) {
|
|
1083
|
+
console.log(chalk2.red("\n\u2717 Cannot add project due to validation errors."));
|
|
1084
|
+
process.exit(1);
|
|
1085
|
+
}
|
|
1086
|
+
const { proceed } = await inquirer.prompt([
|
|
1087
|
+
{
|
|
1088
|
+
type: "confirm",
|
|
1089
|
+
name: "proceed",
|
|
1090
|
+
message: "Continue with these warnings?",
|
|
1091
|
+
default: true
|
|
1092
|
+
}
|
|
1093
|
+
]);
|
|
1094
|
+
if (!proceed) {
|
|
1095
|
+
console.log(chalk2.yellow("Aborted."));
|
|
1096
|
+
process.exit(0);
|
|
904
1097
|
}
|
|
905
1098
|
}
|
|
906
1099
|
try {
|
|
907
1100
|
addProject(project);
|
|
908
|
-
console.log(
|
|
1101
|
+
console.log(chalk2.green(`
|
|
909
1102
|
Project "${project.name}" added successfully!`));
|
|
910
1103
|
if (project.local) {
|
|
911
|
-
console.log(
|
|
1104
|
+
console.log(chalk2.yellow(`
|
|
912
1105
|
Local project - add to /etc/hosts:`));
|
|
913
|
-
console.log(
|
|
914
|
-
console.log(
|
|
915
|
-
Run ${
|
|
916
|
-
console.log(
|
|
1106
|
+
console.log(chalk2.cyan(` echo "127.0.0.1 ${project.hostname}" | sudo tee -a /etc/hosts`));
|
|
1107
|
+
console.log(chalk2.dim(`
|
|
1108
|
+
Run ${chalk2.cyan("sudo bindler apply")} to update nginx.`));
|
|
1109
|
+
console.log(chalk2.dim(`Then access at: ${chalk2.cyan(`http://${project.hostname}:8080`)}`));
|
|
917
1110
|
} else {
|
|
918
|
-
console.log(
|
|
919
|
-
Configuration saved. Run ${
|
|
1111
|
+
console.log(chalk2.dim(`
|
|
1112
|
+
Configuration saved. Run ${chalk2.cyan("sudo bindler apply")} to update nginx and cloudflare.`));
|
|
920
1113
|
}
|
|
921
1114
|
if (project.type === "npm") {
|
|
922
|
-
console.log(
|
|
1115
|
+
console.log(chalk2.dim(`Run ${chalk2.cyan(`bindler start ${project.name}`)} to start the application.`));
|
|
923
1116
|
}
|
|
924
1117
|
} catch (error) {
|
|
925
|
-
console.error(
|
|
1118
|
+
console.error(chalk2.red(`Error: ${error instanceof Error ? error.message : error}`));
|
|
926
1119
|
process.exit(1);
|
|
927
1120
|
}
|
|
928
1121
|
}
|
|
929
1122
|
|
|
930
1123
|
// src/commands/list.ts
|
|
931
|
-
import
|
|
1124
|
+
import chalk3 from "chalk";
|
|
932
1125
|
import Table from "cli-table3";
|
|
933
1126
|
async function listCommand() {
|
|
934
1127
|
const projects = listProjects();
|
|
935
1128
|
if (projects.length === 0) {
|
|
936
|
-
console.log(
|
|
937
|
-
console.log(
|
|
1129
|
+
console.log(chalk3.yellow("No projects registered."));
|
|
1130
|
+
console.log(chalk3.dim(`Run ${chalk3.cyan("bindler new")} to create one.`));
|
|
938
1131
|
return;
|
|
939
1132
|
}
|
|
940
1133
|
const table = new Table({
|
|
941
1134
|
head: [
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1135
|
+
chalk3.cyan("Name"),
|
|
1136
|
+
chalk3.cyan("Type"),
|
|
1137
|
+
chalk3.cyan("Hostname"),
|
|
1138
|
+
chalk3.cyan("Port"),
|
|
1139
|
+
chalk3.cyan("Path"),
|
|
1140
|
+
chalk3.cyan("Status")
|
|
948
1141
|
],
|
|
949
1142
|
style: {
|
|
950
1143
|
head: [],
|
|
@@ -956,15 +1149,15 @@ async function listCommand() {
|
|
|
956
1149
|
if (project.type === "npm") {
|
|
957
1150
|
const process2 = getProcessByName(project.name);
|
|
958
1151
|
if (process2) {
|
|
959
|
-
status = process2.status === "online" ?
|
|
1152
|
+
status = process2.status === "online" ? chalk3.green("online") : process2.status === "stopped" ? chalk3.yellow("stopped") : chalk3.red(process2.status);
|
|
960
1153
|
} else {
|
|
961
|
-
status =
|
|
1154
|
+
status = chalk3.dim("not started");
|
|
962
1155
|
}
|
|
963
1156
|
} else {
|
|
964
|
-
status = project.enabled !== false ?
|
|
1157
|
+
status = project.enabled !== false ? chalk3.green("serving") : chalk3.yellow("disabled");
|
|
965
1158
|
}
|
|
966
1159
|
if (project.enabled === false) {
|
|
967
|
-
status =
|
|
1160
|
+
status = chalk3.yellow("disabled");
|
|
968
1161
|
}
|
|
969
1162
|
table.push([
|
|
970
1163
|
project.name,
|
|
@@ -976,17 +1169,17 @@ async function listCommand() {
|
|
|
976
1169
|
]);
|
|
977
1170
|
}
|
|
978
1171
|
console.log(table.toString());
|
|
979
|
-
console.log(
|
|
1172
|
+
console.log(chalk3.dim(`
|
|
980
1173
|
${projects.length} project(s) registered`));
|
|
981
1174
|
}
|
|
982
1175
|
|
|
983
1176
|
// src/commands/status.ts
|
|
984
|
-
import
|
|
1177
|
+
import chalk4 from "chalk";
|
|
985
1178
|
import Table2 from "cli-table3";
|
|
986
1179
|
async function statusCommand() {
|
|
987
1180
|
const projects = listProjects();
|
|
988
1181
|
if (projects.length === 0) {
|
|
989
|
-
console.log(
|
|
1182
|
+
console.log(chalk4.yellow("No projects registered."));
|
|
990
1183
|
return;
|
|
991
1184
|
}
|
|
992
1185
|
const statuses = [];
|
|
@@ -1010,12 +1203,12 @@ async function statusCommand() {
|
|
|
1010
1203
|
}
|
|
1011
1204
|
const table = new Table2({
|
|
1012
1205
|
head: [
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1206
|
+
chalk4.cyan("Name"),
|
|
1207
|
+
chalk4.cyan("Type"),
|
|
1208
|
+
chalk4.cyan("Hostname"),
|
|
1209
|
+
chalk4.cyan("Port"),
|
|
1210
|
+
chalk4.cyan("PM2 Status"),
|
|
1211
|
+
chalk4.cyan("Port Check")
|
|
1019
1212
|
],
|
|
1020
1213
|
style: {
|
|
1021
1214
|
head: [],
|
|
@@ -1028,25 +1221,25 @@ async function statusCommand() {
|
|
|
1028
1221
|
if (status.type === "npm") {
|
|
1029
1222
|
switch (status.pm2Status) {
|
|
1030
1223
|
case "online":
|
|
1031
|
-
pm2StatusStr =
|
|
1224
|
+
pm2StatusStr = chalk4.green("online");
|
|
1032
1225
|
break;
|
|
1033
1226
|
case "stopped":
|
|
1034
|
-
pm2StatusStr =
|
|
1227
|
+
pm2StatusStr = chalk4.yellow("stopped");
|
|
1035
1228
|
break;
|
|
1036
1229
|
case "errored":
|
|
1037
|
-
pm2StatusStr =
|
|
1230
|
+
pm2StatusStr = chalk4.red("errored");
|
|
1038
1231
|
break;
|
|
1039
1232
|
case "not_managed":
|
|
1040
|
-
pm2StatusStr =
|
|
1233
|
+
pm2StatusStr = chalk4.dim("not started");
|
|
1041
1234
|
break;
|
|
1042
1235
|
default:
|
|
1043
|
-
pm2StatusStr =
|
|
1236
|
+
pm2StatusStr = chalk4.dim(status.pm2Status || "-");
|
|
1044
1237
|
}
|
|
1045
|
-
portCheckStr = status.portListening ?
|
|
1238
|
+
portCheckStr = status.portListening ? chalk4.green("listening") : chalk4.red("not listening");
|
|
1046
1239
|
}
|
|
1047
1240
|
if (status.enabled === false) {
|
|
1048
|
-
pm2StatusStr =
|
|
1049
|
-
portCheckStr =
|
|
1241
|
+
pm2StatusStr = chalk4.yellow("disabled");
|
|
1242
|
+
portCheckStr = chalk4.dim("-");
|
|
1050
1243
|
}
|
|
1051
1244
|
table.push([
|
|
1052
1245
|
status.name,
|
|
@@ -1060,14 +1253,14 @@ async function statusCommand() {
|
|
|
1060
1253
|
console.log(table.toString());
|
|
1061
1254
|
const runningProcesses = getPm2List().filter((p) => p.name.startsWith("bindler:"));
|
|
1062
1255
|
if (runningProcesses.length > 0) {
|
|
1063
|
-
console.log(
|
|
1256
|
+
console.log(chalk4.bold("\nPM2 Process Details:"));
|
|
1064
1257
|
const detailTable = new Table2({
|
|
1065
1258
|
head: [
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1259
|
+
chalk4.cyan("Process"),
|
|
1260
|
+
chalk4.cyan("CPU"),
|
|
1261
|
+
chalk4.cyan("Memory"),
|
|
1262
|
+
chalk4.cyan("Uptime"),
|
|
1263
|
+
chalk4.cyan("Restarts")
|
|
1071
1264
|
],
|
|
1072
1265
|
style: {
|
|
1073
1266
|
head: [],
|
|
@@ -1088,197 +1281,198 @@ async function statusCommand() {
|
|
|
1088
1281
|
}
|
|
1089
1282
|
|
|
1090
1283
|
// src/commands/start.ts
|
|
1091
|
-
import
|
|
1284
|
+
import chalk5 from "chalk";
|
|
1092
1285
|
async function startCommand(name, options) {
|
|
1093
1286
|
if (options.all) {
|
|
1094
1287
|
const projects = listProjects();
|
|
1095
1288
|
const npmProjects = projects.filter((p) => p.type === "npm");
|
|
1096
1289
|
if (npmProjects.length === 0) {
|
|
1097
|
-
console.log(
|
|
1290
|
+
console.log(chalk5.yellow("No npm projects to start."));
|
|
1098
1291
|
return;
|
|
1099
1292
|
}
|
|
1100
|
-
console.log(
|
|
1293
|
+
console.log(chalk5.blue(`Starting ${npmProjects.length} npm project(s)...`));
|
|
1101
1294
|
const results = startAllProjects(npmProjects);
|
|
1102
1295
|
for (const result2 of results) {
|
|
1103
1296
|
if (result2.success) {
|
|
1104
|
-
console.log(
|
|
1297
|
+
console.log(chalk5.green(` \u2713 ${result2.name}`));
|
|
1105
1298
|
} else {
|
|
1106
|
-
console.log(
|
|
1299
|
+
console.log(chalk5.red(` \u2717 ${result2.name}: ${result2.error}`));
|
|
1107
1300
|
}
|
|
1108
1301
|
}
|
|
1109
1302
|
const succeeded = results.filter((r) => r.success).length;
|
|
1110
|
-
console.log(
|
|
1303
|
+
console.log(chalk5.dim(`
|
|
1111
1304
|
${succeeded}/${results.length} started successfully`));
|
|
1112
1305
|
return;
|
|
1113
1306
|
}
|
|
1114
1307
|
if (!name) {
|
|
1115
|
-
console.error(
|
|
1308
|
+
console.error(chalk5.red("Error: Project name is required. Use --all to start all projects."));
|
|
1116
1309
|
process.exit(1);
|
|
1117
1310
|
}
|
|
1118
1311
|
const project = getProject(name);
|
|
1119
1312
|
if (!project) {
|
|
1120
|
-
console.error(
|
|
1313
|
+
console.error(chalk5.red(`Error: Project "${name}" not found`));
|
|
1121
1314
|
process.exit(1);
|
|
1122
1315
|
}
|
|
1123
1316
|
if (project.type !== "npm") {
|
|
1124
|
-
console.log(
|
|
1125
|
-
console.log(
|
|
1317
|
+
console.log(chalk5.blue(`Project "${name}" is a static site - no process to start.`));
|
|
1318
|
+
console.log(chalk5.dim("Static sites are served directly by nginx."));
|
|
1126
1319
|
return;
|
|
1127
1320
|
}
|
|
1128
|
-
console.log(
|
|
1321
|
+
console.log(chalk5.blue(`Starting ${name}...`));
|
|
1129
1322
|
const result = startProject(project);
|
|
1130
1323
|
if (result.success) {
|
|
1131
|
-
console.log(
|
|
1132
|
-
console.log(
|
|
1133
|
-
console.log(
|
|
1324
|
+
console.log(chalk5.green(`\u2713 ${name} started successfully`));
|
|
1325
|
+
console.log(chalk5.dim(` Port: ${project.port}`));
|
|
1326
|
+
console.log(chalk5.dim(` URL: https://${project.hostname}`));
|
|
1134
1327
|
} else {
|
|
1135
|
-
console.error(
|
|
1328
|
+
console.error(chalk5.red(`\u2717 Failed to start ${name}: ${result.error}`));
|
|
1136
1329
|
process.exit(1);
|
|
1137
1330
|
}
|
|
1138
1331
|
}
|
|
1139
1332
|
|
|
1140
1333
|
// src/commands/stop.ts
|
|
1141
|
-
import
|
|
1334
|
+
import chalk6 from "chalk";
|
|
1142
1335
|
async function stopCommand(name, options) {
|
|
1143
1336
|
if (options.all) {
|
|
1144
1337
|
const projects = listProjects();
|
|
1145
1338
|
const npmProjects = projects.filter((p) => p.type === "npm");
|
|
1146
1339
|
if (npmProjects.length === 0) {
|
|
1147
|
-
console.log(
|
|
1340
|
+
console.log(chalk6.yellow("No npm projects to stop."));
|
|
1148
1341
|
return;
|
|
1149
1342
|
}
|
|
1150
|
-
console.log(
|
|
1343
|
+
console.log(chalk6.blue(`Stopping ${npmProjects.length} npm project(s)...`));
|
|
1151
1344
|
const results = stopAllProjects(npmProjects);
|
|
1152
1345
|
for (const result2 of results) {
|
|
1153
1346
|
if (result2.success) {
|
|
1154
|
-
console.log(
|
|
1347
|
+
console.log(chalk6.green(` \u2713 ${result2.name}`));
|
|
1155
1348
|
} else {
|
|
1156
|
-
console.log(
|
|
1349
|
+
console.log(chalk6.red(` \u2717 ${result2.name}: ${result2.error}`));
|
|
1157
1350
|
}
|
|
1158
1351
|
}
|
|
1159
1352
|
const succeeded = results.filter((r) => r.success).length;
|
|
1160
|
-
console.log(
|
|
1353
|
+
console.log(chalk6.dim(`
|
|
1161
1354
|
${succeeded}/${results.length} stopped successfully`));
|
|
1162
1355
|
return;
|
|
1163
1356
|
}
|
|
1164
1357
|
if (!name) {
|
|
1165
|
-
console.error(
|
|
1358
|
+
console.error(chalk6.red("Error: Project name is required. Use --all to stop all projects."));
|
|
1166
1359
|
process.exit(1);
|
|
1167
1360
|
}
|
|
1168
1361
|
const project = getProject(name);
|
|
1169
1362
|
if (!project) {
|
|
1170
|
-
console.error(
|
|
1363
|
+
console.error(chalk6.red(`Error: Project "${name}" not found`));
|
|
1171
1364
|
process.exit(1);
|
|
1172
1365
|
}
|
|
1173
1366
|
if (project.type !== "npm") {
|
|
1174
|
-
console.log(
|
|
1367
|
+
console.log(chalk6.yellow(`Project "${name}" is a static site - no process to stop.`));
|
|
1175
1368
|
return;
|
|
1176
1369
|
}
|
|
1177
|
-
console.log(
|
|
1370
|
+
console.log(chalk6.blue(`Stopping ${name}...`));
|
|
1178
1371
|
const result = stopProject(name);
|
|
1179
1372
|
if (result.success) {
|
|
1180
|
-
console.log(
|
|
1373
|
+
console.log(chalk6.green(`\u2713 ${name} stopped successfully`));
|
|
1181
1374
|
} else {
|
|
1182
|
-
console.error(
|
|
1375
|
+
console.error(chalk6.red(`\u2717 Failed to stop ${name}: ${result.error}`));
|
|
1183
1376
|
process.exit(1);
|
|
1184
1377
|
}
|
|
1185
1378
|
}
|
|
1186
1379
|
|
|
1187
1380
|
// src/commands/restart.ts
|
|
1188
|
-
import
|
|
1381
|
+
import chalk7 from "chalk";
|
|
1189
1382
|
async function restartCommand(name, options) {
|
|
1190
1383
|
if (options.all) {
|
|
1191
1384
|
const projects = listProjects();
|
|
1192
1385
|
const npmProjects = projects.filter((p) => p.type === "npm");
|
|
1193
1386
|
if (npmProjects.length === 0) {
|
|
1194
|
-
console.log(
|
|
1387
|
+
console.log(chalk7.yellow("No npm projects to restart."));
|
|
1195
1388
|
return;
|
|
1196
1389
|
}
|
|
1197
|
-
console.log(
|
|
1390
|
+
console.log(chalk7.blue(`Restarting ${npmProjects.length} npm project(s)...`));
|
|
1198
1391
|
const results = restartAllProjects(npmProjects);
|
|
1199
1392
|
for (const result2 of results) {
|
|
1200
1393
|
if (result2.success) {
|
|
1201
|
-
console.log(
|
|
1394
|
+
console.log(chalk7.green(` \u2713 ${result2.name}`));
|
|
1202
1395
|
} else {
|
|
1203
|
-
console.log(
|
|
1396
|
+
console.log(chalk7.red(` \u2717 ${result2.name}: ${result2.error}`));
|
|
1204
1397
|
}
|
|
1205
1398
|
}
|
|
1206
1399
|
const succeeded = results.filter((r) => r.success).length;
|
|
1207
|
-
console.log(
|
|
1400
|
+
console.log(chalk7.dim(`
|
|
1208
1401
|
${succeeded}/${results.length} restarted successfully`));
|
|
1209
1402
|
return;
|
|
1210
1403
|
}
|
|
1211
1404
|
if (!name) {
|
|
1212
|
-
console.error(
|
|
1405
|
+
console.error(chalk7.red("Error: Project name is required. Use --all to restart all projects."));
|
|
1213
1406
|
process2.exit(1);
|
|
1214
1407
|
}
|
|
1215
1408
|
const project = getProject(name);
|
|
1216
1409
|
if (!project) {
|
|
1217
|
-
console.error(
|
|
1410
|
+
console.error(chalk7.red(`Error: Project "${name}" not found`));
|
|
1218
1411
|
process2.exit(1);
|
|
1219
1412
|
}
|
|
1220
1413
|
if (project.type !== "npm") {
|
|
1221
|
-
console.log(
|
|
1414
|
+
console.log(chalk7.yellow(`Project "${name}" is a static site - no process to restart.`));
|
|
1222
1415
|
return;
|
|
1223
1416
|
}
|
|
1224
|
-
console.log(
|
|
1417
|
+
console.log(chalk7.blue(`Restarting ${name}...`));
|
|
1225
1418
|
const process2 = getProcessByName(name);
|
|
1226
1419
|
let result;
|
|
1227
1420
|
if (process2) {
|
|
1228
1421
|
result = restartProject(name);
|
|
1229
1422
|
} else {
|
|
1230
|
-
console.log(
|
|
1423
|
+
console.log(chalk7.dim("Process not running, starting..."));
|
|
1231
1424
|
result = startProject(project);
|
|
1232
1425
|
}
|
|
1233
1426
|
if (result.success) {
|
|
1234
|
-
console.log(
|
|
1427
|
+
console.log(chalk7.green(`\u2713 ${name} restarted successfully`));
|
|
1235
1428
|
} else {
|
|
1236
|
-
console.error(
|
|
1429
|
+
console.error(chalk7.red(`\u2717 Failed to restart ${name}: ${result.error}`));
|
|
1237
1430
|
process2.exit(1);
|
|
1238
1431
|
}
|
|
1239
1432
|
}
|
|
1240
1433
|
|
|
1241
1434
|
// src/commands/logs.ts
|
|
1242
|
-
import
|
|
1435
|
+
import chalk8 from "chalk";
|
|
1243
1436
|
async function logsCommand(name, options) {
|
|
1244
1437
|
const project = getProject(name);
|
|
1245
1438
|
if (!project) {
|
|
1246
|
-
console.error(
|
|
1439
|
+
console.error(chalk8.red(`Error: Project "${name}" not found`));
|
|
1247
1440
|
process2.exit(1);
|
|
1248
1441
|
}
|
|
1249
1442
|
if (project.type !== "npm") {
|
|
1250
|
-
console.log(
|
|
1251
|
-
console.log(
|
|
1252
|
-
console.log(
|
|
1253
|
-
console.log(
|
|
1443
|
+
console.log(chalk8.yellow(`Project "${name}" is a static site - no logs available.`));
|
|
1444
|
+
console.log(chalk8.dim("Check nginx access/error logs instead:"));
|
|
1445
|
+
console.log(chalk8.dim(" /var/log/nginx/access.log"));
|
|
1446
|
+
console.log(chalk8.dim(" /var/log/nginx/error.log"));
|
|
1254
1447
|
return;
|
|
1255
1448
|
}
|
|
1256
1449
|
const process2 = getProcessByName(name);
|
|
1257
1450
|
if (!process2) {
|
|
1258
|
-
console.log(
|
|
1259
|
-
console.log(
|
|
1451
|
+
console.log(chalk8.yellow(`Project "${name}" has not been started yet.`));
|
|
1452
|
+
console.log(chalk8.dim(`Run ${chalk8.cyan(`bindler start ${name}`)} first.`));
|
|
1260
1453
|
return;
|
|
1261
1454
|
}
|
|
1262
1455
|
const lines = options.lines || 200;
|
|
1263
1456
|
const follow = options.follow || false;
|
|
1264
1457
|
if (follow) {
|
|
1265
|
-
console.log(
|
|
1458
|
+
console.log(chalk8.dim(`Following logs for ${name}... (Ctrl+C to exit)`));
|
|
1266
1459
|
}
|
|
1267
1460
|
await showLogs(name, follow, lines);
|
|
1268
1461
|
}
|
|
1269
1462
|
|
|
1270
1463
|
// src/commands/update.ts
|
|
1271
|
-
import
|
|
1464
|
+
import chalk9 from "chalk";
|
|
1465
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1272
1466
|
async function updateCommand(name, options) {
|
|
1273
1467
|
const project = getProject(name);
|
|
1274
1468
|
if (!project) {
|
|
1275
|
-
console.error(
|
|
1469
|
+
console.error(chalk9.red(`Error: Project "${name}" not found`));
|
|
1276
1470
|
process.exit(1);
|
|
1277
1471
|
}
|
|
1278
1472
|
const updates = {};
|
|
1279
1473
|
if (options.hostname) {
|
|
1280
1474
|
if (!validateHostname(options.hostname)) {
|
|
1281
|
-
console.error(
|
|
1475
|
+
console.error(chalk9.red("Error: Invalid hostname format"));
|
|
1282
1476
|
process.exit(1);
|
|
1283
1477
|
}
|
|
1284
1478
|
updates.hostname = options.hostname;
|
|
@@ -1286,11 +1480,11 @@ async function updateCommand(name, options) {
|
|
|
1286
1480
|
if (options.port) {
|
|
1287
1481
|
const port = parseInt(options.port, 10);
|
|
1288
1482
|
if (!validatePort(port)) {
|
|
1289
|
-
console.error(
|
|
1483
|
+
console.error(chalk9.red("Error: Invalid port. Use a number between 1024 and 65535."));
|
|
1290
1484
|
process.exit(1);
|
|
1291
1485
|
}
|
|
1292
1486
|
if (!isPortAvailable(port) && port !== project.port) {
|
|
1293
|
-
console.error(
|
|
1487
|
+
console.error(chalk9.red(`Error: Port ${port} is already in use by another project.`));
|
|
1294
1488
|
process.exit(1);
|
|
1295
1489
|
}
|
|
1296
1490
|
updates.port = port;
|
|
@@ -1300,12 +1494,22 @@ async function updateCommand(name, options) {
|
|
|
1300
1494
|
}
|
|
1301
1495
|
if (options.start) {
|
|
1302
1496
|
if (project.type !== "npm") {
|
|
1303
|
-
console.error(
|
|
1497
|
+
console.error(chalk9.red("Error: Start command only applies to npm projects"));
|
|
1304
1498
|
process.exit(1);
|
|
1305
1499
|
}
|
|
1306
1500
|
updates.start = options.start;
|
|
1307
1501
|
}
|
|
1308
1502
|
if (options.path) {
|
|
1503
|
+
if (!existsSync7(options.path)) {
|
|
1504
|
+
console.error(chalk9.red(`Error: Path does not exist: ${options.path}`));
|
|
1505
|
+
process.exit(1);
|
|
1506
|
+
}
|
|
1507
|
+
if (isProtectedPath(options.path)) {
|
|
1508
|
+
console.error(chalk9.red(`Error: Path is in a macOS protected folder (Desktop/Documents/Downloads).`));
|
|
1509
|
+
console.error(chalk9.yellow(`Nginx cannot access these folders without Full Disk Access.`));
|
|
1510
|
+
console.error(chalk9.dim(`Move your project to ~/projects or another accessible location.`));
|
|
1511
|
+
process.exit(1);
|
|
1512
|
+
}
|
|
1309
1513
|
updates.path = options.path;
|
|
1310
1514
|
}
|
|
1311
1515
|
if (options.env && options.env.length > 0) {
|
|
@@ -1313,7 +1517,7 @@ async function updateCommand(name, options) {
|
|
|
1313
1517
|
for (const envStr of options.env) {
|
|
1314
1518
|
const [key, ...valueParts] = envStr.split("=");
|
|
1315
1519
|
if (!key) {
|
|
1316
|
-
console.error(
|
|
1520
|
+
console.error(chalk9.red(`Error: Invalid env format: ${envStr}. Use KEY=value`));
|
|
1317
1521
|
process.exit(1);
|
|
1318
1522
|
}
|
|
1319
1523
|
env[key] = valueParts.join("=");
|
|
@@ -1326,23 +1530,23 @@ async function updateCommand(name, options) {
|
|
|
1326
1530
|
updates.enabled = false;
|
|
1327
1531
|
}
|
|
1328
1532
|
if (Object.keys(updates).length === 0) {
|
|
1329
|
-
console.log(
|
|
1330
|
-
console.log(
|
|
1533
|
+
console.log(chalk9.yellow("No updates specified."));
|
|
1534
|
+
console.log(chalk9.dim("Available options: --hostname, --port, --start, --path, --env, --enable, --disable"));
|
|
1331
1535
|
return;
|
|
1332
1536
|
}
|
|
1333
1537
|
try {
|
|
1334
1538
|
updateProject(name, updates);
|
|
1335
|
-
console.log(
|
|
1539
|
+
console.log(chalk9.green(`\u2713 Project "${name}" updated successfully`));
|
|
1336
1540
|
for (const [key, value] of Object.entries(updates)) {
|
|
1337
|
-
console.log(
|
|
1541
|
+
console.log(chalk9.dim(` ${key}: ${typeof value === "object" ? JSON.stringify(value) : value}`));
|
|
1338
1542
|
}
|
|
1339
|
-
console.log(
|
|
1340
|
-
Run ${
|
|
1543
|
+
console.log(chalk9.dim(`
|
|
1544
|
+
Run ${chalk9.cyan("sudo bindler apply")} to apply changes to nginx.`));
|
|
1341
1545
|
if (project.type === "npm" && (updates.port || updates.start || updates.env)) {
|
|
1342
|
-
console.log(
|
|
1546
|
+
console.log(chalk9.dim(`Run ${chalk9.cyan(`bindler restart ${name}`)} to apply changes to the running process.`));
|
|
1343
1547
|
}
|
|
1344
1548
|
} catch (error) {
|
|
1345
|
-
console.error(
|
|
1549
|
+
console.error(chalk9.red(`Error: ${error instanceof Error ? error.message : error}`));
|
|
1346
1550
|
process.exit(1);
|
|
1347
1551
|
}
|
|
1348
1552
|
}
|
|
@@ -1350,21 +1554,21 @@ Run ${chalk8.cyan("sudo bindler apply")} to apply changes to nginx.`));
|
|
|
1350
1554
|
// src/commands/edit.ts
|
|
1351
1555
|
import { writeFileSync as writeFileSync4, readFileSync as readFileSync4, unlinkSync } from "fs";
|
|
1352
1556
|
import { tmpdir } from "os";
|
|
1353
|
-
import { join as
|
|
1354
|
-
import
|
|
1557
|
+
import { join as join6 } from "path";
|
|
1558
|
+
import chalk10 from "chalk";
|
|
1355
1559
|
async function editCommand(name) {
|
|
1356
1560
|
const project = getProject(name);
|
|
1357
1561
|
if (!project) {
|
|
1358
|
-
console.error(
|
|
1562
|
+
console.error(chalk10.red(`Error: Project "${name}" not found`));
|
|
1359
1563
|
process.exit(1);
|
|
1360
1564
|
}
|
|
1361
1565
|
const editor = process.env.EDITOR || process.env.VISUAL || "vi";
|
|
1362
|
-
const tmpFile =
|
|
1566
|
+
const tmpFile = join6(tmpdir(), `bindler-${name}-${Date.now()}.json`);
|
|
1363
1567
|
writeFileSync4(tmpFile, JSON.stringify(project, null, 2) + "\n");
|
|
1364
|
-
console.log(
|
|
1568
|
+
console.log(chalk10.dim(`Opening ${name} config in ${editor}...`));
|
|
1365
1569
|
const exitCode = await spawnInteractive(editor, [tmpFile]);
|
|
1366
1570
|
if (exitCode !== 0) {
|
|
1367
|
-
console.error(
|
|
1571
|
+
console.error(chalk10.red("Editor exited with error"));
|
|
1368
1572
|
unlinkSync(tmpFile);
|
|
1369
1573
|
process.exit(1);
|
|
1370
1574
|
}
|
|
@@ -1372,7 +1576,7 @@ async function editCommand(name) {
|
|
|
1372
1576
|
try {
|
|
1373
1577
|
editedContent = readFileSync4(tmpFile, "utf-8");
|
|
1374
1578
|
} catch (error) {
|
|
1375
|
-
console.error(
|
|
1579
|
+
console.error(chalk10.red("Failed to read edited file"));
|
|
1376
1580
|
process.exit(1);
|
|
1377
1581
|
} finally {
|
|
1378
1582
|
unlinkSync(tmpFile);
|
|
@@ -1381,44 +1585,44 @@ async function editCommand(name) {
|
|
|
1381
1585
|
try {
|
|
1382
1586
|
editedProject = JSON.parse(editedContent);
|
|
1383
1587
|
} catch (error) {
|
|
1384
|
-
console.error(
|
|
1588
|
+
console.error(chalk10.red("Error: Invalid JSON in edited file"));
|
|
1385
1589
|
process.exit(1);
|
|
1386
1590
|
}
|
|
1387
1591
|
if (editedProject.name !== project.name) {
|
|
1388
|
-
console.error(
|
|
1592
|
+
console.error(chalk10.red("Error: Cannot change project name via edit. Use a new project instead."));
|
|
1389
1593
|
process.exit(1);
|
|
1390
1594
|
}
|
|
1391
1595
|
const originalStr = JSON.stringify(project);
|
|
1392
1596
|
const editedStr = JSON.stringify(editedProject);
|
|
1393
1597
|
if (originalStr === editedStr) {
|
|
1394
|
-
console.log(
|
|
1598
|
+
console.log(chalk10.yellow("No changes made."));
|
|
1395
1599
|
return;
|
|
1396
1600
|
}
|
|
1397
1601
|
try {
|
|
1398
1602
|
const config = readConfig();
|
|
1399
1603
|
const index = config.projects.findIndex((p) => p.name === name);
|
|
1400
1604
|
if (index === -1) {
|
|
1401
|
-
console.error(
|
|
1605
|
+
console.error(chalk10.red("Error: Project not found"));
|
|
1402
1606
|
process.exit(1);
|
|
1403
1607
|
}
|
|
1404
1608
|
config.projects[index] = editedProject;
|
|
1405
1609
|
writeConfig(config);
|
|
1406
|
-
console.log(
|
|
1407
|
-
console.log(
|
|
1408
|
-
Run ${
|
|
1610
|
+
console.log(chalk10.green(`\u2713 Project "${name}" updated successfully`));
|
|
1611
|
+
console.log(chalk10.dim(`
|
|
1612
|
+
Run ${chalk10.cyan("sudo bindler apply")} to apply changes to nginx.`));
|
|
1409
1613
|
} catch (error) {
|
|
1410
|
-
console.error(
|
|
1614
|
+
console.error(chalk10.red(`Error: ${error instanceof Error ? error.message : error}`));
|
|
1411
1615
|
process.exit(1);
|
|
1412
1616
|
}
|
|
1413
1617
|
}
|
|
1414
1618
|
|
|
1415
1619
|
// src/commands/remove.ts
|
|
1416
1620
|
import inquirer2 from "inquirer";
|
|
1417
|
-
import
|
|
1621
|
+
import chalk11 from "chalk";
|
|
1418
1622
|
async function removeCommand(name, options) {
|
|
1419
1623
|
const project = getProject(name);
|
|
1420
1624
|
if (!project) {
|
|
1421
|
-
console.error(
|
|
1625
|
+
console.error(chalk11.red(`Error: Project "${name}" not found`));
|
|
1422
1626
|
process.exit(1);
|
|
1423
1627
|
}
|
|
1424
1628
|
if (!options.force) {
|
|
@@ -1431,34 +1635,56 @@ async function removeCommand(name, options) {
|
|
|
1431
1635
|
}
|
|
1432
1636
|
]);
|
|
1433
1637
|
if (!confirm) {
|
|
1434
|
-
console.log(
|
|
1638
|
+
console.log(chalk11.yellow("Cancelled."));
|
|
1435
1639
|
return;
|
|
1436
1640
|
}
|
|
1437
1641
|
}
|
|
1438
1642
|
if (project.type === "npm") {
|
|
1439
1643
|
const process2 = getProcessByName(name);
|
|
1440
1644
|
if (process2) {
|
|
1441
|
-
console.log(
|
|
1645
|
+
console.log(chalk11.dim("Stopping PM2 process..."));
|
|
1442
1646
|
deleteProject(name);
|
|
1443
1647
|
}
|
|
1444
1648
|
}
|
|
1445
1649
|
try {
|
|
1446
1650
|
removeProject(name);
|
|
1447
|
-
console.log(
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1651
|
+
console.log(chalk11.green(`\u2713 Project "${name}" removed from registry`));
|
|
1652
|
+
if (options.apply) {
|
|
1653
|
+
console.log(chalk11.dim("\nApplying nginx configuration..."));
|
|
1654
|
+
const config = readConfig();
|
|
1655
|
+
try {
|
|
1656
|
+
writeNginxConfig(config);
|
|
1657
|
+
const testResult = testNginxConfig();
|
|
1658
|
+
if (testResult.success) {
|
|
1659
|
+
reloadNginx();
|
|
1660
|
+
console.log(chalk11.green("\u2713 Nginx configuration updated"));
|
|
1661
|
+
} else {
|
|
1662
|
+
console.log(chalk11.yellow("! Nginx config test failed, reload skipped"));
|
|
1663
|
+
}
|
|
1664
|
+
} catch (err) {
|
|
1665
|
+
console.log(chalk11.yellow(`! Failed to update nginx: ${err}`));
|
|
1666
|
+
console.log(chalk11.dim(" Try running: sudo bindler apply"));
|
|
1667
|
+
}
|
|
1668
|
+
} else {
|
|
1669
|
+
console.log(chalk11.dim(`
|
|
1670
|
+
Run ${chalk11.cyan("sudo bindler apply")} to update nginx configuration.`));
|
|
1671
|
+
}
|
|
1672
|
+
console.log(chalk11.yellow("\nNote: Project files were not deleted."));
|
|
1673
|
+
console.log(chalk11.dim(` Path: ${project.path}`));
|
|
1674
|
+
if (!project.local) {
|
|
1675
|
+
console.log(chalk11.yellow("\nCloudflare DNS route was not removed."));
|
|
1676
|
+
console.log(chalk11.dim(" Remove it manually from the Cloudflare dashboard:"));
|
|
1677
|
+
console.log(chalk11.dim(" https://dash.cloudflare.com \u2192 DNS \u2192 Records \u2192 Delete the CNAME for " + project.hostname));
|
|
1678
|
+
}
|
|
1453
1679
|
} catch (error) {
|
|
1454
|
-
console.error(
|
|
1680
|
+
console.error(chalk11.red(`Error: ${error instanceof Error ? error.message : error}`));
|
|
1455
1681
|
process.exit(1);
|
|
1456
1682
|
}
|
|
1457
1683
|
}
|
|
1458
1684
|
|
|
1459
1685
|
// src/commands/apply.ts
|
|
1460
|
-
import
|
|
1461
|
-
import { existsSync as
|
|
1686
|
+
import chalk12 from "chalk";
|
|
1687
|
+
import { existsSync as existsSync8 } from "fs";
|
|
1462
1688
|
|
|
1463
1689
|
// src/lib/cloudflare.ts
|
|
1464
1690
|
function isCloudflaredInstalled() {
|
|
@@ -1553,56 +1779,69 @@ async function applyCommand(options) {
|
|
|
1553
1779
|
let config = readConfig();
|
|
1554
1780
|
const defaults = getDefaults();
|
|
1555
1781
|
if (options.sync) {
|
|
1556
|
-
console.log(
|
|
1782
|
+
console.log(chalk12.dim("Syncing bindler.yaml from project directories...\n"));
|
|
1557
1783
|
let synced = 0;
|
|
1558
1784
|
for (const project of config.projects) {
|
|
1559
|
-
if (!
|
|
1785
|
+
if (!existsSync8(project.path)) continue;
|
|
1560
1786
|
const yamlConfig = readBindlerYaml(project.path);
|
|
1561
1787
|
if (yamlConfig) {
|
|
1562
1788
|
const merged = mergeYamlWithProject(project, yamlConfig);
|
|
1563
1789
|
updateProject(project.name, merged);
|
|
1564
|
-
console.log(
|
|
1790
|
+
console.log(chalk12.green(` \u2713 Synced ${project.name} from bindler.yaml`));
|
|
1565
1791
|
synced++;
|
|
1566
1792
|
}
|
|
1567
1793
|
}
|
|
1568
1794
|
if (synced === 0) {
|
|
1569
|
-
console.log(
|
|
1795
|
+
console.log(chalk12.dim(" No bindler.yaml files found in project directories"));
|
|
1570
1796
|
} else {
|
|
1571
|
-
console.log(
|
|
1797
|
+
console.log(chalk12.dim(`
|
|
1572
1798
|
Synced ${synced} project(s)
|
|
1573
1799
|
`));
|
|
1574
1800
|
}
|
|
1575
1801
|
config = readConfig();
|
|
1576
1802
|
}
|
|
1577
1803
|
if (options.env) {
|
|
1578
|
-
console.log(
|
|
1804
|
+
console.log(chalk12.dim(`Using ${options.env} environment configuration...
|
|
1579
1805
|
`));
|
|
1580
1806
|
const envProjects = listProjectsForEnv(options.env);
|
|
1581
1807
|
config = { ...config, projects: envProjects };
|
|
1582
1808
|
}
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1809
|
+
const hasProjects = config.projects.length > 0;
|
|
1810
|
+
console.log(chalk12.blue("Applying configuration...\n"));
|
|
1811
|
+
if (hasProjects && !options.skipChecks) {
|
|
1812
|
+
console.log(chalk12.dim("Running preflight checks..."));
|
|
1813
|
+
const checkResult = runPreflightChecks(config);
|
|
1814
|
+
if (!checkResult.valid) {
|
|
1815
|
+
printValidationResult(checkResult);
|
|
1816
|
+
console.log(chalk12.red("\n\u2717 Preflight checks failed. Fix the errors above before applying."));
|
|
1817
|
+
console.log(chalk12.dim(" Use --skip-checks to bypass (not recommended)"));
|
|
1818
|
+
process.exit(1);
|
|
1819
|
+
}
|
|
1820
|
+
if (checkResult.warnings.length > 0) {
|
|
1821
|
+
printValidationResult(checkResult);
|
|
1822
|
+
console.log("");
|
|
1823
|
+
} else {
|
|
1824
|
+
console.log(chalk12.green(" \u2713 Preflight checks passed"));
|
|
1825
|
+
}
|
|
1586
1826
|
}
|
|
1587
|
-
console.log(
|
|
1588
|
-
console.log(chalk11.dim("Generating nginx configuration..."));
|
|
1827
|
+
console.log(chalk12.dim("Generating nginx configuration..."));
|
|
1589
1828
|
if (options.dryRun) {
|
|
1590
1829
|
const nginxConfig = generateNginxConfig(config);
|
|
1591
|
-
console.log(
|
|
1830
|
+
console.log(chalk12.cyan("\n--- Generated nginx config (dry-run) ---\n"));
|
|
1592
1831
|
console.log(nginxConfig);
|
|
1593
|
-
console.log(
|
|
1594
|
-
console.log(
|
|
1832
|
+
console.log(chalk12.cyan("--- End of config ---\n"));
|
|
1833
|
+
console.log(chalk12.yellow("Dry run mode - no changes were made."));
|
|
1595
1834
|
return;
|
|
1596
1835
|
}
|
|
1597
1836
|
try {
|
|
1598
1837
|
const { path, content } = writeNginxConfig(config);
|
|
1599
|
-
console.log(
|
|
1838
|
+
console.log(chalk12.green(` \u2713 Wrote nginx config to ${path}`));
|
|
1600
1839
|
} catch (error) {
|
|
1601
1840
|
const errMsg = error instanceof Error ? error.message : String(error);
|
|
1602
|
-
console.error(
|
|
1841
|
+
console.error(chalk12.red(` \u2717 Failed to write nginx config: ${errMsg}`));
|
|
1603
1842
|
if (errMsg.includes("EACCES") || errMsg.includes("permission denied")) {
|
|
1604
|
-
console.log(
|
|
1605
|
-
Try running with sudo: ${
|
|
1843
|
+
console.log(chalk12.yellow(`
|
|
1844
|
+
Try running with sudo: ${chalk12.cyan("sudo bindler apply")}`));
|
|
1606
1845
|
}
|
|
1607
1846
|
process.exit(1);
|
|
1608
1847
|
}
|
|
@@ -1610,106 +1849,111 @@ Try running with sudo: ${chalk11.cyan("sudo bindler apply")}`));
|
|
|
1610
1849
|
(p) => p.security?.basicAuth?.enabled && p.security.basicAuth.users?.length
|
|
1611
1850
|
);
|
|
1612
1851
|
if (authProjects.length > 0) {
|
|
1613
|
-
console.log(
|
|
1852
|
+
console.log(chalk12.dim("Generating htpasswd files..."));
|
|
1614
1853
|
try {
|
|
1615
1854
|
generateHtpasswdFiles(config.projects);
|
|
1616
|
-
console.log(
|
|
1855
|
+
console.log(chalk12.green(` \u2713 Generated htpasswd files for ${authProjects.length} project(s)`));
|
|
1617
1856
|
} catch (error) {
|
|
1618
|
-
console.log(
|
|
1857
|
+
console.log(chalk12.yellow(` ! Failed to generate htpasswd files: ${error}`));
|
|
1619
1858
|
}
|
|
1620
1859
|
}
|
|
1621
|
-
console.log(
|
|
1860
|
+
console.log(chalk12.dim("Testing nginx configuration..."));
|
|
1622
1861
|
const testResult = testNginxConfig();
|
|
1623
1862
|
if (!testResult.success) {
|
|
1624
|
-
console.error(
|
|
1625
|
-
console.error(
|
|
1626
|
-
console.log(
|
|
1627
|
-
console.log(
|
|
1863
|
+
console.error(chalk12.red(" \u2717 Nginx configuration test failed:"));
|
|
1864
|
+
console.error(chalk12.red(testResult.output));
|
|
1865
|
+
console.log(chalk12.yellow("\nConfiguration was written but nginx was NOT reloaded."));
|
|
1866
|
+
console.log(chalk12.dim("Fix the configuration and run `sudo bindler apply` again."));
|
|
1628
1867
|
process.exit(1);
|
|
1629
1868
|
}
|
|
1630
|
-
console.log(
|
|
1869
|
+
console.log(chalk12.green(" \u2713 Nginx configuration test passed"));
|
|
1631
1870
|
if (!options.noReload) {
|
|
1632
|
-
console.log(
|
|
1871
|
+
console.log(chalk12.dim("Reloading nginx..."));
|
|
1633
1872
|
const reloadResult = reloadNginx();
|
|
1634
1873
|
if (!reloadResult.success) {
|
|
1635
|
-
console.error(
|
|
1636
|
-
console.log(
|
|
1874
|
+
console.error(chalk12.red(` \u2717 Failed to reload nginx: ${reloadResult.error}`));
|
|
1875
|
+
console.log(chalk12.dim("You may need to reload nginx manually: sudo systemctl reload nginx"));
|
|
1637
1876
|
process.exit(1);
|
|
1638
1877
|
}
|
|
1639
|
-
console.log(
|
|
1878
|
+
console.log(chalk12.green(" \u2713 Nginx reloaded successfully"));
|
|
1640
1879
|
} else {
|
|
1641
|
-
console.log(
|
|
1880
|
+
console.log(chalk12.yellow(" - Skipped nginx reload (--no-reload)"));
|
|
1642
1881
|
}
|
|
1643
1882
|
const isDirectMode = defaults.mode === "direct";
|
|
1644
|
-
if (
|
|
1645
|
-
|
|
1883
|
+
if (!hasProjects) {
|
|
1884
|
+
} else if (isDirectMode) {
|
|
1885
|
+
console.log(chalk12.dim("\n - Direct mode: skipping Cloudflare DNS routes"));
|
|
1646
1886
|
} else if (!options.noCloudflare && defaults.applyCloudflareDnsRoutes) {
|
|
1647
|
-
console.log(
|
|
1887
|
+
console.log(chalk12.dim("\nConfiguring Cloudflare DNS routes..."));
|
|
1648
1888
|
if (!isCloudflaredInstalled()) {
|
|
1649
|
-
console.log(
|
|
1889
|
+
console.log(chalk12.yellow(" - cloudflared not installed, skipping DNS routes"));
|
|
1650
1890
|
} else {
|
|
1651
1891
|
const dnsResults = routeDnsForAllProjects();
|
|
1652
1892
|
if (dnsResults.length === 0) {
|
|
1653
|
-
console.log(
|
|
1893
|
+
console.log(chalk12.dim(" No hostnames to route"));
|
|
1654
1894
|
} else {
|
|
1655
1895
|
for (const result of dnsResults) {
|
|
1656
1896
|
if (result.skipped) {
|
|
1657
|
-
console.log(
|
|
1897
|
+
console.log(chalk12.dim(` - ${result.hostname} (local - skipped)`));
|
|
1658
1898
|
} else if (result.success) {
|
|
1659
1899
|
const msg = result.output?.includes("already exists") ? "exists" : "routed";
|
|
1660
|
-
console.log(
|
|
1900
|
+
console.log(chalk12.green(` \u2713 ${result.hostname} (${msg})`));
|
|
1661
1901
|
} else {
|
|
1662
|
-
console.log(
|
|
1902
|
+
console.log(chalk12.red(` \u2717 ${result.hostname}: ${result.error}`));
|
|
1663
1903
|
}
|
|
1664
1904
|
}
|
|
1665
1905
|
}
|
|
1666
1906
|
}
|
|
1667
1907
|
} else if (options.noCloudflare) {
|
|
1668
|
-
console.log(
|
|
1908
|
+
console.log(chalk12.dim("\n - Skipped Cloudflare DNS routes (--no-cloudflare)"));
|
|
1669
1909
|
}
|
|
1670
|
-
if (isDirectMode && defaults.sslEnabled && options.ssl !== false) {
|
|
1671
|
-
console.log(
|
|
1910
|
+
if (hasProjects && isDirectMode && defaults.sslEnabled && options.ssl !== false) {
|
|
1911
|
+
console.log(chalk12.dim("\nSetting up SSL certificates..."));
|
|
1672
1912
|
const hostnames = config.projects.filter((p) => p.enabled !== false && !p.local).map((p) => p.hostname);
|
|
1673
1913
|
if (hostnames.length === 0) {
|
|
1674
|
-
console.log(
|
|
1914
|
+
console.log(chalk12.dim(" No hostnames to secure"));
|
|
1675
1915
|
} else {
|
|
1676
1916
|
const certbotResult = execCommandSafe("which certbot");
|
|
1677
1917
|
if (!certbotResult.success) {
|
|
1678
|
-
console.log(
|
|
1679
|
-
console.log(
|
|
1918
|
+
console.log(chalk12.yellow(" - certbot not installed, skipping SSL"));
|
|
1919
|
+
console.log(chalk12.dim(" Run: bindler setup --direct"));
|
|
1680
1920
|
} else {
|
|
1681
1921
|
for (const hostname of hostnames) {
|
|
1682
|
-
console.log(
|
|
1922
|
+
console.log(chalk12.dim(` Requesting certificate for ${hostname}...`));
|
|
1683
1923
|
const email = defaults.sslEmail || "admin@" + hostname.split(".").slice(-2).join(".");
|
|
1684
1924
|
const result = execCommandSafe(
|
|
1685
1925
|
`sudo certbot --nginx -d ${hostname} --non-interactive --agree-tos --email ${email} 2>&1`
|
|
1686
1926
|
);
|
|
1687
1927
|
if (result.success || result.output?.includes("Certificate not yet due for renewal")) {
|
|
1688
|
-
console.log(
|
|
1928
|
+
console.log(chalk12.green(` \u2713 ${hostname} (secured)`));
|
|
1689
1929
|
} else if (result.output?.includes("already exists")) {
|
|
1690
|
-
console.log(
|
|
1930
|
+
console.log(chalk12.green(` \u2713 ${hostname} (exists)`));
|
|
1691
1931
|
} else {
|
|
1692
|
-
console.log(
|
|
1693
|
-
console.log(
|
|
1932
|
+
console.log(chalk12.yellow(` ! ${hostname}: ${result.error || "failed"}`));
|
|
1933
|
+
console.log(chalk12.dim(" Run manually: sudo certbot --nginx -d " + hostname));
|
|
1694
1934
|
}
|
|
1695
1935
|
}
|
|
1696
1936
|
}
|
|
1697
1937
|
}
|
|
1698
1938
|
}
|
|
1699
|
-
console.log(
|
|
1700
|
-
|
|
1939
|
+
console.log(chalk12.green("\n\u2713 Configuration applied successfully!"));
|
|
1940
|
+
if (hasProjects) {
|
|
1941
|
+
console.log(chalk12.dim(`
|
|
1701
1942
|
${config.projects.length} project(s) configured:`));
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1943
|
+
for (const project of config.projects) {
|
|
1944
|
+
const status = project.enabled !== false ? chalk12.green("enabled") : chalk12.yellow("disabled");
|
|
1945
|
+
console.log(chalk12.dim(` - ${project.name} \u2192 ${project.hostname} (${status})`));
|
|
1946
|
+
}
|
|
1947
|
+
} else {
|
|
1948
|
+
console.log(chalk12.dim("\nNo projects configured. Nginx config cleared."));
|
|
1705
1949
|
}
|
|
1706
1950
|
}
|
|
1707
1951
|
|
|
1708
1952
|
// src/commands/doctor.ts
|
|
1709
|
-
import
|
|
1710
|
-
import { existsSync as
|
|
1953
|
+
import chalk13 from "chalk";
|
|
1954
|
+
import { existsSync as existsSync9 } from "fs";
|
|
1711
1955
|
async function doctorCommand() {
|
|
1712
|
-
console.log(
|
|
1956
|
+
console.log(chalk13.blue("Running diagnostics...\n"));
|
|
1713
1957
|
const checks = [];
|
|
1714
1958
|
const nodeVersion = process.version;
|
|
1715
1959
|
const nodeMajor = parseInt(nodeVersion.slice(1).split(".")[0], 10);
|
|
@@ -1845,7 +2089,7 @@ async function doctorCommand() {
|
|
|
1845
2089
|
}
|
|
1846
2090
|
}
|
|
1847
2091
|
const defaults = getDefaults();
|
|
1848
|
-
if (
|
|
2092
|
+
if (existsSync9(defaults.nginxManagedPath)) {
|
|
1849
2093
|
checks.push({
|
|
1850
2094
|
name: "Nginx config file",
|
|
1851
2095
|
status: "ok",
|
|
@@ -1859,7 +2103,7 @@ async function doctorCommand() {
|
|
|
1859
2103
|
fix: "Run: sudo bindler apply"
|
|
1860
2104
|
});
|
|
1861
2105
|
}
|
|
1862
|
-
if (
|
|
2106
|
+
if (existsSync9(defaults.projectsRoot)) {
|
|
1863
2107
|
checks.push({
|
|
1864
2108
|
name: "Projects root",
|
|
1865
2109
|
status: "ok",
|
|
@@ -1870,7 +2114,67 @@ async function doctorCommand() {
|
|
|
1870
2114
|
name: "Projects root",
|
|
1871
2115
|
status: "warning",
|
|
1872
2116
|
message: `Not found at ${defaults.projectsRoot}`,
|
|
1873
|
-
fix: `Create directory:
|
|
2117
|
+
fix: `Create directory: mkdir -p ${defaults.projectsRoot}`
|
|
2118
|
+
});
|
|
2119
|
+
}
|
|
2120
|
+
const validation = validateConfig(config);
|
|
2121
|
+
if (validation.errors.length > 0) {
|
|
2122
|
+
for (const error of validation.errors) {
|
|
2123
|
+
checks.push({
|
|
2124
|
+
name: "Project validation",
|
|
2125
|
+
status: "error",
|
|
2126
|
+
message: error
|
|
2127
|
+
});
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
if (validation.warnings.length > 0) {
|
|
2131
|
+
for (const warning of validation.warnings) {
|
|
2132
|
+
checks.push({
|
|
2133
|
+
name: "Project validation",
|
|
2134
|
+
status: "warning",
|
|
2135
|
+
message: warning
|
|
2136
|
+
});
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
if (validation.valid && validation.warnings.length === 0) {
|
|
2140
|
+
checks.push({
|
|
2141
|
+
name: "Project validation",
|
|
2142
|
+
status: "ok",
|
|
2143
|
+
message: "All projects validated successfully"
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
const localProjects = config.projects.filter((p) => p.local && p.enabled !== false);
|
|
2147
|
+
if (localProjects.length > 0) {
|
|
2148
|
+
const hostsResult = execCommandSafe("cat /etc/hosts");
|
|
2149
|
+
if (hostsResult.success) {
|
|
2150
|
+
const missingHosts = localProjects.filter(
|
|
2151
|
+
(p) => !hostsResult.output.includes(p.hostname)
|
|
2152
|
+
);
|
|
2153
|
+
if (missingHosts.length > 0) {
|
|
2154
|
+
for (const p of missingHosts) {
|
|
2155
|
+
checks.push({
|
|
2156
|
+
name: "Hosts file",
|
|
2157
|
+
status: "warning",
|
|
2158
|
+
message: `Missing entry for ${p.hostname}`,
|
|
2159
|
+
fix: `echo "127.0.0.1 ${p.hostname}" | sudo tee -a /etc/hosts`
|
|
2160
|
+
});
|
|
2161
|
+
}
|
|
2162
|
+
} else {
|
|
2163
|
+
checks.push({
|
|
2164
|
+
name: "Hosts file",
|
|
2165
|
+
status: "ok",
|
|
2166
|
+
message: `All ${localProjects.length} local hostname(s) configured`
|
|
2167
|
+
});
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
const listenPort = parseInt(defaults.nginxListen.split(":").pop() || "80", 10);
|
|
2172
|
+
if (listenPort <= 1024 && process.getuid && process.getuid() !== 0) {
|
|
2173
|
+
checks.push({
|
|
2174
|
+
name: "Nginx port",
|
|
2175
|
+
status: "warning",
|
|
2176
|
+
message: `Port ${listenPort} requires root privileges`,
|
|
2177
|
+
fix: "Run: sudo bindler apply (or change nginxListen to a port > 1024)"
|
|
1874
2178
|
});
|
|
1875
2179
|
}
|
|
1876
2180
|
} else {
|
|
@@ -1889,47 +2193,47 @@ async function doctorCommand() {
|
|
|
1889
2193
|
switch (check.status) {
|
|
1890
2194
|
case "ok":
|
|
1891
2195
|
icon = "\u2713";
|
|
1892
|
-
color =
|
|
2196
|
+
color = chalk13.green;
|
|
1893
2197
|
break;
|
|
1894
2198
|
case "warning":
|
|
1895
2199
|
icon = "!";
|
|
1896
|
-
color =
|
|
2200
|
+
color = chalk13.yellow;
|
|
1897
2201
|
hasWarnings = true;
|
|
1898
2202
|
break;
|
|
1899
2203
|
case "error":
|
|
1900
2204
|
icon = "\u2717";
|
|
1901
|
-
color =
|
|
2205
|
+
color = chalk13.red;
|
|
1902
2206
|
hasErrors = true;
|
|
1903
2207
|
break;
|
|
1904
2208
|
}
|
|
1905
|
-
console.log(`${color(icon)} ${
|
|
2209
|
+
console.log(`${color(icon)} ${chalk13.bold(check.name)}: ${check.message}`);
|
|
1906
2210
|
if (check.fix) {
|
|
1907
|
-
console.log(
|
|
2211
|
+
console.log(chalk13.dim(` Fix: ${check.fix}`));
|
|
1908
2212
|
}
|
|
1909
2213
|
}
|
|
1910
2214
|
console.log("");
|
|
1911
2215
|
if (hasErrors) {
|
|
1912
|
-
console.log(
|
|
2216
|
+
console.log(chalk13.red("Some checks failed. Please fix the issues above."));
|
|
1913
2217
|
process.exit(1);
|
|
1914
2218
|
} else if (hasWarnings) {
|
|
1915
|
-
console.log(
|
|
2219
|
+
console.log(chalk13.yellow("Some warnings detected. Review the suggestions above."));
|
|
1916
2220
|
} else {
|
|
1917
|
-
console.log(
|
|
2221
|
+
console.log(chalk13.green("All checks passed!"));
|
|
1918
2222
|
}
|
|
1919
2223
|
}
|
|
1920
2224
|
|
|
1921
2225
|
// src/commands/ports.ts
|
|
1922
|
-
import
|
|
2226
|
+
import chalk14 from "chalk";
|
|
1923
2227
|
import Table3 from "cli-table3";
|
|
1924
2228
|
async function portsCommand() {
|
|
1925
2229
|
const ports = getPortsTable();
|
|
1926
2230
|
if (ports.length === 0) {
|
|
1927
|
-
console.log(
|
|
1928
|
-
console.log(
|
|
2231
|
+
console.log(chalk14.yellow("No ports allocated."));
|
|
2232
|
+
console.log(chalk14.dim("npm projects will have ports allocated when created."));
|
|
1929
2233
|
return;
|
|
1930
2234
|
}
|
|
1931
2235
|
const table = new Table3({
|
|
1932
|
-
head: [
|
|
2236
|
+
head: [chalk14.cyan("Port"), chalk14.cyan("Project"), chalk14.cyan("Hostname")],
|
|
1933
2237
|
style: {
|
|
1934
2238
|
head: [],
|
|
1935
2239
|
border: []
|
|
@@ -1939,25 +2243,25 @@ async function portsCommand() {
|
|
|
1939
2243
|
table.push([String(entry.port), entry.project, entry.hostname]);
|
|
1940
2244
|
}
|
|
1941
2245
|
console.log(table.toString());
|
|
1942
|
-
console.log(
|
|
2246
|
+
console.log(chalk14.dim(`
|
|
1943
2247
|
${ports.length} port(s) allocated`));
|
|
1944
2248
|
}
|
|
1945
2249
|
|
|
1946
2250
|
// src/commands/info.ts
|
|
1947
|
-
import
|
|
2251
|
+
import chalk15 from "chalk";
|
|
1948
2252
|
async function infoCommand() {
|
|
1949
|
-
console.log(
|
|
2253
|
+
console.log(chalk15.bold.cyan(String.raw`
|
|
1950
2254
|
_ _ _ _
|
|
1951
2255
|
| |_|_|___ _| | |___ ___
|
|
1952
2256
|
| . | | | . | | -_| _|
|
|
1953
2257
|
|___|_|_|_|___|_|___|_|
|
|
1954
2258
|
`));
|
|
1955
|
-
console.log(
|
|
1956
|
-
console.log(
|
|
1957
|
-
console.log(
|
|
1958
|
-
console.log(
|
|
1959
|
-
console.log(
|
|
1960
|
-
console.log(
|
|
2259
|
+
console.log(chalk15.white(" Manage multiple projects behind Cloudflare Tunnel"));
|
|
2260
|
+
console.log(chalk15.white(" with Nginx and PM2\n"));
|
|
2261
|
+
console.log(chalk15.dim(" Version: ") + chalk15.white("1.4.1"));
|
|
2262
|
+
console.log(chalk15.dim(" Author: ") + chalk15.white("alfaoz"));
|
|
2263
|
+
console.log(chalk15.dim(" License: ") + chalk15.white("MIT"));
|
|
2264
|
+
console.log(chalk15.dim(" GitHub: ") + chalk15.cyan("https://github.com/alfaoz/bindler"));
|
|
1961
2265
|
console.log("");
|
|
1962
2266
|
if (configExists()) {
|
|
1963
2267
|
const config = readConfig();
|
|
@@ -1965,23 +2269,23 @@ async function infoCommand() {
|
|
|
1965
2269
|
const runningCount = pm2Processes.filter((p) => p.status === "online").length;
|
|
1966
2270
|
const npmProjects = config.projects.filter((p) => p.type === "npm").length;
|
|
1967
2271
|
const staticProjects = config.projects.filter((p) => p.type === "static").length;
|
|
1968
|
-
console.log(
|
|
1969
|
-
console.log(
|
|
1970
|
-
console.log(
|
|
2272
|
+
console.log(chalk15.dim(" Config: ") + chalk15.white(getConfigPath()));
|
|
2273
|
+
console.log(chalk15.dim(" Nginx: ") + chalk15.white(config.defaults.nginxManagedPath));
|
|
2274
|
+
console.log(chalk15.dim(" Tunnel: ") + chalk15.white(config.defaults.tunnelName));
|
|
1971
2275
|
console.log("");
|
|
1972
|
-
console.log(
|
|
2276
|
+
console.log(chalk15.dim(" Projects: ") + chalk15.white(`${config.projects.length} total (${staticProjects} static, ${npmProjects} npm)`));
|
|
1973
2277
|
if (npmProjects > 0) {
|
|
1974
|
-
console.log(
|
|
2278
|
+
console.log(chalk15.dim(" Running: ") + chalk15.green(`${runningCount}/${npmProjects} npm apps online`));
|
|
1975
2279
|
}
|
|
1976
2280
|
} else {
|
|
1977
|
-
console.log(
|
|
1978
|
-
console.log(
|
|
2281
|
+
console.log(chalk15.dim(" Config: ") + chalk15.yellow("Not initialized"));
|
|
2282
|
+
console.log(chalk15.dim(" ") + chalk15.dim("Run `bindler new` to get started"));
|
|
1979
2283
|
}
|
|
1980
2284
|
console.log("");
|
|
1981
2285
|
}
|
|
1982
2286
|
|
|
1983
2287
|
// src/commands/check.ts
|
|
1984
|
-
import
|
|
2288
|
+
import chalk16 from "chalk";
|
|
1985
2289
|
import { resolve4, resolve6, resolveCname } from "dns/promises";
|
|
1986
2290
|
async function checkDns(hostname) {
|
|
1987
2291
|
const result = {
|
|
@@ -2042,111 +2346,111 @@ async function checkCommand(hostnameOrName, options) {
|
|
|
2042
2346
|
const hostname = project ? project.hostname : hostnameOrName;
|
|
2043
2347
|
const basePath = project?.basePath || "/";
|
|
2044
2348
|
const isLocal = project?.local || hostname.endsWith(".local") || hostname.endsWith(".localhost");
|
|
2045
|
-
console.log(
|
|
2349
|
+
console.log(chalk16.blue(`
|
|
2046
2350
|
Checking ${hostname}...
|
|
2047
2351
|
`));
|
|
2048
|
-
console.log(
|
|
2352
|
+
console.log(chalk16.bold("DNS Resolution:"));
|
|
2049
2353
|
const dns = await checkDns(hostname);
|
|
2050
2354
|
if (!dns.resolved) {
|
|
2051
|
-
console.log(
|
|
2355
|
+
console.log(chalk16.red(" \u2717 DNS not resolving"));
|
|
2052
2356
|
if (isLocal) {
|
|
2053
|
-
console.log(
|
|
2054
|
-
console.log(
|
|
2357
|
+
console.log(chalk16.dim(" Local hostname - add to /etc/hosts:"));
|
|
2358
|
+
console.log(chalk16.cyan(` echo "127.0.0.1 ${hostname}" | sudo tee -a /etc/hosts`));
|
|
2055
2359
|
} else {
|
|
2056
|
-
console.log(
|
|
2057
|
-
console.log(
|
|
2058
|
-
console.log(
|
|
2360
|
+
console.log(chalk16.dim(" No DNS records found for this hostname."));
|
|
2361
|
+
console.log(chalk16.dim(" If using Cloudflare Tunnel, run: bindler apply"));
|
|
2362
|
+
console.log(chalk16.dim(" If using direct mode, add an A record pointing to your server IP."));
|
|
2059
2363
|
}
|
|
2060
2364
|
console.log("");
|
|
2061
2365
|
return;
|
|
2062
2366
|
}
|
|
2063
2367
|
if (dns.cname.length > 0) {
|
|
2064
|
-
console.log(
|
|
2368
|
+
console.log(chalk16.green(" \u2713 CNAME: ") + dns.cname.join(", "));
|
|
2065
2369
|
if (dns.isCloudflare) {
|
|
2066
|
-
console.log(
|
|
2370
|
+
console.log(chalk16.green(" \u2713 Points to Cloudflare Tunnel"));
|
|
2067
2371
|
}
|
|
2068
2372
|
}
|
|
2069
2373
|
if (dns.ipv4.length > 0) {
|
|
2070
|
-
console.log(
|
|
2374
|
+
console.log(chalk16.green(" \u2713 A (IPv4): ") + dns.ipv4.join(", "));
|
|
2071
2375
|
}
|
|
2072
2376
|
if (dns.ipv6.length > 0) {
|
|
2073
|
-
console.log(
|
|
2377
|
+
console.log(chalk16.green(" \u2713 AAAA (IPv6): ") + dns.ipv6.join(", "));
|
|
2074
2378
|
}
|
|
2075
2379
|
console.log("");
|
|
2076
|
-
console.log(
|
|
2380
|
+
console.log(chalk16.bold("HTTP Check:"));
|
|
2077
2381
|
const http = await checkHttp(hostname, basePath);
|
|
2078
2382
|
if (!http.reachable) {
|
|
2079
|
-
console.log(
|
|
2383
|
+
console.log(chalk16.red(" \u2717 Not reachable"));
|
|
2080
2384
|
const err = http.error || "";
|
|
2081
2385
|
if (err.includes("ECONNREFUSED")) {
|
|
2082
|
-
console.log(
|
|
2083
|
-
console.log(
|
|
2084
|
-
console.log(
|
|
2386
|
+
console.log(chalk16.dim(" Connection refused - server not accepting connections"));
|
|
2387
|
+
console.log(chalk16.yellow("\n Check:"));
|
|
2388
|
+
console.log(chalk16.dim(" - Is nginx running? Run: bindler doctor"));
|
|
2085
2389
|
if (project?.type === "npm") {
|
|
2086
|
-
console.log(
|
|
2390
|
+
console.log(chalk16.dim(` - Is the app running? Run: bindler start ${project.name}`));
|
|
2087
2391
|
}
|
|
2088
2392
|
} else if (err.includes("ENOTFOUND")) {
|
|
2089
|
-
console.log(
|
|
2090
|
-
console.log(
|
|
2091
|
-
console.log(
|
|
2092
|
-
console.log(
|
|
2393
|
+
console.log(chalk16.dim(" Hostname not found"));
|
|
2394
|
+
console.log(chalk16.yellow("\n Check:"));
|
|
2395
|
+
console.log(chalk16.dim(" - DNS may not be propagated yet (wait a few minutes)"));
|
|
2396
|
+
console.log(chalk16.dim(" - Verify DNS records are correct"));
|
|
2093
2397
|
} else if (err.includes("ETIMEDOUT") || err.includes("timeout")) {
|
|
2094
|
-
console.log(
|
|
2095
|
-
console.log(
|
|
2398
|
+
console.log(chalk16.dim(" Connection timed out"));
|
|
2399
|
+
console.log(chalk16.yellow("\n Check:"));
|
|
2096
2400
|
if (isLocal) {
|
|
2097
|
-
console.log(
|
|
2401
|
+
console.log(chalk16.dim(" - Is nginx running on port 8080?"));
|
|
2098
2402
|
} else {
|
|
2099
|
-
console.log(
|
|
2100
|
-
console.log(
|
|
2403
|
+
console.log(chalk16.dim(" - Is your server/tunnel reachable from the internet?"));
|
|
2404
|
+
console.log(chalk16.dim(" - Check firewall rules"));
|
|
2101
2405
|
}
|
|
2102
2406
|
} else if (err.includes("CERT") || err.includes("SSL") || err.includes("certificate")) {
|
|
2103
|
-
console.log(
|
|
2104
|
-
console.log(
|
|
2105
|
-
console.log(
|
|
2106
|
-
console.log(
|
|
2407
|
+
console.log(chalk16.dim(" SSL/TLS certificate error"));
|
|
2408
|
+
console.log(chalk16.yellow("\n Check:"));
|
|
2409
|
+
console.log(chalk16.dim(" - Run: sudo bindler apply (to refresh SSL certs)"));
|
|
2410
|
+
console.log(chalk16.dim(" - Or check certbot: sudo certbot certificates"));
|
|
2107
2411
|
} else {
|
|
2108
|
-
console.log(
|
|
2109
|
-
console.log(
|
|
2412
|
+
console.log(chalk16.dim(` Error: ${err}`));
|
|
2413
|
+
console.log(chalk16.yellow("\n Possible issues:"));
|
|
2110
2414
|
if (!isLocal) {
|
|
2111
|
-
console.log(
|
|
2415
|
+
console.log(chalk16.dim(" - Cloudflare tunnel not running"));
|
|
2112
2416
|
}
|
|
2113
|
-
console.log(
|
|
2417
|
+
console.log(chalk16.dim(" - Nginx not running or misconfigured"));
|
|
2114
2418
|
if (project?.type === "npm") {
|
|
2115
|
-
console.log(
|
|
2419
|
+
console.log(chalk16.dim(" - Project not started"));
|
|
2116
2420
|
}
|
|
2117
2421
|
}
|
|
2118
2422
|
console.log("");
|
|
2119
2423
|
return;
|
|
2120
2424
|
}
|
|
2121
|
-
const statusColor = http.statusCode < 400 ?
|
|
2425
|
+
const statusColor = http.statusCode < 400 ? chalk16.green : chalk16.red;
|
|
2122
2426
|
console.log(statusColor(` \u2713 Status: ${http.statusCode}`));
|
|
2123
|
-
console.log(
|
|
2427
|
+
console.log(chalk16.dim(` Response time: ${http.responseTime}ms`));
|
|
2124
2428
|
if (http.redirectUrl) {
|
|
2125
|
-
console.log(
|
|
2429
|
+
console.log(chalk16.dim(` Redirects to: ${http.redirectUrl}`));
|
|
2126
2430
|
}
|
|
2127
2431
|
console.log("");
|
|
2128
2432
|
if (dns.resolved && http.reachable && http.statusCode < 400) {
|
|
2129
|
-
console.log(
|
|
2433
|
+
console.log(chalk16.green("\u2713 All checks passed! Site is accessible."));
|
|
2130
2434
|
} else if (dns.resolved && http.reachable) {
|
|
2131
|
-
console.log(
|
|
2435
|
+
console.log(chalk16.yellow("! Site is reachable but returned an error status."));
|
|
2132
2436
|
} else {
|
|
2133
|
-
console.log(
|
|
2437
|
+
console.log(chalk16.red("\u2717 Some checks failed. See details above."));
|
|
2134
2438
|
}
|
|
2135
2439
|
if (project) {
|
|
2136
|
-
console.log(
|
|
2440
|
+
console.log(chalk16.dim(`
|
|
2137
2441
|
Project: ${project.name} (${project.type})`));
|
|
2138
2442
|
if (project.type === "npm") {
|
|
2139
|
-
console.log(
|
|
2443
|
+
console.log(chalk16.dim(`Port: ${project.port}`));
|
|
2140
2444
|
}
|
|
2141
2445
|
}
|
|
2142
2446
|
console.log("");
|
|
2143
2447
|
}
|
|
2144
2448
|
|
|
2145
2449
|
// src/commands/setup.ts
|
|
2146
|
-
import
|
|
2450
|
+
import chalk17 from "chalk";
|
|
2147
2451
|
import inquirer3 from "inquirer";
|
|
2148
2452
|
import { execSync as execSync2 } from "child_process";
|
|
2149
|
-
import { existsSync as
|
|
2453
|
+
import { existsSync as existsSync10, readFileSync as readFileSync5 } from "fs";
|
|
2150
2454
|
function detectOs() {
|
|
2151
2455
|
const platform = process.platform;
|
|
2152
2456
|
if (platform === "darwin") {
|
|
@@ -2157,7 +2461,7 @@ function detectOs() {
|
|
|
2157
2461
|
}
|
|
2158
2462
|
if (platform === "linux") {
|
|
2159
2463
|
try {
|
|
2160
|
-
if (
|
|
2464
|
+
if (existsSync10("/etc/os-release")) {
|
|
2161
2465
|
const osRelease = readFileSync5("/etc/os-release", "utf-8");
|
|
2162
2466
|
const lines = osRelease.split("\n");
|
|
2163
2467
|
const info = {};
|
|
@@ -2174,7 +2478,7 @@ function detectOs() {
|
|
|
2174
2478
|
if (["ubuntu", "debian", "pop", "mint", "elementary"].includes(distro)) {
|
|
2175
2479
|
packageManager = "apt";
|
|
2176
2480
|
} else if (["fedora", "rhel", "centos", "rocky", "alma"].includes(distro)) {
|
|
2177
|
-
packageManager =
|
|
2481
|
+
packageManager = existsSync10("/usr/bin/dnf") ? "dnf" : "yum";
|
|
2178
2482
|
} else if (["amzn"].includes(distro)) {
|
|
2179
2483
|
packageManager = "yum";
|
|
2180
2484
|
}
|
|
@@ -2187,7 +2491,7 @@ function detectOs() {
|
|
|
2187
2491
|
return { platform: "unknown" };
|
|
2188
2492
|
}
|
|
2189
2493
|
function runCommand(command, description) {
|
|
2190
|
-
console.log(
|
|
2494
|
+
console.log(chalk17.dim(` \u2192 ${description}...`));
|
|
2191
2495
|
try {
|
|
2192
2496
|
execSync2(command, { stdio: "inherit" });
|
|
2193
2497
|
return true;
|
|
@@ -2196,7 +2500,7 @@ function runCommand(command, description) {
|
|
|
2196
2500
|
}
|
|
2197
2501
|
}
|
|
2198
2502
|
async function installNginx(os) {
|
|
2199
|
-
console.log(
|
|
2503
|
+
console.log(chalk17.blue("\nInstalling nginx...\n"));
|
|
2200
2504
|
if (os.platform === "darwin") {
|
|
2201
2505
|
return runCommand("brew install nginx", "Installing via Homebrew");
|
|
2202
2506
|
}
|
|
@@ -2207,16 +2511,16 @@ async function installNginx(os) {
|
|
|
2207
2511
|
if (os.platform === "linux" && (os.packageManager === "yum" || os.packageManager === "dnf")) {
|
|
2208
2512
|
return runCommand(`sudo ${os.packageManager} install -y nginx`, "Installing nginx");
|
|
2209
2513
|
}
|
|
2210
|
-
console.log(
|
|
2211
|
-
console.log(
|
|
2514
|
+
console.log(chalk17.yellow(" Automatic installation not supported for your OS."));
|
|
2515
|
+
console.log(chalk17.dim(" Please install nginx manually."));
|
|
2212
2516
|
return false;
|
|
2213
2517
|
}
|
|
2214
2518
|
async function installPm2() {
|
|
2215
|
-
console.log(
|
|
2519
|
+
console.log(chalk17.blue("\nInstalling PM2...\n"));
|
|
2216
2520
|
return runCommand("npm install -g pm2", "Installing via npm");
|
|
2217
2521
|
}
|
|
2218
2522
|
async function installCertbot(os) {
|
|
2219
|
-
console.log(
|
|
2523
|
+
console.log(chalk17.blue("\nInstalling certbot (Let's Encrypt)...\n"));
|
|
2220
2524
|
if (os.platform === "darwin") {
|
|
2221
2525
|
return runCommand("brew install certbot", "Installing via Homebrew");
|
|
2222
2526
|
}
|
|
@@ -2227,8 +2531,8 @@ async function installCertbot(os) {
|
|
|
2227
2531
|
if (os.platform === "linux" && (os.packageManager === "yum" || os.packageManager === "dnf")) {
|
|
2228
2532
|
return runCommand(`sudo ${os.packageManager} install -y certbot python3-certbot-nginx`, "Installing certbot");
|
|
2229
2533
|
}
|
|
2230
|
-
console.log(
|
|
2231
|
-
console.log(
|
|
2534
|
+
console.log(chalk17.yellow(" Automatic installation not supported for your OS."));
|
|
2535
|
+
console.log(chalk17.dim(" Please install certbot manually."));
|
|
2232
2536
|
return false;
|
|
2233
2537
|
}
|
|
2234
2538
|
function isCertbotInstalled() {
|
|
@@ -2236,7 +2540,7 @@ function isCertbotInstalled() {
|
|
|
2236
2540
|
return result.success;
|
|
2237
2541
|
}
|
|
2238
2542
|
async function installCloudflared(os) {
|
|
2239
|
-
console.log(
|
|
2543
|
+
console.log(chalk17.blue("\nInstalling cloudflared...\n"));
|
|
2240
2544
|
if (os.platform === "darwin") {
|
|
2241
2545
|
return runCommand("brew install cloudflared", "Installing via Homebrew");
|
|
2242
2546
|
}
|
|
@@ -2270,21 +2574,21 @@ async function installCloudflared(os) {
|
|
|
2270
2574
|
);
|
|
2271
2575
|
return runCommand(`sudo ${os.packageManager} install -y cloudflared`, "Installing cloudflared");
|
|
2272
2576
|
}
|
|
2273
|
-
console.log(
|
|
2274
|
-
console.log(
|
|
2577
|
+
console.log(chalk17.yellow(" Automatic installation not supported for your OS."));
|
|
2578
|
+
console.log(chalk17.dim(" Visit: https://pkg.cloudflare.com/index.html"));
|
|
2275
2579
|
return false;
|
|
2276
2580
|
}
|
|
2277
2581
|
async function setupCommand(options = {}) {
|
|
2278
|
-
console.log(
|
|
2582
|
+
console.log(chalk17.bold.cyan("\nBindler Setup\n"));
|
|
2279
2583
|
const os = detectOs();
|
|
2280
|
-
console.log(
|
|
2584
|
+
console.log(chalk17.dim(`Detected: ${os.distro || os.platform}${os.version ? ` ${os.version}` : ""}${os.codename ? ` (${os.codename})` : ""}`));
|
|
2281
2585
|
const isDirect = options.direct;
|
|
2282
2586
|
if (isDirect) {
|
|
2283
|
-
console.log(
|
|
2284
|
-
console.log(
|
|
2587
|
+
console.log(chalk17.cyan("\nDirect mode (VPS without Cloudflare Tunnel)"));
|
|
2588
|
+
console.log(chalk17.dim("nginx will listen on port 80/443 directly\n"));
|
|
2285
2589
|
} else {
|
|
2286
|
-
console.log(
|
|
2287
|
-
console.log(
|
|
2590
|
+
console.log(chalk17.cyan("\nTunnel mode (via Cloudflare Tunnel)"));
|
|
2591
|
+
console.log(chalk17.dim("nginx will listen on 127.0.0.1:8080\n"));
|
|
2288
2592
|
}
|
|
2289
2593
|
const missing = [];
|
|
2290
2594
|
if (!isNginxInstalled()) {
|
|
@@ -2303,11 +2607,11 @@ async function setupCommand(options = {}) {
|
|
|
2303
2607
|
}
|
|
2304
2608
|
}
|
|
2305
2609
|
if (missing.length === 0) {
|
|
2306
|
-
console.log(
|
|
2610
|
+
console.log(chalk17.green("\u2713 All dependencies are already installed!\n"));
|
|
2307
2611
|
} else {
|
|
2308
|
-
console.log(
|
|
2612
|
+
console.log(chalk17.yellow("Missing dependencies:\n"));
|
|
2309
2613
|
for (const dep of missing) {
|
|
2310
|
-
console.log(
|
|
2614
|
+
console.log(chalk17.red(` \u2717 ${dep.name}`));
|
|
2311
2615
|
}
|
|
2312
2616
|
console.log("");
|
|
2313
2617
|
const { toInstall } = await inquirer3.prompt([
|
|
@@ -2331,12 +2635,12 @@ async function setupCommand(options = {}) {
|
|
|
2331
2635
|
results.push({ name: depName, success });
|
|
2332
2636
|
}
|
|
2333
2637
|
}
|
|
2334
|
-
console.log(
|
|
2638
|
+
console.log(chalk17.bold("\n\nInstallation Summary:\n"));
|
|
2335
2639
|
for (const result of results) {
|
|
2336
2640
|
if (result.success) {
|
|
2337
|
-
console.log(
|
|
2641
|
+
console.log(chalk17.green(` \u2713 ${result.name} installed`));
|
|
2338
2642
|
} else {
|
|
2339
|
-
console.log(
|
|
2643
|
+
console.log(chalk17.red(` \u2717 ${result.name} failed`));
|
|
2340
2644
|
}
|
|
2341
2645
|
}
|
|
2342
2646
|
console.log("");
|
|
@@ -2378,32 +2682,32 @@ async function setupCommand(options = {}) {
|
|
|
2378
2682
|
config.defaults.applyCloudflareDnsRoutes = true;
|
|
2379
2683
|
}
|
|
2380
2684
|
writeConfig(config);
|
|
2381
|
-
console.log(
|
|
2382
|
-
console.log(
|
|
2383
|
-
console.log(
|
|
2685
|
+
console.log(chalk17.green("\n\u2713 Setup complete!\n"));
|
|
2686
|
+
console.log(chalk17.dim(`Mode: ${config.defaults.mode}`));
|
|
2687
|
+
console.log(chalk17.dim(`nginx listen: ${config.defaults.nginxListen}`));
|
|
2384
2688
|
if (config.defaults.sslEnabled) {
|
|
2385
|
-
console.log(
|
|
2689
|
+
console.log(chalk17.dim(`SSL: enabled (${config.defaults.sslEmail})`));
|
|
2386
2690
|
}
|
|
2387
|
-
console.log(
|
|
2691
|
+
console.log(chalk17.dim("\nRun `bindler new` to create your first project."));
|
|
2388
2692
|
}
|
|
2389
2693
|
|
|
2390
2694
|
// src/commands/init.ts
|
|
2391
|
-
import
|
|
2695
|
+
import chalk18 from "chalk";
|
|
2392
2696
|
import inquirer4 from "inquirer";
|
|
2393
2697
|
async function initCommand() {
|
|
2394
|
-
console.log(
|
|
2698
|
+
console.log(chalk18.bold.cyan(`
|
|
2395
2699
|
_ _ _ _
|
|
2396
2700
|
| |_|_|___ _| | |___ ___
|
|
2397
2701
|
| . | | | . | | -_| _|
|
|
2398
2702
|
|___|_|_|_|___|_|___|_|
|
|
2399
2703
|
`));
|
|
2400
|
-
console.log(
|
|
2704
|
+
console.log(chalk18.white(" Welcome to bindler!\n"));
|
|
2401
2705
|
if (configExists()) {
|
|
2402
2706
|
const config2 = readConfig();
|
|
2403
|
-
console.log(
|
|
2404
|
-
console.log(
|
|
2405
|
-
console.log(
|
|
2406
|
-
console.log(
|
|
2707
|
+
console.log(chalk18.yellow("Bindler is already initialized."));
|
|
2708
|
+
console.log(chalk18.dim(` Config: ~/.config/bindler/config.json`));
|
|
2709
|
+
console.log(chalk18.dim(` Mode: ${config2.defaults.mode || "tunnel"}`));
|
|
2710
|
+
console.log(chalk18.dim(` Projects: ${config2.projects.length}`));
|
|
2407
2711
|
console.log("");
|
|
2408
2712
|
const { reinit } = await inquirer4.prompt([
|
|
2409
2713
|
{
|
|
@@ -2414,11 +2718,11 @@ async function initCommand() {
|
|
|
2414
2718
|
}
|
|
2415
2719
|
]);
|
|
2416
2720
|
if (!reinit) {
|
|
2417
|
-
console.log(
|
|
2721
|
+
console.log(chalk18.dim("\nRun `bindler new` to add a project."));
|
|
2418
2722
|
return;
|
|
2419
2723
|
}
|
|
2420
2724
|
}
|
|
2421
|
-
console.log(
|
|
2725
|
+
console.log(chalk18.bold("\n1. Choose your setup:\n"));
|
|
2422
2726
|
const { mode } = await inquirer4.prompt([
|
|
2423
2727
|
{
|
|
2424
2728
|
type: "list",
|
|
@@ -2440,19 +2744,19 @@ async function initCommand() {
|
|
|
2440
2744
|
]
|
|
2441
2745
|
}
|
|
2442
2746
|
]);
|
|
2443
|
-
console.log(
|
|
2747
|
+
console.log(chalk18.bold("\n2. Checking dependencies...\n"));
|
|
2444
2748
|
const deps = {
|
|
2445
2749
|
nginx: isNginxInstalled(),
|
|
2446
2750
|
pm2: isPm2Installed(),
|
|
2447
2751
|
cloudflared: isCloudflaredInstalled()
|
|
2448
2752
|
};
|
|
2449
2753
|
const nginxRunning = deps.nginx && isNginxRunning();
|
|
2450
|
-
console.log(deps.nginx ?
|
|
2451
|
-
console.log(deps.pm2 ?
|
|
2754
|
+
console.log(deps.nginx ? chalk18.green(" \u2713 nginx") : chalk18.red(" \u2717 nginx"));
|
|
2755
|
+
console.log(deps.pm2 ? chalk18.green(" \u2713 pm2") : chalk18.red(" \u2717 pm2"));
|
|
2452
2756
|
if (mode === "tunnel") {
|
|
2453
|
-
console.log(deps.cloudflared ?
|
|
2757
|
+
console.log(deps.cloudflared ? chalk18.green(" \u2713 cloudflared") : chalk18.red(" \u2717 cloudflared"));
|
|
2454
2758
|
} else if (mode === "direct") {
|
|
2455
|
-
console.log(
|
|
2759
|
+
console.log(chalk18.dim(" - cloudflared (not needed for direct mode)"));
|
|
2456
2760
|
}
|
|
2457
2761
|
const missingDeps = !deps.nginx || !deps.pm2 || mode === "tunnel" && !deps.cloudflared;
|
|
2458
2762
|
if (missingDeps) {
|
|
@@ -2469,7 +2773,7 @@ async function initCommand() {
|
|
|
2469
2773
|
await setupCommand({ direct: mode === "direct" });
|
|
2470
2774
|
}
|
|
2471
2775
|
}
|
|
2472
|
-
console.log(
|
|
2776
|
+
console.log(chalk18.bold("\n3. Configuration:\n"));
|
|
2473
2777
|
let tunnelName = "homelab";
|
|
2474
2778
|
let sslEmail = "";
|
|
2475
2779
|
if (mode === "tunnel") {
|
|
@@ -2521,29 +2825,29 @@ async function initCommand() {
|
|
|
2521
2825
|
config.defaults.nginxListen = "127.0.0.1:8080";
|
|
2522
2826
|
}
|
|
2523
2827
|
writeConfig(config);
|
|
2524
|
-
console.log(
|
|
2525
|
-
console.log(
|
|
2526
|
-
console.log(
|
|
2828
|
+
console.log(chalk18.green("\n\u2713 Bindler initialized!\n"));
|
|
2829
|
+
console.log(chalk18.dim(" Mode: ") + chalk18.white(mode));
|
|
2830
|
+
console.log(chalk18.dim(" Listen: ") + chalk18.white(config.defaults.nginxListen));
|
|
2527
2831
|
if (mode === "tunnel") {
|
|
2528
|
-
console.log(
|
|
2832
|
+
console.log(chalk18.dim(" Tunnel: ") + chalk18.white(tunnelName));
|
|
2529
2833
|
}
|
|
2530
2834
|
if (sslEmail) {
|
|
2531
|
-
console.log(
|
|
2835
|
+
console.log(chalk18.dim(" SSL: ") + chalk18.white(sslEmail));
|
|
2532
2836
|
}
|
|
2533
|
-
console.log(
|
|
2534
|
-
console.log(
|
|
2535
|
-
console.log(
|
|
2837
|
+
console.log(chalk18.bold("\nNext steps:\n"));
|
|
2838
|
+
console.log(chalk18.dim(" 1. ") + chalk18.white("bindler new") + chalk18.dim(" # add your first project"));
|
|
2839
|
+
console.log(chalk18.dim(" 2. ") + chalk18.white("bindler apply") + chalk18.dim(" # apply nginx config"));
|
|
2536
2840
|
if (mode === "tunnel") {
|
|
2537
|
-
console.log(
|
|
2841
|
+
console.log(chalk18.dim(" 3. ") + chalk18.white(`cloudflared tunnel run ${tunnelName}`) + chalk18.dim(" # start tunnel"));
|
|
2538
2842
|
}
|
|
2539
2843
|
console.log("");
|
|
2540
2844
|
}
|
|
2541
2845
|
|
|
2542
2846
|
// src/commands/deploy.ts
|
|
2543
|
-
import
|
|
2847
|
+
import chalk19 from "chalk";
|
|
2544
2848
|
import { execSync as execSync3 } from "child_process";
|
|
2545
|
-
import { existsSync as
|
|
2546
|
-
import { join as
|
|
2849
|
+
import { existsSync as existsSync11 } from "fs";
|
|
2850
|
+
import { join as join7 } from "path";
|
|
2547
2851
|
function runInDir(command, cwd) {
|
|
2548
2852
|
try {
|
|
2549
2853
|
const output = execSync3(command, { cwd, encoding: "utf-8", stdio: "pipe" });
|
|
@@ -2555,101 +2859,101 @@ function runInDir(command, cwd) {
|
|
|
2555
2859
|
}
|
|
2556
2860
|
async function deployCommand(name, options) {
|
|
2557
2861
|
if (!name) {
|
|
2558
|
-
console.log(
|
|
2559
|
-
console.log(
|
|
2560
|
-
console.log(
|
|
2561
|
-
console.log(
|
|
2562
|
-
console.log(
|
|
2563
|
-
console.log(
|
|
2862
|
+
console.log(chalk19.red("Usage: bindler deploy <name>"));
|
|
2863
|
+
console.log(chalk19.dim("\nDeploys a project: git pull + npm install + restart"));
|
|
2864
|
+
console.log(chalk19.dim("\nExamples:"));
|
|
2865
|
+
console.log(chalk19.dim(" bindler deploy myapp"));
|
|
2866
|
+
console.log(chalk19.dim(" bindler deploy myapp --skip-install"));
|
|
2867
|
+
console.log(chalk19.dim(" bindler deploy myapp --skip-pull"));
|
|
2564
2868
|
process.exit(1);
|
|
2565
2869
|
}
|
|
2566
2870
|
const project = getProject(name);
|
|
2567
2871
|
if (!project) {
|
|
2568
|
-
console.log(
|
|
2569
|
-
console.log(
|
|
2872
|
+
console.log(chalk19.red(`Project "${name}" not found.`));
|
|
2873
|
+
console.log(chalk19.dim("\nAvailable projects:"));
|
|
2570
2874
|
const projects = listProjects();
|
|
2571
2875
|
for (const p of projects) {
|
|
2572
|
-
console.log(
|
|
2876
|
+
console.log(chalk19.dim(` - ${p.name}`));
|
|
2573
2877
|
}
|
|
2574
2878
|
process.exit(1);
|
|
2575
2879
|
}
|
|
2576
|
-
if (!
|
|
2577
|
-
console.log(
|
|
2880
|
+
if (!existsSync11(project.path)) {
|
|
2881
|
+
console.log(chalk19.red(`Project path does not exist: ${project.path}`));
|
|
2578
2882
|
process.exit(1);
|
|
2579
2883
|
}
|
|
2580
|
-
console.log(
|
|
2884
|
+
console.log(chalk19.blue(`
|
|
2581
2885
|
Deploying ${project.name}...
|
|
2582
2886
|
`));
|
|
2583
2887
|
if (!options.skipPull) {
|
|
2584
|
-
const isGitRepo =
|
|
2888
|
+
const isGitRepo = existsSync11(join7(project.path, ".git"));
|
|
2585
2889
|
if (isGitRepo) {
|
|
2586
|
-
console.log(
|
|
2890
|
+
console.log(chalk19.dim("Pulling latest changes..."));
|
|
2587
2891
|
const result = runInDir("git pull", project.path);
|
|
2588
2892
|
if (result.success) {
|
|
2589
2893
|
if (result.output.includes("Already up to date")) {
|
|
2590
|
-
console.log(
|
|
2894
|
+
console.log(chalk19.green(" \u2713 Already up to date"));
|
|
2591
2895
|
} else {
|
|
2592
|
-
console.log(
|
|
2896
|
+
console.log(chalk19.green(" \u2713 Pulled latest changes"));
|
|
2593
2897
|
if (result.output) {
|
|
2594
|
-
console.log(
|
|
2898
|
+
console.log(chalk19.dim(` ${result.output.split("\n")[0]}`));
|
|
2595
2899
|
}
|
|
2596
2900
|
}
|
|
2597
2901
|
} else {
|
|
2598
|
-
console.log(
|
|
2599
|
-
console.log(
|
|
2902
|
+
console.log(chalk19.yellow(" ! Git pull failed"));
|
|
2903
|
+
console.log(chalk19.dim(` ${result.output}`));
|
|
2600
2904
|
}
|
|
2601
2905
|
} else {
|
|
2602
|
-
console.log(
|
|
2906
|
+
console.log(chalk19.dim(" - Not a git repository, skipping pull"));
|
|
2603
2907
|
}
|
|
2604
2908
|
} else {
|
|
2605
|
-
console.log(
|
|
2909
|
+
console.log(chalk19.dim(" - Skipped git pull (--skip-pull)"));
|
|
2606
2910
|
}
|
|
2607
2911
|
if (project.type === "npm" && !options.skipInstall) {
|
|
2608
|
-
const hasPackageJson =
|
|
2912
|
+
const hasPackageJson = existsSync11(join7(project.path, "package.json"));
|
|
2609
2913
|
if (hasPackageJson) {
|
|
2610
|
-
console.log(
|
|
2914
|
+
console.log(chalk19.dim("Installing dependencies..."));
|
|
2611
2915
|
const result = runInDir("npm install", project.path);
|
|
2612
2916
|
if (result.success) {
|
|
2613
|
-
console.log(
|
|
2917
|
+
console.log(chalk19.green(" \u2713 Dependencies installed"));
|
|
2614
2918
|
} else {
|
|
2615
|
-
console.log(
|
|
2616
|
-
console.log(
|
|
2919
|
+
console.log(chalk19.yellow(" ! npm install failed"));
|
|
2920
|
+
console.log(chalk19.dim(` ${result.output.split("\n")[0]}`));
|
|
2617
2921
|
}
|
|
2618
2922
|
}
|
|
2619
2923
|
} else if (options.skipInstall) {
|
|
2620
|
-
console.log(
|
|
2924
|
+
console.log(chalk19.dim(" - Skipped npm install (--skip-install)"));
|
|
2621
2925
|
}
|
|
2622
2926
|
if (project.type === "npm" && !options.skipRestart) {
|
|
2623
|
-
console.log(
|
|
2927
|
+
console.log(chalk19.dim("Restarting application..."));
|
|
2624
2928
|
const result = restartProject(name);
|
|
2625
2929
|
if (result.success) {
|
|
2626
|
-
console.log(
|
|
2930
|
+
console.log(chalk19.green(" \u2713 Application restarted"));
|
|
2627
2931
|
} else {
|
|
2628
|
-
console.log(
|
|
2629
|
-
console.log(
|
|
2932
|
+
console.log(chalk19.yellow(` ! Restart failed: ${result.error}`));
|
|
2933
|
+
console.log(chalk19.dim(` Try: bindler start ${name}`));
|
|
2630
2934
|
}
|
|
2631
2935
|
} else if (project.type === "static") {
|
|
2632
|
-
console.log(
|
|
2936
|
+
console.log(chalk19.dim(" - Static project, no restart needed"));
|
|
2633
2937
|
} else if (options.skipRestart) {
|
|
2634
|
-
console.log(
|
|
2938
|
+
console.log(chalk19.dim(" - Skipped restart (--skip-restart)"));
|
|
2635
2939
|
}
|
|
2636
|
-
console.log(
|
|
2940
|
+
console.log(chalk19.green(`
|
|
2637
2941
|
\u2713 Deploy complete for ${project.name}
|
|
2638
2942
|
`));
|
|
2639
2943
|
}
|
|
2640
2944
|
|
|
2641
2945
|
// src/commands/backup.ts
|
|
2642
|
-
import
|
|
2643
|
-
import { existsSync as
|
|
2946
|
+
import chalk20 from "chalk";
|
|
2947
|
+
import { existsSync as existsSync12, readFileSync as readFileSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4 } from "fs";
|
|
2644
2948
|
import { dirname as dirname3, resolve } from "path";
|
|
2645
|
-
import { homedir as
|
|
2949
|
+
import { homedir as homedir3 } from "os";
|
|
2646
2950
|
async function backupCommand(options) {
|
|
2647
2951
|
const config = readConfig();
|
|
2648
2952
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
2649
|
-
const defaultPath = resolve(
|
|
2953
|
+
const defaultPath = resolve(homedir3(), `bindler-backup-${timestamp}.json`);
|
|
2650
2954
|
const outputPath = options.output || defaultPath;
|
|
2651
2955
|
const dir = dirname3(outputPath);
|
|
2652
|
-
if (!
|
|
2956
|
+
if (!existsSync12(dir)) {
|
|
2653
2957
|
mkdirSync4(dir, { recursive: true });
|
|
2654
2958
|
}
|
|
2655
2959
|
const backup = {
|
|
@@ -2658,27 +2962,27 @@ async function backupCommand(options) {
|
|
|
2658
2962
|
config
|
|
2659
2963
|
};
|
|
2660
2964
|
writeFileSync5(outputPath, JSON.stringify(backup, null, 2) + "\n");
|
|
2661
|
-
console.log(
|
|
2965
|
+
console.log(chalk20.green(`
|
|
2662
2966
|
\u2713 Backup saved to ${outputPath}
|
|
2663
2967
|
`));
|
|
2664
|
-
console.log(
|
|
2665
|
-
console.log(
|
|
2968
|
+
console.log(chalk20.dim(` Projects: ${config.projects.length}`));
|
|
2969
|
+
console.log(chalk20.dim(` Mode: ${config.defaults.mode || "tunnel"}`));
|
|
2666
2970
|
console.log("");
|
|
2667
|
-
console.log(
|
|
2668
|
-
console.log(
|
|
2971
|
+
console.log(chalk20.dim("Restore with:"));
|
|
2972
|
+
console.log(chalk20.cyan(` bindler restore ${outputPath}`));
|
|
2669
2973
|
console.log("");
|
|
2670
2974
|
}
|
|
2671
2975
|
async function restoreCommand(file, options) {
|
|
2672
2976
|
if (!file) {
|
|
2673
|
-
console.log(
|
|
2674
|
-
console.log(
|
|
2675
|
-
console.log(
|
|
2676
|
-
console.log(
|
|
2977
|
+
console.log(chalk20.red("Usage: bindler restore <file>"));
|
|
2978
|
+
console.log(chalk20.dim("\nExamples:"));
|
|
2979
|
+
console.log(chalk20.dim(" bindler restore ~/bindler-backup.json"));
|
|
2980
|
+
console.log(chalk20.dim(" bindler restore backup.json --force"));
|
|
2677
2981
|
process.exit(1);
|
|
2678
2982
|
}
|
|
2679
2983
|
const filePath = resolve(file);
|
|
2680
|
-
if (!
|
|
2681
|
-
console.log(
|
|
2984
|
+
if (!existsSync12(filePath)) {
|
|
2985
|
+
console.log(chalk20.red(`File not found: ${filePath}`));
|
|
2682
2986
|
process.exit(1);
|
|
2683
2987
|
}
|
|
2684
2988
|
let backup;
|
|
@@ -2686,140 +2990,140 @@ async function restoreCommand(file, options) {
|
|
|
2686
2990
|
const content = readFileSync6(filePath, "utf-8");
|
|
2687
2991
|
backup = JSON.parse(content);
|
|
2688
2992
|
} catch (error) {
|
|
2689
|
-
console.log(
|
|
2993
|
+
console.log(chalk20.red("Invalid backup file. Must be valid JSON."));
|
|
2690
2994
|
process.exit(1);
|
|
2691
2995
|
}
|
|
2692
2996
|
if (!backup.config || !backup.config.defaults || !Array.isArray(backup.config.projects)) {
|
|
2693
|
-
console.log(
|
|
2997
|
+
console.log(chalk20.red("Invalid backup format. Missing config data."));
|
|
2694
2998
|
process.exit(1);
|
|
2695
2999
|
}
|
|
2696
|
-
console.log(
|
|
2697
|
-
console.log(
|
|
2698
|
-
console.log(
|
|
2699
|
-
console.log(
|
|
3000
|
+
console.log(chalk20.blue("\nBackup info:\n"));
|
|
3001
|
+
console.log(chalk20.dim(" Exported: ") + chalk20.white(backup.exportedAt || "unknown"));
|
|
3002
|
+
console.log(chalk20.dim(" Projects: ") + chalk20.white(backup.config.projects.length));
|
|
3003
|
+
console.log(chalk20.dim(" Mode: ") + chalk20.white(backup.config.defaults.mode || "tunnel"));
|
|
2700
3004
|
console.log("");
|
|
2701
3005
|
const currentConfig = readConfig();
|
|
2702
3006
|
if (currentConfig.projects.length > 0 && !options.force) {
|
|
2703
|
-
console.log(
|
|
2704
|
-
console.log(
|
|
3007
|
+
console.log(chalk20.yellow(`Warning: You have ${currentConfig.projects.length} existing project(s).`));
|
|
3008
|
+
console.log(chalk20.dim("Use --force to overwrite.\n"));
|
|
2705
3009
|
process.exit(1);
|
|
2706
3010
|
}
|
|
2707
3011
|
writeConfig(backup.config);
|
|
2708
|
-
console.log(
|
|
2709
|
-
console.log(
|
|
3012
|
+
console.log(chalk20.green("\u2713 Config restored!\n"));
|
|
3013
|
+
console.log(chalk20.dim("Run `bindler apply` to apply nginx configuration."));
|
|
2710
3014
|
console.log("");
|
|
2711
3015
|
}
|
|
2712
3016
|
|
|
2713
3017
|
// src/commands/ssl.ts
|
|
2714
|
-
import
|
|
3018
|
+
import chalk21 from "chalk";
|
|
2715
3019
|
async function sslCommand(hostname, options) {
|
|
2716
3020
|
if (!hostname) {
|
|
2717
|
-
console.log(
|
|
2718
|
-
console.log(
|
|
2719
|
-
console.log(
|
|
2720
|
-
console.log(
|
|
2721
|
-
console.log(
|
|
2722
|
-
console.log(
|
|
3021
|
+
console.log(chalk21.red("Usage: bindler ssl <hostname>"));
|
|
3022
|
+
console.log(chalk21.dim("\nRequest SSL certificate for a hostname"));
|
|
3023
|
+
console.log(chalk21.dim("\nExamples:"));
|
|
3024
|
+
console.log(chalk21.dim(" bindler ssl myapp.example.com"));
|
|
3025
|
+
console.log(chalk21.dim(" bindler ssl myapp # uses project hostname"));
|
|
3026
|
+
console.log(chalk21.dim(" bindler ssl myapp --email me@example.com"));
|
|
2723
3027
|
process.exit(1);
|
|
2724
3028
|
}
|
|
2725
3029
|
const project = getProject(hostname);
|
|
2726
3030
|
const targetHostname = project ? project.hostname : hostname;
|
|
2727
3031
|
const certbotCheck = execCommandSafe("which certbot");
|
|
2728
3032
|
if (!certbotCheck.success) {
|
|
2729
|
-
console.log(
|
|
2730
|
-
console.log(
|
|
2731
|
-
console.log(
|
|
2732
|
-
console.log(
|
|
3033
|
+
console.log(chalk21.red("certbot is not installed."));
|
|
3034
|
+
console.log(chalk21.dim("\nInstall with:"));
|
|
3035
|
+
console.log(chalk21.dim(" macOS: brew install certbot"));
|
|
3036
|
+
console.log(chalk21.dim(" Linux: apt install certbot python3-certbot-nginx"));
|
|
2733
3037
|
process.exit(1);
|
|
2734
3038
|
}
|
|
2735
3039
|
const defaults = getDefaults();
|
|
2736
3040
|
const email = options.email || defaults.sslEmail || `admin@${targetHostname.split(".").slice(-2).join(".")}`;
|
|
2737
|
-
console.log(
|
|
3041
|
+
console.log(chalk21.blue(`
|
|
2738
3042
|
Requesting SSL certificate for ${targetHostname}...
|
|
2739
3043
|
`));
|
|
2740
3044
|
let cmd = `sudo certbot --nginx -d ${targetHostname} --non-interactive --agree-tos --email ${email}`;
|
|
2741
3045
|
if (options.staging) {
|
|
2742
3046
|
cmd += " --staging";
|
|
2743
|
-
console.log(
|
|
3047
|
+
console.log(chalk21.yellow("Using staging server (test certificate)\n"));
|
|
2744
3048
|
}
|
|
2745
|
-
console.log(
|
|
3049
|
+
console.log(chalk21.dim(`Running: ${cmd}
|
|
2746
3050
|
`));
|
|
2747
3051
|
const result = execCommandSafe(cmd + " 2>&1");
|
|
2748
3052
|
if (result.success) {
|
|
2749
|
-
console.log(
|
|
3053
|
+
console.log(chalk21.green(`
|
|
2750
3054
|
\u2713 SSL certificate installed for ${targetHostname}
|
|
2751
3055
|
`));
|
|
2752
|
-
console.log(
|
|
3056
|
+
console.log(chalk21.dim("Certificate will auto-renew via certbot timer."));
|
|
2753
3057
|
} else if (result.output?.includes("Certificate not yet due for renewal")) {
|
|
2754
|
-
console.log(
|
|
3058
|
+
console.log(chalk21.green(`
|
|
2755
3059
|
\u2713 Certificate already exists and is valid
|
|
2756
3060
|
`));
|
|
2757
|
-
console.log(
|
|
3061
|
+
console.log(chalk21.dim("Use --force with certbot to renew early if needed."));
|
|
2758
3062
|
} else if (result.output?.includes("too many certificates")) {
|
|
2759
|
-
console.log(
|
|
2760
|
-
console.log(
|
|
2761
|
-
console.log(
|
|
3063
|
+
console.log(chalk21.red("\n\u2717 Rate limit reached"));
|
|
3064
|
+
console.log(chalk21.dim("Let's Encrypt limits certificates per domain."));
|
|
3065
|
+
console.log(chalk21.dim("Try again later or use --staging for testing."));
|
|
2762
3066
|
} else if (result.output?.includes("Could not bind")) {
|
|
2763
|
-
console.log(
|
|
2764
|
-
console.log(
|
|
3067
|
+
console.log(chalk21.red("\n\u2717 Port 80 is in use"));
|
|
3068
|
+
console.log(chalk21.dim("Stop nginx temporarily or use webroot method."));
|
|
2765
3069
|
} else {
|
|
2766
|
-
console.log(
|
|
3070
|
+
console.log(chalk21.red("\n\u2717 Certificate request failed\n"));
|
|
2767
3071
|
if (result.output) {
|
|
2768
3072
|
const lines = result.output.split("\n").filter(
|
|
2769
3073
|
(l) => l.includes("Error") || l.includes("error") || l.includes("failed") || l.includes("Challenge")
|
|
2770
3074
|
);
|
|
2771
3075
|
for (const line of lines.slice(0, 5)) {
|
|
2772
|
-
console.log(
|
|
3076
|
+
console.log(chalk21.dim(` ${line.trim()}`));
|
|
2773
3077
|
}
|
|
2774
3078
|
}
|
|
2775
|
-
console.log(
|
|
2776
|
-
console.log(
|
|
2777
|
-
console.log(
|
|
2778
|
-
console.log(
|
|
3079
|
+
console.log(chalk21.dim("\nCommon issues:"));
|
|
3080
|
+
console.log(chalk21.dim(" - DNS not pointing to this server"));
|
|
3081
|
+
console.log(chalk21.dim(" - Port 80 not accessible from internet"));
|
|
3082
|
+
console.log(chalk21.dim(" - Firewall blocking HTTP validation"));
|
|
2779
3083
|
}
|
|
2780
3084
|
console.log("");
|
|
2781
3085
|
}
|
|
2782
3086
|
|
|
2783
3087
|
// src/commands/tunnel.ts
|
|
2784
|
-
import
|
|
3088
|
+
import chalk22 from "chalk";
|
|
2785
3089
|
import { execSync as execSync4, spawn as spawn2 } from "child_process";
|
|
2786
3090
|
async function tunnelCommand(action, options) {
|
|
2787
3091
|
if (!action) {
|
|
2788
|
-
console.log(
|
|
2789
|
-
console.log(
|
|
2790
|
-
console.log(
|
|
2791
|
-
console.log(
|
|
2792
|
-
console.log(
|
|
2793
|
-
console.log(
|
|
2794
|
-
console.log(
|
|
2795
|
-
console.log(
|
|
2796
|
-
console.log(
|
|
2797
|
-
console.log(
|
|
2798
|
-
console.log(
|
|
2799
|
-
console.log(
|
|
3092
|
+
console.log(chalk22.red("Usage: bindler tunnel <action>"));
|
|
3093
|
+
console.log(chalk22.dim("\nActions:"));
|
|
3094
|
+
console.log(chalk22.dim(" status Show tunnel status"));
|
|
3095
|
+
console.log(chalk22.dim(" start Start the tunnel"));
|
|
3096
|
+
console.log(chalk22.dim(" stop Stop the tunnel"));
|
|
3097
|
+
console.log(chalk22.dim(" login Authenticate with Cloudflare"));
|
|
3098
|
+
console.log(chalk22.dim(" create Create a new tunnel"));
|
|
3099
|
+
console.log(chalk22.dim(" list List all tunnels"));
|
|
3100
|
+
console.log(chalk22.dim("\nExamples:"));
|
|
3101
|
+
console.log(chalk22.dim(" bindler tunnel status"));
|
|
3102
|
+
console.log(chalk22.dim(" bindler tunnel start"));
|
|
3103
|
+
console.log(chalk22.dim(" bindler tunnel create --name mytunnel"));
|
|
2800
3104
|
process.exit(1);
|
|
2801
3105
|
}
|
|
2802
3106
|
if (!isCloudflaredInstalled()) {
|
|
2803
|
-
console.log(
|
|
2804
|
-
console.log(
|
|
3107
|
+
console.log(chalk22.red("cloudflared is not installed."));
|
|
3108
|
+
console.log(chalk22.dim("\nInstall with: bindler setup"));
|
|
2805
3109
|
process.exit(1);
|
|
2806
3110
|
}
|
|
2807
3111
|
const defaults = getDefaults();
|
|
2808
3112
|
const tunnelName = options.name || defaults.tunnelName;
|
|
2809
3113
|
switch (action) {
|
|
2810
3114
|
case "status": {
|
|
2811
|
-
console.log(
|
|
3115
|
+
console.log(chalk22.blue("\nTunnel Status\n"));
|
|
2812
3116
|
const info = getTunnelInfo(tunnelName);
|
|
2813
3117
|
if (!info.exists) {
|
|
2814
|
-
console.log(
|
|
2815
|
-
console.log(
|
|
3118
|
+
console.log(chalk22.yellow(`Tunnel "${tunnelName}" does not exist.`));
|
|
3119
|
+
console.log(chalk22.dim(`
|
|
2816
3120
|
Create with: bindler tunnel create --name ${tunnelName}`));
|
|
2817
3121
|
} else {
|
|
2818
|
-
console.log(
|
|
2819
|
-
console.log(
|
|
2820
|
-
console.log(
|
|
3122
|
+
console.log(chalk22.dim(" Name: ") + chalk22.white(tunnelName));
|
|
3123
|
+
console.log(chalk22.dim(" ID: ") + chalk22.white(info.id || "unknown"));
|
|
3124
|
+
console.log(chalk22.dim(" Running: ") + (info.running ? chalk22.green("yes") : chalk22.red("no")));
|
|
2821
3125
|
if (!info.running) {
|
|
2822
|
-
console.log(
|
|
3126
|
+
console.log(chalk22.dim(`
|
|
2823
3127
|
Start with: bindler tunnel start`));
|
|
2824
3128
|
}
|
|
2825
3129
|
}
|
|
@@ -2829,50 +3133,50 @@ Start with: bindler tunnel start`));
|
|
|
2829
3133
|
case "start": {
|
|
2830
3134
|
const info = getTunnelInfo(tunnelName);
|
|
2831
3135
|
if (!info.exists) {
|
|
2832
|
-
console.log(
|
|
2833
|
-
console.log(
|
|
3136
|
+
console.log(chalk22.red(`Tunnel "${tunnelName}" does not exist.`));
|
|
3137
|
+
console.log(chalk22.dim(`Create with: bindler tunnel create`));
|
|
2834
3138
|
process.exit(1);
|
|
2835
3139
|
}
|
|
2836
3140
|
if (info.running) {
|
|
2837
|
-
console.log(
|
|
3141
|
+
console.log(chalk22.yellow(`Tunnel "${tunnelName}" is already running.`));
|
|
2838
3142
|
process.exit(0);
|
|
2839
3143
|
}
|
|
2840
|
-
console.log(
|
|
3144
|
+
console.log(chalk22.blue(`Starting tunnel "${tunnelName}"...
|
|
2841
3145
|
`));
|
|
2842
|
-
console.log(
|
|
3146
|
+
console.log(chalk22.dim("Running in foreground. Press Ctrl+C to stop.\n"));
|
|
2843
3147
|
const child = spawn2("cloudflared", ["tunnel", "run", tunnelName], {
|
|
2844
3148
|
stdio: "inherit"
|
|
2845
3149
|
});
|
|
2846
3150
|
child.on("error", (err) => {
|
|
2847
|
-
console.log(
|
|
3151
|
+
console.log(chalk22.red(`Failed to start tunnel: ${err.message}`));
|
|
2848
3152
|
process.exit(1);
|
|
2849
3153
|
});
|
|
2850
3154
|
child.on("exit", (code) => {
|
|
2851
|
-
console.log(
|
|
3155
|
+
console.log(chalk22.dim(`
|
|
2852
3156
|
Tunnel exited with code ${code}`));
|
|
2853
3157
|
process.exit(code || 0);
|
|
2854
3158
|
});
|
|
2855
3159
|
break;
|
|
2856
3160
|
}
|
|
2857
3161
|
case "stop": {
|
|
2858
|
-
console.log(
|
|
3162
|
+
console.log(chalk22.blue("Stopping tunnel...\n"));
|
|
2859
3163
|
const result = execCommandSafe(`pkill -f "cloudflared.*tunnel.*run.*${tunnelName}"`);
|
|
2860
3164
|
if (result.success) {
|
|
2861
|
-
console.log(
|
|
3165
|
+
console.log(chalk22.green(`\u2713 Tunnel "${tunnelName}" stopped`));
|
|
2862
3166
|
} else {
|
|
2863
|
-
console.log(
|
|
3167
|
+
console.log(chalk22.yellow(`Tunnel "${tunnelName}" was not running.`));
|
|
2864
3168
|
}
|
|
2865
3169
|
console.log("");
|
|
2866
3170
|
break;
|
|
2867
3171
|
}
|
|
2868
3172
|
case "login": {
|
|
2869
|
-
console.log(
|
|
2870
|
-
console.log(
|
|
3173
|
+
console.log(chalk22.blue("Authenticating with Cloudflare...\n"));
|
|
3174
|
+
console.log(chalk22.dim("A browser window will open. Follow the instructions.\n"));
|
|
2871
3175
|
try {
|
|
2872
3176
|
execSync4("cloudflared tunnel login", { stdio: "inherit" });
|
|
2873
|
-
console.log(
|
|
3177
|
+
console.log(chalk22.green("\n\u2713 Authentication successful!"));
|
|
2874
3178
|
} catch {
|
|
2875
|
-
console.log(
|
|
3179
|
+
console.log(chalk22.red("\n\u2717 Authentication failed or cancelled."));
|
|
2876
3180
|
}
|
|
2877
3181
|
console.log("");
|
|
2878
3182
|
break;
|
|
@@ -2881,69 +3185,69 @@ Tunnel exited with code ${code}`));
|
|
|
2881
3185
|
const existingTunnels = listTunnels();
|
|
2882
3186
|
const exists = existingTunnels.some((t) => t.name === tunnelName);
|
|
2883
3187
|
if (exists) {
|
|
2884
|
-
console.log(
|
|
2885
|
-
console.log(
|
|
3188
|
+
console.log(chalk22.yellow(`Tunnel "${tunnelName}" already exists.`));
|
|
3189
|
+
console.log(chalk22.dim("\nUse a different name with --name"));
|
|
2886
3190
|
process.exit(1);
|
|
2887
3191
|
}
|
|
2888
|
-
console.log(
|
|
3192
|
+
console.log(chalk22.blue(`Creating tunnel "${tunnelName}"...
|
|
2889
3193
|
`));
|
|
2890
3194
|
try {
|
|
2891
3195
|
execSync4(`cloudflared tunnel create ${tunnelName}`, { stdio: "inherit" });
|
|
2892
|
-
console.log(
|
|
3196
|
+
console.log(chalk22.green(`
|
|
2893
3197
|
\u2713 Tunnel "${tunnelName}" created!`));
|
|
2894
|
-
console.log(
|
|
2895
|
-
console.log(
|
|
2896
|
-
console.log(
|
|
3198
|
+
console.log(chalk22.dim("\nNext steps:"));
|
|
3199
|
+
console.log(chalk22.dim(" 1. Create ~/.cloudflared/config.yml"));
|
|
3200
|
+
console.log(chalk22.dim(" 2. Run: bindler tunnel start"));
|
|
2897
3201
|
} catch {
|
|
2898
|
-
console.log(
|
|
2899
|
-
console.log(
|
|
3202
|
+
console.log(chalk22.red("\n\u2717 Failed to create tunnel."));
|
|
3203
|
+
console.log(chalk22.dim("Make sure you're logged in: bindler tunnel login"));
|
|
2900
3204
|
}
|
|
2901
3205
|
console.log("");
|
|
2902
3206
|
break;
|
|
2903
3207
|
}
|
|
2904
3208
|
case "list": {
|
|
2905
|
-
console.log(
|
|
3209
|
+
console.log(chalk22.blue("\nCloudflare Tunnels\n"));
|
|
2906
3210
|
const tunnels = listTunnels();
|
|
2907
3211
|
if (tunnels.length === 0) {
|
|
2908
|
-
console.log(
|
|
2909
|
-
console.log(
|
|
3212
|
+
console.log(chalk22.dim("No tunnels found."));
|
|
3213
|
+
console.log(chalk22.dim("\nCreate one with: bindler tunnel create"));
|
|
2910
3214
|
} else {
|
|
2911
3215
|
for (const tunnel of tunnels) {
|
|
2912
3216
|
const isDefault = tunnel.name === tunnelName;
|
|
2913
|
-
const prefix = isDefault ?
|
|
2914
|
-
console.log(prefix +
|
|
3217
|
+
const prefix = isDefault ? chalk22.cyan("\u2192 ") : " ";
|
|
3218
|
+
console.log(prefix + chalk22.white(tunnel.name) + chalk22.dim(` (${tunnel.id.slice(0, 8)}...)`));
|
|
2915
3219
|
}
|
|
2916
|
-
console.log(
|
|
3220
|
+
console.log(chalk22.dim(`
|
|
2917
3221
|
${tunnels.length} tunnel(s)`));
|
|
2918
3222
|
}
|
|
2919
3223
|
console.log("");
|
|
2920
3224
|
break;
|
|
2921
3225
|
}
|
|
2922
3226
|
default:
|
|
2923
|
-
console.log(
|
|
2924
|
-
console.log(
|
|
3227
|
+
console.log(chalk22.red(`Unknown action: ${action}`));
|
|
3228
|
+
console.log(chalk22.dim("Run `bindler tunnel` for usage."));
|
|
2925
3229
|
process.exit(1);
|
|
2926
3230
|
}
|
|
2927
3231
|
}
|
|
2928
3232
|
|
|
2929
3233
|
// src/commands/open.ts
|
|
2930
|
-
import
|
|
3234
|
+
import chalk23 from "chalk";
|
|
2931
3235
|
import { exec } from "child_process";
|
|
2932
3236
|
async function openCommand(name) {
|
|
2933
3237
|
if (!name) {
|
|
2934
|
-
console.log(
|
|
2935
|
-
console.log(
|
|
2936
|
-
console.log(
|
|
2937
|
-
console.log(
|
|
3238
|
+
console.log(chalk23.red("Usage: bindler open <name>"));
|
|
3239
|
+
console.log(chalk23.dim("\nOpen a project in your browser"));
|
|
3240
|
+
console.log(chalk23.dim("\nExamples:"));
|
|
3241
|
+
console.log(chalk23.dim(" bindler open myapp"));
|
|
2938
3242
|
process.exit(1);
|
|
2939
3243
|
}
|
|
2940
3244
|
const project = getProject(name);
|
|
2941
3245
|
if (!project) {
|
|
2942
|
-
console.log(
|
|
2943
|
-
console.log(
|
|
3246
|
+
console.log(chalk23.red(`Project "${name}" not found.`));
|
|
3247
|
+
console.log(chalk23.dim("\nAvailable projects:"));
|
|
2944
3248
|
const projects = listProjects();
|
|
2945
3249
|
for (const p of projects) {
|
|
2946
|
-
console.log(
|
|
3250
|
+
console.log(chalk23.dim(` - ${p.name}`));
|
|
2947
3251
|
}
|
|
2948
3252
|
process.exit(1);
|
|
2949
3253
|
}
|
|
@@ -2964,7 +3268,7 @@ async function openCommand(name) {
|
|
|
2964
3268
|
if (project.basePath && project.basePath !== "/") {
|
|
2965
3269
|
url += project.basePath;
|
|
2966
3270
|
}
|
|
2967
|
-
console.log(
|
|
3271
|
+
console.log(chalk23.dim(`Opening ${url}...`));
|
|
2968
3272
|
const platform = process.platform;
|
|
2969
3273
|
let cmd;
|
|
2970
3274
|
if (platform === "darwin") {
|
|
@@ -2976,15 +3280,15 @@ async function openCommand(name) {
|
|
|
2976
3280
|
}
|
|
2977
3281
|
exec(cmd, (error) => {
|
|
2978
3282
|
if (error) {
|
|
2979
|
-
console.log(
|
|
2980
|
-
console.log(
|
|
2981
|
-
Open manually: ${
|
|
3283
|
+
console.log(chalk23.yellow(`Could not open browser automatically.`));
|
|
3284
|
+
console.log(chalk23.dim(`
|
|
3285
|
+
Open manually: ${chalk23.cyan(url)}`));
|
|
2982
3286
|
}
|
|
2983
3287
|
});
|
|
2984
3288
|
}
|
|
2985
3289
|
|
|
2986
3290
|
// src/commands/health.ts
|
|
2987
|
-
import
|
|
3291
|
+
import chalk24 from "chalk";
|
|
2988
3292
|
async function pingUrl(url, timeout = 5e3) {
|
|
2989
3293
|
const start = Date.now();
|
|
2990
3294
|
try {
|
|
@@ -3014,15 +3318,15 @@ async function healthCommand() {
|
|
|
3014
3318
|
const projects = listProjects();
|
|
3015
3319
|
const defaults = getDefaults();
|
|
3016
3320
|
if (projects.length === 0) {
|
|
3017
|
-
console.log(
|
|
3018
|
-
console.log(
|
|
3321
|
+
console.log(chalk24.yellow("\nNo projects registered."));
|
|
3322
|
+
console.log(chalk24.dim("Run `bindler new` to add a project.\n"));
|
|
3019
3323
|
return;
|
|
3020
3324
|
}
|
|
3021
|
-
console.log(
|
|
3325
|
+
console.log(chalk24.blue("\nHealth Check\n"));
|
|
3022
3326
|
const results = [];
|
|
3023
3327
|
for (const project of projects) {
|
|
3024
3328
|
if (project.enabled === false) {
|
|
3025
|
-
console.log(
|
|
3329
|
+
console.log(chalk24.dim(` - ${project.name} (disabled)`));
|
|
3026
3330
|
continue;
|
|
3027
3331
|
}
|
|
3028
3332
|
const isLocal = project.local || project.hostname.endsWith(".local");
|
|
@@ -3041,33 +3345,33 @@ async function healthCommand() {
|
|
|
3041
3345
|
if (project.basePath && project.basePath !== "/") {
|
|
3042
3346
|
url += project.basePath;
|
|
3043
3347
|
}
|
|
3044
|
-
process.stdout.write(
|
|
3348
|
+
process.stdout.write(chalk24.dim(` Checking ${project.name}...`));
|
|
3045
3349
|
const result = await pingUrl(url);
|
|
3046
3350
|
results.push({ name: project.name, hostname: project.hostname, ...result });
|
|
3047
3351
|
process.stdout.write("\r\x1B[K");
|
|
3048
3352
|
if (result.ok) {
|
|
3049
|
-
console.log(
|
|
3353
|
+
console.log(chalk24.green(" \u2713 ") + chalk24.white(project.name) + chalk24.dim(` (${result.time}ms)`));
|
|
3050
3354
|
} else if (result.status) {
|
|
3051
|
-
console.log(
|
|
3355
|
+
console.log(chalk24.yellow(" ! ") + chalk24.white(project.name) + chalk24.dim(` (${result.status})`));
|
|
3052
3356
|
} else {
|
|
3053
|
-
console.log(
|
|
3357
|
+
console.log(chalk24.red(" \u2717 ") + chalk24.white(project.name) + chalk24.dim(` (${result.error})`));
|
|
3054
3358
|
}
|
|
3055
3359
|
}
|
|
3056
3360
|
const healthy = results.filter((r) => r.ok).length;
|
|
3057
3361
|
const unhealthy = results.filter((r) => !r.ok).length;
|
|
3058
3362
|
console.log("");
|
|
3059
3363
|
if (unhealthy === 0) {
|
|
3060
|
-
console.log(
|
|
3364
|
+
console.log(chalk24.green(`\u2713 All ${healthy} project(s) healthy`));
|
|
3061
3365
|
} else if (healthy === 0) {
|
|
3062
|
-
console.log(
|
|
3366
|
+
console.log(chalk24.red(`\u2717 All ${unhealthy} project(s) down`));
|
|
3063
3367
|
} else {
|
|
3064
|
-
console.log(
|
|
3368
|
+
console.log(chalk24.yellow(`! ${healthy} healthy, ${unhealthy} down`));
|
|
3065
3369
|
}
|
|
3066
3370
|
console.log("");
|
|
3067
3371
|
}
|
|
3068
3372
|
|
|
3069
3373
|
// src/commands/stats.ts
|
|
3070
|
-
import
|
|
3374
|
+
import chalk25 from "chalk";
|
|
3071
3375
|
function formatBytes2(bytes) {
|
|
3072
3376
|
if (bytes === 0) return "0 B";
|
|
3073
3377
|
const k = 1024;
|
|
@@ -3090,15 +3394,15 @@ async function statsCommand() {
|
|
|
3090
3394
|
const pm2Processes = getPm2List();
|
|
3091
3395
|
const npmProjects = projects.filter((p) => p.type === "npm");
|
|
3092
3396
|
if (npmProjects.length === 0) {
|
|
3093
|
-
console.log(
|
|
3094
|
-
console.log(
|
|
3397
|
+
console.log(chalk25.yellow("\nNo npm projects registered."));
|
|
3398
|
+
console.log(chalk25.dim("Stats are only available for npm projects.\n"));
|
|
3095
3399
|
return;
|
|
3096
3400
|
}
|
|
3097
|
-
console.log(
|
|
3401
|
+
console.log(chalk25.blue("\nProject Stats\n"));
|
|
3098
3402
|
console.log(
|
|
3099
|
-
|
|
3403
|
+
chalk25.dim(" ") + chalk25.dim("NAME".padEnd(20)) + chalk25.dim("STATUS".padEnd(10)) + chalk25.dim("CPU".padEnd(8)) + chalk25.dim("MEM".padEnd(10)) + chalk25.dim("UPTIME".padEnd(10)) + chalk25.dim("RESTARTS")
|
|
3100
3404
|
);
|
|
3101
|
-
console.log(
|
|
3405
|
+
console.log(chalk25.dim(" " + "-".repeat(70)));
|
|
3102
3406
|
let totalCpu = 0;
|
|
3103
3407
|
let totalMem = 0;
|
|
3104
3408
|
for (const project of npmProjects) {
|
|
@@ -3107,11 +3411,11 @@ async function statsCommand() {
|
|
|
3107
3411
|
const name = project.name.slice(0, 18).padEnd(20);
|
|
3108
3412
|
if (!pm2Process) {
|
|
3109
3413
|
console.log(
|
|
3110
|
-
" " +
|
|
3414
|
+
" " + chalk25.white(name) + chalk25.dim("not managed".padEnd(10)) + chalk25.dim("-".padEnd(8)) + chalk25.dim("-".padEnd(10)) + chalk25.dim("-".padEnd(10)) + chalk25.dim("-")
|
|
3111
3415
|
);
|
|
3112
3416
|
continue;
|
|
3113
3417
|
}
|
|
3114
|
-
const statusColor = pm2Process.status === "online" ?
|
|
3418
|
+
const statusColor = pm2Process.status === "online" ? chalk25.green : chalk25.red;
|
|
3115
3419
|
const status = statusColor(pm2Process.status.padEnd(10));
|
|
3116
3420
|
const cpu = `${pm2Process.cpu.toFixed(1)}%`.padEnd(8);
|
|
3117
3421
|
const mem = formatBytes2(pm2Process.memory).padEnd(10);
|
|
@@ -3120,19 +3424,19 @@ async function statsCommand() {
|
|
|
3120
3424
|
totalCpu += pm2Process.cpu;
|
|
3121
3425
|
totalMem += pm2Process.memory;
|
|
3122
3426
|
console.log(
|
|
3123
|
-
" " +
|
|
3427
|
+
" " + chalk25.white(name) + status + (pm2Process.cpu > 50 ? chalk25.yellow(cpu) : chalk25.dim(cpu)) + (pm2Process.memory > 500 * 1024 * 1024 ? chalk25.yellow(mem) : chalk25.dim(mem)) + chalk25.dim(uptime) + (pm2Process.restarts > 0 ? chalk25.yellow(restarts) : chalk25.dim(restarts))
|
|
3124
3428
|
);
|
|
3125
3429
|
}
|
|
3126
3430
|
const runningCount = pm2Processes.filter((p) => p.name.startsWith("bindler:") && p.status === "online").length;
|
|
3127
|
-
console.log(
|
|
3431
|
+
console.log(chalk25.dim(" " + "-".repeat(70)));
|
|
3128
3432
|
console.log(
|
|
3129
|
-
" " +
|
|
3433
|
+
" " + chalk25.bold("TOTAL".padEnd(20)) + chalk25.dim(`${runningCount}/${npmProjects.length}`.padEnd(10)) + chalk25.dim(`${totalCpu.toFixed(1)}%`.padEnd(8)) + chalk25.dim(formatBytes2(totalMem).padEnd(10))
|
|
3130
3434
|
);
|
|
3131
3435
|
console.log("");
|
|
3132
3436
|
}
|
|
3133
3437
|
|
|
3134
3438
|
// src/commands/completion.ts
|
|
3135
|
-
import
|
|
3439
|
+
import chalk26 from "chalk";
|
|
3136
3440
|
var BASH_COMPLETION = `
|
|
3137
3441
|
# bindler bash completion
|
|
3138
3442
|
_bindler_completions() {
|
|
@@ -3265,12 +3569,12 @@ complete -c bindler -n '__fish_seen_subcommand_from tunnel' -a 'status start sto
|
|
|
3265
3569
|
`;
|
|
3266
3570
|
async function completionCommand(shell) {
|
|
3267
3571
|
if (!shell) {
|
|
3268
|
-
console.log(
|
|
3269
|
-
console.log(
|
|
3270
|
-
console.log(
|
|
3271
|
-
console.log(
|
|
3272
|
-
console.log(
|
|
3273
|
-
console.log(
|
|
3572
|
+
console.log(chalk26.red("Usage: bindler completion <shell>"));
|
|
3573
|
+
console.log(chalk26.dim("\nSupported shells: bash, zsh, fish"));
|
|
3574
|
+
console.log(chalk26.dim("\nSetup:"));
|
|
3575
|
+
console.log(chalk26.dim(" bash: bindler completion bash >> ~/.bashrc"));
|
|
3576
|
+
console.log(chalk26.dim(" zsh: bindler completion zsh >> ~/.zshrc"));
|
|
3577
|
+
console.log(chalk26.dim(" fish: bindler completion fish > ~/.config/fish/completions/bindler.fish"));
|
|
3274
3578
|
process.exit(1);
|
|
3275
3579
|
}
|
|
3276
3580
|
switch (shell) {
|
|
@@ -3284,32 +3588,32 @@ async function completionCommand(shell) {
|
|
|
3284
3588
|
console.log(FISH_COMPLETION.trim());
|
|
3285
3589
|
break;
|
|
3286
3590
|
default:
|
|
3287
|
-
console.log(
|
|
3288
|
-
console.log(
|
|
3591
|
+
console.log(chalk26.red(`Unknown shell: ${shell}`));
|
|
3592
|
+
console.log(chalk26.dim("Supported: bash, zsh, fish"));
|
|
3289
3593
|
process.exit(1);
|
|
3290
3594
|
}
|
|
3291
3595
|
}
|
|
3292
3596
|
|
|
3293
3597
|
// src/commands/clone.ts
|
|
3294
|
-
import
|
|
3598
|
+
import chalk27 from "chalk";
|
|
3295
3599
|
import inquirer5 from "inquirer";
|
|
3296
3600
|
async function cloneCommand(source, newName, options) {
|
|
3297
3601
|
if (!source) {
|
|
3298
|
-
console.log(
|
|
3299
|
-
console.log(
|
|
3300
|
-
console.log(
|
|
3301
|
-
console.log(
|
|
3302
|
-
console.log(
|
|
3303
|
-
console.log(
|
|
3602
|
+
console.log(chalk27.red("Usage: bindler clone <source> <new-name>"));
|
|
3603
|
+
console.log(chalk27.dim("\nClones a project configuration with a new name"));
|
|
3604
|
+
console.log(chalk27.dim("\nExamples:"));
|
|
3605
|
+
console.log(chalk27.dim(" bindler clone myapp myapp-staging"));
|
|
3606
|
+
console.log(chalk27.dim(" bindler clone myapp myapp-v2 --hostname newapp.example.com"));
|
|
3607
|
+
console.log(chalk27.dim(" bindler clone myapp myapp-copy --path /var/www/newapp"));
|
|
3304
3608
|
process.exit(1);
|
|
3305
3609
|
}
|
|
3306
3610
|
const sourceProject = getProject(source);
|
|
3307
3611
|
if (!sourceProject) {
|
|
3308
|
-
console.log(
|
|
3309
|
-
console.log(
|
|
3612
|
+
console.log(chalk27.red(`Project "${source}" not found.`));
|
|
3613
|
+
console.log(chalk27.dim("\nAvailable projects:"));
|
|
3310
3614
|
const projects = listProjects();
|
|
3311
3615
|
for (const p of projects) {
|
|
3312
|
-
console.log(
|
|
3616
|
+
console.log(chalk27.dim(` - ${p.name}`));
|
|
3313
3617
|
}
|
|
3314
3618
|
process.exit(1);
|
|
3315
3619
|
}
|
|
@@ -3335,11 +3639,11 @@ async function cloneCommand(source, newName, options) {
|
|
|
3335
3639
|
targetName = answer.name;
|
|
3336
3640
|
} else {
|
|
3337
3641
|
if (!validateProjectName(targetName)) {
|
|
3338
|
-
console.log(
|
|
3642
|
+
console.log(chalk27.red("Invalid project name. Use alphanumeric characters, dashes, and underscores."));
|
|
3339
3643
|
process.exit(1);
|
|
3340
3644
|
}
|
|
3341
3645
|
if (getProject(targetName)) {
|
|
3342
|
-
console.log(
|
|
3646
|
+
console.log(chalk27.red(`Project "${targetName}" already exists.`));
|
|
3343
3647
|
process.exit(1);
|
|
3344
3648
|
}
|
|
3345
3649
|
}
|
|
@@ -3362,7 +3666,7 @@ async function cloneCommand(source, newName, options) {
|
|
|
3362
3666
|
targetHostname = answer.hostname;
|
|
3363
3667
|
} else {
|
|
3364
3668
|
if (!validateHostname(targetHostname)) {
|
|
3365
|
-
console.log(
|
|
3669
|
+
console.log(chalk27.red("Invalid hostname format."));
|
|
3366
3670
|
process.exit(1);
|
|
3367
3671
|
}
|
|
3368
3672
|
}
|
|
@@ -3380,36 +3684,36 @@ async function cloneCommand(source, newName, options) {
|
|
|
3380
3684
|
}
|
|
3381
3685
|
try {
|
|
3382
3686
|
addProject(newProject);
|
|
3383
|
-
console.log(
|
|
3687
|
+
console.log(chalk27.green(`
|
|
3384
3688
|
Project "${targetName}" cloned from "${source}"!
|
|
3385
3689
|
`));
|
|
3386
|
-
console.log(
|
|
3387
|
-
console.log(
|
|
3388
|
-
console.log(
|
|
3389
|
-
console.log(
|
|
3390
|
-
console.log(
|
|
3690
|
+
console.log(chalk27.dim("Configuration:"));
|
|
3691
|
+
console.log(chalk27.dim(` Name: ${newProject.name}`));
|
|
3692
|
+
console.log(chalk27.dim(` Type: ${newProject.type}`));
|
|
3693
|
+
console.log(chalk27.dim(` Path: ${newProject.path}`));
|
|
3694
|
+
console.log(chalk27.dim(` Hostname: ${newProject.hostname}`));
|
|
3391
3695
|
if (newProject.port) {
|
|
3392
|
-
console.log(
|
|
3696
|
+
console.log(chalk27.dim(` Port: ${newProject.port}`));
|
|
3393
3697
|
}
|
|
3394
|
-
console.log(
|
|
3395
|
-
Run ${
|
|
3698
|
+
console.log(chalk27.dim(`
|
|
3699
|
+
Run ${chalk27.cyan("sudo bindler apply")} to update nginx configuration.`));
|
|
3396
3700
|
if (newProject.type === "npm") {
|
|
3397
|
-
console.log(
|
|
3701
|
+
console.log(chalk27.dim(`Run ${chalk27.cyan(`bindler start ${targetName}`)} to start the application.`));
|
|
3398
3702
|
}
|
|
3399
3703
|
} catch (error) {
|
|
3400
|
-
console.error(
|
|
3704
|
+
console.error(chalk27.red(`Error: ${error instanceof Error ? error.message : error}`));
|
|
3401
3705
|
process.exit(1);
|
|
3402
3706
|
}
|
|
3403
3707
|
}
|
|
3404
3708
|
|
|
3405
3709
|
// src/commands/dev.ts
|
|
3406
|
-
import
|
|
3710
|
+
import chalk28 from "chalk";
|
|
3407
3711
|
import { spawn as spawn3 } from "child_process";
|
|
3408
|
-
import { existsSync as
|
|
3409
|
-
import { basename as basename3, join as
|
|
3712
|
+
import { existsSync as existsSync13, readFileSync as readFileSync7 } from "fs";
|
|
3713
|
+
import { basename as basename3, join as join8 } from "path";
|
|
3410
3714
|
function getPackageJson(dir) {
|
|
3411
|
-
const pkgPath =
|
|
3412
|
-
if (!
|
|
3715
|
+
const pkgPath = join8(dir, "package.json");
|
|
3716
|
+
if (!existsSync13(pkgPath)) return null;
|
|
3413
3717
|
try {
|
|
3414
3718
|
return JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
3415
3719
|
} catch {
|
|
@@ -3433,11 +3737,11 @@ async function devCommand(name, options) {
|
|
|
3433
3737
|
if (name) {
|
|
3434
3738
|
project = getProject(name);
|
|
3435
3739
|
if (!project) {
|
|
3436
|
-
console.log(
|
|
3437
|
-
console.log(
|
|
3740
|
+
console.log(chalk28.red(`Project "${name}" not found.`));
|
|
3741
|
+
console.log(chalk28.dim("\nAvailable projects:"));
|
|
3438
3742
|
const projects = listProjects();
|
|
3439
3743
|
for (const p of projects) {
|
|
3440
|
-
console.log(
|
|
3744
|
+
console.log(chalk28.dim(` - ${p.name}`));
|
|
3441
3745
|
}
|
|
3442
3746
|
process.exit(1);
|
|
3443
3747
|
}
|
|
@@ -3448,7 +3752,7 @@ async function devCommand(name, options) {
|
|
|
3448
3752
|
if (!project) {
|
|
3449
3753
|
const yamlConfig = readBindlerYaml(cwd);
|
|
3450
3754
|
if (yamlConfig) {
|
|
3451
|
-
console.log(
|
|
3755
|
+
console.log(chalk28.cyan("Found bindler.yaml - creating temporary dev project\n"));
|
|
3452
3756
|
const yamlProject = yamlToProject(yamlConfig, cwd);
|
|
3453
3757
|
project = {
|
|
3454
3758
|
name: yamlProject.name || basename3(cwd),
|
|
@@ -3462,9 +3766,9 @@ async function devCommand(name, options) {
|
|
|
3462
3766
|
} else {
|
|
3463
3767
|
const pkg = getPackageJson(cwd);
|
|
3464
3768
|
if (!pkg) {
|
|
3465
|
-
console.log(
|
|
3466
|
-
console.log(
|
|
3467
|
-
console.log(
|
|
3769
|
+
console.log(chalk28.red("No package.json found in current directory."));
|
|
3770
|
+
console.log(chalk28.dim("\nUsage: bindler dev [name]"));
|
|
3771
|
+
console.log(chalk28.dim(" Run in a project directory or specify a project name"));
|
|
3468
3772
|
process.exit(1);
|
|
3469
3773
|
}
|
|
3470
3774
|
project = {
|
|
@@ -3480,35 +3784,35 @@ async function devCommand(name, options) {
|
|
|
3480
3784
|
projectDir = cwd;
|
|
3481
3785
|
}
|
|
3482
3786
|
if (project.type !== "npm") {
|
|
3483
|
-
console.log(
|
|
3484
|
-
console.log(
|
|
3485
|
-
console.log(
|
|
3787
|
+
console.log(chalk28.red("Dev mode is only supported for npm projects."));
|
|
3788
|
+
console.log(chalk28.dim("\nFor static projects, use a local web server:"));
|
|
3789
|
+
console.log(chalk28.dim(" npx serve " + project.path));
|
|
3486
3790
|
process.exit(1);
|
|
3487
3791
|
}
|
|
3488
|
-
if (!
|
|
3489
|
-
console.log(
|
|
3792
|
+
if (!existsSync13(projectDir)) {
|
|
3793
|
+
console.log(chalk28.red(`Project directory not found: ${projectDir}`));
|
|
3490
3794
|
process.exit(1);
|
|
3491
3795
|
}
|
|
3492
3796
|
const devCmd = getDevCommand(projectDir) || project.start || "npm start";
|
|
3493
3797
|
const port = options.port || project.port || findAvailablePort();
|
|
3494
|
-
console.log(
|
|
3798
|
+
console.log(chalk28.blue(`
|
|
3495
3799
|
Starting ${project.name} in dev mode...
|
|
3496
3800
|
`));
|
|
3497
|
-
console.log(
|
|
3498
|
-
console.log(
|
|
3499
|
-
console.log(
|
|
3500
|
-
console.log(
|
|
3801
|
+
console.log(chalk28.dim(` Directory: ${projectDir}`));
|
|
3802
|
+
console.log(chalk28.dim(` Command: ${devCmd}`));
|
|
3803
|
+
console.log(chalk28.dim(` Port: ${port}`));
|
|
3804
|
+
console.log(chalk28.dim(` Hostname: ${project.hostname}`));
|
|
3501
3805
|
if (project.hostname.endsWith(".local") || project.local) {
|
|
3502
|
-
console.log(
|
|
3806
|
+
console.log(chalk28.yellow(`
|
|
3503
3807
|
Note: Add to /etc/hosts if not already:`));
|
|
3504
|
-
console.log(
|
|
3808
|
+
console.log(chalk28.cyan(` echo "127.0.0.1 ${project.hostname}" | sudo tee -a /etc/hosts`));
|
|
3505
3809
|
}
|
|
3506
3810
|
const defaults = getDefaults();
|
|
3507
3811
|
const listenPort = defaults.nginxListen.split(":")[1] || "8080";
|
|
3508
|
-
console.log(
|
|
3812
|
+
console.log(chalk28.green(`
|
|
3509
3813
|
Access at: http://${project.hostname}:${listenPort}`));
|
|
3510
|
-
console.log(
|
|
3511
|
-
console.log(
|
|
3814
|
+
console.log(chalk28.dim("Press Ctrl+C to stop\n"));
|
|
3815
|
+
console.log(chalk28.dim("---"));
|
|
3512
3816
|
const env = {
|
|
3513
3817
|
...process.env,
|
|
3514
3818
|
PORT: String(port),
|
|
@@ -3522,19 +3826,19 @@ Access at: http://${project.hostname}:${listenPort}`));
|
|
|
3522
3826
|
shell: true
|
|
3523
3827
|
});
|
|
3524
3828
|
child.on("error", (error) => {
|
|
3525
|
-
console.error(
|
|
3829
|
+
console.error(chalk28.red(`
|
|
3526
3830
|
Failed to start: ${error.message}`));
|
|
3527
3831
|
process.exit(1);
|
|
3528
3832
|
});
|
|
3529
3833
|
child.on("exit", (code) => {
|
|
3530
3834
|
if (code !== 0) {
|
|
3531
|
-
console.log(
|
|
3835
|
+
console.log(chalk28.yellow(`
|
|
3532
3836
|
Process exited with code ${code}`));
|
|
3533
3837
|
}
|
|
3534
3838
|
process.exit(code || 0);
|
|
3535
3839
|
});
|
|
3536
3840
|
process.on("SIGINT", () => {
|
|
3537
|
-
console.log(
|
|
3841
|
+
console.log(chalk28.dim("\n\nStopping dev server..."));
|
|
3538
3842
|
child.kill("SIGINT");
|
|
3539
3843
|
});
|
|
3540
3844
|
process.on("SIGTERM", () => {
|
|
@@ -3543,16 +3847,16 @@ Process exited with code ${code}`));
|
|
|
3543
3847
|
}
|
|
3544
3848
|
|
|
3545
3849
|
// src/lib/update-check.ts
|
|
3546
|
-
import
|
|
3547
|
-
import { existsSync as
|
|
3548
|
-
import { join as
|
|
3549
|
-
import { homedir as
|
|
3850
|
+
import chalk29 from "chalk";
|
|
3851
|
+
import { existsSync as existsSync14, readFileSync as readFileSync8, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5 } from "fs";
|
|
3852
|
+
import { join as join9 } from "path";
|
|
3853
|
+
import { homedir as homedir4 } from "os";
|
|
3550
3854
|
var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
|
|
3551
|
-
var CACHE_DIR =
|
|
3552
|
-
var CACHE_FILE =
|
|
3855
|
+
var CACHE_DIR = join9(homedir4(), ".config", "bindler");
|
|
3856
|
+
var CACHE_FILE = join9(CACHE_DIR, ".update-check");
|
|
3553
3857
|
function readCache() {
|
|
3554
3858
|
try {
|
|
3555
|
-
if (
|
|
3859
|
+
if (existsSync14(CACHE_FILE)) {
|
|
3556
3860
|
return JSON.parse(readFileSync8(CACHE_FILE, "utf-8"));
|
|
3557
3861
|
}
|
|
3558
3862
|
} catch {
|
|
@@ -3561,7 +3865,7 @@ function readCache() {
|
|
|
3561
3865
|
}
|
|
3562
3866
|
function writeCache(data) {
|
|
3563
3867
|
try {
|
|
3564
|
-
if (!
|
|
3868
|
+
if (!existsSync14(CACHE_DIR)) {
|
|
3565
3869
|
mkdirSync5(CACHE_DIR, { recursive: true });
|
|
3566
3870
|
}
|
|
3567
3871
|
writeFileSync6(CACHE_FILE, JSON.stringify(data));
|
|
@@ -3596,28 +3900,28 @@ async function checkForUpdates() {
|
|
|
3596
3900
|
const cache = readCache();
|
|
3597
3901
|
const now = Date.now();
|
|
3598
3902
|
if (now - cache.lastCheck < CHECK_INTERVAL) {
|
|
3599
|
-
if (cache.latestVersion && compareVersions("1.
|
|
3903
|
+
if (cache.latestVersion && compareVersions("1.4.1", cache.latestVersion) < 0) {
|
|
3600
3904
|
showUpdateMessage(cache.latestVersion);
|
|
3601
3905
|
}
|
|
3602
3906
|
return;
|
|
3603
3907
|
}
|
|
3604
3908
|
fetchLatestVersion().then((latestVersion) => {
|
|
3605
3909
|
writeCache({ lastCheck: now, latestVersion });
|
|
3606
|
-
if (latestVersion && compareVersions("1.
|
|
3910
|
+
if (latestVersion && compareVersions("1.4.1", latestVersion) < 0) {
|
|
3607
3911
|
showUpdateMessage(latestVersion);
|
|
3608
3912
|
}
|
|
3609
3913
|
});
|
|
3610
3914
|
}
|
|
3611
3915
|
function showUpdateMessage(latestVersion) {
|
|
3612
3916
|
console.log("");
|
|
3613
|
-
console.log(
|
|
3614
|
-
console.log(
|
|
3917
|
+
console.log(chalk29.yellow(` Update available: ${"1.4.1"} \u2192 ${latestVersion}`));
|
|
3918
|
+
console.log(chalk29.dim(` Run: npm update -g bindler`));
|
|
3615
3919
|
console.log("");
|
|
3616
3920
|
}
|
|
3617
3921
|
|
|
3618
3922
|
// src/cli.ts
|
|
3619
3923
|
var program = new Command();
|
|
3620
|
-
program.name("bindler").description("Manage multiple projects behind Cloudflare Tunnel with Nginx and PM2").version("1.
|
|
3924
|
+
program.name("bindler").description("Manage multiple projects behind Cloudflare Tunnel with Nginx and PM2").version("1.4.1");
|
|
3621
3925
|
program.hook("preAction", async () => {
|
|
3622
3926
|
try {
|
|
3623
3927
|
initConfig();
|
|
@@ -3636,78 +3940,78 @@ program.command("status").description("Show detailed status of all projects").ac
|
|
|
3636
3940
|
});
|
|
3637
3941
|
program.command("start [name]").description("Start an npm project with PM2").option("-a, --all", "Start all npm projects").action(async (name, options) => {
|
|
3638
3942
|
if (!name && !options.all) {
|
|
3639
|
-
console.log(
|
|
3640
|
-
console.log(
|
|
3641
|
-
console.log(
|
|
3642
|
-
console.log(
|
|
3943
|
+
console.log(chalk30.red("Usage: bindler start <name> or bindler start --all"));
|
|
3944
|
+
console.log(chalk30.dim("\nExamples:"));
|
|
3945
|
+
console.log(chalk30.dim(" bindler start myapp"));
|
|
3946
|
+
console.log(chalk30.dim(" bindler start --all # start all npm projects"));
|
|
3643
3947
|
process.exit(1);
|
|
3644
3948
|
}
|
|
3645
3949
|
await startCommand(name, options);
|
|
3646
3950
|
});
|
|
3647
3951
|
program.command("stop [name]").description("Stop an npm project").option("-a, --all", "Stop all npm projects").action(async (name, options) => {
|
|
3648
3952
|
if (!name && !options.all) {
|
|
3649
|
-
console.log(
|
|
3650
|
-
console.log(
|
|
3651
|
-
console.log(
|
|
3652
|
-
console.log(
|
|
3953
|
+
console.log(chalk30.red("Usage: bindler stop <name> or bindler stop --all"));
|
|
3954
|
+
console.log(chalk30.dim("\nExamples:"));
|
|
3955
|
+
console.log(chalk30.dim(" bindler stop myapp"));
|
|
3956
|
+
console.log(chalk30.dim(" bindler stop --all # stop all npm projects"));
|
|
3653
3957
|
process.exit(1);
|
|
3654
3958
|
}
|
|
3655
3959
|
await stopCommand(name, options);
|
|
3656
3960
|
});
|
|
3657
3961
|
program.command("restart [name]").description("Restart an npm project").option("-a, --all", "Restart all npm projects").action(async (name, options) => {
|
|
3658
3962
|
if (!name && !options.all) {
|
|
3659
|
-
console.log(
|
|
3660
|
-
console.log(
|
|
3661
|
-
console.log(
|
|
3662
|
-
console.log(
|
|
3963
|
+
console.log(chalk30.red("Usage: bindler restart <name> or bindler restart --all"));
|
|
3964
|
+
console.log(chalk30.dim("\nExamples:"));
|
|
3965
|
+
console.log(chalk30.dim(" bindler restart myapp"));
|
|
3966
|
+
console.log(chalk30.dim(" bindler restart --all # restart all npm projects"));
|
|
3663
3967
|
process.exit(1);
|
|
3664
3968
|
}
|
|
3665
3969
|
await restartCommand(name, options);
|
|
3666
3970
|
});
|
|
3667
3971
|
program.command("logs [name]").description("Show logs for an npm project").option("-f, --follow", "Follow log output").option("-l, --lines <n>", "Number of lines to show", "200").action(async (name, options) => {
|
|
3668
3972
|
if (!name) {
|
|
3669
|
-
console.log(
|
|
3670
|
-
console.log(
|
|
3671
|
-
console.log(
|
|
3672
|
-
console.log(
|
|
3673
|
-
console.log(
|
|
3973
|
+
console.log(chalk30.red("Usage: bindler logs <name>"));
|
|
3974
|
+
console.log(chalk30.dim("\nExamples:"));
|
|
3975
|
+
console.log(chalk30.dim(" bindler logs myapp"));
|
|
3976
|
+
console.log(chalk30.dim(" bindler logs myapp --follow"));
|
|
3977
|
+
console.log(chalk30.dim(" bindler logs myapp --lines 500"));
|
|
3674
3978
|
process.exit(1);
|
|
3675
3979
|
}
|
|
3676
3980
|
await logsCommand(name, { ...options, lines: parseInt(options.lines, 10) });
|
|
3677
3981
|
});
|
|
3678
3982
|
program.command("update [name]").description("Update project configuration").option("-h, --hostname <hostname>", "New hostname").option("--port <port>", "New port number").option("-s, --start <command>", "New start command").option("-p, --path <path>", "New project path").option("-e, --env <vars...>", "Environment variables (KEY=value)").option("--enable", "Enable the project").option("--disable", "Disable the project").action(async (name, options) => {
|
|
3679
3983
|
if (!name) {
|
|
3680
|
-
console.log(
|
|
3681
|
-
console.log(
|
|
3682
|
-
console.log(
|
|
3683
|
-
console.log(
|
|
3684
|
-
console.log(
|
|
3984
|
+
console.log(chalk30.red("Usage: bindler update <name> [options]"));
|
|
3985
|
+
console.log(chalk30.dim("\nExamples:"));
|
|
3986
|
+
console.log(chalk30.dim(" bindler update myapp --hostname newapp.example.com"));
|
|
3987
|
+
console.log(chalk30.dim(" bindler update myapp --port 4000"));
|
|
3988
|
+
console.log(chalk30.dim(" bindler update myapp --disable"));
|
|
3685
3989
|
process.exit(1);
|
|
3686
3990
|
}
|
|
3687
3991
|
await updateCommand(name, options);
|
|
3688
3992
|
});
|
|
3689
3993
|
program.command("edit [name]").description("Edit project configuration in $EDITOR").action(async (name) => {
|
|
3690
3994
|
if (!name) {
|
|
3691
|
-
console.log(
|
|
3692
|
-
console.log(
|
|
3693
|
-
console.log(
|
|
3694
|
-
console.log(
|
|
3995
|
+
console.log(chalk30.red("Usage: bindler edit <name>"));
|
|
3996
|
+
console.log(chalk30.dim("\nOpens the project config in your $EDITOR"));
|
|
3997
|
+
console.log(chalk30.dim("\nExample:"));
|
|
3998
|
+
console.log(chalk30.dim(" bindler edit myapp"));
|
|
3695
3999
|
process.exit(1);
|
|
3696
4000
|
}
|
|
3697
4001
|
await editCommand(name);
|
|
3698
4002
|
});
|
|
3699
4003
|
program.command("remove [name]").alias("rm").description("Remove a project from registry").option("-f, --force", "Skip confirmation").option("--apply", "Apply nginx config after removing").action(async (name, options) => {
|
|
3700
4004
|
if (!name) {
|
|
3701
|
-
console.log(
|
|
3702
|
-
console.log(
|
|
3703
|
-
console.log(
|
|
3704
|
-
console.log(
|
|
3705
|
-
console.log(
|
|
4005
|
+
console.log(chalk30.red("Usage: bindler remove <name>"));
|
|
4006
|
+
console.log(chalk30.dim("\nExamples:"));
|
|
4007
|
+
console.log(chalk30.dim(" bindler remove myapp"));
|
|
4008
|
+
console.log(chalk30.dim(" bindler remove myapp --force # skip confirmation"));
|
|
4009
|
+
console.log(chalk30.dim(" bindler rm myapp # alias"));
|
|
3706
4010
|
process.exit(1);
|
|
3707
4011
|
}
|
|
3708
4012
|
await removeCommand(name, options);
|
|
3709
4013
|
});
|
|
3710
|
-
program.command("apply").description("Generate and apply nginx configuration + Cloudflare DNS routes").option("-d, --dry-run", "Print config without applying").option("--no-reload", "Write config but do not reload nginx").option("--no-cloudflare", "Skip Cloudflare DNS route configuration").option("--no-ssl", "Skip SSL certificate setup (direct mode)").option("--sync", "Sync bindler.yaml from project directories before applying").option("-e, --env <env>", "Use environment-specific config (staging, production)").action(async (options) => {
|
|
4014
|
+
program.command("apply").description("Generate and apply nginx configuration + Cloudflare DNS routes").option("-d, --dry-run", "Print config without applying").option("--no-reload", "Write config but do not reload nginx").option("--no-cloudflare", "Skip Cloudflare DNS route configuration").option("--no-ssl", "Skip SSL certificate setup (direct mode)").option("--sync", "Sync bindler.yaml from project directories before applying").option("-e, --env <env>", "Use environment-specific config (staging, production)").option("--skip-checks", "Skip preflight validation checks (not recommended)").action(async (options) => {
|
|
3711
4015
|
await applyCommand(options);
|
|
3712
4016
|
});
|
|
3713
4017
|
program.command("doctor").description("Run system diagnostics and check dependencies").action(async () => {
|
|
@@ -3721,10 +4025,10 @@ program.command("info").description("Show bindler information and stats").action
|
|
|
3721
4025
|
});
|
|
3722
4026
|
program.command("check [hostname]").description("Check DNS propagation and HTTP accessibility for a hostname").option("-v, --verbose", "Show verbose output").action(async (hostname, options) => {
|
|
3723
4027
|
if (!hostname) {
|
|
3724
|
-
console.log(
|
|
3725
|
-
console.log(
|
|
3726
|
-
console.log(
|
|
3727
|
-
console.log(
|
|
4028
|
+
console.log(chalk30.red("Usage: bindler check <hostname>"));
|
|
4029
|
+
console.log(chalk30.dim("\nExamples:"));
|
|
4030
|
+
console.log(chalk30.dim(" bindler check myapp.example.com"));
|
|
4031
|
+
console.log(chalk30.dim(" bindler check myapp # uses project name"));
|
|
3728
4032
|
process.exit(1);
|
|
3729
4033
|
}
|
|
3730
4034
|
await checkCommand(hostname, options);
|