threadlines 0.2.16 → 0.2.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.
@@ -42,10 +42,7 @@ const file_1 = require("../git/file");
42
42
  const client_1 = require("../api/client");
43
43
  const config_1 = require("../utils/config");
44
44
  const environment_1 = require("../utils/environment");
45
- const github_1 = require("../git/github");
46
- const gitlab_1 = require("../git/gitlab");
47
- const bitbucket_1 = require("../git/bitbucket");
48
- const vercel_1 = require("../git/vercel");
45
+ const ci_context_1 = require("../git/ci-context");
49
46
  const local_1 = require("../git/local");
50
47
  const config_file_1 = require("../utils/config-file");
51
48
  const logger_1 = require("../utils/logger");
@@ -55,22 +52,20 @@ const chalk_1 = __importDefault(require("chalk"));
55
52
  const simple_git_1 = __importDefault(require("simple-git"));
56
53
  /**
57
54
  * Helper to get context for any environment.
58
- * This centralizes the environment switch logic.
55
+ * CI environments use the unified getCIContext().
56
+ * Local environment has special handling for flags.
59
57
  */
60
58
  async function getContextForEnvironment(environment, repoRoot, commitSha) {
61
59
  switch (environment) {
60
+ case 'local':
61
+ return (0, local_1.getLocalContext)(repoRoot, commitSha);
62
62
  case 'github':
63
- return (0, github_1.getGitHubContext)(repoRoot);
64
63
  case 'gitlab':
65
- return (0, gitlab_1.getGitLabContext)(repoRoot);
66
64
  case 'bitbucket':
67
- return (0, bitbucket_1.getBitbucketContext)(repoRoot);
68
65
  case 'vercel':
69
- return (0, vercel_1.getVercelContext)(repoRoot);
70
- case 'local':
71
- return (0, local_1.getLocalContext)(repoRoot, commitSha);
66
+ return (0, ci_context_1.getCIContext)(repoRoot, environment);
72
67
  default:
73
- // TypeScript exhaustiveness check - should never happen
68
+ // TypeScript exhaustiveness check - fails at compile time if new Environment added
74
69
  throw new Error(`Unrecognized environment: ${environment}`);
75
70
  }
76
71
  }
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ /**
3
+ * CI Environment Configuration
4
+ *
5
+ * This is the ONLY place where CI-specific environment variables are read.
6
+ * Each CI provider has different env var names for the same concepts.
7
+ * This config maps them to a common interface.
8
+ *
9
+ * Everything else (repo URL, commit SHA, author, diff) uses shared git commands.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.CI_CONFIGS = void 0;
13
+ /**
14
+ * CI configuration for each environment.
15
+ * Only reads env vars - no git commands here.
16
+ */
17
+ exports.CI_CONFIGS = {
18
+ github: {
19
+ isPullRequest: () => process.env.GITHUB_EVENT_NAME === 'pull_request',
20
+ getTargetBranch: () => process.env.GITHUB_BASE_REF,
21
+ getBranchName: () => {
22
+ const refName = process.env.GITHUB_REF_NAME;
23
+ if (!refName) {
24
+ throw new Error('GitHub Actions: GITHUB_REF_NAME is not set. ' +
25
+ 'This should be automatically provided by GitHub Actions.');
26
+ }
27
+ return refName;
28
+ },
29
+ getPRTitle: () => process.env.PR_TITLE, // Must be passed from workflow YAML
30
+ },
31
+ gitlab: {
32
+ isPullRequest: () => !!process.env.CI_MERGE_REQUEST_IID,
33
+ getTargetBranch: () => process.env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME,
34
+ getBranchName: () => {
35
+ const refName = process.env.CI_COMMIT_REF_NAME;
36
+ if (!refName) {
37
+ throw new Error('GitLab CI: CI_COMMIT_REF_NAME is not set. ' +
38
+ 'This should be automatically provided by GitLab CI.');
39
+ }
40
+ return refName;
41
+ },
42
+ getPRTitle: () => process.env.CI_MERGE_REQUEST_TITLE,
43
+ },
44
+ bitbucket: {
45
+ isPullRequest: () => !!process.env.BITBUCKET_PR_ID,
46
+ getTargetBranch: () => process.env.BITBUCKET_PR_DESTINATION_BRANCH,
47
+ getBranchName: () => {
48
+ const branchName = process.env.BITBUCKET_BRANCH;
49
+ if (!branchName) {
50
+ throw new Error('Bitbucket Pipelines: BITBUCKET_BRANCH is not set. ' +
51
+ 'This should be automatically provided by Bitbucket Pipelines.');
52
+ }
53
+ return branchName;
54
+ },
55
+ getPRTitle: () => undefined, // Bitbucket doesn't expose PR title as env var
56
+ },
57
+ vercel: {
58
+ isPullRequest: () => false, // Vercel only supports commit deployments
59
+ getTargetBranch: () => undefined,
60
+ getBranchName: () => {
61
+ const branch = process.env.VERCEL_GIT_COMMIT_REF;
62
+ if (!branch) {
63
+ throw new Error('Vercel: VERCEL_GIT_COMMIT_REF is not set. ' +
64
+ 'This should be automatically provided by Vercel.');
65
+ }
66
+ return branch;
67
+ },
68
+ getPRTitle: () => undefined,
69
+ },
70
+ };
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ /**
3
+ * Unified CI Context
4
+ *
5
+ * Single function to get context for ANY CI environment.
6
+ * Uses:
7
+ * - Shared git commands for: repo URL, commit SHA, author, message, diff
8
+ * - CI-specific env vars (via ci-config.ts) for: PR detection, branch name, PR title
9
+ *
10
+ * This replaces the individual github.ts, gitlab.ts, bitbucket.ts, vercel.ts files.
11
+ */
12
+ var __importDefault = (this && this.__importDefault) || function (mod) {
13
+ return (mod && mod.__esModule) ? mod : { "default": mod };
14
+ };
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.getCIContext = getCIContext;
17
+ const simple_git_1 = __importDefault(require("simple-git"));
18
+ const logger_1 = require("../utils/logger");
19
+ const ci_config_1 = require("./ci-config");
20
+ const diff_1 = require("./diff");
21
+ /**
22
+ * Get context for any CI environment.
23
+ *
24
+ * This is the SINGLE entry point for all CI environments.
25
+ * It uses shared git commands for most data, and only reads
26
+ * CI-specific env vars for things git can't provide reliably.
27
+ *
28
+ * @param repoRoot - Path to the repository root
29
+ * @param environment - The CI environment (github, gitlab, bitbucket, vercel)
30
+ */
31
+ async function getCIContext(repoRoot, environment) {
32
+ const git = (0, simple_git_1.default)(repoRoot);
33
+ const config = ci_config_1.CI_CONFIGS[environment];
34
+ // Check if we're in a git repo
35
+ const isRepo = await git.checkIsRepo();
36
+ if (!isRepo) {
37
+ throw new Error('Not a git repository. Threadline requires a git repository.');
38
+ }
39
+ // === SHARED GIT COMMANDS (reliable across all CI environments) ===
40
+ const repoName = await (0, diff_1.getRepoUrl)(repoRoot);
41
+ const commitSha = await (0, diff_1.getHeadCommitSha)(repoRoot);
42
+ const commitAuthor = await (0, diff_1.getCommitAuthor)(repoRoot);
43
+ const commitMessage = await (0, diff_1.getCommitMessage)(repoRoot, commitSha) || undefined;
44
+ // === CI-SPECIFIC ENV VARS (only for things git can't provide) ===
45
+ const branchName = config.getBranchName();
46
+ const isPR = config.isPullRequest();
47
+ const reviewContext = isPR ? 'pr' : 'commit';
48
+ const prTitle = isPR ? config.getPRTitle() : undefined;
49
+ // Get diff using appropriate strategy
50
+ let diff;
51
+ if (isPR) {
52
+ const targetBranch = config.getTargetBranch();
53
+ if (!targetBranch) {
54
+ throw new Error(`${environment} PR context detected but target branch is missing. ` +
55
+ 'This should be automatically provided by the CI environment.');
56
+ }
57
+ diff = await (0, diff_1.getPRDiff)(repoRoot, targetBranch, logger_1.logger);
58
+ }
59
+ else {
60
+ diff = await (0, diff_1.getCommitDiff)(repoRoot);
61
+ }
62
+ return {
63
+ diff,
64
+ repoName,
65
+ branchName,
66
+ commitSha,
67
+ commitMessage,
68
+ commitAuthor,
69
+ prTitle,
70
+ reviewContext,
71
+ };
72
+ }
package/dist/git/diff.js CHANGED
@@ -3,12 +3,88 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getRepoUrl = getRepoUrl;
7
+ exports.getHeadCommitSha = getHeadCommitSha;
6
8
  exports.getCommitMessage = getCommitMessage;
7
9
  exports.getCommitAuthor = getCommitAuthor;
8
10
  exports.getPRDiff = getPRDiff;
9
11
  exports.getCommitDiff = getCommitDiff;
10
12
  const simple_git_1 = __importDefault(require("simple-git"));
11
13
  const child_process_1 = require("child_process");
14
+ // =============================================================================
15
+ // CORE GIT OPERATIONS
16
+ // These functions use raw git commands and work reliably across all CI environments.
17
+ // They are the single source of truth for git information.
18
+ // =============================================================================
19
+ /**
20
+ * Get the repository URL from git remote origin.
21
+ *
22
+ * Uses `git remote get-url origin` which works in all environments,
23
+ * including shallow clones. This is more reliable than CI-specific
24
+ * environment variables as it reads directly from git config.
25
+ *
26
+ * @param repoRoot - Path to the repository root
27
+ * @returns Repository URL (e.g., "https://github.com/user/repo.git")
28
+ */
29
+ /**
30
+ * Sanitize a git remote URL by removing embedded credentials.
31
+ *
32
+ * CI environments often embed tokens in the remote URL for authentication:
33
+ * - GitLab CI: https://gitlab-ci-token:TOKEN@gitlab.com/user/repo
34
+ * - GitHub Actions: https://x-access-token:TOKEN@github.com/user/repo
35
+ *
36
+ * This function strips credentials to prevent token exposure in logs/UI.
37
+ */
38
+ function sanitizeRepoUrl(url) {
39
+ // Handle HTTPS URLs with credentials: https://user:pass@host/path
40
+ // The regex matches: protocol://anything@host/path and removes "anything@"
41
+ const sanitized = url.replace(/^(https?:\/\/)([^@]+@)/, '$1');
42
+ return sanitized;
43
+ }
44
+ async function getRepoUrl(repoRoot) {
45
+ try {
46
+ const url = (0, child_process_1.execSync)('git remote get-url origin', {
47
+ encoding: 'utf-8',
48
+ cwd: repoRoot
49
+ }).trim();
50
+ if (!url) {
51
+ throw new Error('Empty URL returned');
52
+ }
53
+ // Remove embedded credentials (CI tokens) from the URL
54
+ return sanitizeRepoUrl(url);
55
+ }
56
+ catch (error) {
57
+ const message = error instanceof Error ? error.message : String(error);
58
+ throw new Error(`Failed to get repository URL from git remote. ` +
59
+ `Ensure 'origin' remote is configured. Error: ${message}`);
60
+ }
61
+ }
62
+ /**
63
+ * Get the current HEAD commit SHA.
64
+ *
65
+ * Uses `git rev-parse HEAD` which works reliably in all environments,
66
+ * including shallow clones. This is the single source of truth for
67
+ * the current commit SHA.
68
+ *
69
+ * @param repoRoot - Path to the repository root
70
+ * @returns Full commit SHA (40 characters)
71
+ */
72
+ async function getHeadCommitSha(repoRoot) {
73
+ try {
74
+ const sha = (0, child_process_1.execSync)('git rev-parse HEAD', {
75
+ encoding: 'utf-8',
76
+ cwd: repoRoot
77
+ }).trim();
78
+ if (!sha || sha.length !== 40) {
79
+ throw new Error(`Invalid SHA returned: "${sha}"`);
80
+ }
81
+ return sha;
82
+ }
83
+ catch (error) {
84
+ const message = error instanceof Error ? error.message : String(error);
85
+ throw new Error(`Failed to get HEAD commit SHA: ${message}`);
86
+ }
87
+ }
12
88
  /**
13
89
  * Get commit message for a specific commit SHA
14
90
  * Returns full commit message (subject + body) or null if commit not found
package/dist/git/local.js CHANGED
@@ -30,9 +30,8 @@ async function getLocalContext(repoRoot, commitSha) {
30
30
  }
31
31
  // Get all Local context
32
32
  const diff = commitSha ? await (0, diff_1.getCommitDiff)(repoRoot, commitSha) : await getDiff(repoRoot);
33
- const repoName = await getRepoName(repoRoot);
33
+ const repoName = await (0, diff_1.getRepoUrl)(repoRoot); // Shared git command
34
34
  const branchName = await getBranchName(repoRoot);
35
- const context = commitSha ? { type: 'commit', commitSha } : { type: 'local' };
36
35
  const reviewContext = commitSha ? 'commit' : 'local';
37
36
  // Get commit author (fails loudly if unavailable)
38
37
  const commitAuthor = commitSha
@@ -54,7 +53,6 @@ async function getLocalContext(repoRoot, commitSha) {
54
53
  commitMessage,
55
54
  commitAuthor,
56
55
  prTitle: undefined, // Not applicable for local
57
- context,
58
56
  reviewContext
59
57
  };
60
58
  }
@@ -96,26 +94,9 @@ async function getDiff(repoRoot) {
96
94
  changedFiles
97
95
  };
98
96
  }
99
- /**
100
- * Gets repository name for local environment
101
- */
102
- async function getRepoName(repoRoot) {
103
- const git = (0, simple_git_1.default)(repoRoot);
104
- try {
105
- const remotes = await git.getRemotes(true);
106
- const origin = remotes.find(r => r.name === 'origin');
107
- if (!origin || !origin.refs?.fetch) {
108
- throw new Error('No origin remote found. Please set up a git remote named "origin".');
109
- }
110
- return origin.refs.fetch;
111
- }
112
- catch (error) {
113
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
114
- throw new Error(`Failed to get repository name from git remote: ${errorMessage}`);
115
- }
116
- }
117
97
  /**
118
98
  * Gets branch name for local environment
99
+ * (Uses git command directly - works in local because not in detached HEAD state)
119
100
  */
120
101
  async function getBranchName(repoRoot) {
121
102
  const git = (0, simple_git_1.default)(repoRoot);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "threadlines",
3
- "version": "0.2.16",
3
+ "version": "0.2.18",
4
4
  "description": "Threadlines CLI - AI-powered linter based on your natural language documentation",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -1,164 +0,0 @@
1
- "use strict";
2
- /**
3
- * Bitbucket Pipelines Environment
4
- *
5
- * All Bitbucket-specific logic is contained in this file.
6
- * No dependencies on other environment implementations.
7
- *
8
- * Exports a single function: getBitbucketContext() that returns:
9
- * - diff: GitDiffResult
10
- * - repoName: string
11
- * - branchName: string
12
- * - commitAuthor: { name: string; email: string }
13
- * - prTitle?: string (PR title - not available in Bitbucket env vars)
14
- *
15
- * Implementation Status (all tested 2026-01-18):
16
- * - ✅ Direct commit to main
17
- * - ✅ Feature branch push
18
- * - ✅ PR context
19
- */
20
- var __importDefault = (this && this.__importDefault) || function (mod) {
21
- return (mod && mod.__esModule) ? mod : { "default": mod };
22
- };
23
- Object.defineProperty(exports, "__esModule", { value: true });
24
- exports.getBitbucketContext = getBitbucketContext;
25
- const simple_git_1 = __importDefault(require("simple-git"));
26
- const diff_1 = require("./diff");
27
- const logger_1 = require("../utils/logger");
28
- /**
29
- * Gets all Bitbucket context in one call
30
- */
31
- async function getBitbucketContext(repoRoot) {
32
- const git = (0, simple_git_1.default)(repoRoot);
33
- // Check if we're in a git repo
34
- const isRepo = await git.checkIsRepo();
35
- if (!isRepo) {
36
- throw new Error('Not a git repository. Threadline requires a git repository.');
37
- }
38
- // Get all Bitbucket context
39
- const diff = await getDiff(repoRoot);
40
- const repoName = getRepoName();
41
- const branchName = getBranchName();
42
- const context = detectContext();
43
- const reviewContext = detectReviewContext();
44
- const commitSha = getCommitSha();
45
- // Get commit author using shared function (git log)
46
- // getCommitAuthor throws on failure with descriptive error
47
- const commitAuthor = await (0, diff_1.getCommitAuthor)(repoRoot);
48
- // Get commit message if we have a SHA
49
- let commitMessage;
50
- if (commitSha) {
51
- const message = await (0, diff_1.getCommitMessage)(repoRoot, commitSha);
52
- if (message) {
53
- commitMessage = message;
54
- }
55
- }
56
- return {
57
- diff,
58
- repoName,
59
- branchName,
60
- commitSha,
61
- commitMessage,
62
- commitAuthor,
63
- prTitle: undefined, // Bitbucket doesn't expose PR title as env var
64
- context,
65
- reviewContext
66
- };
67
- }
68
- /**
69
- * Get diff for Bitbucket Pipelines environment
70
- *
71
- * Strategy:
72
- * - PR context: Uses shared getPRDiff() - fetches destination branch, compares against HEAD
73
- * - Any push (main or feature branch): Compare last commit only (HEAD~1...HEAD)
74
- *
75
- * Note: We fetch the destination branch on-demand so this works with shallow clones.
76
- * Users don't need `depth: full` in their bitbucket-pipelines.yml.
77
- */
78
- async function getDiff(repoRoot) {
79
- const prId = process.env.BITBUCKET_PR_ID;
80
- const prDestinationBranch = process.env.BITBUCKET_PR_DESTINATION_BRANCH;
81
- // PR Context: Use shared getPRDiff() implementation
82
- if (prId) {
83
- if (!prDestinationBranch) {
84
- throw new Error('Bitbucket PR context detected but BITBUCKET_PR_DESTINATION_BRANCH is not set. ' +
85
- 'This should be automatically provided by Bitbucket Pipelines.');
86
- }
87
- return (0, diff_1.getPRDiff)(repoRoot, prDestinationBranch, logger_1.logger);
88
- }
89
- // Any push (main or feature branch): Review last commit only
90
- // Use shared getCommitDiff (defaults to HEAD)
91
- return (0, diff_1.getCommitDiff)(repoRoot);
92
- }
93
- /**
94
- * Gets repository name for Bitbucket Pipelines
95
- *
96
- * Uses BITBUCKET_REPO_FULL_NAME to construct the repo URL.
97
- * Example: ngrootscholten/threadline -> https://bitbucket.org/ngrootscholten/threadline.git
98
- */
99
- function getRepoName() {
100
- const repoFullName = process.env.BITBUCKET_REPO_FULL_NAME;
101
- if (!repoFullName) {
102
- throw new Error('Bitbucket Pipelines: BITBUCKET_REPO_FULL_NAME environment variable is not set. ' +
103
- 'This should be automatically provided by Bitbucket Pipelines.');
104
- }
105
- return `https://bitbucket.org/${repoFullName}.git`;
106
- }
107
- /**
108
- * Gets branch name for Bitbucket Pipelines
109
- */
110
- function getBranchName() {
111
- const branchName = process.env.BITBUCKET_BRANCH;
112
- if (!branchName) {
113
- throw new Error('Bitbucket Pipelines: BITBUCKET_BRANCH environment variable is not set. ' +
114
- 'This should be automatically provided by Bitbucket Pipelines.');
115
- }
116
- return branchName;
117
- }
118
- /**
119
- * Detects Bitbucket context (PR or commit)
120
- *
121
- * - PR context: When BITBUCKET_PR_ID is set
122
- * - Commit context: Any push (main or feature branch) - reviews single commit
123
- */
124
- function detectContext() {
125
- // PR context
126
- const prId = process.env.BITBUCKET_PR_ID;
127
- const prDestinationBranch = process.env.BITBUCKET_PR_DESTINATION_BRANCH;
128
- const sourceBranch = process.env.BITBUCKET_BRANCH;
129
- if (prId && prDestinationBranch && sourceBranch) {
130
- return {
131
- type: 'pr',
132
- prNumber: prId,
133
- sourceBranch,
134
- targetBranch: prDestinationBranch
135
- };
136
- }
137
- // Any push (main or feature branch) → commit context
138
- if (process.env.BITBUCKET_COMMIT) {
139
- return {
140
- type: 'commit',
141
- commitSha: process.env.BITBUCKET_COMMIT
142
- };
143
- }
144
- throw new Error('Bitbucket Pipelines: Could not detect context. ' +
145
- 'Expected BITBUCKET_PR_ID or BITBUCKET_COMMIT to be set. ' +
146
- 'This should be automatically provided by Bitbucket Pipelines.');
147
- }
148
- /**
149
- * Detects review context type for API (simple string type)
150
- */
151
- function detectReviewContext() {
152
- // PR context
153
- if (process.env.BITBUCKET_PR_ID) {
154
- return 'pr';
155
- }
156
- // Commit context (any push)
157
- return 'commit';
158
- }
159
- /**
160
- * Gets commit SHA from Bitbucket environment
161
- */
162
- function getCommitSha() {
163
- return process.env.BITBUCKET_COMMIT;
164
- }
@@ -1,184 +0,0 @@
1
- "use strict";
2
- /**
3
- * GitHub Actions Environment
4
- *
5
- * All GitHub-specific logic is contained in this file.
6
- * No dependencies on other environment implementations.
7
- *
8
- * Exports a single function: getGitHubContext() that returns:
9
- * - diff: GitDiffResult
10
- * - repoName: string
11
- * - branchName: string
12
- * - commitAuthor: { name: string; email: string }
13
- * - prTitle?: string
14
- */
15
- var __importDefault = (this && this.__importDefault) || function (mod) {
16
- return (mod && mod.__esModule) ? mod : { "default": mod };
17
- };
18
- Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.getGitHubContext = getGitHubContext;
20
- const simple_git_1 = __importDefault(require("simple-git"));
21
- const diff_1 = require("./diff");
22
- const logger_1 = require("../utils/logger");
23
- /**
24
- * Gets all GitHub context
25
- */
26
- async function getGitHubContext(repoRoot) {
27
- const git = (0, simple_git_1.default)(repoRoot);
28
- // Check if we're in a git repo
29
- const isRepo = await git.checkIsRepo();
30
- if (!isRepo) {
31
- throw new Error('Not a git repository. Threadline requires a git repository.');
32
- }
33
- // Get all GitHub context
34
- const diff = await getDiff(repoRoot);
35
- const repoName = await getRepoName();
36
- const branchName = await getBranchName();
37
- const context = detectContext();
38
- const reviewContext = detectReviewContext();
39
- const commitSha = getCommitSha(context);
40
- // Validate commit SHA is available (should always be set in GitHub Actions)
41
- if (!commitSha) {
42
- throw new Error('GitHub Actions: GITHUB_SHA environment variable is not set. ' +
43
- 'This should be automatically provided by GitHub Actions.');
44
- }
45
- // Get commit author using git commands (same approach as Bitbucket/Local)
46
- // getCommitAuthor throws on failure with descriptive error
47
- const commitAuthor = await (0, diff_1.getCommitAuthor)(repoRoot, commitSha);
48
- // Get commit message if we have a SHA
49
- let commitMessage;
50
- if (commitSha) {
51
- const message = await (0, diff_1.getCommitMessage)(repoRoot, commitSha);
52
- if (message) {
53
- commitMessage = message;
54
- }
55
- }
56
- // Get PR title if in PR context
57
- const prTitle = getPRTitle(context);
58
- return {
59
- diff,
60
- repoName,
61
- branchName,
62
- commitSha,
63
- commitMessage,
64
- commitAuthor,
65
- prTitle,
66
- context,
67
- reviewContext
68
- };
69
- }
70
- /**
71
- * Gets diff for GitHub Actions CI environment
72
- *
73
- * Strategy:
74
- * - PR context: Uses shared getPRDiff() - fetches base branch, compares against HEAD
75
- * - Any push (main or feature branch): Compare last commit only using git show HEAD
76
- *
77
- * Note: GitHub Actions does shallow clones by default (fetch-depth: 1), so we fetch
78
- * the base branch on-demand. HEAD points to the merge commit which contains all PR changes.
79
- */
80
- async function getDiff(repoRoot) {
81
- const eventName = process.env.GITHUB_EVENT_NAME;
82
- const baseRef = process.env.GITHUB_BASE_REF;
83
- // PR Context: Use shared getPRDiff() implementation
84
- if (eventName === 'pull_request') {
85
- if (!baseRef) {
86
- throw new Error('GitHub PR context detected but GITHUB_BASE_REF is missing. ' +
87
- 'This should be automatically provided by GitHub Actions.');
88
- }
89
- return (0, diff_1.getPRDiff)(repoRoot, baseRef, logger_1.logger);
90
- }
91
- // Any push (main or feature branch): Review last commit only
92
- // Use shared getCommitDiff (defaults to HEAD)
93
- return (0, diff_1.getCommitDiff)(repoRoot);
94
- }
95
- /**
96
- * Gets repository name for GitHub Actions
97
- */
98
- async function getRepoName() {
99
- const githubRepo = process.env.GITHUB_REPOSITORY;
100
- if (!githubRepo) {
101
- throw new Error('GitHub Actions: GITHUB_REPOSITORY environment variable is not set. ' +
102
- 'This should be automatically provided by GitHub Actions.');
103
- }
104
- const serverUrl = process.env.GITHUB_SERVER_URL || 'https://github.com';
105
- return `${serverUrl}/${githubRepo}.git`;
106
- }
107
- /**
108
- * Gets branch name for GitHub Actions
109
- */
110
- async function getBranchName() {
111
- const refName = process.env.GITHUB_REF_NAME;
112
- if (!refName) {
113
- throw new Error('GitHub Actions: GITHUB_REF_NAME environment variable is not set. ' +
114
- 'This should be automatically provided by GitHub Actions.');
115
- }
116
- return refName;
117
- }
118
- /**
119
- * Detects GitHub context (PR or commit)
120
- *
121
- * - PR context: When GITHUB_EVENT_NAME is 'pull_request'
122
- * - Commit context: Any push (main or feature branch) - reviews single commit
123
- */
124
- function detectContext() {
125
- // PR context
126
- if (process.env.GITHUB_EVENT_NAME === 'pull_request') {
127
- const targetBranch = process.env.GITHUB_BASE_REF;
128
- const sourceBranch = process.env.GITHUB_HEAD_REF;
129
- const prNumber = process.env.GITHUB_EVENT_PULL_REQUEST_NUMBER || process.env.GITHUB_EVENT_NUMBER;
130
- if (targetBranch && sourceBranch && prNumber) {
131
- return {
132
- type: 'pr',
133
- prNumber,
134
- sourceBranch,
135
- targetBranch
136
- };
137
- }
138
- }
139
- // Any push (main or feature branch) → commit context
140
- if (process.env.GITHUB_SHA) {
141
- return {
142
- type: 'commit',
143
- commitSha: process.env.GITHUB_SHA
144
- };
145
- }
146
- throw new Error('GitHub Actions: Could not detect context. ' +
147
- 'Expected GITHUB_EVENT_NAME="pull_request" or GITHUB_SHA to be set. ' +
148
- 'This should be automatically provided by GitHub Actions.');
149
- }
150
- /**
151
- * Gets commit SHA from context
152
- */
153
- function getCommitSha(context) {
154
- if (context.type === 'commit') {
155
- return context.commitSha;
156
- }
157
- if (context.type === 'pr') {
158
- return process.env.GITHUB_SHA;
159
- }
160
- return undefined;
161
- }
162
- /**
163
- * Detects review context type for API (simple string type)
164
- */
165
- function detectReviewContext() {
166
- // PR context
167
- if (process.env.GITHUB_EVENT_NAME === 'pull_request') {
168
- return 'pr';
169
- }
170
- // Commit context (any push)
171
- return 'commit';
172
- }
173
- /**
174
- * Gets PR title for GitHub Actions
175
- * Note: GitHub Actions doesn't provide PR title as an env var by default.
176
- * It would need to be passed from the workflow YAML or fetched via API.
177
- */
178
- function getPRTitle(context) {
179
- if (context.type !== 'pr') {
180
- return undefined;
181
- }
182
- // Only if passed from workflow: PR_TITLE: ${{ github.event.pull_request.title }}
183
- return process.env.PR_TITLE;
184
- }
@@ -1,176 +0,0 @@
1
- "use strict";
2
- /**
3
- * GitLab CI Environment
4
- *
5
- * All GitLab-specific logic is contained in this file.
6
- * No dependencies on other environment implementations.
7
- *
8
- * Exports a single function: getGitLabContext() that returns:
9
- * - diff: GitDiffResult
10
- * - repoName: string
11
- * - branchName: string
12
- * - commitAuthor: { name: string; email: string }
13
- * - prTitle?: string (MR title)
14
- */
15
- var __importDefault = (this && this.__importDefault) || function (mod) {
16
- return (mod && mod.__esModule) ? mod : { "default": mod };
17
- };
18
- Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.getGitLabContext = getGitLabContext;
20
- const simple_git_1 = __importDefault(require("simple-git"));
21
- const diff_1 = require("./diff");
22
- const logger_1 = require("../utils/logger");
23
- /**
24
- * Gets all GitLab context
25
- */
26
- async function getGitLabContext(repoRoot) {
27
- const git = (0, simple_git_1.default)(repoRoot);
28
- // Check if we're in a git repo
29
- const isRepo = await git.checkIsRepo();
30
- if (!isRepo) {
31
- throw new Error('Not a git repository. Threadline requires a git repository.');
32
- }
33
- // Get all GitLab context
34
- const diff = await getDiff(repoRoot);
35
- const repoName = await getRepoName();
36
- const branchName = await getBranchName();
37
- const context = detectContext();
38
- const reviewContext = detectReviewContext();
39
- const commitSha = getCommitSha(context);
40
- // Get commit author using shared function (git log)
41
- // getCommitAuthor throws on failure with descriptive error
42
- const commitAuthor = await (0, diff_1.getCommitAuthor)(repoRoot);
43
- // Get commit message if we have a SHA
44
- let commitMessage;
45
- if (commitSha) {
46
- const message = await (0, diff_1.getCommitMessage)(repoRoot, commitSha);
47
- if (message) {
48
- commitMessage = message;
49
- }
50
- }
51
- // Get MR title if in MR context
52
- const prTitle = getMRTitle(context);
53
- return {
54
- diff,
55
- repoName,
56
- branchName,
57
- commitSha,
58
- commitMessage,
59
- commitAuthor,
60
- prTitle,
61
- context,
62
- reviewContext
63
- };
64
- }
65
- /**
66
- * Get diff for GitLab CI environment
67
- *
68
- * Strategy:
69
- * - MR context: Uses shared getPRDiff() - fetches target branch, compares against HEAD
70
- * - Any push (main or feature branch): Compare last commit only (HEAD~1...HEAD)
71
- *
72
- * Note: GitLab CI does a shallow clone, so we fetch the target branch for MR context.
73
- * For regular pushes, HEAD~1...HEAD works without additional fetching.
74
- */
75
- async function getDiff(repoRoot) {
76
- const mrIid = process.env.CI_MERGE_REQUEST_IID;
77
- const targetBranch = process.env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME;
78
- // MR Context: Use shared getPRDiff() implementation
79
- if (mrIid) {
80
- if (!targetBranch) {
81
- throw new Error('GitLab MR context detected but CI_MERGE_REQUEST_TARGET_BRANCH_NAME is missing. ' +
82
- 'This should be automatically provided by GitLab CI.');
83
- }
84
- return (0, diff_1.getPRDiff)(repoRoot, targetBranch, logger_1.logger);
85
- }
86
- // Any push (main or feature branch): Review last commit only
87
- // Use shared getCommitDiff (defaults to HEAD)
88
- return (0, diff_1.getCommitDiff)(repoRoot);
89
- }
90
- /**
91
- * Gets repository name for GitLab CI
92
- */
93
- async function getRepoName() {
94
- const projectUrl = process.env.CI_PROJECT_URL;
95
- if (!projectUrl) {
96
- throw new Error('GitLab CI: CI_PROJECT_URL environment variable is not set. ' +
97
- 'This should be automatically provided by GitLab CI.');
98
- }
99
- return `${projectUrl}.git`;
100
- }
101
- /**
102
- * Gets branch name for GitLab CI
103
- */
104
- async function getBranchName() {
105
- const refName = process.env.CI_COMMIT_REF_NAME;
106
- if (!refName) {
107
- throw new Error('GitLab CI: CI_COMMIT_REF_NAME environment variable is not set. ' +
108
- 'This should be automatically provided by GitLab CI.');
109
- }
110
- return refName;
111
- }
112
- /**
113
- * Detects GitLab context (MR or commit)
114
- *
115
- * - MR context: When CI_MERGE_REQUEST_IID is set
116
- * - Commit context: Any push (main or feature branch) - reviews single commit
117
- */
118
- function detectContext() {
119
- // MR context
120
- const mrIid = process.env.CI_MERGE_REQUEST_IID;
121
- if (mrIid) {
122
- const targetBranch = process.env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME;
123
- const sourceBranch = process.env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME;
124
- if (targetBranch && sourceBranch) {
125
- return {
126
- type: 'mr',
127
- mrNumber: mrIid,
128
- sourceBranch,
129
- targetBranch
130
- };
131
- }
132
- }
133
- // Any push (main or feature branch) → commit context
134
- if (process.env.CI_COMMIT_SHA) {
135
- return {
136
- type: 'commit',
137
- commitSha: process.env.CI_COMMIT_SHA
138
- };
139
- }
140
- throw new Error('GitLab CI: Could not detect context. ' +
141
- 'Expected CI_MERGE_REQUEST_IID or CI_COMMIT_SHA to be set. ' +
142
- 'This should be automatically provided by GitLab CI.');
143
- }
144
- /**
145
- * Detects review context type for API (simple string type)
146
- */
147
- function detectReviewContext() {
148
- // MR context
149
- if (process.env.CI_MERGE_REQUEST_IID) {
150
- return 'pr';
151
- }
152
- // Commit context (any push)
153
- return 'commit';
154
- }
155
- /**
156
- * Gets commit SHA from context
157
- */
158
- function getCommitSha(context) {
159
- if (context.type === 'commit') {
160
- return context.commitSha;
161
- }
162
- if (context.type === 'mr') {
163
- return process.env.CI_COMMIT_SHA;
164
- }
165
- return undefined;
166
- }
167
- /**
168
- * Gets MR title for GitLab CI
169
- */
170
- function getMRTitle(context) {
171
- if (context.type !== 'mr') {
172
- return undefined;
173
- }
174
- // GitLab CI provides MR title as env var
175
- return process.env.CI_MERGE_REQUEST_TITLE;
176
- }
@@ -1,102 +0,0 @@
1
- "use strict";
2
- /**
3
- * Vercel Environment
4
- *
5
- * All Vercel-specific logic is contained in this file.
6
- * No dependencies on other environment implementations.
7
- *
8
- * Exports a single function: getVercelContext() that returns:
9
- * - diff: GitDiffResult
10
- * - repoName: string
11
- * - branchName: string
12
- * - commitAuthor: { name: string; email: string }
13
- */
14
- var __importDefault = (this && this.__importDefault) || function (mod) {
15
- return (mod && mod.__esModule) ? mod : { "default": mod };
16
- };
17
- Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.getVercelContext = getVercelContext;
19
- const simple_git_1 = __importDefault(require("simple-git"));
20
- const diff_1 = require("./diff");
21
- /**
22
- * Gets all Vercel context
23
- */
24
- async function getVercelContext(repoRoot) {
25
- const git = (0, simple_git_1.default)(repoRoot);
26
- // Check if we're in a git repo
27
- const isRepo = await git.checkIsRepo();
28
- if (!isRepo) {
29
- throw new Error('Not a git repository. Threadline requires a git repository.');
30
- }
31
- // Get all Vercel context
32
- const diff = await getDiff(repoRoot);
33
- const repoName = await getRepoName();
34
- const branchName = await getBranchName();
35
- const commitSha = getCommitSha();
36
- const context = { type: 'commit', commitSha };
37
- const reviewContext = 'commit';
38
- // Get commit author using shared function (git log)
39
- // getCommitAuthor throws on failure with descriptive error
40
- const commitAuthor = await (0, diff_1.getCommitAuthor)(repoRoot, commitSha);
41
- // Get commit message
42
- let commitMessage;
43
- const message = await (0, diff_1.getCommitMessage)(repoRoot, commitSha);
44
- if (message) {
45
- commitMessage = message;
46
- }
47
- return {
48
- diff,
49
- repoName,
50
- branchName,
51
- commitSha,
52
- commitMessage,
53
- commitAuthor,
54
- context,
55
- reviewContext
56
- };
57
- }
58
- /**
59
- * Get diff for Vercel CI environment
60
- *
61
- * Vercel only supports commit context (no PRs).
62
- * Uses shared getCommitDiff with HEAD (which equals VERCEL_GIT_COMMIT_SHA).
63
- */
64
- async function getDiff(repoRoot) {
65
- // Use shared getCommitDiff (defaults to HEAD)
66
- // In Vercel, HEAD is the commit being deployed
67
- return (0, diff_1.getCommitDiff)(repoRoot);
68
- }
69
- /**
70
- * Gets repository name for Vercel
71
- */
72
- async function getRepoName() {
73
- const owner = process.env.VERCEL_GIT_REPO_OWNER;
74
- const slug = process.env.VERCEL_GIT_REPO_SLUG;
75
- if (!owner || !slug) {
76
- throw new Error('Vercel: VERCEL_GIT_REPO_OWNER or VERCEL_GIT_REPO_SLUG environment variable is not set. ' +
77
- 'This should be automatically provided by Vercel CI.');
78
- }
79
- return `https://github.com/${owner}/${slug}.git`;
80
- }
81
- /**
82
- * Gets branch name for Vercel
83
- */
84
- async function getBranchName() {
85
- const branchName = process.env.VERCEL_GIT_COMMIT_REF;
86
- if (!branchName) {
87
- throw new Error('Vercel: VERCEL_GIT_COMMIT_REF environment variable is not set. ' +
88
- 'This should be automatically provided by Vercel CI.');
89
- }
90
- return branchName;
91
- }
92
- /**
93
- * Gets commit SHA for Vercel
94
- */
95
- function getCommitSha() {
96
- const commitSha = process.env.VERCEL_GIT_COMMIT_SHA;
97
- if (!commitSha) {
98
- throw new Error('Vercel: VERCEL_GIT_COMMIT_SHA environment variable is not set. ' +
99
- 'This should be automatically provided by Vercel CI.');
100
- }
101
- return commitSha;
102
- }
@@ -1,10 +0,0 @@
1
- "use strict";
2
- /**
3
- * Review Context Types
4
- *
5
- * Type definitions for the different code review contexts:
6
- * - PR/MR: Comparing source branch vs target branch (branch-level diff)
7
- * - Commit: Single commit changes (any push without PR/MR)
8
- * - Local: Staged/unstaged changes in working directory
9
- */
10
- Object.defineProperty(exports, "__esModule", { value: true });