@page.su/ai-blog-cli 0.1.0-beta.1 → 0.1.0-beta.11

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 (109) hide show
  1. package/dist/api-client.test.js +1 -1
  2. package/dist/api-client.test.js.map +1 -1
  3. package/dist/cli.js +14 -5
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/comments.js +1 -1
  6. package/dist/commands/comments.js.map +1 -1
  7. package/dist/commands/config.d.ts +12 -0
  8. package/dist/commands/config.d.ts.map +1 -0
  9. package/dist/commands/config.js +128 -0
  10. package/dist/commands/config.js.map +1 -0
  11. package/dist/commands/demo.js +2 -2
  12. package/dist/commands/demo.js.map +1 -1
  13. package/dist/commands/post.d.ts.map +1 -1
  14. package/dist/commands/post.js +30 -3
  15. package/dist/commands/post.js.map +1 -1
  16. package/dist/commands/render-errors.d.ts +4 -0
  17. package/dist/commands/render-errors.d.ts.map +1 -0
  18. package/dist/commands/render-errors.js +22 -0
  19. package/dist/commands/render-errors.js.map +1 -0
  20. package/dist/commands/skills.d.ts.map +1 -1
  21. package/dist/commands/skills.js +67 -19
  22. package/dist/commands/skills.js.map +1 -1
  23. package/dist/commands/syntax.d.ts +4 -0
  24. package/dist/commands/syntax.d.ts.map +1 -0
  25. package/dist/commands/syntax.js +30 -0
  26. package/dist/commands/syntax.js.map +1 -0
  27. package/dist/config.d.ts +84 -8
  28. package/dist/config.d.ts.map +1 -1
  29. package/dist/config.js +196 -29
  30. package/dist/config.js.map +1 -1
  31. package/dist/config.test.js +150 -19
  32. package/dist/config.test.js.map +1 -1
  33. package/package.json +12 -6
  34. package/dist/index.d.ts +0 -3
  35. package/dist/index.d.ts.map +0 -1
  36. package/dist/index.js +0 -100
  37. package/dist/index.js.map +0 -1
  38. package/dist/prompts/index.d.ts +0 -9
  39. package/dist/prompts/index.d.ts.map +0 -1
  40. package/dist/prompts/index.js +0 -178
  41. package/dist/prompts/index.js.map +0 -1
  42. package/dist/resources/syntax-spec.d.ts +0 -23
  43. package/dist/resources/syntax-spec.d.ts.map +0 -1
  44. package/dist/resources/syntax-spec.js +0 -105
  45. package/dist/resources/syntax-spec.js.map +0 -1
  46. package/dist/resources/syntax-spec.test.d.ts +0 -2
  47. package/dist/resources/syntax-spec.test.d.ts.map +0 -1
  48. package/dist/resources/syntax-spec.test.js +0 -47
  49. package/dist/resources/syntax-spec.test.js.map +0 -1
  50. package/dist/tools/build.d.ts +0 -13
  51. package/dist/tools/build.d.ts.map +0 -1
  52. package/dist/tools/build.js +0 -181
  53. package/dist/tools/build.js.map +0 -1
  54. package/dist/tools/code-repos.d.ts +0 -5
  55. package/dist/tools/code-repos.d.ts.map +0 -1
  56. package/dist/tools/code-repos.js +0 -149
  57. package/dist/tools/code-repos.js.map +0 -1
  58. package/dist/tools/comments.d.ts +0 -12
  59. package/dist/tools/comments.d.ts.map +0 -1
  60. package/dist/tools/comments.js +0 -87
  61. package/dist/tools/comments.js.map +0 -1
  62. package/dist/tools/demos.d.ts +0 -5
  63. package/dist/tools/demos.d.ts.map +0 -1
  64. package/dist/tools/demos.js +0 -98
  65. package/dist/tools/demos.js.map +0 -1
  66. package/dist/tools/glossary.d.ts +0 -4
  67. package/dist/tools/glossary.d.ts.map +0 -1
  68. package/dist/tools/glossary.js +0 -139
  69. package/dist/tools/glossary.js.map +0 -1
  70. package/dist/tools/helpers.d.ts +0 -44
  71. package/dist/tools/helpers.d.ts.map +0 -1
  72. package/dist/tools/helpers.js +0 -65
  73. package/dist/tools/helpers.js.map +0 -1
  74. package/dist/tools/knowledge.d.ts +0 -4
  75. package/dist/tools/knowledge.d.ts.map +0 -1
  76. package/dist/tools/knowledge.js +0 -140
  77. package/dist/tools/knowledge.js.map +0 -1
  78. package/dist/tools/math-concepts.d.ts +0 -4
  79. package/dist/tools/math-concepts.d.ts.map +0 -1
  80. package/dist/tools/math-concepts.js +0 -158
  81. package/dist/tools/math-concepts.js.map +0 -1
  82. package/dist/tools/posts.d.ts +0 -35
  83. package/dist/tools/posts.d.ts.map +0 -1
  84. package/dist/tools/posts.js +0 -278
  85. package/dist/tools/posts.js.map +0 -1
  86. package/dist/tools/scout.d.ts +0 -10
  87. package/dist/tools/scout.d.ts.map +0 -1
  88. package/dist/tools/scout.js +0 -111
  89. package/dist/tools/scout.js.map +0 -1
  90. package/dist/tools/series.d.ts +0 -12
  91. package/dist/tools/series.d.ts.map +0 -1
  92. package/dist/tools/series.js +0 -120
  93. package/dist/tools/series.js.map +0 -1
  94. package/dist/tools/syntax-capabilities.d.ts +0 -15
  95. package/dist/tools/syntax-capabilities.d.ts.map +0 -1
  96. package/dist/tools/syntax-capabilities.js +0 -859
  97. package/dist/tools/syntax-capabilities.js.map +0 -1
  98. package/dist/tools/taxonomy.d.ts +0 -4
  99. package/dist/tools/taxonomy.d.ts.map +0 -1
  100. package/dist/tools/taxonomy.js +0 -73
  101. package/dist/tools/taxonomy.js.map +0 -1
  102. package/dist/tools/uploads.d.ts +0 -15
  103. package/dist/tools/uploads.d.ts.map +0 -1
  104. package/dist/tools/uploads.js +0 -56
  105. package/dist/tools/uploads.js.map +0 -1
  106. package/dist/tools/workflows.d.ts +0 -9
  107. package/dist/tools/workflows.d.ts.map +0 -1
  108. package/dist/tools/workflows.js +0 -249
  109. package/dist/tools/workflows.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"syntax.d.ts","sourceRoot":"","sources":["../../src/commands/syntax.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAGlD,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,GAAG,IAAI,CA4B7E"}
@@ -0,0 +1,30 @@
1
+ import { run, isJson } from "./helpers.js";
2
+ export function registerSyntaxCommands(program, api) {
3
+ const syntax = program
4
+ .command("syntax")
5
+ .description("Read advanced Markdown syntax documentation");
6
+ // ── syntax list ──
7
+ // 返回所有章节的 { slug, title } 列表(JSON),供 AI 客户端发现可用章节。
8
+ syntax
9
+ .command("list")
10
+ .description("List all syntax spec sections (slug + title)")
11
+ .option("--json", "Output as JSON")
12
+ .action(async (opts) => {
13
+ await run(() => api.get("/api/cli/syntax-spec"), isJson(opts));
14
+ });
15
+ // ── syntax get <section> ──
16
+ syntax
17
+ .command("get <section>")
18
+ .description("Read a single syntax spec section (full documentation)")
19
+ .option("--json", "Output as JSON")
20
+ .action(async (section, opts) => {
21
+ const text = await api.getText(`/api/cli/syntax-spec/${encodeURIComponent(section)}`);
22
+ if (isJson(opts)) {
23
+ console.log(JSON.stringify({ section, content: text }));
24
+ }
25
+ else {
26
+ console.log(text);
27
+ }
28
+ });
29
+ }
30
+ //# sourceMappingURL=syntax.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"syntax.js","sourceRoot":"","sources":["../../src/commands/syntax.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,MAAM,UAAU,sBAAsB,CAAC,OAAgB,EAAE,GAAc;IACrE,MAAM,MAAM,GAAG,OAAO;SACnB,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,6CAA6C,CAAC,CAAC;IAE9D,oBAAoB;IACpB,mDAAmD;IACnD,MAAM;SACH,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,8CAA8C,CAAC;SAC3D,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEL,6BAA6B;IAC7B,MAAM;SACH,OAAO,CAAC,eAAe,CAAC;SACxB,WAAW,CAAC,wDAAwD,CAAC;SACrE,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;SAClC,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QAC9B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,wBAAwB,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACtF,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YACjB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAC1D,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
package/dist/config.d.ts CHANGED
@@ -1,19 +1,95 @@
1
1
  /**
2
- * ai-blog CLI 的运行时配置。
2
+ * ai-blog CLI 的运行时配置,支持多环境。
3
+ *
4
+ * .aib.env 文件格式:
5
+ * ```
6
+ * # 顶层仅放元数据:默认环境名
7
+ * AIB_DEFAULT_ENV=prod
8
+ *
9
+ * # 每个环境必须独立配置在 [section] 段中
10
+ * [local]
11
+ * AI_BLOG_BASE_URL=http://localhost:3000
12
+ * AI_BLOG_PAT=aib_pat_local
13
+ *
14
+ * [prod]
15
+ * AI_BLOG_BASE_URL=https://blog.example.com
16
+ * AI_BLOG_PAT=aib_pat_prod
17
+ *
18
+ * [staging]
19
+ * AI_BLOG_BASE_URL=https://staging.blog.example.com
20
+ * AI_BLOG_PAT=aib_pat_staging
21
+ * ```
3
22
  *
4
23
  * 配置优先级(从高到低):
5
- * 1. 环境变量(AI_BLOG_BASE_URL / AI_BLOG_PAT / AI_BLOG_REQUEST_TIMEOUT_MS)
6
- * 2. ~/.aib.env 文件(KEY=VALUE 格式,与 .env 相同)
7
- * 3. ./.aib.env 文件(当前目录,适合项目级覆盖)
24
+ * 1. 环境变量(AI_BLOG_BASE_URL / AI_BLOG_PAT 等)
25
+ * 2. 本地 ./.aib.env 的 [envName] 段
26
+ * 3. 全局 ~/.aib.env 的 [envName] 段
8
27
  *
9
- * 任何缺失关键变量的情况都直接 throw,避免起来一个"半残"的 CLI
10
- * 让用户在调用命令时才看到错误,定位成本高。
28
+ * 环境选择优先级:
29
+ * 1. --env <name> CLI flag
30
+ * 2. AIB_DEFAULT_ENV 环境变量
31
+ * 3. .aib.env 顶层 AIB_DEFAULT_ENV
11
32
  */
12
33
  export interface CliConfig {
13
34
  baseUrl: string;
14
35
  pat: string;
15
36
  requestTimeoutMs: number;
16
37
  }
17
- /** 读取并校验环境变量;返回归一化后的配置对象 */
18
- export declare function loadCliConfig(env?: NodeJS.ProcessEnv): CliConfig;
38
+ interface ParsedEnvFile {
39
+ /** 顶层键值对(仅用于 AIB_DEFAULT_ENV 元数据) */
40
+ default: Record<string, string>;
41
+ /** 命名段:{ [sectionName]: { KEY: VALUE } } */
42
+ sections: Record<string, Record<string, string>>;
43
+ }
44
+ export interface EnvInfo {
45
+ name: string;
46
+ baseUrl?: string;
47
+ hasPat: boolean;
48
+ /** 配置来源:global ~/.aib.env / local ./.aib.env / both */
49
+ source: "global" | "local" | "both";
50
+ }
51
+ /**
52
+ * 解析 .env 格式文件内容,支持 [section] 环境段。
53
+ * 忽略空行和 # 注释行,支持 VALUE 中包含 = 号。
54
+ */
55
+ export declare function parseEnvFileWithSections(content: string): ParsedEnvFile;
56
+ export declare function getGlobalEnvPath(): string;
57
+ export declare function getLocalEnvPath(): string;
58
+ /**
59
+ * 从两个解析后的文件 + process.env 合并出最终配置键值对。
60
+ *
61
+ * 环境配置必须全部在 [section] 段中,顶层不承载业务配置。
62
+ * @internal 导出仅供测试使用
63
+ */
64
+ export declare function resolveConfigFromParsed(globalParsed: ParsedEnvFile, localParsed: ParsedEnvFile, processEnv: Record<string, string | undefined>, envName?: string): Record<string, string>;
65
+ /** 读取并校验配置;返回归一化后的 CliConfig */
66
+ export declare function loadCliConfig(opts?: {
67
+ env?: string;
68
+ }): CliConfig;
69
+ /**
70
+ * 从已合并的 env-like 对象构建并校验 CliConfig。
71
+ * @internal 导出仅供测试使用
72
+ */
73
+ export declare function buildCliConfig(env: Record<string, string>): CliConfig;
74
+ /** 列出所有环境及其基本信息 */
75
+ export declare function listEnvironments(): {
76
+ environments: EnvInfo[];
77
+ defaultEnv: string | null;
78
+ };
79
+ /**
80
+ * 查看指定环境的配置详情。
81
+ * 不传 name 时查看默认环境(由 AIB_DEFAULT_ENV 决定)。
82
+ * 返回 null 表示环境不存在;返回 { name: null } 表示未设置默认环境。
83
+ */
84
+ export declare function showEnvironment(name?: string): {
85
+ name: string | null;
86
+ baseUrl?: string;
87
+ hasPat: boolean;
88
+ requestTimeoutMs?: string;
89
+ } | null;
90
+ /** 添加或更新环境配置,写入本地 ./.aib.env */
91
+ export declare function addEnvironment(name: string, baseUrl: string, pat: string): void;
92
+ /** 设置默认环境(写入 AIB_DEFAULT_ENV 到本地 ./.aib.env) */
93
+ export declare function setDefaultEnv(name: string): void;
94
+ export {};
19
95
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAuDD,4BAA4B;AAC5B,wBAAgB,aAAa,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,SAAS,CAqChE"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAOD,UAAU,aAAa;IACrB,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,uDAAuD;IACvD,MAAM,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;CACrC;AAID;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CAuCvE;AAID,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAaD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,aAAa,EAC3B,WAAW,EAAE,aAAa,EAC1B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,EAC9C,OAAO,CAAC,EAAE,MAAM,GACf,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAkCxB;AAUD,gCAAgC;AAChC,wBAAgB,aAAa,CAAC,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAGhE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CA+BrE;AAcD,mBAAmB;AACnB,wBAAgB,gBAAgB,IAAI;IAAE,YAAY,EAAE,OAAO,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CA6BzF;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,IAAI,CAAC,EAAE,MAAM,GACZ;IAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA4B9F;AAED,gCAAgC;AAChC,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAW/E;AAED,gDAAgD;AAChD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAmBhD"}
package/dist/config.js CHANGED
@@ -1,18 +1,30 @@
1
- import { readFileSync } from "node:fs";
1
+ import { readFileSync, writeFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  const DEFAULT_TIMEOUT_MS = 30_000;
5
+ const ENV_FILE_NAME = ".aib.env";
6
+ // ── Parsing ──
5
7
  /**
6
- * 解析 .env 格式文件内容为 key-value 对象。
8
+ * 解析 .env 格式文件内容,支持 [section] 环境段。
7
9
  * 忽略空行和 # 注释行,支持 VALUE 中包含 = 号。
8
10
  */
9
- function parseEnvFile(content) {
10
- const result = {};
11
+ export function parseEnvFileWithSections(content) {
12
+ const result = { default: {}, sections: {} };
13
+ let currentSection = null;
11
14
  for (const line of content.split("\n")) {
12
15
  const trimmed = line.trim();
13
16
  if (!trimmed || trimmed.startsWith("#")) {
14
17
  continue;
15
18
  }
19
+ // [section] 头
20
+ const sectionMatch = trimmed.match(/^\[([^\]]+)\]$/);
21
+ if (sectionMatch) {
22
+ const name = sectionMatch[1].trim();
23
+ currentSection = {};
24
+ result.sections[name] = currentSection;
25
+ continue;
26
+ }
27
+ // KEY=VALUE
16
28
  const eqIndex = trimmed.indexOf("=");
17
29
  if (eqIndex === -1) {
18
30
  continue;
@@ -23,62 +35,217 @@ function parseEnvFile(content) {
23
35
  if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
24
36
  value = value.slice(1, -1);
25
37
  }
26
- result[key] = value;
38
+ if (currentSection) {
39
+ currentSection[key] = value;
40
+ }
41
+ else {
42
+ result.default[key] = value;
43
+ }
27
44
  }
28
45
  return result;
29
46
  }
30
- /**
31
- * 尝试读取 .aib.env 文件并返回解析后的 key-value。
32
- * 文件不存在时返回空对象,不抛错。
33
- */
34
- function loadEnvFile(filePath) {
47
+ // ── File paths ──
48
+ export function getGlobalEnvPath() {
49
+ return join(homedir(), ENV_FILE_NAME);
50
+ }
51
+ export function getLocalEnvPath() {
52
+ return join(process.cwd(), ENV_FILE_NAME);
53
+ }
54
+ function loadParsedFile(filePath) {
35
55
  try {
36
56
  const content = readFileSync(filePath, "utf-8");
37
- return parseEnvFile(content);
57
+ return parseEnvFileWithSections(content);
38
58
  }
39
59
  catch {
40
- return {};
60
+ return { default: {}, sections: {} };
41
61
  }
42
62
  }
63
+ // ── Config resolution ──
43
64
  /**
44
- * 合并配置源:环境变量优先于 .env 文件。
45
- * 本地 ./.aib.env 优先于全局 ~/.aib.env。
65
+ * 从两个解析后的文件 + process.env 合并出最终配置键值对。
66
+ *
67
+ * 环境配置必须全部在 [section] 段中,顶层不承载业务配置。
68
+ * @internal 导出仅供测试使用
46
69
  */
47
- function resolveEnv() {
48
- const globalEnv = loadEnvFile(join(homedir(), ".aib.env"));
49
- const localEnv = loadEnvFile(join(process.cwd(), ".aib.env"));
50
- // 优先级:process.env > local .aib.env > global ~/.aib.env
51
- return { ...globalEnv, ...localEnv, ...process.env };
70
+ export function resolveConfigFromParsed(globalParsed, localParsed, processEnv, envName) {
71
+ // 1. 顶层仅用于 AIB_DEFAULT_ENV 元数据
72
+ const mergedDefault = { ...globalParsed.default, ...localParsed.default };
73
+ // 2. 确定环境名
74
+ const resolvedEnvName = envName ?? processEnv.AIB_DEFAULT_ENV ?? mergedDefault.AIB_DEFAULT_ENV ?? null;
75
+ if (!resolvedEnvName) {
76
+ const available = listEnvNames(globalParsed, localParsed);
77
+ throw new Error("No environment selected. Use --env <name>, set AIB_DEFAULT_ENV, or run 'aib config use <name>'.\n" +
78
+ (available.length > 0
79
+ ? `Available: ${available.join(", ")}`
80
+ : "No environments defined. Use 'aib config add <name>' to create one."));
81
+ }
82
+ // 3. 只从段读取配置(不继承顶层)
83
+ const globalSection = globalParsed.sections[resolvedEnvName];
84
+ const localSection = localParsed.sections[resolvedEnvName];
85
+ if (!globalSection && !localSection) {
86
+ const available = listEnvNames(globalParsed, localParsed);
87
+ throw new Error(`Environment "${resolvedEnvName}" not found in .aib.env.\n` +
88
+ (available.length > 0
89
+ ? `Available: ${available.join(", ")}`
90
+ : "No environments defined. Use 'aib config add <name>' to create one."));
91
+ }
92
+ const sectionConfig = { ...globalSection, ...localSection };
93
+ // 4. 段 + process.env
94
+ return { ...sectionConfig, ...processEnv };
95
+ }
96
+ function resolveConfig(envName) {
97
+ const globalParsed = loadParsedFile(getGlobalEnvPath());
98
+ const localParsed = loadParsedFile(getLocalEnvPath());
99
+ return resolveConfigFromParsed(globalParsed, localParsed, process.env, envName);
52
100
  }
53
- /** 读取并校验环境变量;返回归一化后的配置对象 */
54
- export function loadCliConfig(env) {
55
- const resolvedEnv = env ?? resolveEnv();
56
- const baseUrl = (resolvedEnv.AI_BLOG_BASE_URL ?? "").trim();
101
+ // ── Config loading ──
102
+ /** 读取并校验配置;返回归一化后的 CliConfig */
103
+ export function loadCliConfig(opts) {
104
+ const resolvedEnv = resolveConfig(opts?.env);
105
+ return buildCliConfig(resolvedEnv);
106
+ }
107
+ /**
108
+ * 从已合并的 env-like 对象构建并校验 CliConfig。
109
+ * @internal 导出仅供测试使用
110
+ */
111
+ export function buildCliConfig(env) {
112
+ const baseUrl = (env.AI_BLOG_BASE_URL ?? "").trim();
57
113
  if (!baseUrl) {
58
114
  throw new Error("AI_BLOG_BASE_URL is required.\n" +
59
- "Set it via environment variable or in ~/.aib.env:\n" +
115
+ "Set it via environment variable, --env flag, or in ~/.aib.env:\n" +
60
116
  ' AI_BLOG_BASE_URL=https://your-blog.example.com');
61
117
  }
62
118
  // 去掉末尾的 `/`,避免拼接 URL 时出现 `//` 双斜杠
63
119
  const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
64
- const pat = (resolvedEnv.AI_BLOG_PAT ?? "").trim();
120
+ const pat = (env.AI_BLOG_PAT ?? "").trim();
65
121
  if (!pat) {
66
122
  throw new Error("AI_BLOG_PAT is required.\n" +
67
123
  "Create a token at /admin/settings/tokens, then set it:\n" +
68
124
  " export AI_BLOG_PAT=aib_pat_xxx # or put it in ~/.aib.env");
69
125
  }
70
126
  let requestTimeoutMs = DEFAULT_TIMEOUT_MS;
71
- const rawTimeout = resolvedEnv.AI_BLOG_REQUEST_TIMEOUT_MS;
127
+ const rawTimeout = env.AI_BLOG_REQUEST_TIMEOUT_MS;
72
128
  if (rawTimeout) {
73
129
  const parsed = Number.parseInt(rawTimeout, 10);
74
130
  if (Number.isFinite(parsed) && parsed > 0) {
75
131
  requestTimeoutMs = parsed;
76
132
  }
77
133
  }
134
+ return { baseUrl: normalizedBaseUrl, pat, requestTimeoutMs };
135
+ }
136
+ // ── Config management helpers (for `aib config` command) ──
137
+ function listEnvNames(...parsed) {
138
+ const names = new Set();
139
+ for (const p of parsed) {
140
+ for (const name of Object.keys(p.sections)) {
141
+ names.add(name);
142
+ }
143
+ }
144
+ return [...names];
145
+ }
146
+ /** 列出所有环境及其基本信息 */
147
+ export function listEnvironments() {
148
+ const globalParsed = loadParsedFile(getGlobalEnvPath());
149
+ const localParsed = loadParsedFile(getLocalEnvPath());
150
+ const mergedDefault = { ...globalParsed.default, ...localParsed.default };
151
+ const defaultEnv = process.env.AIB_DEFAULT_ENV ?? mergedDefault.AIB_DEFAULT_ENV ?? null;
152
+ const names = listEnvNames(globalParsed, localParsed);
153
+ const environments = names.map((name) => {
154
+ const globalSection = globalParsed.sections[name] ?? {};
155
+ const localSection = localParsed.sections[name] ?? {};
156
+ const merged = { ...globalSection, ...localSection };
157
+ const source = globalParsed.sections[name] && localParsed.sections[name]
158
+ ? "both"
159
+ : localParsed.sections[name]
160
+ ? "local"
161
+ : "global";
162
+ return {
163
+ name,
164
+ baseUrl: merged.AI_BLOG_BASE_URL,
165
+ hasPat: !!merged.AI_BLOG_PAT,
166
+ source,
167
+ };
168
+ });
169
+ return { environments, defaultEnv };
170
+ }
171
+ /**
172
+ * 查看指定环境的配置详情。
173
+ * 不传 name 时查看默认环境(由 AIB_DEFAULT_ENV 决定)。
174
+ * 返回 null 表示环境不存在;返回 { name: null } 表示未设置默认环境。
175
+ */
176
+ export function showEnvironment(name) {
177
+ const globalParsed = loadParsedFile(getGlobalEnvPath());
178
+ const localParsed = loadParsedFile(getLocalEnvPath());
179
+ let envName = name;
180
+ if (!envName) {
181
+ const mergedDefault = { ...globalParsed.default, ...localParsed.default };
182
+ envName = process.env.AIB_DEFAULT_ENV ?? mergedDefault.AIB_DEFAULT_ENV ?? null;
183
+ }
184
+ if (!envName) {
185
+ return { name: null, hasPat: false };
186
+ }
187
+ const globalSection = globalParsed.sections[envName] ?? {};
188
+ const localSection = localParsed.sections[envName] ?? {};
189
+ if (!globalParsed.sections[envName] && !localParsed.sections[envName]) {
190
+ return null;
191
+ }
192
+ const merged = { ...globalSection, ...localSection };
78
193
  return {
79
- baseUrl: normalizedBaseUrl,
80
- pat,
81
- requestTimeoutMs,
194
+ name: envName,
195
+ baseUrl: merged.AI_BLOG_BASE_URL,
196
+ hasPat: !!merged.AI_BLOG_PAT,
197
+ requestTimeoutMs: merged.AI_BLOG_REQUEST_TIMEOUT_MS,
82
198
  };
83
199
  }
200
+ /** 添加或更新环境配置,写入本地 ./.aib.env */
201
+ export function addEnvironment(name, baseUrl, pat) {
202
+ const localPath = getLocalEnvPath();
203
+ const parsed = loadParsedFile(localPath);
204
+ if (!parsed.sections[name]) {
205
+ parsed.sections[name] = {};
206
+ }
207
+ parsed.sections[name].AI_BLOG_BASE_URL = baseUrl;
208
+ parsed.sections[name].AI_BLOG_PAT = pat;
209
+ writeFileSync(localPath, serializeEnvFile(parsed), { mode: 0o600 });
210
+ }
211
+ /** 设置默认环境(写入 AIB_DEFAULT_ENV 到本地 ./.aib.env) */
212
+ export function setDefaultEnv(name) {
213
+ const localPath = getLocalEnvPath();
214
+ const parsed = loadParsedFile(localPath);
215
+ // 验证环境存在
216
+ const globalParsed = loadParsedFile(getGlobalEnvPath());
217
+ if (!parsed.sections[name] && !globalParsed.sections[name]) {
218
+ const available = listEnvNames(parsed, globalParsed);
219
+ throw new Error(`Environment "${name}" not found.\n` +
220
+ (available.length > 0
221
+ ? `Available: ${available.join(", ")}\n`
222
+ : "No environments defined.\n") +
223
+ `Add it first: aib config add ${name} --base-url <url> --pat <token>`);
224
+ }
225
+ parsed.default.AIB_DEFAULT_ENV = name;
226
+ writeFileSync(localPath, serializeEnvFile(parsed), { mode: 0o600 });
227
+ }
228
+ // ── Serialization ──
229
+ function serializeEnvFile(parsed) {
230
+ const lines = [];
231
+ // 顶层
232
+ for (const [key, value] of Object.entries(parsed.default)) {
233
+ lines.push(`${key}=${formatValue(value)}`);
234
+ }
235
+ // 命名段
236
+ for (const [sectionName, values] of Object.entries(parsed.sections)) {
237
+ lines.push("");
238
+ lines.push(`[${sectionName}]`);
239
+ for (const [key, value] of Object.entries(values)) {
240
+ lines.push(`${key}=${formatValue(value)}`);
241
+ }
242
+ }
243
+ return lines.join("\n") + "\n";
244
+ }
245
+ function formatValue(value) {
246
+ if (value.includes(" ") || value.includes("#")) {
247
+ return `"${value}"`;
248
+ }
249
+ return value;
250
+ }
84
251
  //# sourceMappingURL=config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAyCjC,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAClC,MAAM,aAAa,GAAG,UAAU,CAAC;AAmBjC,gBAAgB;AAEhB;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAe;IACtD,MAAM,MAAM,GAAkB,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC5D,IAAI,cAAc,GAAkC,IAAI,CAAC;IAEzD,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACxC,SAAS;QACX,CAAC;QAED,cAAc;QACd,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;QACrD,IAAI,YAAY,EAAE,CAAC;YACjB,MAAM,IAAI,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACpC,cAAc,GAAG,EAAE,CAAC;YACpB,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC;YACvC,SAAS;QACX,CAAC;QAED,YAAY;QACZ,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,OAAO,KAAK,CAAC,CAAC,EAAE,CAAC;YACnB,SAAS;QACX,CAAC;QACD,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7C,IAAI,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC9C,SAAS;QACT,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACrG,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7B,CAAC;QAED,IAAI,cAAc,EAAE,CAAC;YACnB,cAAc,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QAC9B,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QAC9B,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,mBAAmB;AAEnB,MAAM,UAAU,gBAAgB;IAC9B,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,aAAa,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,CAAC,CAAC;AAC5C,CAAC;AAED,SAAS,cAAc,CAAC,QAAgB;IACtC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAChD,OAAO,wBAAwB,CAAC,OAAO,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IACvC,CAAC;AACH,CAAC;AAED,0BAA0B;AAE1B;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CACrC,YAA2B,EAC3B,WAA0B,EAC1B,UAA8C,EAC9C,OAAgB;IAEhB,+BAA+B;IAC/B,MAAM,aAAa,GAAG,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,GAAG,WAAW,CAAC,OAAO,EAAE,CAAC;IAE1E,WAAW;IACX,MAAM,eAAe,GACnB,OAAO,IAAI,UAAU,CAAC,eAAe,IAAI,aAAa,CAAC,eAAe,IAAI,IAAI,CAAC;IAEjF,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,MAAM,SAAS,GAAG,YAAY,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;QAC1D,MAAM,IAAI,KAAK,CACb,mGAAmG;YACjG,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC;gBACnB,CAAC,CAAC,cAAc,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;gBACtC,CAAC,CAAC,qEAAqE,CAAC,CAC7E,CAAC;IACJ,CAAC;IAED,oBAAoB;IACpB,MAAM,aAAa,GAAG,YAAY,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;IAC7D,MAAM,YAAY,GAAG,WAAW,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;IAC3D,IAAI,CAAC,aAAa,IAAI,CAAC,YAAY,EAAE,CAAC;QACpC,MAAM,SAAS,GAAG,YAAY,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;QAC1D,MAAM,IAAI,KAAK,CACb,gBAAgB,eAAe,4BAA4B;YACzD,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC;gBACnB,CAAC,CAAC,cAAc,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;gBACtC,CAAC,CAAC,qEAAqE,CAAC,CAC7E,CAAC;IACJ,CAAC;IACD,MAAM,aAAa,GAAG,EAAE,GAAG,aAAa,EAAE,GAAG,YAAY,EAAE,CAAC;IAE5D,qBAAqB;IACrB,OAAO,EAAE,GAAG,aAAa,EAAE,GAAG,UAAU,EAA4B,CAAC;AACvE,CAAC;AAED,SAAS,aAAa,CAAC,OAAgB;IACrC,MAAM,YAAY,GAAG,cAAc,CAAC,gBAAgB,EAAE,CAAC,CAAC;IACxD,MAAM,WAAW,GAAG,cAAc,CAAC,eAAe,EAAE,CAAC,CAAC;IACtD,OAAO,uBAAuB,CAAC,YAAY,EAAE,WAAW,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AAClF,CAAC;AAED,uBAAuB;AAEvB,gCAAgC;AAChC,MAAM,UAAU,aAAa,CAAC,IAAuB;IACnD,MAAM,WAAW,GAAG,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC7C,OAAO,cAAc,CAAC,WAAW,CAAC,CAAC;AACrC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,GAA2B;IACxD,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACpD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,iCAAiC;YAC/B,kEAAkE;YAClE,kDAAkD,CACrD,CAAC;IACJ,CAAC;IACD,kCAAkC;IAClC,MAAM,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAEtD,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3C,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,4BAA4B;YAC1B,0DAA0D;YAC1D,6DAA6D,CAChE,CAAC;IACJ,CAAC;IAED,IAAI,gBAAgB,GAAG,kBAAkB,CAAC;IAC1C,MAAM,UAAU,GAAG,GAAG,CAAC,0BAA0B,CAAC;IAClD,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QAC/C,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1C,gBAAgB,GAAG,MAAM,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,GAAG,EAAE,gBAAgB,EAAE,CAAC;AAC/D,CAAC;AAED,6DAA6D;AAE7D,SAAS,YAAY,CAAC,GAAG,MAAuB;IAC9C,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC3C,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC;AACpB,CAAC;AAED,mBAAmB;AACnB,MAAM,UAAU,gBAAgB;IAC9B,MAAM,YAAY,GAAG,cAAc,CAAC,gBAAgB,EAAE,CAAC,CAAC;IACxD,MAAM,WAAW,GAAG,cAAc,CAAC,eAAe,EAAE,CAAC,CAAC;IAEtD,MAAM,aAAa,GAAG,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,GAAG,WAAW,CAAC,OAAO,EAAE,CAAC;IAC1E,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,aAAa,CAAC,eAAe,IAAI,IAAI,CAAC;IAExF,MAAM,KAAK,GAAG,YAAY,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;IACtD,MAAM,YAAY,GAAc,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACjD,MAAM,aAAa,GAAG,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACxD,MAAM,YAAY,GAAG,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACtD,MAAM,MAAM,GAAG,EAAE,GAAG,aAAa,EAAE,GAAG,YAAY,EAAE,CAAC;QAErD,MAAM,MAAM,GACV,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC;YACvD,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAC1B,CAAC,CAAC,OAAO;gBACT,CAAC,CAAC,QAAQ,CAAC;QAEjB,OAAO;YACL,IAAI;YACJ,OAAO,EAAE,MAAM,CAAC,gBAAgB;YAChC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,WAAW;YAC5B,MAAM;SACP,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC;AACtC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAa;IAEb,MAAM,YAAY,GAAG,cAAc,CAAC,gBAAgB,EAAE,CAAC,CAAC;IACxD,MAAM,WAAW,GAAG,cAAc,CAAC,eAAe,EAAE,CAAC,CAAC;IAEtD,IAAI,OAAO,GAAG,IAAI,CAAC;IACnB,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,aAAa,GAAG,EAAE,GAAG,YAAY,CAAC,OAAO,EAAE,GAAG,WAAW,CAAC,OAAO,EAAE,CAAC;QAC1E,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,aAAa,CAAC,eAAe,IAAI,IAAI,CAAC;IACjF,CAAC;IAED,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACvC,CAAC;IAED,MAAM,aAAa,GAAG,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IAC3D,MAAM,YAAY,GAAG,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IACzD,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACtE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,MAAM,GAAG,EAAE,GAAG,aAAa,EAAE,GAAG,YAAY,EAAE,CAAC;IAErD,OAAO;QACL,IAAI,EAAE,OAAO;QACb,OAAO,EAAE,MAAM,CAAC,gBAAgB;QAChC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,WAAW;QAC5B,gBAAgB,EAAE,MAAM,CAAC,0BAA0B;KACpD,CAAC;AACJ,CAAC;AAED,gCAAgC;AAChC,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,OAAe,EAAE,GAAW;IACvE,MAAM,SAAS,GAAG,eAAe,EAAE,CAAC;IACpC,MAAM,MAAM,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC;IAEzC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3B,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,CAAC;IACD,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAE,CAAC,gBAAgB,GAAG,OAAO,CAAC;IAClD,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAE,CAAC,WAAW,GAAG,GAAG,CAAC;IAEzC,aAAa,CAAC,SAAS,EAAE,gBAAgB,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACtE,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,SAAS,GAAG,eAAe,EAAE,CAAC;IACpC,MAAM,MAAM,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC;IAEzC,SAAS;IACT,MAAM,YAAY,GAAG,cAAc,CAAC,gBAAgB,EAAE,CAAC,CAAC;IACxD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3D,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QACrD,MAAM,IAAI,KAAK,CACb,gBAAgB,IAAI,gBAAgB;YAClC,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC;gBACnB,CAAC,CAAC,cAAc,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;gBACxC,CAAC,CAAC,4BAA4B,CAAC;YACjC,gCAAgC,IAAI,iCAAiC,CACxE,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IACtC,aAAa,CAAC,SAAS,EAAE,gBAAgB,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACtE,CAAC;AAED,sBAAsB;AAEtB,SAAS,gBAAgB,CAAC,MAAqB;IAC7C,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK;IACL,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1D,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM;IACN,KAAK,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,IAAI,WAAW,GAAG,CAAC,CAAC;QAC/B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAClD,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AACjC,CAAC;AAED,SAAS,WAAW,CAAC,KAAa;IAChC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,OAAO,IAAI,KAAK,GAAG,CAAC;IACtB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -1,40 +1,34 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { loadCliConfig } from "./config.js";
3
- /**
4
- * 验证 loadCliConfig 的边界行为:
5
- * - 缺关键变量直接抛
6
- * - URL 末尾斜杠会被归一化
7
- * - 默认超时存在
8
- * - AI_BLOG_REQUEST_TIMEOUT_MS 合法时被采纳
9
- *
10
- * 注:NodeJS.ProcessEnv 在 @types/node 里被声明为含 NODE_ENV 等必填字段的接口,
11
- * 但 loadCliConfig 只读取自己关心的几个字段。这里通过 unknown 中转,
12
- * 让 TypeScript 接受我们构造的"局部 env 对象"。
13
- */
2
+ import { buildCliConfig, parseEnvFileWithSections, resolveConfigFromParsed, } from "./config.js";
3
+ // ── Helpers ──
14
4
  function makeEnv(env) {
15
5
  return env;
16
6
  }
17
- describe("loadCliConfig", () => {
7
+ function emptyParsed() {
8
+ return { default: {}, sections: {} };
9
+ }
10
+ // ── buildCliConfig: 校验与归一化 ──
11
+ describe("buildCliConfig", () => {
18
12
  it("缺少 AI_BLOG_BASE_URL 时直接抛错", () => {
19
- expect(() => loadCliConfig(makeEnv({ AI_BLOG_PAT: "x" }))).toThrow(/AI_BLOG_BASE_URL/);
13
+ expect(() => buildCliConfig(makeEnv({ AI_BLOG_PAT: "x" }))).toThrow(/AI_BLOG_BASE_URL/);
20
14
  });
21
15
  it("缺少 AI_BLOG_PAT 时直接抛错", () => {
22
- expect(() => loadCliConfig(makeEnv({ AI_BLOG_BASE_URL: "https://example.com" }))).toThrow(/AI_BLOG_PAT/);
16
+ expect(() => buildCliConfig(makeEnv({ AI_BLOG_BASE_URL: "https://example.com" }))).toThrow(/AI_BLOG_PAT/);
23
17
  });
24
18
  it("BASE_URL 末尾的斜杠被归一化", () => {
25
- const config = loadCliConfig(makeEnv({
19
+ const config = buildCliConfig(makeEnv({
26
20
  AI_BLOG_BASE_URL: "https://example.com///",
27
21
  AI_BLOG_PAT: "aib_pat_xxx",
28
22
  }));
29
23
  expect(config.baseUrl).toBe("https://example.com");
30
24
  });
31
25
  it("默认超时为 30s,合法配置覆盖默认值", () => {
32
- const defaults = loadCliConfig(makeEnv({
26
+ const defaults = buildCliConfig(makeEnv({
33
27
  AI_BLOG_BASE_URL: "https://example.com",
34
28
  AI_BLOG_PAT: "aib_pat_xxx",
35
29
  }));
36
30
  expect(defaults.requestTimeoutMs).toBe(30_000);
37
- const overridden = loadCliConfig(makeEnv({
31
+ const overridden = buildCliConfig(makeEnv({
38
32
  AI_BLOG_BASE_URL: "https://example.com",
39
33
  AI_BLOG_PAT: "aib_pat_xxx",
40
34
  AI_BLOG_REQUEST_TIMEOUT_MS: "5000",
@@ -42,7 +36,7 @@ describe("loadCliConfig", () => {
42
36
  expect(overridden.requestTimeoutMs).toBe(5_000);
43
37
  });
44
38
  it("非法超时值被忽略并回落默认", () => {
45
- const config = loadCliConfig(makeEnv({
39
+ const config = buildCliConfig(makeEnv({
46
40
  AI_BLOG_BASE_URL: "https://example.com",
47
41
  AI_BLOG_PAT: "aib_pat_xxx",
48
42
  AI_BLOG_REQUEST_TIMEOUT_MS: "not-a-number",
@@ -50,4 +44,141 @@ describe("loadCliConfig", () => {
50
44
  expect(config.requestTimeoutMs).toBe(30_000);
51
45
  });
52
46
  });
47
+ // ── parseEnvFileWithSections: 段解析 ──
48
+ describe("parseEnvFileWithSections", () => {
49
+ it("正确解析 [section] 段", () => {
50
+ const content = `AIB_DEFAULT_ENV=prod
51
+
52
+ [local]
53
+ AI_BLOG_BASE_URL=http://localhost:3000
54
+ AI_BLOG_PAT=local_pat
55
+
56
+ [prod]
57
+ AI_BLOG_BASE_URL=https://blog.example.com
58
+ AI_BLOG_PAT=prod_pat
59
+
60
+ [staging]
61
+ AI_BLOG_BASE_URL=https://staging.example.com
62
+ AI_BLOG_PAT=staging_pat
63
+ `;
64
+ const parsed = parseEnvFileWithSections(content);
65
+ expect(parsed.default.AIB_DEFAULT_ENV).toBe("prod");
66
+ expect(parsed.sections.local.AI_BLOG_BASE_URL).toBe("http://localhost:3000");
67
+ expect(parsed.sections.prod.AI_BLOG_BASE_URL).toBe("https://blog.example.com");
68
+ expect(parsed.sections.staging.AI_BLOG_PAT).toBe("staging_pat");
69
+ });
70
+ it("忽略空行和注释", () => {
71
+ const content = `# comment
72
+ AIB_DEFAULT_ENV=prod
73
+
74
+ # another comment
75
+ [prod]
76
+ AI_BLOG_BASE_URL=https://prod.example.com
77
+ `;
78
+ const parsed = parseEnvFileWithSections(content);
79
+ expect(parsed.default.AIB_DEFAULT_ENV).toBe("prod");
80
+ expect(parsed.sections.prod.AI_BLOG_BASE_URL).toBe("https://prod.example.com");
81
+ });
82
+ it("VALUE 中包含 = 号", () => {
83
+ const content = "[prod]\nAI_BLOG_PAT=aib_pat_key=value\n";
84
+ const parsed = parseEnvFileWithSections(content);
85
+ expect(parsed.sections.prod.AI_BLOG_PAT).toBe("aib_pat_key=value");
86
+ });
87
+ it("引号包裹的值被正确去除", () => {
88
+ const content = '[prod]\nAI_BLOG_BASE_URL="https://example.com"\nAI_BLOG_PAT=\'single\'\n';
89
+ const parsed = parseEnvFileWithSections(content);
90
+ expect(parsed.sections.prod.AI_BLOG_BASE_URL).toBe("https://example.com");
91
+ expect(parsed.sections.prod.AI_BLOG_PAT).toBe("single");
92
+ });
93
+ });
94
+ // ── resolveConfigFromParsed: 多环境合并 ──
95
+ describe("resolveConfigFromParsed", () => {
96
+ it("无 --env 且无 AIB_DEFAULT_ENV 时抛错", () => {
97
+ expect(() => resolveConfigFromParsed(emptyParsed(), emptyParsed(), {})).toThrow(/No environment selected/);
98
+ });
99
+ it("--env 选择对应段", () => {
100
+ const globalParsed = {
101
+ default: {},
102
+ sections: {
103
+ prod: { AI_BLOG_BASE_URL: "https://blog.example.com", AI_BLOG_PAT: "prod_pat" },
104
+ },
105
+ };
106
+ const result = resolveConfigFromParsed(globalParsed, emptyParsed(), {}, "prod");
107
+ expect(result.AI_BLOG_BASE_URL).toBe("https://blog.example.com");
108
+ expect(result.AI_BLOG_PAT).toBe("prod_pat");
109
+ });
110
+ it("AIB_DEFAULT_ENV 在顶层配置时自动选择", () => {
111
+ const globalParsed = {
112
+ default: { AIB_DEFAULT_ENV: "prod" },
113
+ sections: {
114
+ prod: { AI_BLOG_BASE_URL: "https://blog.example.com", AI_BLOG_PAT: "prod_pat" },
115
+ },
116
+ };
117
+ const result = resolveConfigFromParsed(globalParsed, emptyParsed(), {});
118
+ expect(result.AI_BLOG_BASE_URL).toBe("https://blog.example.com");
119
+ expect(result.AI_BLOG_PAT).toBe("prod_pat");
120
+ });
121
+ it("--env 优先于 AIB_DEFAULT_ENV", () => {
122
+ const globalParsed = {
123
+ default: { AIB_DEFAULT_ENV: "prod" },
124
+ sections: {
125
+ prod: { AI_BLOG_BASE_URL: "https://prod.example.com", AI_BLOG_PAT: "prod_pat" },
126
+ staging: { AI_BLOG_BASE_URL: "https://staging.example.com", AI_BLOG_PAT: "staging_pat" },
127
+ },
128
+ };
129
+ const result = resolveConfigFromParsed(globalParsed, emptyParsed(), {}, "staging");
130
+ expect(result.AI_BLOG_BASE_URL).toBe("https://staging.example.com");
131
+ });
132
+ it("process.env.AIB_DEFAULT_ENV 优先于文件中的 AIB_DEFAULT_ENV", () => {
133
+ const globalParsed = {
134
+ default: { AIB_DEFAULT_ENV: "prod" },
135
+ sections: {
136
+ prod: { AI_BLOG_BASE_URL: "https://prod.example.com", AI_BLOG_PAT: "prod_pat" },
137
+ staging: { AI_BLOG_BASE_URL: "https://staging.example.com", AI_BLOG_PAT: "staging_pat" },
138
+ },
139
+ };
140
+ const result = resolveConfigFromParsed(globalParsed, emptyParsed(), { AIB_DEFAULT_ENV: "staging" });
141
+ expect(result.AI_BLOG_BASE_URL).toBe("https://staging.example.com");
142
+ });
143
+ it("process.env 最终覆盖段值", () => {
144
+ const globalParsed = {
145
+ default: {},
146
+ sections: {
147
+ prod: { AI_BLOG_BASE_URL: "https://blog.example.com", AI_BLOG_PAT: "prod_pat" },
148
+ },
149
+ };
150
+ const result = resolveConfigFromParsed(globalParsed, emptyParsed(), {
151
+ AI_BLOG_PAT: "env_override_pat",
152
+ }, "prod");
153
+ expect(result.AI_BLOG_BASE_URL).toBe("https://blog.example.com");
154
+ expect(result.AI_BLOG_PAT).toBe("env_override_pat");
155
+ });
156
+ it("local 段补充 global 段缺失的键", () => {
157
+ const globalParsed = {
158
+ default: {},
159
+ sections: {
160
+ prod: { AI_BLOG_PAT: "g_pat" },
161
+ },
162
+ };
163
+ const localParsed = {
164
+ default: {},
165
+ sections: {
166
+ prod: { AI_BLOG_BASE_URL: "https://local.example.com" },
167
+ },
168
+ };
169
+ const result = resolveConfigFromParsed(globalParsed, localParsed, {}, "prod");
170
+ expect(result.AI_BLOG_BASE_URL).toBe("https://local.example.com");
171
+ expect(result.AI_BLOG_PAT).toBe("g_pat");
172
+ });
173
+ it("--env 指定不存在的环境时抛错并列出可用环境", () => {
174
+ const globalParsed = {
175
+ default: {},
176
+ sections: {
177
+ prod: { AI_BLOG_BASE_URL: "https://prod.example.com", AI_BLOG_PAT: "prod_pat" },
178
+ staging: { AI_BLOG_BASE_URL: "https://staging.example.com", AI_BLOG_PAT: "staging_pat" },
179
+ },
180
+ };
181
+ expect(() => resolveConfigFromParsed(globalParsed, emptyParsed(), {}, "nonexistent")).toThrow(/Environment "nonexistent" not found[\s\S]*prod[\s\S]*staging/);
182
+ });
183
+ });
53
184
  //# sourceMappingURL=config.test.js.map