返回 CodeWhale
validate-config.mjs
根目录 / integrations / telegram-bridge / scripts / validate-config.mjs
1 #!/usr/bin/env node
2 import { constants as fsConstants } from "node:fs";
3 import fs from "node:fs/promises";
4 import path from "node:path";
5
6 import {
7 cleanEnvValue,
8 envFirst,
9 formatValidationReport,
10 parseEnvText,
11 validateBridgeConfig
12 } from "../src/lib.mjs";
13
14 const args = parseArgs(process.argv.slice(2));
15
16 try {
17 const bridgeEnv = args.env ? parseEnvText(await fs.readFile(args.env, "utf8")) : process.env;
18 const runtimeEnv = args.runtimeEnv
19 ? parseEnvText(await fs.readFile(args.runtimeEnv, "utf8"))
20 : null;
21 const result = validateBridgeConfig(bridgeEnv, {
22 runtimeEnv,
23 workspaceRoot: args.workspaceRoot || "/opt/whalebro",
24 requireLocalRuntime: args.requireLocalRuntime
25 });
26
27 if (args.checkFilesystem) {
28 await appendFilesystemChecks(result, bridgeEnv, args);
29 }
30
31 if (args.json) {
32 console.log(JSON.stringify(result, null, 2));
33 } else {
34 console.log(formatValidationReport(result));
35 }
36 process.exitCode = result.ok ? 0 : 1;
37 } catch (error) {
38 console.error(`Config validation failed: ${error.message}`);
39 process.exitCode = 1;
40 }
41
42 function parseArgs(argv) {
43 const parsed = {
44 env: "",
45 runtimeEnv: "",
46 workspaceRoot: "/opt/whalebro",
47 checkFilesystem: false,
48 json: false,
49 requireLocalRuntime: true
50 };
51 for (let index = 0; index < argv.length; index += 1) {
52 const arg = argv[index];
53 switch (arg) {
54 case "--env":
55 parsed.env = argv[++index];
56 break;
57 case "--runtime-env":
58 parsed.runtimeEnv = argv[++index];
59 break;
60 case "--workspace-root":
61 parsed.workspaceRoot = argv[++index];
62 break;
63 case "--check-filesystem":
64 parsed.checkFilesystem = true;
65 break;
66 case "--allow-remote-runtime":
67 parsed.requireLocalRuntime = false;
68 break;
69 case "--json":
70 parsed.json = true;
71 break;
72 case "-h":
73 case "--help":
74 printHelp();
75 process.exit(0);
76 break;
77 default:
78 throw new Error(`unknown argument: ${arg}`);
79 }
80 }
81 return parsed;
82 }
83
84 async function appendFilesystemChecks(result, env, args) {
85 const workspace = envFirst(env, "CODEWHALE_WORKSPACE", "DEEPSEEK_WORKSPACE");
86 if (workspace) {
87 await checkReadableDirectory(result, workspace, "workspace");
88 }
89
90 const threadMapPath = cleanEnvValue(env.TELEGRAM_THREAD_MAP_PATH);
91 if (threadMapPath) {
92 const parent = path.dirname(threadMapPath);
93 await checkWritableDirectory(result, parent, "thread map directory");
94 }
95
96 if (args.env) {
97 await checkReadableFile(result, args.env, "bridge env file");
98 }
99 if (args.runtimeEnv) {
100 await checkReadableFile(result, args.runtimeEnv, "runtime env file");
101 }
102 }
103
104 async function checkReadableDirectory(result, dir, label) {
105 try {
106 const stat = await fs.stat(dir);
107 if (!stat.isDirectory()) {
108 result.errors.push({ code: "not_directory", message: `${label} is not a directory: ${dir}` });
109 result.ok = false;
110 return;
111 }
112 await fs.access(dir, fsConstants.R_OK | fsConstants.X_OK);
113 result.info.push({ code: "readable_directory", message: `${label} is readable: ${dir}` });
114 } catch {
115 result.errors.push({ code: "directory_access", message: `${label} is not readable: ${dir}` });
116 result.ok = false;
117 }
118 }
119
120 async function checkWritableDirectory(result, dir, label) {
121 try {
122 const stat = await fs.stat(dir);
123 if (!stat.isDirectory()) {
124 result.errors.push({ code: "not_directory", message: `${label} is not a directory: ${dir}` });
125 result.ok = false;
126 return;
127 }
128 await fs.access(dir, fsConstants.R_OK | fsConstants.W_OK | fsConstants.X_OK);
129 result.info.push({ code: "writable_directory", message: `${label} is writable: ${dir}` });
130 } catch {
131 result.errors.push({ code: "directory_access", message: `${label} is not writable: ${dir}` });
132 result.ok = false;
133 }
134 }
135
136 async function checkReadableFile(result, filePath, label) {
137 try {
138 const stat = await fs.stat(filePath);
139 if (!stat.isFile()) {
140 result.errors.push({ code: "not_file", message: `${label} is not a file: ${filePath}` });
141 result.ok = false;
142 return;
143 }
144 await fs.access(filePath, fsConstants.R_OK);
145 result.info.push({ code: "readable_file", message: `${label} is readable: ${filePath}` });
146 } catch {
147 result.errors.push({ code: "file_access", message: `${label} is not readable: ${filePath}` });
148 result.ok = false;
149 }
150 }
151
152 function printHelp() {
153 console.log(`Usage: node scripts/validate-config.mjs [options]
154
155 Options:
156 --env FILE Read bridge env from FILE instead of process.env.
157 --runtime-env FILE Read runtime env and verify the shared bearer token.
158 --workspace-root DIR Expected remote workspace root (default: /opt/whalebro).
159 --check-filesystem Verify workspace and thread-map paths are usable.
160 --allow-remote-runtime Permit CODEWHALE_RUNTIME_URL to point outside localhost.
161 --json Print machine-readable JSON.
162 -h, --help Show this help.
163 `);
164 }
165
165 lines Plain Text