Skip to content

Commit 9bf33c1

Browse files
committed
Add error tracing
1 parent 261f0a7 commit 9bf33c1

File tree

8 files changed

+209
-54
lines changed

8 files changed

+209
-54
lines changed

src/commands/init.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,11 @@ export async function initCommand() {
8080
try {
8181
mkdirSync(dirname(repoPath), { recursive: true });
8282
} catch (error) {
83-
p.cancel(`Failed to create directory: ${error}`);
83+
const { logError, getErrorLogFile } = require("../utils/trace");
84+
const logFile = logError(error);
85+
p.cancel(
86+
`Failed to create directory: ${error}\n${getErrorLogFile(logFile)}`,
87+
);
8488
return;
8589
}
8690

@@ -204,6 +208,10 @@ Next steps:
204208
• Configs in ${contractHome(repoPath)}/configs will apply to this machine`,
205209
);
206210
} catch (error) {
207-
p.cancel(`Failed to create configuration: ${error}`);
211+
const { logError, getErrorLogFile } = require("../utils/trace");
212+
const logFile = logError(error);
213+
p.cancel(
214+
`Failed to create configuration: ${error}\n${getErrorLogFile(logFile)}`,
215+
);
208216
}
209217
}

src/commands/machine/status.ts

Lines changed: 54 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -29,62 +29,68 @@ export async function machineStatusCommand(options?: { skipIntro?: boolean }) {
2929
return;
3030
}
3131

32-
p.log.info(`Platform: ${getPlatformName()}`);
33-
p.log.info(`Repository: ${contractHome(repoPath)}`);
32+
try {
33+
p.log.info(`Platform: ${getPlatformName()}`);
34+
p.log.info(`Repository: ${contractHome(repoPath)}`);
3435

35-
const packageManager = getPackageManager();
36-
if (packageManager) {
37-
p.log.info(`Package manager: ${packageManager}`);
38-
} else {
39-
p.log.warning("Package manager: not detected");
40-
}
36+
const packageManager = getPackageManager();
37+
if (packageManager) {
38+
p.log.info(`Package manager: ${packageManager}`);
39+
} else {
40+
p.log.warning("Package manager: not detected");
41+
}
4142

42-
console.log("");
43+
console.log("");
4344

44-
if (!existsSync(repoPath)) {
45-
p.log.warning("Repository path not found");
46-
p.outro("Done");
47-
return;
48-
}
45+
if (!existsSync(repoPath)) {
46+
p.log.warning("Repository path not found");
47+
p.outro("Done");
48+
return;
49+
}
4950

50-
const gitStatus = await exec(`git -C "${repoPath}" status --short`);
51-
if (!gitStatus.success) {
52-
p.log.warning("Git: not a repository");
53-
} else if (gitStatus.stdout.length === 0) {
54-
p.log.success("Git: clean");
55-
} else {
56-
p.log.warning("Git: uncommitted changes");
57-
console.log(
58-
gitStatus.stdout
59-
.split("\n")
60-
.map((line) => ` ${line}`)
61-
.join("\n"),
62-
);
63-
}
51+
const gitStatus = await exec(`git -C "${repoPath}" status --short`);
52+
if (!gitStatus.success) {
53+
p.log.warning("Git: not a repository");
54+
} else if (gitStatus.stdout.length === 0) {
55+
p.log.success("Git: clean");
56+
} else {
57+
p.log.warning("Git: uncommitted changes");
58+
console.log(
59+
gitStatus.stdout
60+
.split("\n")
61+
.map((line) => ` ${line}`)
62+
.join("\n"),
63+
);
64+
}
6465

65-
const dotfilesDir = join(getConfigsDir(), "dotfiles");
66-
if (existsSync(dotfilesDir)) {
67-
p.log.info("Dotfiles: present in repo");
68-
} else {
69-
p.log.info("Dotfiles: not found in repo");
70-
}
66+
const dotfilesDir = join(getConfigsDir(), "dotfiles");
67+
if (existsSync(dotfilesDir)) {
68+
p.log.info("Dotfiles: present in repo");
69+
} else {
70+
p.log.info("Dotfiles: not found in repo");
71+
}
7172

72-
console.log("");
73+
console.log("");
7374

74-
if (isMacOS) {
75-
reportDependencyFile("Brewfile", repoPath, ["Brewfile"]);
76-
} else if (isLinux && isArch()) {
77-
reportDependencyFile("packages-arch.txt", repoPath, [
78-
"packages-arch.txt",
79-
"packages.txt",
80-
]);
81-
} else if (isLinux && isDebian()) {
82-
reportDependencyFile("packages-debian.txt", repoPath, [
83-
"packages-debian.txt",
84-
]);
85-
}
75+
if (isMacOS) {
76+
reportDependencyFile("Brewfile", repoPath, ["Brewfile"]);
77+
} else if (isLinux && isArch()) {
78+
reportDependencyFile("packages-arch.txt", repoPath, [
79+
"packages-arch.txt",
80+
"packages.txt",
81+
]);
82+
} else if (isLinux && isDebian()) {
83+
reportDependencyFile("packages-debian.txt", repoPath, [
84+
"packages-debian.txt",
85+
]);
86+
}
8687

87-
p.outro("Done");
88+
p.outro("Done");
89+
} catch (error) {
90+
const { logError, getErrorLogFile } = require("../../utils/trace");
91+
const logFile = logError(error);
92+
p.cancel(`Error: ${error}\n${getErrorLogFile(logFile)}`);
93+
}
8894
}
8995

9096
function reportDependencyFile(

src/commands/new.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,11 @@ export async function newCommand() {
6565
try {
6666
mkdirSync(repoPath, { recursive: true });
6767
} catch (error) {
68-
p.cancel(`Failed to create directory: ${error}`);
68+
const { logError, getErrorLogFile } = require("../utils/trace");
69+
const logFile = logError(error);
70+
p.cancel(
71+
`Failed to create directory: ${error}\n${getErrorLogFile(logFile)}`,
72+
);
6973
return;
7074
}
7175
}
@@ -316,7 +320,9 @@ syncode push
316320
s.stop("Repository structure created");
317321
} catch (error) {
318322
s.stop("Failed to create repository structure");
319-
p.cancel(`Error: ${error}`);
323+
const { logError, getErrorLogFile } = require("../utils/trace");
324+
const logFile = logError(error);
325+
p.cancel(`Error: ${error}\n${getErrorLogFile(logFile)}`);
320326
return;
321327
}
322328

@@ -352,6 +358,10 @@ Next steps:
352358
• Run 'syncode push' to commit and push changes`,
353359
);
354360
} catch (error) {
355-
p.cancel(`Failed to create configuration: ${error}`);
361+
const { logError, getErrorLogFile } = require("../utils/trace");
362+
const logFile = logError(error);
363+
p.cancel(
364+
`Failed to create configuration: ${error}\n${getErrorLogFile(logFile)}`,
365+
);
356366
}
357367
}

src/commands/push.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,9 @@ export async function pushCommand() {
132132
p.outro("Successfully pushed to remote");
133133
} else {
134134
spinner.stop("Push failed");
135+
const { logError, getErrorLogFile } = require("../utils/trace");
136+
const logFile = logError(new Error(result.message), "push");
135137
p.log.error(result.message);
136-
p.cancel("Failed to push to remote");
138+
p.cancel(`Failed to push to remote\n${getErrorLogFile(logFile)}`);
137139
}
138140
}

src/commands/sync.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ export async function syncCommand() {
160160
}
161161
} catch (error) {
162162
const errorMsg = error instanceof Error ? error.message : String(error);
163+
const errorObj =
164+
error instanceof Error ? error : new Error(String(error));
165+
const { logError } = require("../utils/trace");
166+
logError(errorObj, "sync");
163167
s.message(`✗ ${adapter.name}: ${errorMsg}`);
164168
errors.push({ agent: adapter.name, error: errorMsg });
165169
failCount++;

src/commands/unsync.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ export async function unsyncCommand() {
132132
failCount++;
133133
}
134134
} catch (error) {
135+
const errorObj =
136+
error instanceof Error ? error : new Error(String(error));
137+
const { logError } = require("../utils/trace");
138+
logError(errorObj, "unsync");
135139
s.message(`✗ Error unsyncing ${adapter.name}: ${error}`);
136140
failCount++;
137141
}

src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ Quick Start:
166166
}
167167

168168
main().catch((error) => {
169+
const { logError, getErrorLogFile } = require("./utils/trace");
170+
171+
const logFile = logError(error);
169172
console.error("Error:", error.message);
173+
console.error(getErrorLogFile(logFile));
170174
process.exit(1);
171175
});

src/utils/trace.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2+
import { join } from "node:path";
3+
import { getRepoRoot } from "./paths";
4+
5+
export interface TraceData {
6+
timestamp: string;
7+
version: string;
8+
platform: string;
9+
command: string | undefined;
10+
error: {
11+
message: string;
12+
stack?: string;
13+
cause?: unknown;
14+
name?: string;
15+
};
16+
args?: string[];
17+
}
18+
19+
export function logError(
20+
error: Error,
21+
command?: string,
22+
args?: string[],
23+
): string {
24+
const repoRoot = getRepoRoot();
25+
const timestamp = Date.now();
26+
const logDir = join(repoRoot, ".syncode-logs");
27+
28+
// Ensure log directory exists
29+
if (!existsSync(logDir)) {
30+
mkdirSync(logDir, { recursive: true });
31+
}
32+
33+
const logFile = join(logDir, `syncode-trace-${timestamp}.log`);
34+
35+
const traceData: TraceData = {
36+
timestamp: new Date().toISOString(),
37+
version: getVersion(),
38+
platform: `${process.platform} ${process.arch}`,
39+
command,
40+
error: {
41+
message: error.message,
42+
stack: error.stack,
43+
cause: error.cause,
44+
name: error.name,
45+
},
46+
args,
47+
};
48+
49+
const logContent = formatLog(traceData);
50+
writeFileSync(logFile, logContent, "utf-8");
51+
52+
return logFile;
53+
}
54+
55+
export function getErrorLogFile(logFile: string): string {
56+
return `If this issue persists, open an issue at https://github.com/donnes/syncode/issues and paste the full error trace from file ${logFile}`;
57+
}
58+
59+
function getVersion(): string {
60+
try {
61+
const { readFileSync } = require("node:fs");
62+
const { join, dirname } = require("node:path");
63+
const { fileURLToPath } = require("node:url");
64+
const __filename = fileURLToPath(import.meta.url);
65+
const __dirname = dirname(__filename);
66+
const packageJson = JSON.parse(
67+
readFileSync(join(dirname(dirname(__dirname)), "package.json"), "utf-8"),
68+
);
69+
return packageJson.version;
70+
} catch {
71+
return "unknown";
72+
}
73+
}
74+
75+
function formatLog(data: TraceData): string {
76+
const lines: string[] = [];
77+
78+
lines.push("=== Syncode Error Trace ===");
79+
lines.push("");
80+
lines.push(`Timestamp: ${data.timestamp}`);
81+
lines.push(`Version: ${data.version}`);
82+
lines.push(`Platform: ${data.platform}`);
83+
84+
if (data.command) {
85+
lines.push(`Command: ${data.command}`);
86+
}
87+
88+
if (data.args && data.args.length > 0) {
89+
lines.push(`Arguments: ${data.args.join(" ")}`);
90+
}
91+
92+
lines.push("");
93+
lines.push("=== Error Details ===");
94+
lines.push(`Type: ${data.error.name || "Unknown"}`);
95+
lines.push(`Message: ${data.error.message}`);
96+
97+
if (data.error.stack) {
98+
lines.push("");
99+
lines.push("=== Stack Trace ===");
100+
lines.push(data.error.stack);
101+
}
102+
103+
if (data.error.cause) {
104+
lines.push("");
105+
lines.push("=== Cause ===");
106+
lines.push(
107+
typeof data.error.cause === "string"
108+
? data.error.cause
109+
: JSON.stringify(data.error.cause, null, 2),
110+
);
111+
}
112+
113+
lines.push("");
114+
lines.push("=== End of Trace ===");
115+
116+
return lines.join("\n");
117+
}

0 commit comments

Comments
 (0)