Преглед изворни кода

fix: surface real errors instead of swallowing with "inkos.json not found"

loadConfig() previously wrapped ALL exceptions (JSON parse errors,
missing API key, zod validation failures, file permission issues) in
a single "inkos.json not found" message. Users could not diagnose the
actual problem.

Now checks file existence separately, then lets each subsequent error
(bad JSON, missing API key, schema validation) propagate with its
real message.

Closes #7, #12, #19, #29
Ma пре 7 месеци
родитељ
комит
4c747e7cfd
1 измењених фајлова са 38 додато и 25 уклоњено
  1. 38 25
      packages/cli/src/utils.ts

+ 38 - 25
packages/cli/src/utils.ts

@@ -1,4 +1,4 @@
-import { readFile } from "node:fs/promises";
+import { readFile, access } from "node:fs/promises";
 import { homedir } from "node:os";
 import { join, resolve } from "node:path";
 import { config as loadEnv } from "dotenv";
@@ -39,35 +39,48 @@ export async function loadConfig(): Promise<ProjectConfig> {
   loadEnv({ path: join(root, ".env"), override: true });
 
   const configPath = join(root, "inkos.json");
-  try {
-    const raw = await readFile(configPath, "utf-8");
-    const config = JSON.parse(raw);
-
-    // .env overrides inkos.json for LLM settings
-    const env = process.env;
-    if (env.INKOS_LLM_PROVIDER) config.llm.provider = env.INKOS_LLM_PROVIDER;
-    if (env.INKOS_LLM_BASE_URL) config.llm.baseUrl = env.INKOS_LLM_BASE_URL;
-    if (env.INKOS_LLM_MODEL) config.llm.model = env.INKOS_LLM_MODEL;
-    if (env.INKOS_LLM_TEMPERATURE) config.llm.temperature = parseFloat(env.INKOS_LLM_TEMPERATURE);
-    if (env.INKOS_LLM_MAX_TOKENS) config.llm.maxTokens = parseInt(env.INKOS_LLM_MAX_TOKENS, 10);
-    if (env.INKOS_LLM_THINKING_BUDGET) config.llm.thinkingBudget = parseInt(env.INKOS_LLM_THINKING_BUDGET, 10);
-    if (env.INKOS_LLM_API_FORMAT) config.llm.apiFormat = env.INKOS_LLM_API_FORMAT;
-
-    // API key ONLY from env — never stored in inkos.json
-    const apiKey = env.INKOS_LLM_API_KEY;
-    if (!apiKey) {
-      throw new Error(
-        "INKOS_LLM_API_KEY not set. Run 'inkos config set-global' or add it to project .env file.",
-      );
-    }
-    config.llm.apiKey = apiKey;
 
-    return ProjectConfigSchema.parse(config);
-  } catch (e) {
+  // Step 1: Check file exists — give a clear message if not
+  try {
+    await access(configPath);
+  } catch {
     throw new Error(
       `inkos.json not found in ${root}.\nMake sure you are inside an InkOS project directory (cd into the project created by 'inkos init').`,
     );
   }
+
+  // Step 2: Read and parse — surface real errors instead of swallowing them
+  const raw = await readFile(configPath, "utf-8");
+
+  let config: Record<string, unknown>;
+  try {
+    config = JSON.parse(raw);
+  } catch {
+    throw new Error(`inkos.json in ${root} is not valid JSON. Check the file for syntax errors.`);
+  }
+
+  // .env overrides inkos.json for LLM settings
+  const env = process.env;
+  const llm = (config.llm ?? {}) as Record<string, unknown>;
+  if (env.INKOS_LLM_PROVIDER) llm.provider = env.INKOS_LLM_PROVIDER;
+  if (env.INKOS_LLM_BASE_URL) llm.baseUrl = env.INKOS_LLM_BASE_URL;
+  if (env.INKOS_LLM_MODEL) llm.model = env.INKOS_LLM_MODEL;
+  if (env.INKOS_LLM_TEMPERATURE) llm.temperature = parseFloat(env.INKOS_LLM_TEMPERATURE);
+  if (env.INKOS_LLM_MAX_TOKENS) llm.maxTokens = parseInt(env.INKOS_LLM_MAX_TOKENS, 10);
+  if (env.INKOS_LLM_THINKING_BUDGET) llm.thinkingBudget = parseInt(env.INKOS_LLM_THINKING_BUDGET, 10);
+  if (env.INKOS_LLM_API_FORMAT) llm.apiFormat = env.INKOS_LLM_API_FORMAT;
+  config.llm = llm;
+
+  // API key ONLY from env — never stored in inkos.json
+  const apiKey = env.INKOS_LLM_API_KEY;
+  if (!apiKey) {
+    throw new Error(
+      "INKOS_LLM_API_KEY not set. Run 'inkos config set-global' or add it to project .env file.",
+    );
+  }
+  llm.apiKey = apiKey;
+
+  return ProjectConfigSchema.parse(config);
 }
 
 export function createClient(config: ProjectConfig) {