swadhinbiswas feat: OpenCodeHub v1.1.0 - Enterprise Features & Storage Adapters · 2/15/2026
719a1cb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import { PipelineRunner } from "./pipeline";
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
import { getDatabase, schema } from "@/db";
import { logger } from "@/lib/logger";
import { eq } from "drizzle-orm";
import path from "path";
import fs from "fs/promises";
import { existsSync } from "fs";
// Initialize Runner (Singleton-ish for this context)
const runner = new PipelineRunner({
workDir: path.join(process.cwd(), "data", "runner", "work"),
artifactsDir: path.join(process.cwd(), "data", "runner", "artifacts"),
cacheDir: path.join(process.cwd(), "data", "runner", "cache")
});
// Ensure dirs exist
async function ensureDirs() {
await fs.mkdir(path.join(process.cwd(), "data", "runner", "work"), { recursive: true });
await fs.mkdir(path.join(process.cwd(), "data", "runner", "artifacts"), { recursive: true });
await fs.mkdir(path.join(process.cwd(), "data", "runner", "cache"), { recursive: true });
}
ensureDirs();
export async function triggerRepoWorkflows(repoId: string, commitSha: string, ref: string, pusherId: string) {
logger.info({ repoId, ref, commitSha }, "Checking workflows");
try {
// 1. Get Repo Disk Path
const db = getDatabase() as NodePgDatabase<typeof schema>;
const repo = await db.query.repositories.findFirst({
where: eq(schema.repositories.id, repoId)
});
if (!repo) return;
// 2. For cloud storage, we need to resolve the repo path to a local directory
// For local storage, diskPath is already local.
const { resolveRepoPath } = await import("./git-storage");
const { getGit } = await import("./git");
let localRepoPath: string;
try {
localRepoPath = await resolveRepoPath(repo.diskPath);
} catch (resolveErr) {
// If we can't resolve the path (e.g., repo doesn't exist yet in storage), skip workflows
logger.debug({ err: resolveErr, diskPath: repo.diskPath }, "Could not resolve repo path for workflows, skipping");
return;
}
const git = getGit(localRepoPath);
// Look for .github/workflows/*.yml in the actual repo content
// List files in .github/workflows
const treeInfo = await git.raw(["ls-tree", "-r", "--name-only", commitSha, ".github/workflows"]);
const workflowFiles = treeInfo.split("\n").filter(f => f.endsWith(".yml") || f.endsWith(".yaml"));
if (workflowFiles.length === 0) {
logger.debug("No workflows found");
return;
}
logger.info({ count: workflowFiles.length, files: workflowFiles }, "Found workflows");
// 3. Process each workflow
for (const file of workflowFiles) {
try {
const content = await git.show([`${commitSha}:${file}`]);
// Parse manually since PipelineRunner expects a file path usually,
// but we can create a temp file or refactor runner.
// Let's write to a temp file for the runner to parse
const tempWorkflowPath = path.join("/tmp", `workflow-${crypto.randomUUID()}.yml`);
await fs.writeFile(tempWorkflowPath, content);
const config = await runner.parseWorkflow(tempWorkflowPath);
await fs.unlink(tempWorkflowPath); // Clean up
// Get changed files by diffing with parent commit
let changedPaths: string[] = [];
try {
// Get parent commit(s)
const parentOutput = await git.raw(["rev-parse", `${commitSha}^`]).catch(() => "");
const parentSha = parentOutput.trim();
if (parentSha) {
// Get diff between parent and current commit
const diffOutput = await git.raw([
"diff", "--name-only", parentSha, commitSha
]);
changedPaths = diffOutput.split("\n").filter(Boolean);
logger.debug({ changedPaths, count: changedPaths.length }, "Changed files in push");
} else {
// Initial commit - all files are new
const treeOutput = await git.raw(["ls-tree", "-r", "--name-only", commitSha]);
changedPaths = treeOutput.split("\n").filter(Boolean);
logger.debug({ changedPaths, count: changedPaths.length }, "Initial commit files");
}
} catch (diffErr) {
logger.warn({ err: diffErr }, "Could not get changed paths, skipping path filter");
}
// Check Trigger with path filtering
const shouldRun = runner.shouldTrigger(config, "push", {
ref: ref,
paths: changedPaths.length > 0 ? changedPaths : undefined,
});
if (shouldRun) {
logger.info({ workflow: config.name }, "Triggering workflow");
// Run it (Fire and forget, or track?)
runner.runWorkflow(config, {
repositoryId: repoId,
repositoryPath: repo.diskPath,
branch: ref.replace("refs/heads/", ""),
commit: commitSha,
triggeredBy: "push",
triggerEvent: "push"
}).catch(err => {
logger.error({ err, workflow: config.name }, "Workflow failed");
});
} else {
logger.debug({ workflow: config.name }, "Skipping workflow (conditions not met)");
}
} catch (e) {
logger.error({ err: e, file }, "Error processing workflow");
}
}
} catch (e) {
// likely dir doesn't exist
// console.log(" No .github/workflows directory or error checking.");
}
}