threadlines 0.1.21 → 0.1.23
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 +28 -12
- package/dist/git/context.js +50 -0
- package/dist/git/github-diff.js +61 -13
- package/dist/git/repo.js +192 -37
- package/package.json +1 -1
package/dist/commands/check.js
CHANGED
|
@@ -39,13 +39,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
39
|
exports.checkCommand = checkCommand;
|
|
40
40
|
const experts_1 = require("../validators/experts");
|
|
41
41
|
const file_1 = require("../git/file");
|
|
42
|
-
const repo_1 = require("../git/repo");
|
|
43
42
|
const client_1 = require("../api/client");
|
|
44
43
|
const config_1 = require("../utils/config");
|
|
45
44
|
const environment_1 = require("../utils/environment");
|
|
46
45
|
const context_1 = require("../utils/context");
|
|
47
46
|
const metadata_1 = require("../utils/metadata");
|
|
48
47
|
const git_diff_executor_1 = require("../utils/git-diff-executor");
|
|
48
|
+
const context_2 = require("../git/context");
|
|
49
49
|
const fs = __importStar(require("fs"));
|
|
50
50
|
const path = __importStar(require("path"));
|
|
51
51
|
const chalk_1 = __importDefault(require("chalk"));
|
|
@@ -92,6 +92,8 @@ async function checkCommand(options) {
|
|
|
92
92
|
const environment = (0, environment_1.detectEnvironment)();
|
|
93
93
|
let context;
|
|
94
94
|
let gitDiff;
|
|
95
|
+
let repoName;
|
|
96
|
+
let branchName;
|
|
95
97
|
// Validate mutually exclusive flags
|
|
96
98
|
const explicitFlags = [options.branch, options.commit, options.file, options.folder, options.files].filter(Boolean);
|
|
97
99
|
if (explicitFlags.length > 1) {
|
|
@@ -104,37 +106,52 @@ async function checkCommand(options) {
|
|
|
104
106
|
console.log(chalk_1.default.gray(`📝 Reading file: ${options.file}...`));
|
|
105
107
|
gitDiff = await (0, file_1.getFileContent)(repoRoot, options.file);
|
|
106
108
|
context = { type: 'local' }; // File context doesn't need git context
|
|
109
|
+
// For file/folder/files, repo/branch are not available - skip them
|
|
107
110
|
}
|
|
108
111
|
else if (options.folder) {
|
|
109
112
|
console.log(chalk_1.default.gray(`📝 Reading folder: ${options.folder}...`));
|
|
110
113
|
gitDiff = await (0, file_1.getFolderContent)(repoRoot, options.folder);
|
|
111
114
|
context = { type: 'local' };
|
|
115
|
+
// For file/folder/files, repo/branch are not available - skip them
|
|
112
116
|
}
|
|
113
117
|
else if (options.files && options.files.length > 0) {
|
|
114
118
|
console.log(chalk_1.default.gray(`📝 Reading ${options.files.length} file(s)...`));
|
|
115
119
|
gitDiff = await (0, file_1.getMultipleFilesContent)(repoRoot, options.files);
|
|
116
120
|
context = { type: 'local' };
|
|
121
|
+
// For file/folder/files, repo/branch are not available - skip them
|
|
117
122
|
}
|
|
118
123
|
else if (options.branch) {
|
|
119
124
|
console.log(chalk_1.default.gray(`📝 Collecting git changes for branch: ${options.branch}...`));
|
|
120
125
|
context = { type: 'branch', branchName: options.branch };
|
|
121
126
|
gitDiff = await (0, git_diff_executor_1.getDiffForContext)(context, repoRoot, environment);
|
|
127
|
+
// Get repo/branch using unified approach
|
|
128
|
+
const gitContext = await (0, context_2.getGitContextForEnvironment)(environment, repoRoot);
|
|
129
|
+
repoName = gitContext.repoName;
|
|
130
|
+
branchName = gitContext.branchName;
|
|
122
131
|
}
|
|
123
132
|
else if (options.commit) {
|
|
124
133
|
console.log(chalk_1.default.gray(`📝 Collecting git changes for commit: ${options.commit}...`));
|
|
125
134
|
context = { type: 'commit', commitSha: options.commit };
|
|
126
135
|
gitDiff = await (0, git_diff_executor_1.getDiffForContext)(context, repoRoot, environment);
|
|
136
|
+
// Get repo/branch using unified approach
|
|
137
|
+
const gitContext = await (0, context_2.getGitContextForEnvironment)(environment, repoRoot);
|
|
138
|
+
repoName = gitContext.repoName;
|
|
139
|
+
branchName = gitContext.branchName;
|
|
127
140
|
}
|
|
128
141
|
else {
|
|
129
|
-
// Auto-detect: Use
|
|
142
|
+
// Auto-detect: Use unified git context collection
|
|
130
143
|
const envNames = {
|
|
131
144
|
vercel: 'Vercel',
|
|
132
145
|
github: 'GitHub',
|
|
133
146
|
gitlab: 'GitLab',
|
|
134
147
|
local: 'Local'
|
|
135
148
|
};
|
|
136
|
-
console.log(chalk_1.default.gray(`📝 Collecting git
|
|
137
|
-
|
|
149
|
+
console.log(chalk_1.default.gray(`📝 Collecting git context for ${envNames[environment]}...`));
|
|
150
|
+
// Use unified git context collection (diff + repo + branch)
|
|
151
|
+
const gitContext = await (0, context_2.getGitContextForEnvironment)(environment, repoRoot);
|
|
152
|
+
gitDiff = gitContext.diff;
|
|
153
|
+
repoName = gitContext.repoName;
|
|
154
|
+
branchName = gitContext.branchName;
|
|
138
155
|
// Create context for metadata collection
|
|
139
156
|
if (environment === 'vercel') {
|
|
140
157
|
context = { type: 'commit', commitSha: process.env.VERCEL_GIT_COMMIT_SHA };
|
|
@@ -151,8 +168,10 @@ async function checkCommand(options) {
|
|
|
151
168
|
// 3. Collect metadata (commit SHA, commit message, PR title)
|
|
152
169
|
const metadata = await (0, metadata_1.collectMetadata)(context, environment, repoRoot);
|
|
153
170
|
if (gitDiff.changedFiles.length === 0) {
|
|
154
|
-
console.
|
|
155
|
-
|
|
171
|
+
console.error(chalk_1.default.red('❌ Error: No changes detected.'));
|
|
172
|
+
console.error(chalk_1.default.red(' Threadline check requires code changes to analyze.'));
|
|
173
|
+
console.error(chalk_1.default.red(' This may indicate a problem with git diff detection.'));
|
|
174
|
+
process.exit(1);
|
|
156
175
|
}
|
|
157
176
|
// Check for zero diff (files changed but no actual code changes)
|
|
158
177
|
if (!gitDiff.diff || gitDiff.diff.trim() === '') {
|
|
@@ -166,7 +185,7 @@ async function checkCommand(options) {
|
|
|
166
185
|
process.exit(0);
|
|
167
186
|
}
|
|
168
187
|
console.log(chalk_1.default.green(`✓ Found ${gitDiff.changedFiles.length} changed file(s)\n`));
|
|
169
|
-
//
|
|
188
|
+
// 4. Read context files for each threadline
|
|
170
189
|
const threadlinesWithContext = threadlines.map(threadline => {
|
|
171
190
|
const contextContent = {};
|
|
172
191
|
if (threadline.contextFiles) {
|
|
@@ -186,9 +205,6 @@ async function checkCommand(options) {
|
|
|
186
205
|
contextContent
|
|
187
206
|
};
|
|
188
207
|
});
|
|
189
|
-
// 4. Get repo name and branch name
|
|
190
|
-
const repoName = await (0, repo_1.getRepoName)(repoRoot);
|
|
191
|
-
const branchName = await (0, repo_1.getBranchName)(repoRoot);
|
|
192
208
|
// 5. Get API URL
|
|
193
209
|
const apiUrl = options.apiUrl ||
|
|
194
210
|
process.env.THREADLINE_API_URL ||
|
|
@@ -202,8 +218,8 @@ async function checkCommand(options) {
|
|
|
202
218
|
files: gitDiff.changedFiles,
|
|
203
219
|
apiKey,
|
|
204
220
|
account,
|
|
205
|
-
repoName: repoName
|
|
206
|
-
branchName: branchName
|
|
221
|
+
repoName: repoName,
|
|
222
|
+
branchName: branchName,
|
|
207
223
|
commitSha: metadata.commitSha,
|
|
208
224
|
commitMessage: metadata.commitMessage,
|
|
209
225
|
prTitle: metadata.prTitle,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Unified Git Context Collection
|
|
4
|
+
*
|
|
5
|
+
* Collects all git-related information (diff, repo name, branch name) in a unified way.
|
|
6
|
+
* Each environment has isolated implementations - changes to one don't affect others.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.getGitContextForEnvironment = getGitContextForEnvironment;
|
|
10
|
+
const git_diff_executor_1 = require("../utils/git-diff-executor");
|
|
11
|
+
const repo_1 = require("./repo");
|
|
12
|
+
const repo_2 = require("./repo");
|
|
13
|
+
/**
|
|
14
|
+
* Collects all git context (diff, repo name, branch name) for the given environment.
|
|
15
|
+
*
|
|
16
|
+
* Each environment has a single, specific implementation:
|
|
17
|
+
* - GitHub: Uses GITHUB_REPOSITORY, GITHUB_REF_NAME, and GitHub-specific diff logic
|
|
18
|
+
* - Vercel: Uses VERCEL_GIT_REPO_OWNER/SLUG, VERCEL_GIT_COMMIT_REF, and Vercel-specific diff logic
|
|
19
|
+
* - Local: Uses git commands for repo/branch, and local diff logic
|
|
20
|
+
*
|
|
21
|
+
* All methods fail loudly if they can't get the required information.
|
|
22
|
+
*/
|
|
23
|
+
async function getGitContextForEnvironment(environment, repoRoot) {
|
|
24
|
+
switch (environment) {
|
|
25
|
+
case 'github':
|
|
26
|
+
return {
|
|
27
|
+
diff: await (0, git_diff_executor_1.getDiffForEnvironment)('github', repoRoot),
|
|
28
|
+
repoName: await (0, repo_1.getGitHubRepoName)(repoRoot),
|
|
29
|
+
branchName: await (0, repo_2.getGitHubBranchName)(repoRoot)
|
|
30
|
+
};
|
|
31
|
+
case 'vercel':
|
|
32
|
+
return {
|
|
33
|
+
diff: await (0, git_diff_executor_1.getDiffForEnvironment)('vercel', repoRoot),
|
|
34
|
+
repoName: await (0, repo_1.getVercelRepoName)(repoRoot),
|
|
35
|
+
branchName: await (0, repo_2.getVercelBranchName)(repoRoot)
|
|
36
|
+
};
|
|
37
|
+
case 'local':
|
|
38
|
+
return {
|
|
39
|
+
diff: await (0, git_diff_executor_1.getDiffForEnvironment)('local', repoRoot),
|
|
40
|
+
repoName: await (0, repo_1.getLocalRepoName)(repoRoot),
|
|
41
|
+
branchName: await (0, repo_2.getLocalBranchName)(repoRoot)
|
|
42
|
+
};
|
|
43
|
+
case 'gitlab':
|
|
44
|
+
// GitLab not implemented yet - will be added later
|
|
45
|
+
throw new Error('GitLab environment not yet supported for unified git context collection.');
|
|
46
|
+
default:
|
|
47
|
+
const _exhaustive = environment;
|
|
48
|
+
throw new Error(`Unknown environment: ${_exhaustive}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
package/dist/git/github-diff.js
CHANGED
|
@@ -5,16 +5,29 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.getGitHubDiff = getGitHubDiff;
|
|
7
7
|
const simple_git_1 = __importDefault(require("simple-git"));
|
|
8
|
+
const repo_1 = require("./repo");
|
|
8
9
|
/**
|
|
9
10
|
* Get diff for GitHub Actions CI environment
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
* - PR context: GITHUB_BASE_REF (target branch) and GITHUB_HEAD_REF (source branch)
|
|
13
|
-
* - Branch context: GITHUB_REF_NAME (current branch), compare against origin/main
|
|
12
|
+
* Handles four scenarios:
|
|
14
13
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
|
|
14
|
+
* 1. PR Context (pull_request event):
|
|
15
|
+
* Uses GITHUB_BASE_REF vs GITHUB_HEAD_REF
|
|
16
|
+
* Shows: All changes in the PR
|
|
17
|
+
*
|
|
18
|
+
* 2. Merge Commit to Default Branch (push event, default branch):
|
|
19
|
+
* Compare: origin/default~1 vs origin/default
|
|
20
|
+
* Shows: All changes that were merged in
|
|
21
|
+
*
|
|
22
|
+
* 3. Feature Branch Push (push event, feature branch):
|
|
23
|
+
* Compare: origin/default vs origin/feature-branch
|
|
24
|
+
* Shows: Cumulative changes in feature branch vs default
|
|
25
|
+
*
|
|
26
|
+
* 4. Direct Commit to Default Branch (push event, default branch, non-merge):
|
|
27
|
+
* Compare: origin/default~1 vs origin/default
|
|
28
|
+
* Shows: Changes in the direct commit
|
|
29
|
+
*
|
|
30
|
+
*/
|
|
18
31
|
async function getGitHubDiff(repoRoot) {
|
|
19
32
|
const git = (0, simple_git_1.default)(repoRoot);
|
|
20
33
|
// Check if we're in a git repo
|
|
@@ -22,18 +35,25 @@ async function getGitHubDiff(repoRoot) {
|
|
|
22
35
|
if (!isRepo) {
|
|
23
36
|
throw new Error('Not a git repository. Threadline requires a git repository.');
|
|
24
37
|
}
|
|
38
|
+
// Detect the default branch name (e.g., "main", "master")
|
|
39
|
+
// This is used for scenarios 2, 3, and 4
|
|
40
|
+
const defaultBranch = await (0, repo_1.getDefaultBranchName)(repoRoot);
|
|
25
41
|
// Determine context from GitHub environment variables
|
|
26
42
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
27
43
|
const baseRef = process.env.GITHUB_BASE_REF;
|
|
28
44
|
const headRef = process.env.GITHUB_HEAD_REF;
|
|
29
45
|
const refName = process.env.GITHUB_REF_NAME;
|
|
30
|
-
|
|
46
|
+
const commitSha = process.env.GITHUB_SHA;
|
|
47
|
+
// Scenario 1: PR Context
|
|
48
|
+
// When a PR is created or updated, GitHub provides both base and head branches
|
|
49
|
+
// This is the simplest case - we use what GitHub gives us directly
|
|
31
50
|
if (eventName === 'pull_request') {
|
|
32
51
|
if (!baseRef || !headRef) {
|
|
33
52
|
throw new Error('GitHub PR context detected but GITHUB_BASE_REF or GITHUB_HEAD_REF is missing. ' +
|
|
34
53
|
'This should be automatically provided by GitHub Actions.');
|
|
35
54
|
}
|
|
36
|
-
//
|
|
55
|
+
// Compare target branch (base) vs source branch (head)
|
|
56
|
+
// This shows all changes in the PR
|
|
37
57
|
const diff = await git.diff([`origin/${baseRef}...origin/${headRef}`, '-U200']);
|
|
38
58
|
const diffSummary = await git.diffSummary([`origin/${baseRef}...origin/${headRef}`]);
|
|
39
59
|
const changedFiles = diffSummary.files.map(f => f.file);
|
|
@@ -42,12 +62,40 @@ async function getGitHubDiff(repoRoot) {
|
|
|
42
62
|
changedFiles
|
|
43
63
|
};
|
|
44
64
|
}
|
|
45
|
-
//
|
|
65
|
+
// Scenario 2 & 4: Default Branch Push (merge commit or direct commit)
|
|
66
|
+
// When code is pushed to the default branch, we compare default~1 vs default
|
|
67
|
+
// This works for both merge commits and direct commits:
|
|
68
|
+
// - Merge commits: Shows all changes that were merged in
|
|
69
|
+
// - Direct commits: Shows the changes in the direct commit
|
|
70
|
+
if (refName === defaultBranch && commitSha) {
|
|
71
|
+
// Compare default branch before the push (default~1) vs default branch after the push (default)
|
|
72
|
+
// This shows all changes introduced by the push, whether merged or direct
|
|
73
|
+
try {
|
|
74
|
+
const diff = await git.diff([`origin/${defaultBranch}~1...origin/${defaultBranch}`, '-U200']);
|
|
75
|
+
const diffSummary = await git.diffSummary([`origin/${defaultBranch}~1...origin/${defaultBranch}`]);
|
|
76
|
+
const changedFiles = diffSummary.files.map(f => f.file);
|
|
77
|
+
return {
|
|
78
|
+
diff: diff || '',
|
|
79
|
+
changedFiles
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
// If we can't get the diff (e.g., first commit on branch), throw a clear error
|
|
84
|
+
throw new Error(`Could not get diff for default branch '${defaultBranch}'. ` +
|
|
85
|
+
`This might be the first commit on the branch. Error: ${error.message}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Scenario 3: Feature Branch Push
|
|
89
|
+
// When code is pushed to a feature branch, we want to see all changes vs the default branch
|
|
90
|
+
// Compare: origin/default vs origin/feature-branch
|
|
91
|
+
// This shows cumulative changes in the feature branch (all commits vs default branch)
|
|
92
|
+
// Note: We don't use HEAD~1 vs HEAD because that only shows the last commit,
|
|
93
|
+
// not the cumulative changes in the branch
|
|
46
94
|
if (refName) {
|
|
47
|
-
// For branch pushes, compare against origin/
|
|
48
|
-
// GitHub Actions with fetch-depth: 0 should have origin/
|
|
49
|
-
const diff = await git.diff([`origin
|
|
50
|
-
const diffSummary = await git.diffSummary([`origin
|
|
95
|
+
// For branch pushes, compare against origin/default (detected default branch)
|
|
96
|
+
// GitHub Actions with fetch-depth: 0 should have origin/default available
|
|
97
|
+
const diff = await git.diff([`origin/${defaultBranch}...origin/${refName}`, '-U200']);
|
|
98
|
+
const diffSummary = await git.diffSummary([`origin/${defaultBranch}...origin/${refName}`]);
|
|
51
99
|
const changedFiles = diffSummary.files.map(f => f.file);
|
|
52
100
|
return {
|
|
53
101
|
diff: diff || '',
|
package/dist/git/repo.js
CHANGED
|
@@ -1,63 +1,218 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
5
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
7
|
-
exports.
|
|
39
|
+
exports.getGitHubRepoName = getGitHubRepoName;
|
|
40
|
+
exports.getVercelRepoName = getVercelRepoName;
|
|
41
|
+
exports.getLocalRepoName = getLocalRepoName;
|
|
42
|
+
exports.getGitHubBranchName = getGitHubBranchName;
|
|
43
|
+
exports.getVercelBranchName = getVercelBranchName;
|
|
44
|
+
exports.getLocalBranchName = getLocalBranchName;
|
|
45
|
+
exports.getDefaultBranchName = getDefaultBranchName;
|
|
8
46
|
const simple_git_1 = __importDefault(require("simple-git"));
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
9
49
|
/**
|
|
10
|
-
*
|
|
50
|
+
* GitHub Actions: Get repository name
|
|
11
51
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* - Vercel: VERCEL_GIT_REPO_OWNER, VERCEL_GIT_REPO_SLUG
|
|
15
|
-
* - GitLab CI: Not available (falls back to git)
|
|
16
|
-
* - Local: Falls back to git origin remote
|
|
52
|
+
* Uses GITHUB_REPOSITORY environment variable (format: "owner/repo").
|
|
53
|
+
* This is the ONLY method for GitHub - no fallbacks, no alternatives.
|
|
17
54
|
*/
|
|
18
|
-
async function
|
|
19
|
-
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
|
|
55
|
+
async function getGitHubRepoName(repoRoot) {
|
|
56
|
+
const githubRepo = process.env.GITHUB_REPOSITORY;
|
|
57
|
+
if (!githubRepo) {
|
|
58
|
+
throw new Error('GitHub Actions: GITHUB_REPOSITORY environment variable is not set. ' +
|
|
59
|
+
'This should be automatically provided by GitHub Actions.');
|
|
23
60
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
61
|
+
const serverUrl = process.env.GITHUB_SERVER_URL || 'https://github.com';
|
|
62
|
+
return `${serverUrl}/${githubRepo}.git`;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Vercel: Get repository name
|
|
66
|
+
*
|
|
67
|
+
* Uses VERCEL_GIT_REPO_OWNER and VERCEL_GIT_REPO_SLUG environment variables.
|
|
68
|
+
* This is the ONLY method for Vercel - no fallbacks, no alternatives.
|
|
69
|
+
*/
|
|
70
|
+
async function getVercelRepoName(repoRoot) {
|
|
71
|
+
const owner = process.env.VERCEL_GIT_REPO_OWNER;
|
|
72
|
+
const slug = process.env.VERCEL_GIT_REPO_SLUG;
|
|
73
|
+
if (!owner || !slug) {
|
|
74
|
+
throw new Error('Vercel: VERCEL_GIT_REPO_OWNER or VERCEL_GIT_REPO_SLUG environment variable is not set. ' +
|
|
75
|
+
'This should be automatically provided by Vercel CI.');
|
|
27
76
|
}
|
|
28
|
-
|
|
77
|
+
return `https://github.com/${owner}/${slug}.git`;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Local: Get repository name
|
|
81
|
+
*
|
|
82
|
+
* Uses git command to get origin remote URL.
|
|
83
|
+
* This is the ONLY method for local - no fallbacks, no alternatives.
|
|
84
|
+
* Git should always be available in local development.
|
|
85
|
+
*/
|
|
86
|
+
async function getLocalRepoName(repoRoot) {
|
|
29
87
|
const git = (0, simple_git_1.default)(repoRoot);
|
|
88
|
+
// Check if we're in a git repo
|
|
89
|
+
const isRepo = await git.checkIsRepo();
|
|
90
|
+
if (!isRepo) {
|
|
91
|
+
throw new Error('Local: Not a git repository. Threadline requires a git repository.');
|
|
92
|
+
}
|
|
30
93
|
try {
|
|
31
94
|
const remotes = await git.getRemotes(true);
|
|
32
95
|
const origin = remotes.find(r => r.name === 'origin');
|
|
33
|
-
|
|
96
|
+
if (!origin || !origin.refs?.fetch) {
|
|
97
|
+
throw new Error('Local: No origin remote found. ' +
|
|
98
|
+
'Please configure an origin remote: git remote add origin <url>');
|
|
99
|
+
}
|
|
100
|
+
return origin.refs.fetch;
|
|
34
101
|
}
|
|
35
|
-
catch {
|
|
36
|
-
|
|
102
|
+
catch (error) {
|
|
103
|
+
// If it's already our error, re-throw it
|
|
104
|
+
if (error.message.includes('Local:')) {
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
// Otherwise, wrap it
|
|
108
|
+
throw new Error(`Local: Failed to get repository name from git: ${error.message}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* GitHub Actions: Get branch name
|
|
113
|
+
*
|
|
114
|
+
* Uses GITHUB_REF_NAME environment variable.
|
|
115
|
+
* This is the ONLY method for GitHub - no fallbacks, no alternatives.
|
|
116
|
+
*/
|
|
117
|
+
async function getGitHubBranchName(repoRoot) {
|
|
118
|
+
const refName = process.env.GITHUB_REF_NAME;
|
|
119
|
+
if (!refName) {
|
|
120
|
+
throw new Error('GitHub Actions: GITHUB_REF_NAME environment variable is not set. ' +
|
|
121
|
+
'This should be automatically provided by GitHub Actions.');
|
|
37
122
|
}
|
|
123
|
+
return refName;
|
|
38
124
|
}
|
|
39
125
|
/**
|
|
40
|
-
*
|
|
126
|
+
* Vercel: Get branch name
|
|
41
127
|
*
|
|
42
|
-
*
|
|
43
|
-
* -
|
|
44
|
-
* - Vercel: VERCEL_GIT_COMMIT_REF
|
|
45
|
-
* - GitLab CI: CI_COMMIT_REF_NAME
|
|
46
|
-
* - Local: Falls back to git revparse --abbrev-ref HEAD
|
|
128
|
+
* Uses VERCEL_GIT_COMMIT_REF environment variable.
|
|
129
|
+
* This is the ONLY method for Vercel - no fallbacks, no alternatives.
|
|
47
130
|
*/
|
|
48
|
-
async function
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
131
|
+
async function getVercelBranchName(repoRoot) {
|
|
132
|
+
const branchName = process.env.VERCEL_GIT_COMMIT_REF;
|
|
133
|
+
if (!branchName) {
|
|
134
|
+
throw new Error('Vercel: VERCEL_GIT_COMMIT_REF environment variable is not set. ' +
|
|
135
|
+
'This should be automatically provided by Vercel CI.');
|
|
136
|
+
}
|
|
137
|
+
return branchName;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Local: Get branch name
|
|
141
|
+
*
|
|
142
|
+
* Uses git command to get current branch name.
|
|
143
|
+
* This is the ONLY method for local - no fallbacks, no alternatives.
|
|
144
|
+
* Git should always be available in local development.
|
|
145
|
+
*/
|
|
146
|
+
async function getLocalBranchName(repoRoot) {
|
|
56
147
|
const git = (0, simple_git_1.default)(repoRoot);
|
|
148
|
+
// Check if we're in a git repo
|
|
149
|
+
const isRepo = await git.checkIsRepo();
|
|
150
|
+
if (!isRepo) {
|
|
151
|
+
throw new Error('Local: Not a git repository. Threadline requires a git repository.');
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
const branchName = await git.revparse(['--abbrev-ref', 'HEAD']);
|
|
155
|
+
if (!branchName || branchName.trim() === '') {
|
|
156
|
+
throw new Error('Local: Could not determine branch name. ' +
|
|
157
|
+
'This might be a brand new repository with no commits. ' +
|
|
158
|
+
'Make at least one commit before running threadlines check.');
|
|
159
|
+
}
|
|
160
|
+
// Handle detached HEAD state
|
|
161
|
+
if (branchName === 'HEAD') {
|
|
162
|
+
throw new Error('Local: Currently in detached HEAD state. ' +
|
|
163
|
+
'Please checkout a branch before running threadlines check.');
|
|
164
|
+
}
|
|
165
|
+
return branchName.trim();
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
// If it's already our error, re-throw it
|
|
169
|
+
if (error.message.includes('Local:')) {
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
// Otherwise, wrap it
|
|
173
|
+
throw new Error(`Local: Failed to get branch name from git: ${error.message}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Detects the default branch name of the repository for GitHub Actions.
|
|
178
|
+
*
|
|
179
|
+
* Uses GITHUB_EVENT_PATH JSON (repository.default_branch) - the most authoritative source
|
|
180
|
+
* provided directly by GitHub Actions.
|
|
181
|
+
*
|
|
182
|
+
* This function is ONLY called from GitHub Actions context (getGitHubDiff),
|
|
183
|
+
* so GITHUB_EVENT_PATH should always be available. If it's not, we fail with a clear error.
|
|
184
|
+
*
|
|
185
|
+
* Returns the branch name (e.g., "main", "master") without the "origin/" prefix.
|
|
186
|
+
* Throws an error if the default branch cannot be detected.
|
|
187
|
+
*/
|
|
188
|
+
async function getDefaultBranchName(repoRoot) {
|
|
189
|
+
// GitHub Actions provides GITHUB_EVENT_PATH which contains repository.default_branch
|
|
190
|
+
const githubEventPath = process.env.GITHUB_EVENT_PATH;
|
|
191
|
+
if (!githubEventPath) {
|
|
192
|
+
throw new Error('GITHUB_EVENT_PATH environment variable is not set. ' +
|
|
193
|
+
'This should be automatically provided by GitHub Actions. ' +
|
|
194
|
+
'This function should only be called in GitHub Actions context.');
|
|
195
|
+
}
|
|
57
196
|
try {
|
|
58
|
-
|
|
197
|
+
const eventPath = path.resolve(githubEventPath);
|
|
198
|
+
if (!fs.existsSync(eventPath)) {
|
|
199
|
+
throw new Error(`GITHUB_EVENT_PATH file does not exist: ${eventPath}`);
|
|
200
|
+
}
|
|
201
|
+
const eventJson = JSON.parse(fs.readFileSync(eventPath, 'utf8'));
|
|
202
|
+
const defaultBranch = eventJson.repository?.default_branch;
|
|
203
|
+
if (!defaultBranch || typeof defaultBranch !== 'string') {
|
|
204
|
+
throw new Error('Could not find repository.default_branch in GITHUB_EVENT_PATH JSON. ' +
|
|
205
|
+
'This should be automatically provided by GitHub Actions.');
|
|
206
|
+
}
|
|
207
|
+
return defaultBranch;
|
|
59
208
|
}
|
|
60
|
-
catch {
|
|
61
|
-
|
|
209
|
+
catch (error) {
|
|
210
|
+
// If it's already our error, re-throw it
|
|
211
|
+
if (error.message.includes('GITHUB_EVENT_PATH') || error.message.includes('default_branch')) {
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
// Otherwise, wrap it
|
|
215
|
+
throw new Error(`Failed to read or parse GITHUB_EVENT_PATH: ${error.message}. ` +
|
|
216
|
+
'This should be automatically provided by GitHub Actions.');
|
|
62
217
|
}
|
|
63
218
|
}
|