threadlines 0.2.12 → 0.2.14
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 +167 -166
- package/dist/git/bitbucket.js +14 -1
- package/dist/git/file.js +17 -11
- package/dist/git/github.js +14 -1
- package/dist/git/gitlab.js +14 -1
- package/dist/git/local.js +3 -1
- package/dist/git/vercel.js +3 -1
- package/package.json +1 -1
package/dist/commands/check.js
CHANGED
|
@@ -47,7 +47,6 @@ const gitlab_1 = require("../git/gitlab");
|
|
|
47
47
|
const bitbucket_1 = require("../git/bitbucket");
|
|
48
48
|
const vercel_1 = require("../git/vercel");
|
|
49
49
|
const local_1 = require("../git/local");
|
|
50
|
-
const diff_1 = require("../git/diff");
|
|
51
50
|
const config_file_1 = require("../utils/config-file");
|
|
52
51
|
const logger_1 = require("../utils/logger");
|
|
53
52
|
const fs = __importStar(require("fs"));
|
|
@@ -68,8 +67,11 @@ async function getContextForEnvironment(environment, repoRoot, commitSha) {
|
|
|
68
67
|
return (0, bitbucket_1.getBitbucketContext)(repoRoot);
|
|
69
68
|
case 'vercel':
|
|
70
69
|
return (0, vercel_1.getVercelContext)(repoRoot);
|
|
71
|
-
|
|
70
|
+
case 'local':
|
|
72
71
|
return (0, local_1.getLocalContext)(repoRoot, commitSha);
|
|
72
|
+
default:
|
|
73
|
+
// TypeScript exhaustiveness check - should never happen
|
|
74
|
+
throw new Error(`Unrecognized environment: ${environment}`);
|
|
73
75
|
}
|
|
74
76
|
}
|
|
75
77
|
// Get CLI version from package.json
|
|
@@ -93,18 +95,20 @@ async function checkCommand(options) {
|
|
|
93
95
|
}
|
|
94
96
|
gitRoot = (await git.revparse(['--show-toplevel'])).trim();
|
|
95
97
|
}
|
|
96
|
-
catch {
|
|
97
|
-
|
|
98
|
+
catch (error) {
|
|
99
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
100
|
+
logger_1.logger.error(`Failed to get git root: ${message}`);
|
|
98
101
|
process.exit(1);
|
|
99
102
|
}
|
|
100
103
|
// Pre-flight check: Validate ALL required environment variables at once
|
|
101
104
|
const apiKey = (0, config_1.getThreadlineApiKey)();
|
|
102
105
|
const account = (0, config_1.getThreadlineAccount)();
|
|
103
106
|
const missingVars = [];
|
|
104
|
-
// Check for undefined, empty string, or literal unexpanded variable
|
|
105
|
-
if
|
|
107
|
+
// Check for undefined, empty string, or literal unexpanded variable
|
|
108
|
+
// GitLab CI keeps variables as literal "$VAR" if not defined in CI/CD settings
|
|
109
|
+
if (!apiKey || apiKey === '$THREADLINE_API_KEY')
|
|
106
110
|
missingVars.push('THREADLINE_API_KEY');
|
|
107
|
-
if (!account || account
|
|
111
|
+
if (!account || account === '$THREADLINE_ACCOUNT')
|
|
108
112
|
missingVars.push('THREADLINE_ACCOUNT');
|
|
109
113
|
if (missingVars.length > 0) {
|
|
110
114
|
logger_1.logger.error('Missing required environment variables:');
|
|
@@ -134,170 +138,171 @@ async function checkCommand(options) {
|
|
|
134
138
|
console.log(chalk_1.default.gray('Get your credentials at: https://devthreadline.com/settings'));
|
|
135
139
|
process.exit(1);
|
|
136
140
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
141
|
+
// 1. Find and validate threadlines
|
|
142
|
+
logger_1.logger.info('Finding threadlines...');
|
|
143
|
+
const threadlines = await (0, experts_1.findThreadlines)(cwd, gitRoot);
|
|
144
|
+
console.log(chalk_1.default.green(`✓ Found ${threadlines.length} threadline(s)\n`));
|
|
145
|
+
if (threadlines.length === 0) {
|
|
146
|
+
console.log(chalk_1.default.yellow('⚠️ No valid threadlines found.'));
|
|
147
|
+
console.log(chalk_1.default.gray(' Run `npx threadlines init` to create your first threadline.'));
|
|
148
|
+
process.exit(0);
|
|
149
|
+
}
|
|
150
|
+
// 2. Detect environment and context
|
|
151
|
+
const environment = (0, environment_1.detectEnvironment)();
|
|
152
|
+
let gitDiff;
|
|
153
|
+
let repoName;
|
|
154
|
+
let branchName;
|
|
155
|
+
let reviewContext;
|
|
156
|
+
let metadata = {};
|
|
157
|
+
// Check for explicit flags
|
|
158
|
+
const explicitFlags = [options.commit, options.file, options.folder, options.files].filter(Boolean);
|
|
159
|
+
// Validate mutually exclusive flags
|
|
160
|
+
if (explicitFlags.length > 1) {
|
|
161
|
+
logger_1.logger.error('Only one review option can be specified at a time');
|
|
162
|
+
console.log(chalk_1.default.gray(' Options: --commit, --file, --folder, --files'));
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
// CI environments: auto-detect only, flags are ignored with warning
|
|
166
|
+
// Local: full flag support for developer flexibility
|
|
167
|
+
if ((0, environment_1.isCIEnvironment)(environment)) {
|
|
168
|
+
// Warn if flags are passed in CI - they're meant for local development
|
|
169
|
+
if (explicitFlags.length > 0) {
|
|
170
|
+
const flagName = options.commit ? '--commit' :
|
|
171
|
+
options.file ? '--file' :
|
|
172
|
+
options.folder ? '--folder' : '--files';
|
|
173
|
+
logger_1.logger.warn(`${flagName} flag ignored in CI environment. Using auto-detection.`);
|
|
146
174
|
}
|
|
147
|
-
//
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
175
|
+
// CI auto-detect: use environment-specific context
|
|
176
|
+
const envNames = {
|
|
177
|
+
vercel: 'Vercel',
|
|
178
|
+
github: 'GitHub Actions',
|
|
179
|
+
gitlab: 'GitLab CI',
|
|
180
|
+
bitbucket: 'Bitbucket Pipelines'
|
|
181
|
+
};
|
|
182
|
+
logger_1.logger.info(`Collecting git context for ${envNames[environment]}...`);
|
|
183
|
+
const envContext = await getContextForEnvironment(environment, repoRoot);
|
|
184
|
+
gitDiff = envContext.diff;
|
|
185
|
+
repoName = envContext.repoName;
|
|
186
|
+
branchName = envContext.branchName;
|
|
187
|
+
reviewContext = envContext.reviewContext; // Get from CI context
|
|
188
|
+
metadata = {
|
|
189
|
+
commitSha: envContext.commitSha,
|
|
190
|
+
commitMessage: envContext.commitMessage,
|
|
191
|
+
commitAuthorName: envContext.commitAuthor.name,
|
|
192
|
+
commitAuthorEmail: envContext.commitAuthor.email,
|
|
193
|
+
prTitle: envContext.prTitle
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
// Local environment: all flags share the same metadata
|
|
198
|
+
// 1. Get context and metadata (pass commit SHA if provided)
|
|
199
|
+
logger_1.logger.info('Collecting local context...');
|
|
200
|
+
const localContext = await (0, local_1.getLocalContext)(repoRoot, options.commit);
|
|
201
|
+
repoName = localContext.repoName;
|
|
202
|
+
branchName = localContext.branchName;
|
|
203
|
+
metadata = {
|
|
204
|
+
commitSha: localContext.commitSha,
|
|
205
|
+
commitMessage: localContext.commitMessage,
|
|
206
|
+
commitAuthorName: localContext.commitAuthor.name,
|
|
207
|
+
commitAuthorEmail: localContext.commitAuthor.email
|
|
208
|
+
};
|
|
209
|
+
// 2. Get diff (override with specific content if flag provided)
|
|
210
|
+
if (options.file) {
|
|
211
|
+
reviewContext = 'file';
|
|
212
|
+
logger_1.logger.info(`Reading file: ${options.file}...`);
|
|
213
|
+
gitDiff = await (0, file_1.getFileContent)(repoRoot, options.file);
|
|
160
214
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (explicitFlags.length > 0) {
|
|
166
|
-
const flagName = options.commit ? '--commit' :
|
|
167
|
-
options.file ? '--file' :
|
|
168
|
-
options.folder ? '--folder' : '--files';
|
|
169
|
-
logger_1.logger.warn(`${flagName} flag ignored in CI environment. Using auto-detection.`);
|
|
170
|
-
}
|
|
171
|
-
// CI auto-detect: use environment-specific context
|
|
172
|
-
const envNames = {
|
|
173
|
-
vercel: 'Vercel',
|
|
174
|
-
github: 'GitHub Actions',
|
|
175
|
-
gitlab: 'GitLab CI',
|
|
176
|
-
bitbucket: 'Bitbucket Pipelines'
|
|
177
|
-
};
|
|
178
|
-
logger_1.logger.info(`Collecting git context for ${envNames[environment]}...`);
|
|
179
|
-
const envContext = await getContextForEnvironment(environment, repoRoot);
|
|
180
|
-
gitDiff = envContext.diff;
|
|
181
|
-
repoName = envContext.repoName;
|
|
182
|
-
branchName = envContext.branchName;
|
|
183
|
-
metadata = {
|
|
184
|
-
commitSha: envContext.commitSha,
|
|
185
|
-
commitMessage: envContext.commitMessage,
|
|
186
|
-
commitAuthorName: envContext.commitAuthor.name,
|
|
187
|
-
commitAuthorEmail: envContext.commitAuthor.email,
|
|
188
|
-
prTitle: envContext.prTitle
|
|
189
|
-
};
|
|
215
|
+
else if (options.folder) {
|
|
216
|
+
reviewContext = 'folder';
|
|
217
|
+
logger_1.logger.info(`Reading folder: ${options.folder}...`);
|
|
218
|
+
gitDiff = await (0, file_1.getFolderContent)(repoRoot, options.folder);
|
|
190
219
|
}
|
|
191
|
-
else {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
gitDiff = await (0, file_1.getFileContent)(repoRoot, options.file);
|
|
196
|
-
}
|
|
197
|
-
else if (options.folder) {
|
|
198
|
-
logger_1.logger.info(`Reading folder: ${options.folder}...`);
|
|
199
|
-
gitDiff = await (0, file_1.getFolderContent)(repoRoot, options.folder);
|
|
200
|
-
}
|
|
201
|
-
else if (options.files && options.files.length > 0) {
|
|
202
|
-
logger_1.logger.info(`Reading ${options.files.length} file(s)...`);
|
|
203
|
-
gitDiff = await (0, file_1.getMultipleFilesContent)(repoRoot, options.files);
|
|
204
|
-
}
|
|
205
|
-
else if (options.commit) {
|
|
206
|
-
logger_1.logger.info(`Collecting git changes for commit: ${options.commit}...`);
|
|
207
|
-
gitDiff = await (0, diff_1.getCommitDiff)(repoRoot, options.commit);
|
|
208
|
-
// Use local context for metadata, passing commit SHA for author lookup
|
|
209
|
-
const localContext = await (0, local_1.getLocalContext)(repoRoot, options.commit);
|
|
210
|
-
repoName = localContext.repoName;
|
|
211
|
-
branchName = localContext.branchName;
|
|
212
|
-
metadata = {
|
|
213
|
-
commitSha: localContext.commitSha,
|
|
214
|
-
commitMessage: localContext.commitMessage,
|
|
215
|
-
commitAuthorName: localContext.commitAuthor.name,
|
|
216
|
-
commitAuthorEmail: localContext.commitAuthor.email
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
// Local auto-detect: staged/unstaged changes
|
|
221
|
-
logger_1.logger.info('Collecting git context for Local...');
|
|
222
|
-
const localContext = await (0, local_1.getLocalContext)(repoRoot);
|
|
223
|
-
gitDiff = localContext.diff;
|
|
224
|
-
repoName = localContext.repoName;
|
|
225
|
-
branchName = localContext.branchName;
|
|
226
|
-
metadata = {
|
|
227
|
-
commitSha: localContext.commitSha,
|
|
228
|
-
commitMessage: localContext.commitMessage,
|
|
229
|
-
commitAuthorName: localContext.commitAuthor.name,
|
|
230
|
-
commitAuthorEmail: localContext.commitAuthor.email
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
if (gitDiff.changedFiles.length === 0) {
|
|
235
|
-
console.error(chalk_1.default.bold('ℹ️ No changes detected.'));
|
|
236
|
-
process.exit(0);
|
|
220
|
+
else if (options.files && options.files.length > 0) {
|
|
221
|
+
reviewContext = 'files';
|
|
222
|
+
logger_1.logger.info(`Reading ${options.files.length} file(s)...`);
|
|
223
|
+
gitDiff = await (0, file_1.getMultipleFilesContent)(repoRoot, options.files);
|
|
237
224
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
console.log('');
|
|
243
|
-
console.log(chalk_1.default.bold('Results:\n'));
|
|
244
|
-
console.log(chalk_1.default.gray(`${threadlines.length} threadlines checked`));
|
|
245
|
-
console.log(chalk_1.default.gray(` ${threadlines.length} not relevant`));
|
|
246
|
-
console.log('');
|
|
247
|
-
process.exit(0);
|
|
225
|
+
else {
|
|
226
|
+
// Default: use diff from localContext (handles commit and staged/unstaged)
|
|
227
|
+
reviewContext = options.commit ? 'commit' : 'local';
|
|
228
|
+
gitDiff = localContext.diff;
|
|
248
229
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
230
|
+
}
|
|
231
|
+
if (gitDiff.changedFiles.length === 0) {
|
|
232
|
+
console.error(chalk_1.default.bold('ℹ️ No changes detected.'));
|
|
233
|
+
process.exit(0);
|
|
234
|
+
}
|
|
235
|
+
// Check for zero diff (files changed but no actual code changes)
|
|
236
|
+
if (!gitDiff.diff || gitDiff.diff.trim() === '') {
|
|
237
|
+
console.log(chalk_1.default.blue('ℹ️ No code changes detected. Diff contains zero lines added or removed.'));
|
|
238
|
+
console.log(chalk_1.default.gray(` ${gitDiff.changedFiles.length} file(s) changed but no content modifications detected.`));
|
|
239
|
+
console.log('');
|
|
240
|
+
console.log(chalk_1.default.bold('Results:\n'));
|
|
241
|
+
console.log(chalk_1.default.gray(`${threadlines.length} threadlines checked`));
|
|
242
|
+
console.log(chalk_1.default.gray(` ${threadlines.length} not relevant`));
|
|
243
|
+
console.log('');
|
|
244
|
+
process.exit(0);
|
|
245
|
+
}
|
|
246
|
+
console.log(chalk_1.default.green(`✓ Found ${gitDiff.changedFiles.length} changed file(s)\n`));
|
|
247
|
+
// Log the files being sent
|
|
248
|
+
for (const file of gitDiff.changedFiles) {
|
|
249
|
+
logger_1.logger.info(` → ${file}`);
|
|
250
|
+
}
|
|
251
|
+
// 4. Read context files for each threadline
|
|
252
|
+
const threadlinesWithContext = threadlines.map(threadline => {
|
|
253
|
+
const contextContent = {};
|
|
254
|
+
if (threadline.contextFiles) {
|
|
255
|
+
for (const contextFile of threadline.contextFiles) {
|
|
256
|
+
const fullPath = path.join(repoRoot, contextFile);
|
|
257
|
+
if (fs.existsSync(fullPath)) {
|
|
258
|
+
try {
|
|
257
259
|
contextContent[contextFile] = fs.readFileSync(fullPath, 'utf-8');
|
|
258
260
|
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
263
|
+
throw new Error(`Failed to read context file '${contextFile}' for threadline '${threadline.id}': ${message}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
throw new Error(`Context file not found for threadline '${threadline.id}': ${contextFile}`);
|
|
259
268
|
}
|
|
260
269
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
298
|
-
logger_1.logger.error(errorMessage);
|
|
299
|
-
process.exit(1);
|
|
300
|
-
}
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
id: threadline.id,
|
|
273
|
+
version: threadline.version,
|
|
274
|
+
patterns: threadline.patterns,
|
|
275
|
+
content: threadline.content,
|
|
276
|
+
filePath: threadline.filePath,
|
|
277
|
+
contextFiles: threadline.contextFiles,
|
|
278
|
+
contextContent
|
|
279
|
+
};
|
|
280
|
+
});
|
|
281
|
+
// 5. Call review API
|
|
282
|
+
logger_1.logger.info('Running threadline checks...');
|
|
283
|
+
const client = new client_1.ReviewAPIClient(config.api_url);
|
|
284
|
+
const response = await client.review({
|
|
285
|
+
threadlines: threadlinesWithContext,
|
|
286
|
+
diff: gitDiff.diff,
|
|
287
|
+
files: gitDiff.changedFiles,
|
|
288
|
+
apiKey: apiKey,
|
|
289
|
+
account: account,
|
|
290
|
+
repoName: repoName,
|
|
291
|
+
branchName: branchName,
|
|
292
|
+
commitSha: metadata.commitSha,
|
|
293
|
+
commitMessage: metadata.commitMessage,
|
|
294
|
+
commitAuthorName: metadata.commitAuthorName,
|
|
295
|
+
commitAuthorEmail: metadata.commitAuthorEmail,
|
|
296
|
+
prTitle: metadata.prTitle,
|
|
297
|
+
environment: environment,
|
|
298
|
+
cliVersion: CLI_VERSION,
|
|
299
|
+
reviewContext: reviewContext
|
|
300
|
+
});
|
|
301
|
+
// 7. Display results (with filtering if --full not specified)
|
|
302
|
+
displayResults(response, options.full || false);
|
|
303
|
+
// Exit with appropriate code (attention or errors = failure)
|
|
304
|
+
const hasIssues = response.results.some(r => r.status === 'attention' || r.status === 'error');
|
|
305
|
+
process.exit(hasIssues ? 1 : 0);
|
|
301
306
|
}
|
|
302
307
|
function displayResults(response, showFull) {
|
|
303
308
|
const { results, metadata, message } = response;
|
|
@@ -410,10 +415,6 @@ function displayResults(response, showFull) {
|
|
|
410
415
|
console.log(chalk_1.default.gray(JSON.stringify(item.error.rawResponse, null, 2).split('\n').map(line => ' ' + line).join('\n')));
|
|
411
416
|
}
|
|
412
417
|
}
|
|
413
|
-
else if (item.reasoning) {
|
|
414
|
-
// Fallback to reasoning if no error object
|
|
415
|
-
console.log(chalk_1.default.red(` ${item.reasoning}`));
|
|
416
|
-
}
|
|
417
418
|
console.log(''); // Empty line between errors
|
|
418
419
|
}
|
|
419
420
|
}
|
package/dist/git/bitbucket.js
CHANGED
|
@@ -41,6 +41,7 @@ async function getBitbucketContext(repoRoot) {
|
|
|
41
41
|
const repoName = getRepoName();
|
|
42
42
|
const branchName = getBranchName();
|
|
43
43
|
const context = detectContext();
|
|
44
|
+
const reviewContext = detectReviewContext();
|
|
44
45
|
const commitSha = getCommitSha();
|
|
45
46
|
// Get commit author (from git log - Bitbucket doesn't provide this as env var)
|
|
46
47
|
const commitAuthor = await getCommitAuthor(repoRoot);
|
|
@@ -60,7 +61,8 @@ async function getBitbucketContext(repoRoot) {
|
|
|
60
61
|
commitMessage,
|
|
61
62
|
commitAuthor,
|
|
62
63
|
prTitle: undefined, // Bitbucket doesn't expose PR title as env var
|
|
63
|
-
context
|
|
64
|
+
context,
|
|
65
|
+
reviewContext
|
|
64
66
|
};
|
|
65
67
|
}
|
|
66
68
|
/**
|
|
@@ -153,6 +155,17 @@ function detectContext() {
|
|
|
153
155
|
'Expected BITBUCKET_PR_ID or BITBUCKET_COMMIT to be set. ' +
|
|
154
156
|
'This should be automatically provided by Bitbucket Pipelines.');
|
|
155
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Detects review context type for API (simple string type)
|
|
160
|
+
*/
|
|
161
|
+
function detectReviewContext() {
|
|
162
|
+
// PR context
|
|
163
|
+
if (process.env.BITBUCKET_PR_ID) {
|
|
164
|
+
return 'pr';
|
|
165
|
+
}
|
|
166
|
+
// Commit context (any push)
|
|
167
|
+
return 'commit';
|
|
168
|
+
}
|
|
156
169
|
/**
|
|
157
170
|
* Gets commit SHA from Bitbucket environment
|
|
158
171
|
*/
|
package/dist/git/file.js
CHANGED
|
@@ -56,15 +56,17 @@ async function getFileContent(repoRoot, filePath) {
|
|
|
56
56
|
}
|
|
57
57
|
// Read file content
|
|
58
58
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
59
|
+
// Normalize path to forward slashes for cross-platform consistency (git uses forward slashes)
|
|
60
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
59
61
|
// Create artificial diff (all lines as additions)
|
|
60
62
|
const lines = content.split('\n');
|
|
61
63
|
const diff = lines.map((line) => `+${line}`).join('\n');
|
|
62
|
-
// Add diff header
|
|
63
|
-
const diffHeader =
|
|
64
|
+
// Add git diff header (matches format expected by server's filterDiffByFiles)
|
|
65
|
+
const diffHeader = `diff --git a/${normalizedPath} b/${normalizedPath}\n--- /dev/null\n+++ b/${normalizedPath}\n@@ -0,0 +1,${lines.length} @@\n`;
|
|
64
66
|
const fullDiff = diffHeader + diff;
|
|
65
67
|
return {
|
|
66
68
|
diff: fullDiff,
|
|
67
|
-
changedFiles: [
|
|
69
|
+
changedFiles: [normalizedPath]
|
|
68
70
|
};
|
|
69
71
|
}
|
|
70
72
|
/**
|
|
@@ -81,8 +83,8 @@ async function getFolderContent(repoRoot, folderPath) {
|
|
|
81
83
|
if (!stats.isDirectory()) {
|
|
82
84
|
throw new Error(`Path '${folderPath}' is not a folder`);
|
|
83
85
|
}
|
|
84
|
-
// Find all files recursively
|
|
85
|
-
const pattern = path.join(fullPath, '**', '*');
|
|
86
|
+
// Find all files recursively (normalize to forward slashes for glob on Windows)
|
|
87
|
+
const pattern = path.join(fullPath, '**', '*').replace(/\\/g, '/');
|
|
86
88
|
const files = await (0, glob_1.glob)(pattern, {
|
|
87
89
|
cwd: repoRoot,
|
|
88
90
|
absolute: false,
|
|
@@ -108,11 +110,13 @@ async function getFolderContent(repoRoot, folderPath) {
|
|
|
108
110
|
try {
|
|
109
111
|
const content = fs.readFileSync(path.resolve(repoRoot, filePath), 'utf-8');
|
|
110
112
|
const lines = content.split('\n');
|
|
111
|
-
//
|
|
113
|
+
// Normalize path to forward slashes for cross-platform consistency
|
|
114
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
115
|
+
// Create artificial diff for this file (git diff format)
|
|
112
116
|
const fileDiff = lines.map((line) => `+${line}`).join('\n');
|
|
113
|
-
const diffHeader =
|
|
117
|
+
const diffHeader = `diff --git a/${normalizedPath} b/${normalizedPath}\n--- /dev/null\n+++ b/${normalizedPath}\n@@ -0,0 +1,${lines.length} @@\n`;
|
|
114
118
|
diffs.push(diffHeader + fileDiff);
|
|
115
|
-
changedFiles.push(
|
|
119
|
+
changedFiles.push(normalizedPath);
|
|
116
120
|
}
|
|
117
121
|
catch (error) {
|
|
118
122
|
// Skip files that can't be read (permissions, etc.)
|
|
@@ -148,11 +152,13 @@ async function getMultipleFilesContent(repoRoot, filePaths) {
|
|
|
148
152
|
// Read file content
|
|
149
153
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
150
154
|
const lines = content.split('\n');
|
|
151
|
-
//
|
|
155
|
+
// Normalize path to forward slashes for cross-platform consistency
|
|
156
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
157
|
+
// Create artificial diff for this file (git diff format)
|
|
152
158
|
const fileDiff = lines.map((line) => `+${line}`).join('\n');
|
|
153
|
-
const diffHeader =
|
|
159
|
+
const diffHeader = `diff --git a/${normalizedPath} b/${normalizedPath}\n--- /dev/null\n+++ b/${normalizedPath}\n@@ -0,0 +1,${lines.length} @@\n`;
|
|
154
160
|
diffs.push(diffHeader + fileDiff);
|
|
155
|
-
changedFiles.push(
|
|
161
|
+
changedFiles.push(normalizedPath);
|
|
156
162
|
}
|
|
157
163
|
return {
|
|
158
164
|
diff: diffs.join('\n'),
|
package/dist/git/github.js
CHANGED
|
@@ -35,6 +35,7 @@ async function getGitHubContext(repoRoot) {
|
|
|
35
35
|
const repoName = await getRepoName();
|
|
36
36
|
const branchName = await getBranchName();
|
|
37
37
|
const context = detectContext();
|
|
38
|
+
const reviewContext = detectReviewContext();
|
|
38
39
|
const commitSha = getCommitSha(context);
|
|
39
40
|
// Validate commit SHA is available (should always be set in GitHub Actions)
|
|
40
41
|
if (!commitSha) {
|
|
@@ -66,7 +67,8 @@ async function getGitHubContext(repoRoot) {
|
|
|
66
67
|
commitMessage,
|
|
67
68
|
commitAuthor,
|
|
68
69
|
prTitle,
|
|
69
|
-
context
|
|
70
|
+
context,
|
|
71
|
+
reviewContext
|
|
70
72
|
};
|
|
71
73
|
}
|
|
72
74
|
/**
|
|
@@ -188,6 +190,17 @@ function getCommitSha(context) {
|
|
|
188
190
|
}
|
|
189
191
|
return undefined;
|
|
190
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Detects review context type for API (simple string type)
|
|
195
|
+
*/
|
|
196
|
+
function detectReviewContext() {
|
|
197
|
+
// PR context
|
|
198
|
+
if (process.env.GITHUB_EVENT_NAME === 'pull_request') {
|
|
199
|
+
return 'pr';
|
|
200
|
+
}
|
|
201
|
+
// Commit context (any push)
|
|
202
|
+
return 'commit';
|
|
203
|
+
}
|
|
191
204
|
/**
|
|
192
205
|
* Gets PR title for GitHub Actions
|
|
193
206
|
* Note: GitHub Actions doesn't provide PR title as an env var by default.
|
package/dist/git/gitlab.js
CHANGED
|
@@ -35,6 +35,7 @@ async function getGitLabContext(repoRoot) {
|
|
|
35
35
|
const repoName = await getRepoName();
|
|
36
36
|
const branchName = await getBranchName();
|
|
37
37
|
const context = detectContext();
|
|
38
|
+
const reviewContext = detectReviewContext();
|
|
38
39
|
const commitSha = getCommitSha(context);
|
|
39
40
|
// Get commit author (fails loudly if unavailable)
|
|
40
41
|
const commitAuthor = await getCommitAuthor();
|
|
@@ -56,7 +57,8 @@ async function getGitLabContext(repoRoot) {
|
|
|
56
57
|
commitMessage,
|
|
57
58
|
commitAuthor,
|
|
58
59
|
prTitle,
|
|
59
|
-
context
|
|
60
|
+
context,
|
|
61
|
+
reviewContext
|
|
60
62
|
};
|
|
61
63
|
}
|
|
62
64
|
/**
|
|
@@ -148,6 +150,17 @@ function detectContext() {
|
|
|
148
150
|
'Expected CI_MERGE_REQUEST_IID or CI_COMMIT_SHA to be set. ' +
|
|
149
151
|
'This should be automatically provided by GitLab CI.');
|
|
150
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Detects review context type for API (simple string type)
|
|
155
|
+
*/
|
|
156
|
+
function detectReviewContext() {
|
|
157
|
+
// MR context
|
|
158
|
+
if (process.env.CI_MERGE_REQUEST_IID) {
|
|
159
|
+
return 'pr';
|
|
160
|
+
}
|
|
161
|
+
// Commit context (any push)
|
|
162
|
+
return 'commit';
|
|
163
|
+
}
|
|
151
164
|
/**
|
|
152
165
|
* Gets commit SHA from context
|
|
153
166
|
*/
|
package/dist/git/local.js
CHANGED
|
@@ -33,6 +33,7 @@ async function getLocalContext(repoRoot, commitSha) {
|
|
|
33
33
|
const repoName = await getRepoName(repoRoot);
|
|
34
34
|
const branchName = await getBranchName(repoRoot);
|
|
35
35
|
const context = commitSha ? { type: 'commit', commitSha } : { type: 'local' };
|
|
36
|
+
const reviewContext = commitSha ? 'commit' : 'local';
|
|
36
37
|
// Get commit author (fails loudly if unavailable)
|
|
37
38
|
const commitAuthor = commitSha
|
|
38
39
|
? await getCommitAuthorFromGit(repoRoot, commitSha)
|
|
@@ -53,7 +54,8 @@ async function getLocalContext(repoRoot, commitSha) {
|
|
|
53
54
|
commitMessage,
|
|
54
55
|
commitAuthor,
|
|
55
56
|
prTitle: undefined, // Not applicable for local
|
|
56
|
-
context
|
|
57
|
+
context,
|
|
58
|
+
reviewContext
|
|
57
59
|
};
|
|
58
60
|
}
|
|
59
61
|
/**
|
package/dist/git/vercel.js
CHANGED
|
@@ -35,6 +35,7 @@ async function getVercelContext(repoRoot) {
|
|
|
35
35
|
const branchName = await getBranchName();
|
|
36
36
|
const commitSha = getCommitSha();
|
|
37
37
|
const context = { type: 'commit', commitSha };
|
|
38
|
+
const reviewContext = 'commit';
|
|
38
39
|
// Get commit author (fails loudly if unavailable)
|
|
39
40
|
const commitAuthor = await getCommitAuthorForVercel(repoRoot, commitSha);
|
|
40
41
|
// Get commit message
|
|
@@ -50,7 +51,8 @@ async function getVercelContext(repoRoot) {
|
|
|
50
51
|
commitSha,
|
|
51
52
|
commitMessage,
|
|
52
53
|
commitAuthor,
|
|
53
|
-
context
|
|
54
|
+
context,
|
|
55
|
+
reviewContext
|
|
54
56
|
};
|
|
55
57
|
}
|
|
56
58
|
/**
|