@rafter-security/cli 0.1.0 → 0.4.0
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/README.md +475 -5
- package/dist/commands/agent/audit-skill.js +199 -0
- package/dist/commands/agent/audit.js +65 -0
- package/dist/commands/agent/config.js +54 -0
- package/dist/commands/agent/exec.js +120 -0
- package/dist/commands/agent/index.js +21 -0
- package/dist/commands/agent/init.js +162 -0
- package/dist/commands/agent/install-hook.js +128 -0
- package/dist/commands/agent/scan.js +233 -0
- package/dist/commands/backend/get.js +41 -0
- package/dist/commands/backend/run.js +84 -0
- package/dist/commands/backend/scan-status.js +67 -0
- package/dist/commands/backend/usage.js +23 -0
- package/dist/core/audit-logger.js +203 -0
- package/dist/core/command-interceptor.js +165 -0
- package/dist/core/config-defaults.js +71 -0
- package/dist/core/config-manager.js +147 -0
- package/dist/core/config-schema.js +1 -0
- package/dist/core/pattern-engine.js +105 -0
- package/dist/index.js +17 -272
- package/dist/scanners/gitleaks.js +205 -0
- package/dist/scanners/regex-scanner.js +125 -0
- package/dist/scanners/secret-patterns.js +155 -0
- package/dist/utils/api.js +20 -0
- package/dist/utils/binary-manager.js +243 -0
- package/dist/utils/git.js +52 -0
- package/dist/utils/skill-manager.js +346 -0
- package/dist/utils/update-checker.js +93 -0
- package/package.json +13 -8
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default secret detection patterns
|
|
3
|
+
* Based on common secret formats and Gitleaks rules
|
|
4
|
+
*/
|
|
5
|
+
export const DEFAULT_SECRET_PATTERNS = [
|
|
6
|
+
// AWS
|
|
7
|
+
{
|
|
8
|
+
name: "AWS Access Key ID",
|
|
9
|
+
regex: "(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}",
|
|
10
|
+
severity: "critical",
|
|
11
|
+
description: "AWS Access Key ID detected"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: "AWS Secret Access Key",
|
|
15
|
+
regex: "(?i)aws(.{0,20})?['\"][0-9a-zA-Z\\/+]{40}['\"]",
|
|
16
|
+
severity: "critical",
|
|
17
|
+
description: "AWS Secret Access Key detected"
|
|
18
|
+
},
|
|
19
|
+
// GitHub
|
|
20
|
+
{
|
|
21
|
+
name: "GitHub Personal Access Token",
|
|
22
|
+
regex: "ghp_[0-9a-zA-Z]{36}",
|
|
23
|
+
severity: "critical",
|
|
24
|
+
description: "GitHub Personal Access Token detected"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: "GitHub OAuth Token",
|
|
28
|
+
regex: "gho_[0-9a-zA-Z]{36}",
|
|
29
|
+
severity: "critical",
|
|
30
|
+
description: "GitHub OAuth Token detected"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "GitHub App Token",
|
|
34
|
+
regex: "(ghu|ghs)_[0-9a-zA-Z]{36}",
|
|
35
|
+
severity: "critical",
|
|
36
|
+
description: "GitHub App Token detected"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "GitHub Refresh Token",
|
|
40
|
+
regex: "ghr_[0-9a-zA-Z]{76}",
|
|
41
|
+
severity: "critical",
|
|
42
|
+
description: "GitHub Refresh Token detected"
|
|
43
|
+
},
|
|
44
|
+
// Google
|
|
45
|
+
{
|
|
46
|
+
name: "Google API Key",
|
|
47
|
+
regex: "AIza[0-9A-Za-z\\-_]{35}",
|
|
48
|
+
severity: "critical",
|
|
49
|
+
description: "Google API Key detected"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "Google OAuth",
|
|
53
|
+
regex: "[0-9]+-[0-9A-Za-z_]{32}\\.apps\\.googleusercontent\\.com",
|
|
54
|
+
severity: "critical",
|
|
55
|
+
description: "Google OAuth Client ID detected"
|
|
56
|
+
},
|
|
57
|
+
// Slack
|
|
58
|
+
{
|
|
59
|
+
name: "Slack Token",
|
|
60
|
+
regex: "xox[baprs]-([0-9a-zA-Z]{10,48})",
|
|
61
|
+
severity: "critical",
|
|
62
|
+
description: "Slack Token detected"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "Slack Webhook",
|
|
66
|
+
regex: "https://hooks\\.slack\\.com/services/T[a-zA-Z0-9_]{8}/B[a-zA-Z0-9_]{8}/[a-zA-Z0-9_]{24}",
|
|
67
|
+
severity: "high",
|
|
68
|
+
description: "Slack Webhook URL detected"
|
|
69
|
+
},
|
|
70
|
+
// Stripe
|
|
71
|
+
{
|
|
72
|
+
name: "Stripe API Key",
|
|
73
|
+
regex: "(?i)sk_live_[0-9a-zA-Z]{24}",
|
|
74
|
+
severity: "critical",
|
|
75
|
+
description: "Stripe Live API Key detected"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "Stripe Restricted API Key",
|
|
79
|
+
regex: "(?i)rk_live_[0-9a-zA-Z]{24}",
|
|
80
|
+
severity: "critical",
|
|
81
|
+
description: "Stripe Restricted API Key detected"
|
|
82
|
+
},
|
|
83
|
+
// Twilio
|
|
84
|
+
{
|
|
85
|
+
name: "Twilio API Key",
|
|
86
|
+
regex: "SK[0-9a-fA-F]{32}",
|
|
87
|
+
severity: "critical",
|
|
88
|
+
description: "Twilio API Key detected"
|
|
89
|
+
},
|
|
90
|
+
// Generic patterns
|
|
91
|
+
{
|
|
92
|
+
name: "Generic API Key",
|
|
93
|
+
regex: "(?i)(api[_-]?key|apikey)[\\s]*[:=][\\s]*['\"]?[0-9a-zA-Z\\-_]{16,}['\"]?",
|
|
94
|
+
severity: "high",
|
|
95
|
+
description: "Generic API key pattern detected"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: "Generic Secret",
|
|
99
|
+
regex: "(?i)(secret|password|passwd|pwd)[\\s]*[:=][\\s]*['\"]?[0-9a-zA-Z\\-_!@#$%^&*()]{8,}['\"]?",
|
|
100
|
+
severity: "high",
|
|
101
|
+
description: "Generic secret pattern detected"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: "Private Key",
|
|
105
|
+
regex: "-----BEGIN (RSA |DSA |EC |OPENSSH )?PRIVATE KEY-----",
|
|
106
|
+
severity: "critical",
|
|
107
|
+
description: "Private key detected"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: "Bearer Token",
|
|
111
|
+
regex: "(?i)bearer[\\s]+[a-zA-Z0-9\\-_\\.=]+",
|
|
112
|
+
severity: "high",
|
|
113
|
+
description: "Bearer token detected"
|
|
114
|
+
},
|
|
115
|
+
// Database connection strings
|
|
116
|
+
{
|
|
117
|
+
name: "Database Connection String",
|
|
118
|
+
regex: "(?i)(postgres|mysql|mongodb)://[^\\s]+:[^\\s]+@[^\\s]+",
|
|
119
|
+
severity: "critical",
|
|
120
|
+
description: "Database connection string with credentials detected"
|
|
121
|
+
},
|
|
122
|
+
// JWT
|
|
123
|
+
{
|
|
124
|
+
name: "JSON Web Token",
|
|
125
|
+
regex: "eyJ[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{10,}",
|
|
126
|
+
severity: "high",
|
|
127
|
+
description: "JWT token detected"
|
|
128
|
+
},
|
|
129
|
+
// npm token
|
|
130
|
+
{
|
|
131
|
+
name: "npm Access Token",
|
|
132
|
+
regex: "npm_[A-Za-z0-9]{36}",
|
|
133
|
+
severity: "critical",
|
|
134
|
+
description: "npm access token detected"
|
|
135
|
+
},
|
|
136
|
+
// PyPI token
|
|
137
|
+
{
|
|
138
|
+
name: "PyPI Token",
|
|
139
|
+
regex: "pypi-AgEIcHlwaS5vcmc[A-Za-z0-9\\-_]{50,}",
|
|
140
|
+
severity: "critical",
|
|
141
|
+
description: "PyPI API token detected"
|
|
142
|
+
}
|
|
143
|
+
];
|
|
144
|
+
/**
|
|
145
|
+
* Get patterns by severity level
|
|
146
|
+
*/
|
|
147
|
+
export function getPatternsBySeverity(severity) {
|
|
148
|
+
return DEFAULT_SECRET_PATTERNS.filter(p => p.severity === severity);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get all critical patterns
|
|
152
|
+
*/
|
|
153
|
+
export function getCriticalPatterns() {
|
|
154
|
+
return getPatternsBySeverity("critical");
|
|
155
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const API = "https://rafter.so/api/";
|
|
2
|
+
// Exit codes
|
|
3
|
+
export const EXIT_SUCCESS = 0;
|
|
4
|
+
export const EXIT_GENERAL_ERROR = 1;
|
|
5
|
+
export const EXIT_SCAN_NOT_FOUND = 2;
|
|
6
|
+
export const EXIT_QUOTA_EXHAUSTED = 3;
|
|
7
|
+
export function resolveKey(cliKey) {
|
|
8
|
+
if (cliKey)
|
|
9
|
+
return cliKey;
|
|
10
|
+
if (process.env.RAFTER_API_KEY)
|
|
11
|
+
return process.env.RAFTER_API_KEY;
|
|
12
|
+
console.error("No API key provided. Use --api-key or set RAFTER_API_KEY");
|
|
13
|
+
process.exit(EXIT_GENERAL_ERROR);
|
|
14
|
+
}
|
|
15
|
+
export function writePayload(data, fmt, quiet) {
|
|
16
|
+
const payload = fmt === "md" && data.markdown ? data.markdown : JSON.stringify(data, null, quiet ? 0 : 2);
|
|
17
|
+
// Stream to stdout for pipelines
|
|
18
|
+
process.stdout.write(payload);
|
|
19
|
+
return EXIT_SUCCESS;
|
|
20
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import https from "https";
|
|
4
|
+
import { exec } from "child_process";
|
|
5
|
+
import { promisify } from "util";
|
|
6
|
+
import { getBinDir } from "../core/config-defaults.js";
|
|
7
|
+
import * as tar from "tar";
|
|
8
|
+
const execAsync = promisify(exec);
|
|
9
|
+
const GITLEAKS_VERSION = "8.18.2";
|
|
10
|
+
export class BinaryManager {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.binDir = getBinDir();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Check if current platform is supported
|
|
16
|
+
*/
|
|
17
|
+
isPlatformSupported() {
|
|
18
|
+
const platform = process.platform;
|
|
19
|
+
const arch = process.arch;
|
|
20
|
+
// Supported platforms
|
|
21
|
+
const supported = [
|
|
22
|
+
"darwin-x64",
|
|
23
|
+
"darwin-arm64",
|
|
24
|
+
"linux-x64",
|
|
25
|
+
"linux-arm64",
|
|
26
|
+
"win32-x64"
|
|
27
|
+
];
|
|
28
|
+
return supported.includes(`${platform}-${arch}`);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get platform info for display
|
|
32
|
+
*/
|
|
33
|
+
getPlatformInfo() {
|
|
34
|
+
return {
|
|
35
|
+
platform: process.platform,
|
|
36
|
+
arch: process.arch,
|
|
37
|
+
supported: this.isPlatformSupported()
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get Gitleaks binary path
|
|
42
|
+
*/
|
|
43
|
+
getGitleaksPath() {
|
|
44
|
+
const platform = process.platform;
|
|
45
|
+
const ext = platform === "win32" ? ".exe" : "";
|
|
46
|
+
return path.join(this.binDir, `gitleaks${ext}`);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Check if Gitleaks is installed
|
|
50
|
+
*/
|
|
51
|
+
isGitleaksInstalled() {
|
|
52
|
+
const gitleaksPath = this.getGitleaksPath();
|
|
53
|
+
return fs.existsSync(gitleaksPath);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Verify Gitleaks binary works
|
|
57
|
+
*/
|
|
58
|
+
async verifyGitleaks() {
|
|
59
|
+
if (!this.isGitleaksInstalled()) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const { stdout } = await execAsync(`"${this.getGitleaksPath()}" version`, {
|
|
64
|
+
timeout: 5000
|
|
65
|
+
});
|
|
66
|
+
return stdout.includes("gitleaks version");
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Download and install Gitleaks
|
|
74
|
+
*/
|
|
75
|
+
async downloadGitleaks(onProgress) {
|
|
76
|
+
const log = onProgress || (() => { });
|
|
77
|
+
// Check platform support
|
|
78
|
+
if (!this.isPlatformSupported()) {
|
|
79
|
+
throw new Error(`Gitleaks not available for ${process.platform}/${process.arch}`);
|
|
80
|
+
}
|
|
81
|
+
// Ensure bin directory exists
|
|
82
|
+
if (!fs.existsSync(this.binDir)) {
|
|
83
|
+
fs.mkdirSync(this.binDir, { recursive: true });
|
|
84
|
+
}
|
|
85
|
+
const platform = this.getPlatformString();
|
|
86
|
+
const arch = this.getArchString();
|
|
87
|
+
const url = this.getDownloadUrl(platform, arch);
|
|
88
|
+
log(`Downloading Gitleaks v${GITLEAKS_VERSION} for ${platform}/${arch}...`);
|
|
89
|
+
const archivePath = path.join(this.binDir, platform === "windows" ? "gitleaks.zip" : "gitleaks.tar.gz");
|
|
90
|
+
try {
|
|
91
|
+
// Download archive
|
|
92
|
+
await this.downloadFile(url, archivePath, log);
|
|
93
|
+
// Extract binary
|
|
94
|
+
log("Extracting binary...");
|
|
95
|
+
if (platform === "windows") {
|
|
96
|
+
// For Windows, we'd need unzip - for now just error
|
|
97
|
+
throw new Error("Windows support coming soon");
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
await this.extractTarball(archivePath);
|
|
101
|
+
}
|
|
102
|
+
// Make executable (Unix systems)
|
|
103
|
+
if (process.platform !== "win32") {
|
|
104
|
+
await execAsync(`chmod +x "${this.getGitleaksPath()}"`);
|
|
105
|
+
}
|
|
106
|
+
// Verify it works
|
|
107
|
+
const works = await this.verifyGitleaks();
|
|
108
|
+
if (!works) {
|
|
109
|
+
throw new Error("Downloaded binary doesn't execute correctly");
|
|
110
|
+
}
|
|
111
|
+
// Clean up archive
|
|
112
|
+
if (fs.existsSync(archivePath)) {
|
|
113
|
+
fs.unlinkSync(archivePath);
|
|
114
|
+
}
|
|
115
|
+
log("✓ Gitleaks installed successfully");
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
// Clean up on failure
|
|
119
|
+
if (fs.existsSync(archivePath)) {
|
|
120
|
+
fs.unlinkSync(archivePath);
|
|
121
|
+
}
|
|
122
|
+
const gitleaksPath = this.getGitleaksPath();
|
|
123
|
+
if (fs.existsSync(gitleaksPath)) {
|
|
124
|
+
fs.unlinkSync(gitleaksPath);
|
|
125
|
+
}
|
|
126
|
+
throw e;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get Gitleaks version
|
|
131
|
+
*/
|
|
132
|
+
async getGitleaksVersion() {
|
|
133
|
+
if (!this.isGitleaksInstalled()) {
|
|
134
|
+
return "not installed";
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const { stdout } = await execAsync(`"${this.getGitleaksPath()}" version`);
|
|
138
|
+
return stdout.trim();
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return "unknown";
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get platform string for download URL
|
|
146
|
+
*/
|
|
147
|
+
getPlatformString() {
|
|
148
|
+
const platform = process.platform;
|
|
149
|
+
if (platform === "darwin")
|
|
150
|
+
return "darwin";
|
|
151
|
+
if (platform === "win32")
|
|
152
|
+
return "windows";
|
|
153
|
+
if (platform === "linux")
|
|
154
|
+
return "linux";
|
|
155
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Get architecture string for download URL
|
|
159
|
+
*/
|
|
160
|
+
getArchString() {
|
|
161
|
+
const arch = process.arch;
|
|
162
|
+
if (arch === "x64")
|
|
163
|
+
return "x64";
|
|
164
|
+
if (arch === "arm64")
|
|
165
|
+
return "arm64";
|
|
166
|
+
throw new Error(`Unsupported architecture: ${arch}`);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Get download URL for platform/arch
|
|
170
|
+
*/
|
|
171
|
+
getDownloadUrl(platform, arch) {
|
|
172
|
+
const baseUrl = `https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}`;
|
|
173
|
+
if (platform === "windows") {
|
|
174
|
+
return `${baseUrl}/gitleaks_${GITLEAKS_VERSION}_windows_${arch}.zip`;
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
return `${baseUrl}/gitleaks_${GITLEAKS_VERSION}_${platform}_${arch}.tar.gz`;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Download file from URL
|
|
182
|
+
*/
|
|
183
|
+
downloadFile(url, dest, onProgress) {
|
|
184
|
+
return new Promise((resolve, reject) => {
|
|
185
|
+
const file = fs.createWriteStream(dest);
|
|
186
|
+
https.get(url, (response) => {
|
|
187
|
+
// Follow redirects
|
|
188
|
+
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
189
|
+
const redirectUrl = response.headers.location;
|
|
190
|
+
if (!redirectUrl) {
|
|
191
|
+
reject(new Error("Redirect without location"));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
file.close();
|
|
195
|
+
fs.unlinkSync(dest);
|
|
196
|
+
this.downloadFile(redirectUrl, dest, onProgress).then(resolve).catch(reject);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (response.statusCode !== 200) {
|
|
200
|
+
reject(new Error(`Download failed: ${response.statusCode}`));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const totalBytes = parseInt(response.headers["content-length"] || "0", 10);
|
|
204
|
+
let downloadedBytes = 0;
|
|
205
|
+
let lastPercent = 0;
|
|
206
|
+
response.on("data", (chunk) => {
|
|
207
|
+
downloadedBytes += chunk.length;
|
|
208
|
+
if (totalBytes > 0) {
|
|
209
|
+
const percent = Math.round((downloadedBytes / totalBytes) * 100);
|
|
210
|
+
if (percent > lastPercent && percent % 10 === 0) {
|
|
211
|
+
onProgress(`Downloading... ${percent}%`);
|
|
212
|
+
lastPercent = percent;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
response.pipe(file);
|
|
217
|
+
file.on("finish", () => {
|
|
218
|
+
file.close();
|
|
219
|
+
resolve();
|
|
220
|
+
});
|
|
221
|
+
file.on("error", (err) => {
|
|
222
|
+
fs.unlinkSync(dest);
|
|
223
|
+
reject(err);
|
|
224
|
+
});
|
|
225
|
+
}).on("error", (err) => {
|
|
226
|
+
if (fs.existsSync(dest)) {
|
|
227
|
+
fs.unlinkSync(dest);
|
|
228
|
+
}
|
|
229
|
+
reject(err);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Extract tarball
|
|
235
|
+
*/
|
|
236
|
+
async extractTarball(tarballPath) {
|
|
237
|
+
await tar.extract({
|
|
238
|
+
file: tarballPath,
|
|
239
|
+
cwd: this.binDir,
|
|
240
|
+
strip: 0
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
export function git(cmd) {
|
|
3
|
+
return execSync(`git ${cmd}`, { stdio: ["ignore", "pipe", "ignore"] })
|
|
4
|
+
.toString()
|
|
5
|
+
.trim();
|
|
6
|
+
}
|
|
7
|
+
export function safeBranch(gitFn) {
|
|
8
|
+
try {
|
|
9
|
+
return gitFn("symbolic-ref --quiet --short HEAD");
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return gitFn("rev-parse --short HEAD");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function parseRemote(url) {
|
|
16
|
+
url = url.replace(/^(https?:\/\/|git@)/, "").replace(":", "/");
|
|
17
|
+
if (url.endsWith(".git"))
|
|
18
|
+
url = url.slice(0, -4);
|
|
19
|
+
const parts = url.split("/");
|
|
20
|
+
return parts.slice(-2).join("/"); // owner/repo
|
|
21
|
+
}
|
|
22
|
+
export function detectRepo(opts) {
|
|
23
|
+
if (opts.repo && opts.branch)
|
|
24
|
+
return opts;
|
|
25
|
+
const repoEnv = process.env.GITHUB_REPOSITORY || process.env.CI_REPOSITORY;
|
|
26
|
+
const branchEnv = process.env.GITHUB_REF_NAME || process.env.CI_COMMIT_BRANCH || process.env.CI_BRANCH;
|
|
27
|
+
let repoSlug = opts.repo || repoEnv;
|
|
28
|
+
let branch = opts.branch || branchEnv;
|
|
29
|
+
try {
|
|
30
|
+
if (!repoSlug || !branch) {
|
|
31
|
+
if (git("rev-parse --is-inside-work-tree") !== "true")
|
|
32
|
+
throw new Error("not a repo");
|
|
33
|
+
if (!repoSlug)
|
|
34
|
+
repoSlug = parseRemote(git("remote get-url origin"));
|
|
35
|
+
if (!branch) {
|
|
36
|
+
try {
|
|
37
|
+
branch = safeBranch(git);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
branch = "main";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if ((!opts.repo || !opts.branch) && !opts.quiet) {
|
|
45
|
+
console.error(`Repo auto-detected: ${repoSlug} @ ${branch} (note: scanning remote)`);
|
|
46
|
+
}
|
|
47
|
+
return { repo: repoSlug, branch };
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
throw new Error("Could not auto-detect Git repository. Please pass --repo and --branch explicitly.");
|
|
51
|
+
}
|
|
52
|
+
}
|