@aaricchen1991/deploy 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/PUBLISHING.md +2 -0
  2. package/README.md +69 -47
  3. package/dist/cli.d.ts +1 -0
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +98 -9
  6. package/dist/cli.js.map +1 -1
  7. package/dist/commands/config.d.ts +29 -0
  8. package/dist/commands/config.d.ts.map +1 -0
  9. package/dist/commands/config.js +134 -0
  10. package/dist/commands/config.js.map +1 -0
  11. package/dist/commands/init.js +17 -15
  12. package/dist/commands/init.js.map +1 -1
  13. package/dist/commands/nginx.d.ts.map +1 -1
  14. package/dist/commands/nginx.js +13 -11
  15. package/dist/commands/nginx.js.map +1 -1
  16. package/dist/commands/ssl-logs.d.ts.map +1 -1
  17. package/dist/commands/ssl-logs.js +3 -2
  18. package/dist/commands/ssl-logs.js.map +1 -1
  19. package/dist/commands/ssl.d.ts.map +1 -1
  20. package/dist/commands/ssl.js +22 -15
  21. package/dist/commands/ssl.js.map +1 -1
  22. package/dist/lib/config-store.d.ts +14 -0
  23. package/dist/lib/config-store.d.ts.map +1 -0
  24. package/dist/lib/config-store.js +111 -0
  25. package/dist/lib/config-store.js.map +1 -0
  26. package/dist/lib/domains.d.ts +17 -2
  27. package/dist/lib/domains.d.ts.map +1 -1
  28. package/dist/lib/domains.js +70 -6
  29. package/dist/lib/domains.js.map +1 -1
  30. package/dist/lib/logger.d.ts +6 -1
  31. package/dist/lib/logger.d.ts.map +1 -1
  32. package/dist/lib/logger.js +19 -21
  33. package/dist/lib/logger.js.map +1 -1
  34. package/dist/lib/nginx.d.ts +2 -1
  35. package/dist/lib/nginx.d.ts.map +1 -1
  36. package/dist/lib/nginx.js +15 -42
  37. package/dist/lib/nginx.js.map +1 -1
  38. package/dist/lib/paths.d.ts +6 -0
  39. package/dist/lib/paths.d.ts.map +1 -1
  40. package/dist/lib/paths.js +14 -0
  41. package/dist/lib/paths.js.map +1 -1
  42. package/package.json +3 -2
  43. package/src/cli.ts +124 -13
  44. package/src/commands/config.ts +195 -0
  45. package/src/commands/init.ts +17 -17
  46. package/src/commands/nginx.ts +13 -13
  47. package/src/commands/ssl-logs.ts +3 -2
  48. package/src/commands/ssl.ts +27 -18
  49. package/src/lib/config-store.ts +146 -0
  50. package/src/lib/domains.ts +79 -5
  51. package/src/lib/logger.ts +21 -24
  52. package/src/lib/nginx.ts +19 -43
  53. package/src/lib/paths.ts +19 -0
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Config management: init, get, set, delete, list.
3
+ */
4
+
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+ import consola from "consola";
8
+ import type { ConfigKey } from "../lib/config-store.js";
9
+ import {
10
+ getConfigValue,
11
+ isValidConfigKey,
12
+ loadConfig,
13
+ saveConfig,
14
+ setConfigValue,
15
+ deleteConfigValue,
16
+ } from "../lib/config-store.js";
17
+ import { getDefaultConfigPath, resolveConfigPath } from "../lib/paths.js";
18
+
19
+ const DEFAULT_TEMPLATE = `# n2-deploy 配置 (~/.deploy/config.yaml)
20
+ # 阿里云 DNS API(用于 ACME 申请证书)
21
+ # aliyun_access_key_id: "your-key"
22
+ # aliyun_access_key_secret: "your-secret"
23
+
24
+ api:
25
+ backend_port: 3000
26
+ domains: []
27
+
28
+ admin:
29
+ domains: []
30
+
31
+ tenant:
32
+ domains: []
33
+
34
+ client:
35
+ domains: []
36
+ `;
37
+
38
+ export interface ConfigInitOptions {
39
+ config?: string;
40
+ force?: boolean;
41
+ }
42
+
43
+ export async function runConfigInit(opts: ConfigInitOptions): Promise<void> {
44
+ const configPath = opts.config
45
+ ? resolveConfigPath(opts.config)
46
+ : getDefaultConfigPath();
47
+ const dir = path.dirname(configPath);
48
+
49
+ if (fs.existsSync(configPath) && !opts.force) {
50
+ consola.info(`配置文件已存在: ${configPath}`);
51
+ consola.info("使用 --force 可覆盖为默认模板");
52
+ return;
53
+ }
54
+
55
+ fs.mkdirSync(dir, { recursive: true });
56
+ fs.writeFileSync(configPath, DEFAULT_TEMPLATE, "utf8");
57
+ consola.success(`已初始化配置: ${configPath}`);
58
+ }
59
+
60
+ export interface ConfigGetOptions {
61
+ config: string;
62
+ key?: string;
63
+ }
64
+
65
+ export async function runConfigGet(opts: ConfigGetOptions): Promise<void> {
66
+ const configPath = resolveConfigPath(opts.config);
67
+ if (!fs.existsSync(configPath)) {
68
+ consola.error(`配置文件不存在: ${configPath}`);
69
+ consola.info("请先执行: n2-deploy config init");
70
+ process.exit(1);
71
+ }
72
+
73
+ const data = loadConfig(configPath);
74
+
75
+ if (!opts.key) {
76
+ // List all
77
+ const lines: string[] = [];
78
+ if (data.aliyunAccessKeyId != null)
79
+ lines.push(`aliyun_access_key_id: ${data.aliyunAccessKeyId}`);
80
+ if (data.aliyunAccessKeySecret != null)
81
+ lines.push(`aliyun_access_key_secret: ***`);
82
+ lines.push(`api.backend_port: ${data.domainsConfig.api.backend_port}`);
83
+ lines.push(
84
+ `api.domains: [${data.domainsConfig.api.domains.join(", ")}]`
85
+ );
86
+ lines.push(
87
+ `admin.domains: [${data.domainsConfig.admin.domains.join(", ")}]`
88
+ );
89
+ lines.push(
90
+ `tenant.domains: [${data.domainsConfig.tenant.domains.join(", ")}]`
91
+ );
92
+ lines.push(
93
+ `client.domains: [${data.domainsConfig.client.domains.join(", ")}]`
94
+ );
95
+ consola.log(lines.join("\n"));
96
+ return;
97
+ }
98
+
99
+ if (!isValidConfigKey(opts.key)) {
100
+ consola.error(
101
+ `无效的配置项: ${opts.key}。可选: aliyun_access_key_id, aliyun_access_key_secret, api.backend_port, api.domains, admin.domains, tenant.domains, client.domains`
102
+ );
103
+ process.exit(1);
104
+ }
105
+
106
+ const value = getConfigValue(data, opts.key);
107
+ if (value === undefined || value === null) {
108
+ consola.log("");
109
+ return;
110
+ }
111
+ if (Array.isArray(value)) {
112
+ consola.log(value.join("\n"));
113
+ } else {
114
+ consola.log(String(value));
115
+ }
116
+ }
117
+
118
+ export interface ConfigSetOptions {
119
+ config: string;
120
+ key: string;
121
+ values: string[];
122
+ }
123
+
124
+ export async function runConfigSet(opts: ConfigSetOptions): Promise<void> {
125
+ const configPath = resolveConfigPath(opts.config);
126
+ if (!fs.existsSync(configPath)) {
127
+ consola.error(`配置文件不存在: ${configPath}`);
128
+ consola.info("请先执行: n2-deploy config init");
129
+ process.exit(1);
130
+ }
131
+
132
+ if (!isValidConfigKey(opts.key)) {
133
+ consola.error(
134
+ `无效的配置项: ${opts.key}。可选: aliyun_access_key_id, aliyun_access_key_secret, api.backend_port, api.domains, admin.domains, tenant.domains, client.domains`
135
+ );
136
+ process.exit(1);
137
+ }
138
+
139
+ const data = loadConfig(configPath);
140
+ const isDomainsKey =
141
+ opts.key === "api.domains" ||
142
+ opts.key === "admin.domains" ||
143
+ opts.key === "tenant.domains" ||
144
+ opts.key === "client.domains";
145
+ const isPortKey = opts.key === "api.backend_port";
146
+
147
+ let value: string | number | string[];
148
+ if (isDomainsKey) {
149
+ value = opts.values.length > 0 ? opts.values : [];
150
+ } else if (isPortKey) {
151
+ const n = opts.values[0] ? parseInt(opts.values[0], 10) : 3000;
152
+ if (Number.isNaN(n)) {
153
+ consola.error("api.backend_port 必须为数字");
154
+ process.exit(1);
155
+ }
156
+ value = n;
157
+ } else {
158
+ value = opts.values[0] ?? "";
159
+ }
160
+
161
+ setConfigValue(data, opts.key as ConfigKey, value);
162
+ saveConfig(configPath, data);
163
+ consola.success(`已设置 ${opts.key}`);
164
+ }
165
+
166
+ export interface ConfigDeleteOptions {
167
+ config: string;
168
+ key: string;
169
+ }
170
+
171
+ export async function runConfigDelete(opts: ConfigDeleteOptions): Promise<void> {
172
+ const configPath = resolveConfigPath(opts.config);
173
+ if (!fs.existsSync(configPath)) {
174
+ consola.error(`配置文件不存在: ${configPath}`);
175
+ process.exit(1);
176
+ }
177
+
178
+ if (!isValidConfigKey(opts.key)) {
179
+ consola.error(`无效的配置项: ${opts.key}`);
180
+ process.exit(1);
181
+ }
182
+
183
+ const data = loadConfig(configPath);
184
+ deleteConfigValue(data, opts.key as ConfigKey);
185
+ saveConfig(configPath, data);
186
+ consola.success(`已清除 ${opts.key}`);
187
+ }
188
+
189
+ export interface ConfigListOptions {
190
+ config: string;
191
+ }
192
+
193
+ export async function runConfigList(opts: ConfigListOptions): Promise<void> {
194
+ await runConfigGet({ config: opts.config });
195
+ }
@@ -6,10 +6,10 @@
6
6
  import { spawn } from "node:child_process";
7
7
  import fs from "node:fs";
8
8
  import path from "node:path";
9
- import { getAllDomains, parseDomainsYaml } from "../lib/domains.js";
10
- import { error, info, warn } from "../lib/logger.js";
9
+ import { getAllDomains, parseDeployConfig } from "../lib/domains.js";
10
+ import { error, info, success, warn } from "../lib/logger.js";
11
11
  import { generateNginxConf } from "../lib/nginx.js";
12
- import { getScriptsDir } from "../lib/paths.js";
12
+ import { getScriptsDir, resolveConfigPath } from "../lib/paths.js";
13
13
 
14
14
  const DEFAULT_SSL_LOG = "/opt/deploy/logs/ssl.log";
15
15
 
@@ -19,16 +19,17 @@ export interface InitOptions {
19
19
  }
20
20
 
21
21
  export async function runInit(opts: InitOptions): Promise<void> {
22
- const configPath = path.resolve(process.cwd(), opts.config);
22
+ const configPath = resolveConfigPath(opts.config);
23
23
  if (!fs.existsSync(configPath)) {
24
- error(`Domains config not found: ${configPath}`);
24
+ error(`配置文件不存在: ${configPath}`);
25
+ info("请先执行: n2-deploy config init");
25
26
  process.exit(1);
26
27
  }
27
28
 
28
29
  const yaml = fs.readFileSync(configPath, "utf8");
29
- const config = parseDomainsYaml(yaml);
30
- const domains = getAllDomains(config);
31
- const nginxConf = generateNginxConf(config);
30
+ const { domainsConfig } = parseDeployConfig(yaml);
31
+ const domains = getAllDomains(domainsConfig);
32
+ const nginxConf = generateNginxConf(domainsConfig);
32
33
 
33
34
  const scriptsDir =
34
35
  opts.scriptsDir ||
@@ -36,13 +37,12 @@ export async function runInit(opts: InitOptions): Promise<void> {
36
37
  getScriptsDir(process.cwd());
37
38
 
38
39
  if (!scriptsDir || !fs.existsSync(path.join(scriptsDir, "server-setup.sh"))) {
39
- error(
40
- "Deploy scripts directory not found. Set N2_DEPLOY_SCRIPTS_DIR or run from repo root (scripts/deploy must exist)."
41
- );
40
+ error("未找到部署脚本目录(需包含 server-setup.sh)");
41
+ info("请设置 N2_DEPLOY_SCRIPTS_DIR 或在仓库根目录执行(需存在 scripts/deploy)");
42
42
  process.exit(1);
43
43
  }
44
44
 
45
- info("Using scripts directory: " + scriptsDir);
45
+ info(`使用脚本目录: ${scriptsDir}`);
46
46
 
47
47
  const tmpDir = path.join(
48
48
  process.env.TMPDIR || "/tmp",
@@ -59,7 +59,7 @@ export async function runInit(opts: InitOptions): Promise<void> {
59
59
  fs.copyFileSync(deploySh, path.join(tmpDir, "deploy.sh"));
60
60
  fs.chmodSync(path.join(tmpDir, "deploy.sh"), 0o755);
61
61
  } else {
62
- error("deploy.sh not found in scripts directory");
62
+ error("脚本目录中未找到 deploy.sh");
63
63
  process.exit(1);
64
64
  }
65
65
 
@@ -92,7 +92,7 @@ export async function runInit(opts: InitOptions): Promise<void> {
92
92
  }
93
93
  }
94
94
 
95
- info("Running server-setup.sh (may require sudo)...");
95
+ info("正在执行 server-setup.sh(可能需要 sudo)…");
96
96
  await new Promise<void>((resolve, reject) => {
97
97
  const child = spawn("bash", [path.join(tmpDir, "server-setup.sh")], {
98
98
  cwd: tmpDir,
@@ -110,13 +110,13 @@ export async function runInit(opts: InitOptions): Promise<void> {
110
110
  if (logDir && !fs.existsSync(logDir)) {
111
111
  try {
112
112
  fs.mkdirSync(logDir, { recursive: true });
113
- info("Created log directory: " + logDir);
113
+ info(`已创建日志目录: ${logDir}`);
114
114
  } catch {
115
- warn("Could not create log directory: " + logDir);
115
+ warn(`无法创建日志目录: ${logDir}`);
116
116
  }
117
117
  }
118
118
 
119
- info("Server initialization completed.");
119
+ success("服务器初始化完成");
120
120
  } finally {
121
121
  try {
122
122
  fs.rmSync(tmpDir, { recursive: true, force: true });
@@ -5,9 +5,10 @@
5
5
  import { spawn } from "node:child_process";
6
6
  import fs from "node:fs";
7
7
  import path from "node:path";
8
- import { parseDomainsYaml } from "../lib/domains.js";
9
- import { error, info, warn } from "../lib/logger.js";
8
+ import { parseDeployConfig } from "../lib/domains.js";
9
+ import { error, info, success, warn } from "../lib/logger.js";
10
10
  import { generateNginxConf } from "../lib/nginx.js";
11
+ import { resolveConfigPath } from "../lib/paths.js";
11
12
 
12
13
  export interface NginxOptions {
13
14
  config: string;
@@ -18,30 +19,31 @@ export interface NginxOptions {
18
19
  const NGINX_CONF_NAME = "n2.conf";
19
20
 
20
21
  export async function runNginx(opts: NginxOptions): Promise<void> {
21
- const configPath = path.resolve(process.cwd(), opts.config);
22
+ const configPath = resolveConfigPath(opts.config);
22
23
  if (!fs.existsSync(configPath)) {
23
- error(`Config not found: ${configPath}`);
24
+ error(`配置文件不存在: ${configPath}`);
25
+ info("请先执行: n2-deploy config init");
24
26
  process.exit(1);
25
27
  }
26
28
 
27
29
  const yaml = fs.readFileSync(configPath, "utf8");
28
- const config = parseDomainsYaml(yaml);
29
- const nginxConf = generateNginxConf(config);
30
+ const { domainsConfig } = parseDeployConfig(yaml);
31
+ const nginxConf = generateNginxConf(domainsConfig);
30
32
 
31
33
  const outPath = path.resolve(process.cwd(), opts.output);
32
34
  const outDir = path.dirname(outPath);
33
35
  fs.mkdirSync(outDir, { recursive: true });
34
36
  fs.writeFileSync(outPath, nginxConf);
35
- info(`Wrote ${outPath}`);
37
+ info(`已写入: ${outPath}`);
36
38
 
37
39
  if (opts.install) {
38
40
  const destDir = detectNginxConfDir();
39
41
  const destPath = path.join(destDir, NGINX_CONF_NAME);
40
42
  try {
41
43
  fs.copyFileSync(outPath, destPath);
42
- info(`Installed to ${destPath}`);
44
+ info(`已安装到: ${destPath}`);
43
45
  } catch (e) {
44
- error(`Install failed (try sudo): ${e}`);
46
+ error(`安装失败(可尝试 sudo): ${e}`);
45
47
  process.exit(1);
46
48
  }
47
49
  await nginxTestAndReload();
@@ -66,12 +68,10 @@ function nginxTestAndReload(): Promise<void> {
66
68
  const r = spawn("systemctl", ["reload", "nginx"], { stdio: "inherit" });
67
69
  r.on("close", c => {
68
70
  if (c === 0) {
69
- info("Nginx reloaded.");
71
+ success("Nginx 已重载");
70
72
  resolve();
71
73
  } else {
72
- warn(
73
- "systemctl reload nginx failed; try: sudo systemctl reload nginx"
74
- );
74
+ warn("systemctl reload nginx 失败,请尝试: sudo systemctl reload nginx");
75
75
  resolve();
76
76
  }
77
77
  });
@@ -4,6 +4,7 @@
4
4
 
5
5
  import fs from "node:fs";
6
6
  import path from "node:path";
7
+ import consola from "consola";
7
8
  import { getLogFilePath } from "../lib/logger.js";
8
9
 
9
10
  const DEFAULT_SSL_LOG = "/opt/deploy/logs/ssl.log";
@@ -26,7 +27,7 @@ export async function runSslLogs(opts: SslLogsOptions): Promise<void> {
26
27
  const resolved = path.resolve(process.cwd(), logPath);
27
28
 
28
29
  if (!fs.existsSync(resolved)) {
29
- console.error("Log file not found:", resolved);
30
+ consola.error("日志文件不存在:", resolved);
30
31
  process.exit(1);
31
32
  }
32
33
 
@@ -67,7 +68,7 @@ export async function runSslLogs(opts: SslLogsOptions): Promise<void> {
67
68
  opts.action || opts.result ? lastLines.filter(filter) : lastLines;
68
69
 
69
70
  for (const line of filtered) {
70
- console.log(line);
71
+ consola.log(line);
71
72
  }
72
73
 
73
74
  if (opts.follow) {
@@ -6,15 +6,16 @@
6
6
  import { spawn } from "node:child_process";
7
7
  import fs from "node:fs";
8
8
  import path from "node:path";
9
- import { getAllDomains, parseDomainsYaml } from "../lib/domains.js";
9
+ import { getAllDomains, parseDeployConfig } from "../lib/domains.js";
10
10
  import {
11
11
  appendRaw,
12
12
  appendSslLog,
13
13
  error,
14
14
  info,
15
15
  setLogFile,
16
+ success,
16
17
  } from "../lib/logger.js";
17
- import { getScriptsDir } from "../lib/paths.js";
18
+ import { getScriptsDir, resolveConfigPath } from "../lib/paths.js";
18
19
 
19
20
  const DEFAULT_SSL_LOG = "/opt/deploy/logs/ssl.log";
20
21
 
@@ -30,29 +31,38 @@ export interface SslOptions {
30
31
  }
31
32
 
32
33
  export async function runSsl(opts: SslOptions): Promise<void> {
33
- const configPath = path.resolve(process.cwd(), opts.config);
34
+ const configPath = resolveConfigPath(opts.config);
34
35
  if (!fs.existsSync(configPath)) {
35
- error(`Config not found: ${configPath}`);
36
+ error(`配置文件不存在: ${configPath}`);
37
+ info("请先执行: n2-deploy config init");
36
38
  process.exit(1);
37
39
  }
38
40
 
41
+ const yaml = fs.readFileSync(configPath, "utf8");
42
+ const {
43
+ domainsConfig,
44
+ aliyunAccessKeyId: configAliId,
45
+ aliyunAccessKeySecret: configAliSecret,
46
+ } = parseDeployConfig(yaml);
47
+
39
48
  const aliKey =
40
- opts.aliKey ?? process.env.ALIYUN_ACCESS_KEY_ID ?? process.env.Ali_Key;
49
+ opts.aliKey ??
50
+ configAliId ??
51
+ process.env.ALIYUN_ACCESS_KEY_ID ??
52
+ process.env.Ali_Key;
41
53
  const aliSecret =
42
54
  opts.aliSecret ??
55
+ configAliSecret ??
43
56
  process.env.ALIYUN_ACCESS_KEY_SECRET ??
44
57
  process.env.Ali_Secret;
45
58
 
46
59
  if (!aliKey || !aliSecret) {
47
- error(
48
- "Aliyun API credentials required. Set --ali-key/--ali-secret or ALIYUN_ACCESS_KEY_ID/ALIYUN_ACCESS_KEY_SECRET (or Ali_Key/Ali_Secret)."
49
- );
60
+ error("缺少阿里云 API 凭证");
61
+ info("请在配置 (~/.deploy/config.yaml)、--ali-key/--ali-secret 或环境变量 ALIYUN_ACCESS_KEY_ID/ALIYUN_ACCESS_KEY_SECRET 中设置");
50
62
  process.exit(1);
51
63
  }
52
64
 
53
- const yaml = fs.readFileSync(configPath, "utf8");
54
- const config = parseDomainsYaml(yaml);
55
- const domains = getAllDomains(config);
65
+ const domains = getAllDomains(domainsConfig);
56
66
  const domainsTxt = domains.join("\n") + "\n";
57
67
 
58
68
  const logPath = opts.logFile ?? DEFAULT_SSL_LOG;
@@ -60,8 +70,8 @@ export async function runSsl(opts: SslOptions): Promise<void> {
60
70
  const logDir = path.dirname(logPath);
61
71
  fs.mkdirSync(logDir, { recursive: true });
62
72
 
63
- info("SSL log file: " + logPath);
64
- info("Domains: " + domains.join(", "));
73
+ info(`SSL 日志: ${logPath}`);
74
+ info(`域名: ${domains.join(", ") || "(无)"}`);
65
75
 
66
76
  const scriptsDir =
67
77
  opts.scriptsDir ||
@@ -75,7 +85,7 @@ export async function runSsl(opts: SslOptions): Promise<void> {
75
85
  scriptPath = "/opt/ssl/check-and-setup-ssl.sh";
76
86
  domainsFilePath = "/opt/ssl/domains.txt";
77
87
  fs.writeFileSync(domainsFilePath, domainsTxt);
78
- info("Updated /opt/ssl/domains.txt");
88
+ info("已更新 /opt/ssl/domains.txt");
79
89
  } else if (
80
90
  scriptsDir &&
81
91
  fs.existsSync(path.join(scriptsDir, "ssl", "check-and-setup-ssl.sh"))
@@ -85,9 +95,8 @@ export async function runSsl(opts: SslOptions): Promise<void> {
85
95
  fs.writeFileSync(tmpDomains, domainsTxt);
86
96
  domainsFilePath = tmpDomains;
87
97
  } else {
88
- error(
89
- "SSL scripts not found. Run 'n2-deploy init' first or set N2_DEPLOY_SCRIPTS_DIR."
90
- );
98
+ error("未找到 SSL 脚本");
99
+ info("请先执行 n2-deploy init 或设置 N2_DEPLOY_SCRIPTS_DIR");
91
100
  process.exit(1);
92
101
  }
93
102
 
@@ -124,7 +133,7 @@ export async function runSsl(opts: SslOptions): Promise<void> {
124
133
  child.on("close", (code, signal) => {
125
134
  if (code === 0) {
126
135
  appendSslLog("run", "", "ok", "done");
127
- info("SSL certificate setup completed.");
136
+ success("SSL 证书配置完成");
128
137
  resolve();
129
138
  } else {
130
139
  appendSslLog("run", "", "fail", `exit ${code} ${signal ?? ""}`);
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Config file CRUD: load, get, set, delete, save for ~/.deploy/config.yaml.
3
+ * Keys: aliyun_access_key_id, aliyun_access_key_secret, api.backend_port,
4
+ * api.domains, admin.domains, tenant.domains, client.domains
5
+ */
6
+
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+ import type { DeployConfigResult } from "./domains.js";
10
+ import {
11
+ parseDeployConfig,
12
+ serializeDeployConfig,
13
+ } from "./domains.js";
14
+
15
+ export type ConfigKey =
16
+ | "aliyun_access_key_id"
17
+ | "aliyun_access_key_secret"
18
+ | "api.backend_port"
19
+ | "api.domains"
20
+ | "admin.domains"
21
+ | "tenant.domains"
22
+ | "client.domains";
23
+
24
+ const SECTION_KEYS: ConfigKey[] = [
25
+ "api.backend_port",
26
+ "api.domains",
27
+ "admin.domains",
28
+ "tenant.domains",
29
+ "client.domains",
30
+ ];
31
+
32
+ export function isValidConfigKey(key: string): key is ConfigKey {
33
+ return (
34
+ key === "aliyun_access_key_id" ||
35
+ key === "aliyun_access_key_secret" ||
36
+ SECTION_KEYS.includes(key as ConfigKey)
37
+ );
38
+ }
39
+
40
+ export function loadConfig(configPath: string): DeployConfigResult {
41
+ const content = fs.readFileSync(configPath, "utf8");
42
+ return parseDeployConfig(content);
43
+ }
44
+
45
+ export function saveConfig(
46
+ configPath: string,
47
+ data: DeployConfigResult
48
+ ): void {
49
+ const dir = path.dirname(configPath);
50
+ if (dir && dir !== ".") {
51
+ fs.mkdirSync(dir, { recursive: true });
52
+ }
53
+ fs.writeFileSync(configPath, serializeDeployConfig(data), "utf8");
54
+ }
55
+
56
+ export function getConfigValue(
57
+ data: DeployConfigResult,
58
+ key: ConfigKey
59
+ ): string | number | string[] | undefined {
60
+ switch (key) {
61
+ case "aliyun_access_key_id":
62
+ return data.aliyunAccessKeyId;
63
+ case "aliyun_access_key_secret":
64
+ return data.aliyunAccessKeySecret;
65
+ case "api.backend_port":
66
+ return data.domainsConfig.api.backend_port;
67
+ case "api.domains":
68
+ return data.domainsConfig.api.domains;
69
+ case "admin.domains":
70
+ return data.domainsConfig.admin.domains;
71
+ case "tenant.domains":
72
+ return data.domainsConfig.tenant.domains;
73
+ case "client.domains":
74
+ return data.domainsConfig.client.domains;
75
+ default:
76
+ return undefined;
77
+ }
78
+ }
79
+
80
+ export function setConfigValue(
81
+ data: DeployConfigResult,
82
+ key: ConfigKey,
83
+ value: string | number | string[]
84
+ ): void {
85
+ switch (key) {
86
+ case "aliyun_access_key_id":
87
+ data.aliyunAccessKeyId = String(value);
88
+ break;
89
+ case "aliyun_access_key_secret":
90
+ data.aliyunAccessKeySecret = String(value);
91
+ break;
92
+ case "api.backend_port":
93
+ data.domainsConfig.api.backend_port =
94
+ typeof value === "number" ? value : parseInt(String(value), 10);
95
+ break;
96
+ case "api.domains":
97
+ data.domainsConfig.api.domains = Array.isArray(value)
98
+ ? value
99
+ : [String(value)];
100
+ break;
101
+ case "admin.domains":
102
+ data.domainsConfig.admin.domains = Array.isArray(value)
103
+ ? value
104
+ : [String(value)];
105
+ break;
106
+ case "tenant.domains":
107
+ data.domainsConfig.tenant.domains = Array.isArray(value)
108
+ ? value
109
+ : [String(value)];
110
+ break;
111
+ case "client.domains":
112
+ data.domainsConfig.client.domains = Array.isArray(value)
113
+ ? value
114
+ : [String(value)];
115
+ break;
116
+ }
117
+ }
118
+
119
+ export function deleteConfigValue(
120
+ data: DeployConfigResult,
121
+ key: ConfigKey
122
+ ): void {
123
+ switch (key) {
124
+ case "aliyun_access_key_id":
125
+ data.aliyunAccessKeyId = undefined;
126
+ break;
127
+ case "aliyun_access_key_secret":
128
+ data.aliyunAccessKeySecret = undefined;
129
+ break;
130
+ case "api.backend_port":
131
+ data.domainsConfig.api.backend_port = 3000;
132
+ break;
133
+ case "api.domains":
134
+ data.domainsConfig.api.domains = [];
135
+ break;
136
+ case "admin.domains":
137
+ data.domainsConfig.admin.domains = [];
138
+ break;
139
+ case "tenant.domains":
140
+ data.domainsConfig.tenant.domains = [];
141
+ break;
142
+ case "client.domains":
143
+ data.domainsConfig.client.domains = [];
144
+ break;
145
+ }
146
+ }