Skip to content

Commit 6b6da53

Browse files
committed
feat(runtime): add debug logging for resource embedding and package resolution
Add comprehensive debug logging throughout the runtime modules to help diagnose issues with resource embedding and package root resolution. This includes logging for: - Embedded file extraction process - Package root resolution from env vars/filesystem - Template loading operations - Environment variable handling The logging helps track the execution flow and identify potential issues in production environments.
1 parent fab7dc5 commit 6b6da53

4 files changed

Lines changed: 131 additions & 20 deletions

File tree

src/runtime/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
#!/usr/bin/env bun
2+
// Import resource manifest to embed files in compiled binary
3+
import '../shared/runtime/resource-manifest.js';
4+
25
export { runCodemachineCli } from './cli-setup.js';
36
export * from './services/index.js';

src/shared/runtime/embed.ts

Lines changed: 90 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@ import { embeddedFiles } from 'bun';
22
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
33
import { dirname, join } from 'node:path';
44
import os from 'node:os';
5+
import { appDebug } from '../logging/logger.js';
56

67
const METADATA_FILE = '.embedded.json';
78

9+
// Debug helper - logs to app-debug.log via the logger
10+
function embedDebug(msg: string, ...args: unknown[]): void {
11+
appDebug(`[Embed] ${msg}`, ...args);
12+
}
13+
814
type EmbeddedMetadata = {
915
version: string;
1016
generatedAt: string;
@@ -17,11 +23,20 @@ function getResourcesBaseDir(): string {
1723
return join(os.homedir(), '.codemachine', 'resources');
1824
}
1925

20-
function setEnvVars(targetDir: string): void {
26+
function setEnvVars(targetDir: string, force = false): void {
2127
const pkgJsonPath = join(targetDir, 'package.json');
22-
process.env.CODEMACHINE_PACKAGE_ROOT ??= targetDir;
23-
process.env.CODEMACHINE_INSTALL_DIR ??= targetDir;
24-
process.env.CODEMACHINE_PACKAGE_JSON ??= pkgJsonPath;
28+
if (force) {
29+
// For compiled binaries: force overwrite any stale env vars from dev sessions
30+
embedDebug('Force setting env vars to: %s', targetDir);
31+
process.env.CODEMACHINE_PACKAGE_ROOT = targetDir;
32+
process.env.CODEMACHINE_INSTALL_DIR = targetDir;
33+
process.env.CODEMACHINE_PACKAGE_JSON = pkgJsonPath;
34+
} else {
35+
// For dev mode: only set if not already set
36+
process.env.CODEMACHINE_PACKAGE_ROOT ??= targetDir;
37+
process.env.CODEMACHINE_INSTALL_DIR ??= targetDir;
38+
process.env.CODEMACHINE_PACKAGE_JSON ??= pkgJsonPath;
39+
}
2540
}
2641

2742
/**
@@ -33,40 +48,76 @@ function setEnvVars(targetDir: string): void {
3348
* Returns the package root directory if resources are installed, or undefined in dev mode.
3449
*/
3550
export async function ensure(): Promise<string | undefined> {
36-
if (process.env.DEBUG_EMBED) {
37-
console.error('[DEBUG] embeddedFiles.length:', embeddedFiles.length);
38-
}
51+
embedDebug('ensure() called');
52+
embedDebug('embeddedFiles.length: %s', embeddedFiles.length);
53+
embedDebug('process.argv[1]: %s', process.argv[1]);
3954

4055
// Bun's embedded files are Blobs; cast to include name/text helpers for type safety
4156
const files = embeddedFiles as Array<Blob & { name: string; text: () => Promise<string> }>;
4257

58+
// Log first few embedded file names for debugging
59+
if (files.length > 0) {
60+
embedDebug('First 5 embedded files: %s', files.slice(0, 5).map(f => f.name).join(', '));
61+
}
62+
4363
// Dev mode: no embedded files, return early (reads repo directly via package root resolver)
4464
if (embeddedFiles.length === 0) {
65+
embedDebug('No embedded files detected (dev mode), returning undefined');
4566
return undefined;
4667
}
4768

69+
embedDebug('Compiled binary detected (has %s embedded files)', embeddedFiles.length);
70+
71+
// Log env vars for debugging
72+
embedDebug('CODEMACHINE_PACKAGE_ROOT: %s', process.env.CODEMACHINE_PACKAGE_ROOT ?? '(not set)');
73+
embedDebug('CODEMACHINE_INSTALL_DIR: %s', process.env.CODEMACHINE_INSTALL_DIR ?? '(not set)');
74+
embedDebug('CODEMACHINE_PACKAGE_JSON: %s', process.env.CODEMACHINE_PACKAGE_JSON ?? '(not set)');
75+
4876
// Compiled binary: check for existing installation via env vars
77+
// IMPORTANT: For compiled binaries, we should NOT use env vars that might be left over
78+
// from dev mode. Only check the resources directory.
4979
const existingRoot =
5080
process.env.CODEMACHINE_PACKAGE_ROOT ??
5181
process.env.CODEMACHINE_INSTALL_DIR ??
5282
(process.env.CODEMACHINE_PACKAGE_JSON && dirname(process.env.CODEMACHINE_PACKAGE_JSON));
5383

54-
if (existingRoot && existsSync(existingRoot)) {
55-
return existingRoot;
84+
embedDebug('existingRoot resolved to: %s', existingRoot ?? '(none)');
85+
86+
if (existingRoot) {
87+
const exists = existsSync(existingRoot);
88+
embedDebug('existingRoot exists: %s', exists);
89+
90+
// Check if this looks like a valid resources directory (has .embedded.json)
91+
// or if it's just a stale env var pointing to dev directory
92+
const metadataInExisting = join(existingRoot, METADATA_FILE);
93+
const hasMetadata = existsSync(metadataInExisting);
94+
embedDebug('existingRoot has .embedded.json: %s', hasMetadata);
95+
96+
if (exists && hasMetadata) {
97+
embedDebug('Using existing installation at: %s', existingRoot);
98+
return existingRoot;
99+
} else if (exists && !hasMetadata) {
100+
embedDebug('existingRoot exists but has no .embedded.json - likely stale dev env var, continuing to extract');
101+
}
56102
}
57103

58104
// Get version from embedded package.json (may have ./ prefix)
59105
const pkgFile = files.find((f) =>
60106
f.name === 'package.json' || f.name === './package.json'
61107
);
108+
embedDebug('Found embedded package.json: %s', pkgFile ? pkgFile.name : '(not found)');
109+
62110
let version = process.env.CODEMACHINE_VERSION;
111+
embedDebug('CODEMACHINE_VERSION env var: %s', version ?? '(not set)');
63112

64113
if (!version && pkgFile) {
65114
try {
66115
const pkgText = await pkgFile.text();
67116
const pkg = JSON.parse(pkgText);
68117
version = pkg.version;
69-
} catch {
118+
embedDebug('Version from embedded package.json: %s', version);
119+
} catch (err) {
120+
embedDebug('Failed to parse embedded package.json: %s', err);
70121
version = 'unknown';
71122
}
72123
}
@@ -75,36 +126,57 @@ export async function ensure(): Promise<string | undefined> {
75126
const targetRoot = join(baseDir, version || 'unknown');
76127
const metadataPath = join(targetRoot, METADATA_FILE);
77128

129+
embedDebug('Resources base dir: %s', baseDir);
130+
embedDebug('Target root: %s', targetRoot);
131+
embedDebug('Metadata path: %s', metadataPath);
132+
78133
// Already installed?
79-
if (existsSync(metadataPath)) {
80-
setEnvVars(targetRoot);
134+
const metadataExists = existsSync(metadataPath);
135+
embedDebug('Metadata file exists: %s', metadataExists);
136+
137+
if (metadataExists) {
138+
embedDebug('Resources already installed, setting env vars and returning');
139+
setEnvVars(targetRoot, true); // force=true for compiled binary
81140
return targetRoot;
82141
}
83142

84143
// First-time install: extract all resources
144+
embedDebug('First-time install: extracting %s files to %s', files.length, targetRoot);
145+
146+
let extractedCount = 0;
147+
let errorCount = 0;
148+
85149
for (const file of files) {
86150
// Remove leading ./ if present
87151
const cleanName = file.name.startsWith('./') ? file.name.slice(2) : file.name;
88152
const destPath = join(targetRoot, cleanName);
89153

90-
if (process.env.DEBUG_EMBED) {
91-
console.error(`[DEBUG] Extracting ${cleanName}`);
154+
try {
155+
mkdirSync(dirname(destPath), { recursive: true });
156+
await Bun.write(destPath, file);
157+
extractedCount++;
158+
embedDebug('Extracted: %s', cleanName);
159+
} catch (err) {
160+
errorCount++;
161+
embedDebug('Failed to extract %s: %s', cleanName, err);
92162
}
93-
94-
mkdirSync(dirname(destPath), { recursive: true });
95-
await Bun.write(destPath, file);
96163
}
97164

165+
embedDebug('Extraction complete: %s succeeded, %s failed', extractedCount, errorCount);
166+
98167
// Write metadata
99168
const metadata: EmbeddedMetadata = {
100169
version: version || 'unknown',
101170
generatedAt: new Date().toISOString(),
102171
source: 'embedded',
103172
};
104173

174+
embedDebug('Writing metadata file');
105175
mkdirSync(targetRoot, { recursive: true });
106176
writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
177+
embedDebug('Metadata written successfully');
107178

108-
setEnvVars(targetRoot);
179+
setEnvVars(targetRoot, true); // force=true for compiled binary
180+
embedDebug('Env vars set (forced), returning targetRoot: %s', targetRoot);
109181
return targetRoot;
110182
}

src/shared/runtime/root.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { existsSync, readFileSync } from 'node:fs';
22
import { dirname, join } from 'node:path';
33
import { fileURLToPath } from 'node:url';
4+
import { appDebug } from '../logging/logger.js';
45

56
let cachedPackageRoot: string | null = null;
67

@@ -29,11 +30,18 @@ function validatePackageRoot(candidate: string | undefined): string | undefined
2930
* 2. Traverse filesystem from the given module URL
3031
*/
3132
export function resolvePackageRoot(moduleUrl: string, errorContext: string): string {
33+
appDebug('[Root] resolvePackageRoot called from: %s', errorContext);
34+
appDebug('[Root] moduleUrl: %s', moduleUrl);
35+
3236
if (cachedPackageRoot) {
37+
appDebug('[Root] Using cached packageRoot: %s', cachedPackageRoot);
3338
return cachedPackageRoot;
3439
}
3540

3641
// 1. Try environment variables first
42+
appDebug('[Root] CODEMACHINE_PACKAGE_ROOT: %s', process.env.CODEMACHINE_PACKAGE_ROOT ?? '(not set)');
43+
appDebug('[Root] CODEMACHINE_INSTALL_DIR: %s', process.env.CODEMACHINE_INSTALL_DIR ?? '(not set)');
44+
3745
const envCandidates = [
3846
process.env.CODEMACHINE_PACKAGE_ROOT,
3947
process.env.CODEMACHINE_INSTALL_DIR,
@@ -42,18 +50,21 @@ export function resolvePackageRoot(moduleUrl: string, errorContext: string): str
4250
for (const candidate of envCandidates) {
4351
const validated = validatePackageRoot(candidate);
4452
if (validated) {
53+
appDebug('[Root] Resolved from env var: %s', validated);
4554
cachedPackageRoot = validated;
4655
return validated;
4756
}
4857
}
4958

5059
// 2. Fallback to filesystem traversal
5160
let currentDir = dirname(fileURLToPath(moduleUrl));
61+
appDebug('[Root] Env vars not valid, traversing filesystem from: %s', currentDir);
5262
const maxDepth = 20;
5363

5464
for (let depth = 0; depth < maxDepth; depth++) {
5565
const validated = validatePackageRoot(currentDir);
5666
if (validated) {
67+
appDebug('[Root] Resolved from filesystem traversal: %s (depth=%s)', validated, depth);
5768
cachedPackageRoot = validated;
5869
// Set env var for other modules (like MCP setup) that check it directly
5970
process.env.CODEMACHINE_PACKAGE_ROOT ??= validated;
@@ -65,6 +76,7 @@ export function resolvePackageRoot(moduleUrl: string, errorContext: string): str
6576
currentDir = parent;
6677
}
6778

79+
appDebug('[Root] Failed to resolve package root');
6880
throw new Error(
6981
`Unable to locate package root from ${errorContext}. ` +
7082
`Searched from ${dirname(fileURLToPath(moduleUrl))}`

src/workflows/templates/loader.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import type { WorkflowTemplate } from './types.js';
55
import { validateWorkflowTemplate } from './validator.js';
66
import { ensureTemplateGlobals } from './globals.js';
77
import { resolvePackageRoot } from '../../shared/runtime/root.js';
8+
import { appDebug } from '../../shared/logging/logger.js';
89

910
// Package root resolution
1011
export const packageRoot = resolvePackageRoot(import.meta.url, 'workflow templates loader');
12+
appDebug('[TemplateLoader] packageRoot resolved to: %s', packageRoot);
1113

1214
// Module loading
1315
export async function loadWorkflowModule(modPath: string): Promise<unknown> {
@@ -32,17 +34,29 @@ export async function loadWorkflowModule(modPath: string): Promise<unknown> {
3234

3335
// Template loading
3436
export async function loadTemplate(cwd: string, templatePath: string): Promise<WorkflowTemplate> {
37+
appDebug('[TemplateLoader] loadTemplate called');
38+
appDebug('[TemplateLoader] templatePath input: %s', templatePath);
39+
appDebug('[TemplateLoader] packageRoot: %s', packageRoot);
40+
appDebug('[TemplateLoader] isAbsolute: %s', path.isAbsolute(templatePath));
41+
3542
const resolvedPath = path.isAbsolute(templatePath)
3643
? templatePath
3744
: path.resolve(packageRoot, templatePath);
3845

46+
appDebug('[TemplateLoader] resolvedPath: %s', resolvedPath);
47+
3948
try {
49+
appDebug('[TemplateLoader] Loading module from: %s', resolvedPath);
4050
const tpl = (await loadWorkflowModule(resolvedPath)) as unknown;
4151
const result = validateWorkflowTemplate(tpl);
42-
if (result.valid) return tpl as WorkflowTemplate;
52+
if (result.valid) {
53+
appDebug('[TemplateLoader] Template loaded successfully from: %s', resolvedPath);
54+
return tpl as WorkflowTemplate;
55+
}
4356
const rel = path.relative(cwd, resolvedPath);
4457
throw new Error(`Template validation failed for ${rel}: ${result.errors.join('; ')}`);
4558
} catch (e) {
59+
appDebug('[TemplateLoader] Failed to load template: %s', e instanceof Error ? e.message : String(e));
4660
if (e instanceof Error && e.message.includes('Template validation failed')) {
4761
throw e;
4862
}
@@ -52,17 +66,27 @@ export async function loadTemplate(cwd: string, templatePath: string): Promise<W
5266
}
5367

5468
export async function loadTemplateWithPath(cwd: string, templatePath: string): Promise<{ template: WorkflowTemplate; resolvedPath: string }> {
69+
appDebug('[TemplateLoader] loadTemplateWithPath called');
70+
appDebug('[TemplateLoader] templatePath input: %s', templatePath);
71+
appDebug('[TemplateLoader] packageRoot: %s', packageRoot);
72+
5573
const resolvedPath = path.isAbsolute(templatePath)
5674
? templatePath
5775
: path.resolve(packageRoot, templatePath);
5876

77+
appDebug('[TemplateLoader] resolvedPath: %s', resolvedPath);
78+
5979
try {
6080
const tpl = (await loadWorkflowModule(resolvedPath)) as unknown;
6181
const result = validateWorkflowTemplate(tpl);
62-
if (result.valid) return { template: tpl as WorkflowTemplate, resolvedPath };
82+
if (result.valid) {
83+
appDebug('[TemplateLoader] Template loaded successfully from: %s', resolvedPath);
84+
return { template: tpl as WorkflowTemplate, resolvedPath };
85+
}
6386
const rel = path.relative(cwd, resolvedPath);
6487
throw new Error(`Template validation failed for ${rel}: ${result.errors.join('; ')}`);
6588
} catch (e) {
89+
appDebug('[TemplateLoader] Failed to load template: %s', e instanceof Error ? e.message : String(e));
6690
if (e instanceof Error && e.message.includes('Template validation failed')) {
6791
throw e;
6892
}

0 commit comments

Comments
 (0)