threadlines 0.1.17 → 0.1.18
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.
- package/dist/commands/check.js +25 -104
- package/dist/git/diff.js +84 -33
- package/dist/utils/context.js +145 -0
- package/dist/utils/environment.js +36 -0
- package/dist/utils/git-diff-executor.js +56 -0
- package/dist/utils/metadata.js +94 -0
- package/package.json +1 -1
package/dist/commands/check.js
CHANGED
|
@@ -38,38 +38,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.checkCommand = checkCommand;
|
|
40
40
|
const experts_1 = require("../validators/experts");
|
|
41
|
-
const diff_1 = require("../git/diff");
|
|
42
41
|
const file_1 = require("../git/file");
|
|
43
42
|
const repo_1 = require("../git/repo");
|
|
44
|
-
const ci_detection_1 = require("../utils/ci-detection");
|
|
45
43
|
const client_1 = require("../api/client");
|
|
46
44
|
const config_1 = require("../utils/config");
|
|
45
|
+
const environment_1 = require("../utils/environment");
|
|
46
|
+
const context_1 = require("../utils/context");
|
|
47
|
+
const metadata_1 = require("../utils/metadata");
|
|
48
|
+
const git_diff_executor_1 = require("../utils/git-diff-executor");
|
|
47
49
|
const fs = __importStar(require("fs"));
|
|
48
50
|
const path = __importStar(require("path"));
|
|
49
51
|
const chalk_1 = __importDefault(require("chalk"));
|
|
50
|
-
/**
|
|
51
|
-
* Detect the environment where the check is being run.
|
|
52
|
-
*
|
|
53
|
-
* CI Environment Detection:
|
|
54
|
-
* - Vercel: VERCEL=1
|
|
55
|
-
* - GitHub Actions: GITHUB_ACTIONS=1
|
|
56
|
-
* - GitLab CI: GITLAB_CI=1 or (CI=1 + CI_COMMIT_SHA)
|
|
57
|
-
* - Local: None of the above
|
|
58
|
-
*
|
|
59
|
-
* Available CI Environment Variables:
|
|
60
|
-
* - GitHub Actions: GITHUB_REPOSITORY, GITHUB_REF_NAME, GITHUB_SHA, GITHUB_BASE_REF, GITHUB_HEAD_REF
|
|
61
|
-
* - Vercel: VERCEL_GIT_REPO_OWNER, VERCEL_GIT_REPO_SLUG, VERCEL_GIT_COMMIT_REF, VERCEL_GIT_COMMIT_SHA
|
|
62
|
-
* - GitLab CI: CI_COMMIT_REF_NAME, CI_COMMIT_SHA, CI_MERGE_REQUEST_IID, CI_MERGE_REQUEST_TITLE
|
|
63
|
-
*/
|
|
64
|
-
function detectEnvironment() {
|
|
65
|
-
if (process.env.VERCEL)
|
|
66
|
-
return 'vercel';
|
|
67
|
-
if (process.env.GITHUB_ACTIONS)
|
|
68
|
-
return 'github';
|
|
69
|
-
if (process.env.GITLAB_CI || (process.env.CI && process.env.CI_COMMIT_SHA))
|
|
70
|
-
return 'gitlab';
|
|
71
|
-
return 'local';
|
|
72
|
-
}
|
|
73
52
|
async function checkCommand(options) {
|
|
74
53
|
const repoRoot = process.cwd();
|
|
75
54
|
console.log(chalk_1.default.blue('🔍 Threadline: Checking code against your threadlines...\n'));
|
|
@@ -109,12 +88,10 @@ async function checkCommand(options) {
|
|
|
109
88
|
console.log(chalk_1.default.gray(' Run `npx threadlines init` to create your first threadline.'));
|
|
110
89
|
process.exit(0);
|
|
111
90
|
}
|
|
112
|
-
// 2.
|
|
91
|
+
// 2. Detect environment and context
|
|
92
|
+
const environment = (0, environment_1.detectEnvironment)();
|
|
93
|
+
let context;
|
|
113
94
|
let gitDiff;
|
|
114
|
-
let reviewContext = { type: 'local' };
|
|
115
|
-
let commitSha = undefined;
|
|
116
|
-
let commitMessage = undefined;
|
|
117
|
-
let prTitle = undefined;
|
|
118
95
|
// Validate mutually exclusive flags
|
|
119
96
|
const explicitFlags = [options.branch, options.commit, options.file, options.folder, options.files].filter(Boolean);
|
|
120
97
|
if (explicitFlags.length > 1) {
|
|
@@ -126,91 +103,37 @@ async function checkCommand(options) {
|
|
|
126
103
|
if (options.file) {
|
|
127
104
|
console.log(chalk_1.default.gray(`📝 Reading file: ${options.file}...`));
|
|
128
105
|
gitDiff = await (0, file_1.getFileContent)(repoRoot, options.file);
|
|
129
|
-
|
|
106
|
+
context = { type: 'local' }; // File context doesn't need git context
|
|
130
107
|
}
|
|
131
108
|
else if (options.folder) {
|
|
132
109
|
console.log(chalk_1.default.gray(`📝 Reading folder: ${options.folder}...`));
|
|
133
110
|
gitDiff = await (0, file_1.getFolderContent)(repoRoot, options.folder);
|
|
134
|
-
|
|
111
|
+
context = { type: 'local' };
|
|
135
112
|
}
|
|
136
113
|
else if (options.files && options.files.length > 0) {
|
|
137
114
|
console.log(chalk_1.default.gray(`📝 Reading ${options.files.length} file(s)...`));
|
|
138
115
|
gitDiff = await (0, file_1.getMultipleFilesContent)(repoRoot, options.files);
|
|
139
|
-
|
|
116
|
+
context = { type: 'local' };
|
|
140
117
|
}
|
|
141
118
|
else if (options.branch) {
|
|
142
119
|
console.log(chalk_1.default.gray(`📝 Collecting git changes for branch: ${options.branch}...`));
|
|
143
|
-
|
|
144
|
-
|
|
120
|
+
context = { type: 'branch', branchName: options.branch };
|
|
121
|
+
gitDiff = await (0, git_diff_executor_1.getDiffForContext)(context, repoRoot, environment);
|
|
145
122
|
}
|
|
146
123
|
else if (options.commit) {
|
|
147
124
|
console.log(chalk_1.default.gray(`📝 Collecting git changes for commit: ${options.commit}...`));
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
commitSha = options.commit;
|
|
151
|
-
// Fetch commit message (reliable when we have SHA)
|
|
152
|
-
const message = await (0, diff_1.getCommitMessage)(repoRoot, options.commit);
|
|
153
|
-
if (message) {
|
|
154
|
-
commitMessage = message;
|
|
155
|
-
}
|
|
125
|
+
context = { type: 'commit', commitSha: options.commit };
|
|
126
|
+
gitDiff = await (0, git_diff_executor_1.getDiffForContext)(context, repoRoot, environment);
|
|
156
127
|
}
|
|
157
128
|
else {
|
|
158
|
-
// Auto-detect
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
console.log(chalk_1.default.gray(`📝 Collecting git changes for ${autoTarget.type.toUpperCase()}: ${autoTarget.value}...`));
|
|
164
|
-
gitDiff = await (0, diff_1.getPRMRDiff)(repoRoot, autoTarget.sourceBranch, autoTarget.targetBranch);
|
|
165
|
-
reviewContext = { type: autoTarget.type, value: autoTarget.value };
|
|
166
|
-
// Use PR title from GitLab CI (reliable env var)
|
|
167
|
-
if (autoTarget.prTitle) {
|
|
168
|
-
prTitle = autoTarget.prTitle;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
else if (autoTarget.type === 'branch') {
|
|
172
|
-
// Branch: use branch vs base
|
|
173
|
-
console.log(chalk_1.default.gray(`📝 Collecting git changes for branch: ${autoTarget.value}...`));
|
|
174
|
-
gitDiff = await (0, diff_1.getBranchDiff)(repoRoot, autoTarget.value);
|
|
175
|
-
reviewContext = { type: 'branch', value: autoTarget.value };
|
|
176
|
-
// Capture commit SHA from CI env vars if available
|
|
177
|
-
if (process.env.GITHUB_SHA) {
|
|
178
|
-
commitSha = process.env.GITHUB_SHA;
|
|
179
|
-
const message = await (0, diff_1.getCommitMessage)(repoRoot, process.env.GITHUB_SHA);
|
|
180
|
-
if (message)
|
|
181
|
-
commitMessage = message;
|
|
182
|
-
}
|
|
183
|
-
else if (process.env.VERCEL_GIT_COMMIT_SHA) {
|
|
184
|
-
commitSha = process.env.VERCEL_GIT_COMMIT_SHA;
|
|
185
|
-
const message = await (0, diff_1.getCommitMessage)(repoRoot, process.env.VERCEL_GIT_COMMIT_SHA);
|
|
186
|
-
if (message)
|
|
187
|
-
commitMessage = message;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
else if (autoTarget.type === 'commit') {
|
|
191
|
-
// Commit: use single commit
|
|
192
|
-
console.log(chalk_1.default.gray(`📝 Collecting git changes for commit: ${autoTarget.value}...`));
|
|
193
|
-
gitDiff = await (0, diff_1.getCommitDiff)(repoRoot, autoTarget.value);
|
|
194
|
-
reviewContext = { type: 'commit', value: autoTarget.value };
|
|
195
|
-
commitSha = autoTarget.value;
|
|
196
|
-
// Fetch commit message (reliable when we have SHA)
|
|
197
|
-
const message = await (0, diff_1.getCommitMessage)(repoRoot, autoTarget.value);
|
|
198
|
-
if (message) {
|
|
199
|
-
commitMessage = message;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
else {
|
|
203
|
-
// Fallback: local development
|
|
204
|
-
console.log(chalk_1.default.gray('📝 Collecting git changes...'));
|
|
205
|
-
gitDiff = await (0, diff_1.getGitDiff)(repoRoot);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
else {
|
|
209
|
-
// Local development: use staged/unstaged changes
|
|
210
|
-
console.log(chalk_1.default.gray('📝 Collecting git changes...'));
|
|
211
|
-
gitDiff = await (0, diff_1.getGitDiff)(repoRoot);
|
|
212
|
-
}
|
|
129
|
+
// Auto-detect context based on environment
|
|
130
|
+
context = (0, context_1.detectContext)(environment);
|
|
131
|
+
const contextDesc = (0, git_diff_executor_1.getContextDescription)(context);
|
|
132
|
+
console.log(chalk_1.default.gray(`📝 Collecting git changes for ${contextDesc}...`));
|
|
133
|
+
gitDiff = await (0, git_diff_executor_1.getDiffForContext)(context, repoRoot, environment);
|
|
213
134
|
}
|
|
135
|
+
// 3. Collect metadata (commit SHA, commit message, PR title)
|
|
136
|
+
const metadata = await (0, metadata_1.collectMetadata)(context, environment, repoRoot);
|
|
214
137
|
if (gitDiff.changedFiles.length === 0) {
|
|
215
138
|
console.log(chalk_1.default.yellow('⚠️ No changes detected. Make some code changes and try again.'));
|
|
216
139
|
process.exit(0);
|
|
@@ -254,9 +177,7 @@ async function checkCommand(options) {
|
|
|
254
177
|
const apiUrl = options.apiUrl ||
|
|
255
178
|
process.env.THREADLINE_API_URL ||
|
|
256
179
|
'https://devthreadline.com';
|
|
257
|
-
// 6.
|
|
258
|
-
const environment = detectEnvironment();
|
|
259
|
-
// 7. Call review API
|
|
180
|
+
// 6. Call review API
|
|
260
181
|
console.log(chalk_1.default.gray('🤖 Running threadline checks...'));
|
|
261
182
|
const client = new client_1.ReviewAPIClient(apiUrl);
|
|
262
183
|
const response = await client.review({
|
|
@@ -267,9 +188,9 @@ async function checkCommand(options) {
|
|
|
267
188
|
account,
|
|
268
189
|
repoName: repoName || undefined,
|
|
269
190
|
branchName: branchName || undefined,
|
|
270
|
-
commitSha: commitSha,
|
|
271
|
-
commitMessage: commitMessage,
|
|
272
|
-
prTitle: prTitle,
|
|
191
|
+
commitSha: metadata.commitSha,
|
|
192
|
+
commitMessage: metadata.commitMessage,
|
|
193
|
+
prTitle: metadata.prTitle,
|
|
273
194
|
environment: environment
|
|
274
195
|
});
|
|
275
196
|
// 7. Display results (with filtering if --full not specified)
|
package/dist/git/diff.js
CHANGED
|
@@ -71,20 +71,19 @@ async function getBranchDiff(repoRoot, branchName, baseBranch) {
|
|
|
71
71
|
// For main/master branch, compare against previous commit (HEAD~1)
|
|
72
72
|
// This checks what changed in the most recent commit
|
|
73
73
|
try {
|
|
74
|
-
const previousCommit = await git.revparse(['HEAD~1'])
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
};
|
|
84
|
-
}
|
|
74
|
+
const previousCommit = await git.revparse(['HEAD~1']);
|
|
75
|
+
// Use commit-based diff instead
|
|
76
|
+
const diff = await git.diff([`${previousCommit}..HEAD`, '-U200']);
|
|
77
|
+
const diffSummary = await git.diffSummary([`${previousCommit}..HEAD`]);
|
|
78
|
+
const changedFiles = diffSummary.files.map(f => f.file);
|
|
79
|
+
return {
|
|
80
|
+
diff: diff || '',
|
|
81
|
+
changedFiles
|
|
82
|
+
};
|
|
85
83
|
}
|
|
86
|
-
catch {
|
|
84
|
+
catch (error) {
|
|
87
85
|
// If no previous commit, return empty (first commit)
|
|
86
|
+
console.log(`[DEBUG] No previous commit found (first commit or error): ${error.message || 'HEAD~1 does not exist'}`);
|
|
88
87
|
return {
|
|
89
88
|
diff: '',
|
|
90
89
|
changedFiles: []
|
|
@@ -96,51 +95,103 @@ async function getBranchDiff(repoRoot, branchName, baseBranch) {
|
|
|
96
95
|
}
|
|
97
96
|
// Helper function to detect base branch
|
|
98
97
|
// Returns the branch name to use in git commands (may be local or remote)
|
|
98
|
+
// In CI environments, prioritizes remote refs since local branches often don't exist
|
|
99
99
|
async function detectBaseBranch(git, branchName) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
const isCI = !!(process.env.CI || process.env.GITHUB_ACTIONS || process.env.VERCEL || process.env.GITLAB_CI);
|
|
101
|
+
// Strategy 1: Try upstream tracking branch (most reliable if set)
|
|
102
|
+
try {
|
|
103
|
+
const upstream = await git.revparse(['--abbrev-ref', '--symbolic-full-name', `${branchName}@{u}`]);
|
|
104
104
|
const upstreamBranch = upstream.replace(/^origin\//, '');
|
|
105
105
|
// Don't use the branch itself as its base
|
|
106
106
|
if (upstreamBranch !== branchName) {
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
// In CI, prefer remote refs since local branches often don't exist
|
|
108
|
+
if (isCI) {
|
|
109
|
+
console.log(`[DEBUG] CI environment detected, using upstream tracking branch (remote): ${upstream}`);
|
|
110
|
+
return upstream;
|
|
111
|
+
}
|
|
112
|
+
// In local dev, check if local branch exists
|
|
113
|
+
try {
|
|
114
|
+
await git.revparse([upstreamBranch]);
|
|
115
|
+
console.log(`[DEBUG] Using upstream tracking branch (local): ${upstreamBranch}`);
|
|
116
|
+
return upstreamBranch;
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
console.log(`[DEBUG] Upstream tracking branch exists but local branch '${upstreamBranch}' not found, using remote: ${upstream}`);
|
|
120
|
+
return upstream;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
console.log(`[DEBUG] Upstream tracking branch '${upstreamBranch}' is the same as current branch, skipping`);
|
|
110
125
|
}
|
|
111
126
|
}
|
|
112
|
-
|
|
127
|
+
catch (error) {
|
|
128
|
+
console.log(`[DEBUG] Upstream tracking branch not set for '${branchName}': ${error.message || 'no upstream configured'}`);
|
|
129
|
+
}
|
|
130
|
+
// Strategy 2: Try default branch from origin/HEAD (reliable if configured)
|
|
113
131
|
try {
|
|
114
132
|
const defaultBranch = await git.revparse(['--abbrev-ref', 'refs/remotes/origin/HEAD']);
|
|
115
133
|
const defaultBranchName = defaultBranch.replace(/^origin\//, '');
|
|
116
134
|
// Don't use the branch itself as its base
|
|
117
135
|
if (defaultBranchName !== branchName) {
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
136
|
+
// In CI, prefer remote refs
|
|
137
|
+
if (isCI) {
|
|
138
|
+
console.log(`[DEBUG] CI environment detected, using default branch (remote): ${defaultBranch}`);
|
|
139
|
+
return defaultBranch;
|
|
140
|
+
}
|
|
141
|
+
// In local dev, check if local branch exists
|
|
142
|
+
try {
|
|
143
|
+
await git.revparse([defaultBranchName]);
|
|
144
|
+
console.log(`[DEBUG] Using default branch (local): ${defaultBranchName}`);
|
|
145
|
+
return defaultBranchName;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
console.log(`[DEBUG] Default branch exists but local branch '${defaultBranchName}' not found, using remote: ${defaultBranch}`);
|
|
149
|
+
return defaultBranch;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
console.log(`[DEBUG] Default branch '${defaultBranchName}' is the same as current branch, skipping`);
|
|
121
154
|
}
|
|
122
155
|
}
|
|
123
|
-
catch {
|
|
124
|
-
|
|
156
|
+
catch (error) {
|
|
157
|
+
console.log(`[DEBUG] Default branch (refs/remotes/origin/HEAD) not configured: ${error.message || 'not found'}`);
|
|
125
158
|
}
|
|
126
|
-
//
|
|
159
|
+
// Strategy 3: Try common branch names by checking remote refs first (most reliable fallback)
|
|
160
|
+
// This works reliably in CI with fetch-depth: 0, and also works locally
|
|
127
161
|
const commonBases = ['main', 'master', 'develop'];
|
|
128
162
|
for (const candidate of commonBases) {
|
|
129
163
|
if (candidate.toLowerCase() === branchName.toLowerCase()) {
|
|
130
164
|
continue; // Skip if it's the same branch
|
|
131
165
|
}
|
|
132
166
|
try {
|
|
133
|
-
//
|
|
167
|
+
// Always check remote ref first (this is reliable in CI with fetch-depth: 0)
|
|
134
168
|
await git.revparse([`origin/${candidate}`]);
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
169
|
+
// In CI, prefer remote refs since local branches often don't exist
|
|
170
|
+
if (isCI) {
|
|
171
|
+
console.log(`[DEBUG] CI environment detected, using common branch name (remote): origin/${candidate}`);
|
|
172
|
+
return `origin/${candidate}`;
|
|
173
|
+
}
|
|
174
|
+
// In local dev, check if local branch exists
|
|
175
|
+
try {
|
|
176
|
+
await git.revparse([candidate]);
|
|
177
|
+
console.log(`[DEBUG] Using common branch name (local): ${candidate}`);
|
|
178
|
+
return candidate;
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
console.log(`[DEBUG] Common branch '${candidate}' exists remotely but not locally, using remote: origin/${candidate}`);
|
|
182
|
+
return `origin/${candidate}`;
|
|
183
|
+
}
|
|
138
184
|
}
|
|
139
|
-
catch {
|
|
140
|
-
|
|
185
|
+
catch (error) {
|
|
186
|
+
console.log(`[DEBUG] Remote branch 'origin/${candidate}' not found: ${error.message || 'does not exist'}`);
|
|
187
|
+
// Continue to next candidate
|
|
141
188
|
}
|
|
142
189
|
}
|
|
143
|
-
|
|
190
|
+
// All strategies failed - provide clear error with context
|
|
191
|
+
throw new Error(`Could not determine base branch for '${branchName}'. ` +
|
|
192
|
+
`Tried: upstream tracking, default branch (origin/HEAD), and common names (main, master, develop). ` +
|
|
193
|
+
`Please specify base branch with --base flag or configure upstream tracking with: ` +
|
|
194
|
+
`git branch --set-upstream-to=origin/main ${branchName}`);
|
|
144
195
|
}
|
|
145
196
|
// Get diff between base and branch (cumulative diff of all commits)
|
|
146
197
|
// Format: git diff base...branch (three-dot notation finds common ancestor)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Review Context Detection
|
|
4
|
+
*
|
|
5
|
+
* Determines what type of code review context we're in:
|
|
6
|
+
* - PR/MR: Multiple commits comparing two branches
|
|
7
|
+
* - Branch: All commits on a branch vs base branch
|
|
8
|
+
* - Commit: Single commit changes
|
|
9
|
+
* - Local: Staged/unstaged changes in working directory
|
|
10
|
+
*
|
|
11
|
+
* Context detection is environment-specific - each CI platform
|
|
12
|
+
* provides different environment variables.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.detectContext = detectContext;
|
|
16
|
+
/**
|
|
17
|
+
* Detects the review context based on the environment.
|
|
18
|
+
*
|
|
19
|
+
* Each environment has different environment variables available,
|
|
20
|
+
* so detection logic is environment-specific.
|
|
21
|
+
*/
|
|
22
|
+
function detectContext(environment) {
|
|
23
|
+
switch (environment) {
|
|
24
|
+
case 'github':
|
|
25
|
+
return detectGitHubContext();
|
|
26
|
+
case 'gitlab':
|
|
27
|
+
return detectGitLabContext();
|
|
28
|
+
case 'vercel':
|
|
29
|
+
return detectVercelContext();
|
|
30
|
+
case 'local':
|
|
31
|
+
return { type: 'local' };
|
|
32
|
+
case 'azure-devops':
|
|
33
|
+
// Future: return detectAzureDevOpsContext();
|
|
34
|
+
return { type: 'local' };
|
|
35
|
+
default:
|
|
36
|
+
return { type: 'local' };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* GitHub Actions context detection
|
|
41
|
+
*
|
|
42
|
+
* Environment Variables:
|
|
43
|
+
* - PR: GITHUB_EVENT_NAME='pull_request', GITHUB_BASE_REF, GITHUB_HEAD_REF, GITHUB_EVENT_NUMBER
|
|
44
|
+
* - Branch: GITHUB_REF_NAME
|
|
45
|
+
* - Commit: GITHUB_SHA
|
|
46
|
+
*/
|
|
47
|
+
function detectGitHubContext() {
|
|
48
|
+
// 1. Check for PR context
|
|
49
|
+
if (process.env.GITHUB_EVENT_NAME === 'pull_request') {
|
|
50
|
+
const targetBranch = process.env.GITHUB_BASE_REF;
|
|
51
|
+
const sourceBranch = process.env.GITHUB_HEAD_REF;
|
|
52
|
+
const prNumber = process.env.GITHUB_EVENT_PULL_REQUEST_NUMBER || process.env.GITHUB_EVENT_NUMBER;
|
|
53
|
+
if (targetBranch && sourceBranch && prNumber) {
|
|
54
|
+
return {
|
|
55
|
+
type: 'pr',
|
|
56
|
+
prNumber,
|
|
57
|
+
sourceBranch,
|
|
58
|
+
targetBranch
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// 2. Check for branch context
|
|
63
|
+
if (process.env.GITHUB_REF_NAME) {
|
|
64
|
+
return {
|
|
65
|
+
type: 'branch',
|
|
66
|
+
branchName: process.env.GITHUB_REF_NAME
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// 3. Check for commit context
|
|
70
|
+
if (process.env.GITHUB_SHA) {
|
|
71
|
+
return {
|
|
72
|
+
type: 'commit',
|
|
73
|
+
commitSha: process.env.GITHUB_SHA
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// 4. Fallback to local
|
|
77
|
+
return { type: 'local' };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* GitLab CI context detection
|
|
81
|
+
*
|
|
82
|
+
* Environment Variables:
|
|
83
|
+
* - MR: CI_MERGE_REQUEST_IID, CI_MERGE_REQUEST_TARGET_BRANCH_NAME, CI_MERGE_REQUEST_SOURCE_BRANCH_NAME, CI_MERGE_REQUEST_TITLE
|
|
84
|
+
* - Branch: CI_COMMIT_REF_NAME
|
|
85
|
+
* - Commit: CI_COMMIT_SHA
|
|
86
|
+
*/
|
|
87
|
+
function detectGitLabContext() {
|
|
88
|
+
// 1. Check for MR context
|
|
89
|
+
if (process.env.CI_MERGE_REQUEST_IID) {
|
|
90
|
+
const targetBranch = process.env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME;
|
|
91
|
+
const sourceBranch = process.env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME;
|
|
92
|
+
const mrNumber = process.env.CI_MERGE_REQUEST_IID;
|
|
93
|
+
const mrTitle = process.env.CI_MERGE_REQUEST_TITLE;
|
|
94
|
+
if (targetBranch && sourceBranch && mrNumber) {
|
|
95
|
+
return {
|
|
96
|
+
type: 'mr',
|
|
97
|
+
mrNumber,
|
|
98
|
+
sourceBranch,
|
|
99
|
+
targetBranch,
|
|
100
|
+
prTitle: mrTitle || undefined
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// 2. Check for branch context
|
|
105
|
+
if (process.env.CI_COMMIT_REF_NAME) {
|
|
106
|
+
return {
|
|
107
|
+
type: 'branch',
|
|
108
|
+
branchName: process.env.CI_COMMIT_REF_NAME
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
// 3. Check for commit context
|
|
112
|
+
if (process.env.CI_COMMIT_SHA) {
|
|
113
|
+
return {
|
|
114
|
+
type: 'commit',
|
|
115
|
+
commitSha: process.env.CI_COMMIT_SHA
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
// 4. Fallback to local
|
|
119
|
+
return { type: 'local' };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Vercel context detection
|
|
123
|
+
*
|
|
124
|
+
* Environment Variables:
|
|
125
|
+
* - Branch: VERCEL_GIT_COMMIT_REF
|
|
126
|
+
* - Commit: VERCEL_GIT_COMMIT_SHA
|
|
127
|
+
*/
|
|
128
|
+
function detectVercelContext() {
|
|
129
|
+
// 1. Check for branch context
|
|
130
|
+
if (process.env.VERCEL_GIT_COMMIT_REF) {
|
|
131
|
+
return {
|
|
132
|
+
type: 'branch',
|
|
133
|
+
branchName: process.env.VERCEL_GIT_COMMIT_REF
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// 2. Check for commit context
|
|
137
|
+
if (process.env.VERCEL_GIT_COMMIT_SHA) {
|
|
138
|
+
return {
|
|
139
|
+
type: 'commit',
|
|
140
|
+
commitSha: process.env.VERCEL_GIT_COMMIT_SHA
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
// 3. Fallback to local
|
|
144
|
+
return { type: 'local' };
|
|
145
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CI/CD Environment Detection
|
|
4
|
+
*
|
|
5
|
+
* Detects which CI/CD platform or local environment the code is running in.
|
|
6
|
+
* This is the first step in determining how to collect context and metadata.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.detectEnvironment = detectEnvironment;
|
|
10
|
+
exports.isCIEnvironment = isCIEnvironment;
|
|
11
|
+
/**
|
|
12
|
+
* Detects the current environment based on environment variables.
|
|
13
|
+
*
|
|
14
|
+
* Detection order:
|
|
15
|
+
* 1. Vercel: VERCEL=1
|
|
16
|
+
* 2. GitHub Actions: GITHUB_ACTIONS=1
|
|
17
|
+
* 3. GitLab CI: GITLAB_CI=1 or (CI=1 + CI_COMMIT_SHA)
|
|
18
|
+
* 4. Azure DevOps: TF_BUILD=1 (future)
|
|
19
|
+
* 5. Local: None of the above
|
|
20
|
+
*/
|
|
21
|
+
function detectEnvironment() {
|
|
22
|
+
if (process.env.VERCEL)
|
|
23
|
+
return 'vercel';
|
|
24
|
+
if (process.env.GITHUB_ACTIONS)
|
|
25
|
+
return 'github';
|
|
26
|
+
if (process.env.GITLAB_CI || (process.env.CI && process.env.CI_COMMIT_SHA))
|
|
27
|
+
return 'gitlab';
|
|
28
|
+
// Future: if (process.env.TF_BUILD) return 'azure-devops';
|
|
29
|
+
return 'local';
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Returns true if running in a CI/CD environment (not local)
|
|
33
|
+
*/
|
|
34
|
+
function isCIEnvironment(env) {
|
|
35
|
+
return env !== 'local';
|
|
36
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Git Diff Execution
|
|
4
|
+
*
|
|
5
|
+
* Executes the appropriate git diff function based on review context.
|
|
6
|
+
* This is environment-agnostic - the git diff functions themselves
|
|
7
|
+
* handle environment-specific details (like base branch detection).
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.getDiffForContext = getDiffForContext;
|
|
11
|
+
exports.getContextDescription = getContextDescription;
|
|
12
|
+
const diff_1 = require("../git/diff");
|
|
13
|
+
/**
|
|
14
|
+
* Executes the appropriate git diff function based on context.
|
|
15
|
+
*
|
|
16
|
+
* This function maps context types to their corresponding git diff functions.
|
|
17
|
+
* The actual git diff functions handle environment-specific details internally.
|
|
18
|
+
*/
|
|
19
|
+
async function getDiffForContext(context, repoRoot, environment) {
|
|
20
|
+
switch (context.type) {
|
|
21
|
+
case 'pr':
|
|
22
|
+
return await (0, diff_1.getPRMRDiff)(repoRoot, context.sourceBranch, context.targetBranch);
|
|
23
|
+
case 'mr':
|
|
24
|
+
return await (0, diff_1.getPRMRDiff)(repoRoot, context.sourceBranch, context.targetBranch);
|
|
25
|
+
case 'branch':
|
|
26
|
+
return await (0, diff_1.getBranchDiff)(repoRoot, context.branchName);
|
|
27
|
+
case 'commit':
|
|
28
|
+
return await (0, diff_1.getCommitDiff)(repoRoot, context.commitSha);
|
|
29
|
+
case 'local':
|
|
30
|
+
return await (0, diff_1.getGitDiff)(repoRoot);
|
|
31
|
+
default:
|
|
32
|
+
// TypeScript exhaustiveness check - should never reach here
|
|
33
|
+
const _exhaustive = context;
|
|
34
|
+
throw new Error(`Unknown context type: ${_exhaustive}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Returns a human-readable description of the context for logging.
|
|
39
|
+
*/
|
|
40
|
+
function getContextDescription(context) {
|
|
41
|
+
switch (context.type) {
|
|
42
|
+
case 'pr':
|
|
43
|
+
return `PR: ${context.prNumber}`;
|
|
44
|
+
case 'mr':
|
|
45
|
+
return `MR: ${context.mrNumber}`;
|
|
46
|
+
case 'branch':
|
|
47
|
+
return `branch: ${context.branchName}`;
|
|
48
|
+
case 'commit':
|
|
49
|
+
return `commit: ${context.commitSha.substring(0, 7)}`;
|
|
50
|
+
case 'local':
|
|
51
|
+
return 'local changes';
|
|
52
|
+
default:
|
|
53
|
+
const _exhaustive = context;
|
|
54
|
+
throw new Error(`Unknown context type: ${_exhaustive}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Metadata Collection
|
|
4
|
+
*
|
|
5
|
+
* Collects environment-specific metadata for review context:
|
|
6
|
+
* - Commit SHA (from env vars or git)
|
|
7
|
+
* - Commit message (from git)
|
|
8
|
+
* - PR/MR title (from env vars or API)
|
|
9
|
+
*
|
|
10
|
+
* Metadata collection is environment-specific because each CI platform
|
|
11
|
+
* provides different environment variables.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.collectMetadata = collectMetadata;
|
|
15
|
+
const diff_1 = require("../git/diff");
|
|
16
|
+
/**
|
|
17
|
+
* Collects metadata for the given context and environment.
|
|
18
|
+
*
|
|
19
|
+
* This function knows how to extract metadata from each environment's
|
|
20
|
+
* specific environment variables and git commands.
|
|
21
|
+
*/
|
|
22
|
+
async function collectMetadata(context, environment, repoRoot) {
|
|
23
|
+
const metadata = {};
|
|
24
|
+
// Collect commit SHA (environment-specific)
|
|
25
|
+
metadata.commitSha = getCommitSha(context, environment);
|
|
26
|
+
// Collect commit message (if we have a commit SHA)
|
|
27
|
+
if (metadata.commitSha) {
|
|
28
|
+
const message = await (0, diff_1.getCommitMessage)(repoRoot, metadata.commitSha);
|
|
29
|
+
if (message) {
|
|
30
|
+
metadata.commitMessage = message;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Collect PR/MR title (environment-specific)
|
|
34
|
+
metadata.prTitle = getPRTitle(context, environment);
|
|
35
|
+
return metadata;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Extracts commit SHA from context and environment.
|
|
39
|
+
*/
|
|
40
|
+
function getCommitSha(context, environment) {
|
|
41
|
+
// If context already has commit SHA, use it
|
|
42
|
+
if (context.type === 'commit') {
|
|
43
|
+
return context.commitSha;
|
|
44
|
+
}
|
|
45
|
+
// For branch contexts, try to get commit SHA from environment variables
|
|
46
|
+
if (context.type === 'branch') {
|
|
47
|
+
switch (environment) {
|
|
48
|
+
case 'github':
|
|
49
|
+
return process.env.GITHUB_SHA;
|
|
50
|
+
case 'gitlab':
|
|
51
|
+
return process.env.CI_COMMIT_SHA;
|
|
52
|
+
case 'vercel':
|
|
53
|
+
return process.env.VERCEL_GIT_COMMIT_SHA;
|
|
54
|
+
default:
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// For PR/MR contexts, commit SHA might be available in env vars
|
|
59
|
+
if (context.type === 'pr' || context.type === 'mr') {
|
|
60
|
+
switch (environment) {
|
|
61
|
+
case 'github':
|
|
62
|
+
// For PRs, GITHUB_SHA is a merge commit, might want GITHUB_HEAD_SHA instead
|
|
63
|
+
return process.env.GITHUB_HEAD_SHA || process.env.GITHUB_SHA;
|
|
64
|
+
case 'gitlab':
|
|
65
|
+
return process.env.CI_COMMIT_SHA;
|
|
66
|
+
default:
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Extracts PR/MR title from context and environment.
|
|
74
|
+
*
|
|
75
|
+
* Note: GitHub Actions doesn't provide PR title as an env var by default.
|
|
76
|
+
* It would need to be passed from the workflow YAML or fetched via API.
|
|
77
|
+
*/
|
|
78
|
+
function getPRTitle(context, environment) {
|
|
79
|
+
// Only PR/MR contexts have titles
|
|
80
|
+
if (context.type !== 'pr' && context.type !== 'mr') {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
// GitLab CI provides MR title as env var
|
|
84
|
+
if (context.type === 'mr' && environment === 'gitlab') {
|
|
85
|
+
return context.prTitle;
|
|
86
|
+
}
|
|
87
|
+
// GitHub Actions doesn't provide PR title as env var
|
|
88
|
+
// Would need to be passed from workflow: PR_TITLE: ${{ github.event.pull_request.title }}
|
|
89
|
+
// or fetched via GitHub API
|
|
90
|
+
if (context.type === 'pr' && environment === 'github') {
|
|
91
|
+
return process.env.PR_TITLE; // Only if passed from workflow
|
|
92
|
+
}
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|