securewithtrace 0.10.0 → 0.11.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 +25 -2
- package/dist/index.js +281 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
`tracecli` is the official CLI for Trace.
|
|
12
12
|
|
|
13
|
-
Use it to authenticate, browse connected repositories, clone repositories, and inspect vulnerabilities directly from the command line.
|
|
13
|
+
Use it to authenticate, browse and filter connected repositories, clone repositories, and inspect vulnerabilities directly from the command line.
|
|
14
14
|
|
|
15
15
|
## Install
|
|
16
16
|
|
|
@@ -42,6 +42,9 @@ tracecli auth status
|
|
|
42
42
|
# 3) List connected repositories
|
|
43
43
|
tracecli repo list
|
|
44
44
|
|
|
45
|
+
# 3b) Filter repositories
|
|
46
|
+
tracecli repo list --platform GITHUB --status CONNECTED --query trace
|
|
47
|
+
|
|
45
48
|
# 4) View vulnerabilities
|
|
46
49
|
tracecli vulnerabilities list --severity HIGH
|
|
47
50
|
```
|
|
@@ -49,7 +52,7 @@ tracecli vulnerabilities list --severity HIGH
|
|
|
49
52
|
## What You Can Do
|
|
50
53
|
|
|
51
54
|
- **Authenticate securely** with device authorization flow (`auth login/logout/status`)
|
|
52
|
-
- **List repositories** connected to your Trace organization (`repo list`)
|
|
55
|
+
- **List and filter repositories** connected to your Trace organization (`repo list`, `repo filter`)
|
|
53
56
|
- **Clone repositories** with secure token handling (`repo clone`)
|
|
54
57
|
- **Filter vulnerabilities** by repo, severity, and status (`vulnerabilities list`)
|
|
55
58
|
- **Export results as JSON or TSV** for automation (`vulnerabilities list --output json`)
|
|
@@ -70,6 +73,15 @@ tracecli auth logout
|
|
|
70
73
|
# List org repositories known to Trace
|
|
71
74
|
tracecli repo list
|
|
72
75
|
|
|
76
|
+
# List only connected GitHub repositories that match "trace"
|
|
77
|
+
tracecli repo list --platform GITHUB --status CONNECTED --query trace
|
|
78
|
+
|
|
79
|
+
# Advanced filtering syntax (repeatable)
|
|
80
|
+
tracecli repo list --filter "platform=GITHUB" --filter "repo:trace" --filter "status!=DISCONNECTED"
|
|
81
|
+
|
|
82
|
+
# Alias command for filtering
|
|
83
|
+
tracecli repo filter --filter "language:typescript" --filter "branch=main"
|
|
84
|
+
|
|
73
85
|
# Clone by owner/repo shorthand
|
|
74
86
|
tracecli repo clone securewithtrace/trace
|
|
75
87
|
|
|
@@ -114,6 +126,17 @@ Supported filter values:
|
|
|
114
126
|
- `--severity`: `CRITICAL`, `HIGH`, `MEDIUM`, `LOW`
|
|
115
127
|
- `--status`: `OPEN`, `IN_PROGRESS`, `FIXED`, `IGNORED`, `FALSE_POSITIVE`
|
|
116
128
|
|
|
129
|
+
Repository filter options:
|
|
130
|
+
|
|
131
|
+
- `--platform`: `GITHUB`, `GITLAB`, `BITBUCKET`
|
|
132
|
+
- `--status`: `CONNECTED`, `DISCONNECTED`
|
|
133
|
+
- `--query`: case-insensitive contains match across URL, repo name, default branch, and language
|
|
134
|
+
- `--default-branch`: case-insensitive contains match for default branch
|
|
135
|
+
- `--language`: case-insensitive contains match for primary language
|
|
136
|
+
- `--filter`: repeatable advanced expression in the form `<field><operator><value>`
|
|
137
|
+
- Supported fields: `platform`, `status`, `url`, `repo`, `branch`, `language`
|
|
138
|
+
- Supported operators: `=` (exact), `!=` (not exact), `:` (contains), `!:` (not contains)
|
|
139
|
+
|
|
117
140
|
### Pentests
|
|
118
141
|
|
|
119
142
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -25736,6 +25736,9 @@ function extractRepoName(input) {
|
|
|
25736
25736
|
return input;
|
|
25737
25737
|
}
|
|
25738
25738
|
var BRANCH_NAME_PATTERN = /^[a-zA-Z0-9._/+-]+$/;
|
|
25739
|
+
var TRACE_GIT_ASKPASS_TOKEN_ENV = "TRACE_GIT_ASKPASS_TOKEN";
|
|
25740
|
+
var REPOSITORY_PLATFORMS = ["GITHUB", "GITLAB", "BITBUCKET"];
|
|
25741
|
+
var REPOSITORY_STATUSES = ["CONNECTED", "DISCONNECTED"];
|
|
25739
25742
|
function validateBranchName(branch) {
|
|
25740
25743
|
if (branch.startsWith("-")) {
|
|
25741
25744
|
throw new Error(`Invalid branch name: '${branch}' \u2014 branch names cannot start with a dash`);
|
|
@@ -25745,29 +25748,268 @@ function validateBranchName(branch) {
|
|
|
25745
25748
|
}
|
|
25746
25749
|
return branch;
|
|
25747
25750
|
}
|
|
25748
|
-
function
|
|
25749
|
-
|
|
25750
|
-
|
|
25751
|
-
|
|
25752
|
-
|
|
25753
|
-
|
|
25754
|
-
|
|
25751
|
+
function parseCloneDepth(value) {
|
|
25752
|
+
const depth = Number.parseInt(value, 10);
|
|
25753
|
+
if (!Number.isInteger(depth) || depth < 1) {
|
|
25754
|
+
throw new Error("--depth must be a positive integer");
|
|
25755
|
+
}
|
|
25756
|
+
return depth;
|
|
25757
|
+
}
|
|
25758
|
+
function normalizeForMatching(value) {
|
|
25759
|
+
return value.trim().toLowerCase();
|
|
25760
|
+
}
|
|
25761
|
+
function normalizePlatform(value, source) {
|
|
25762
|
+
const normalized = value.trim().toUpperCase();
|
|
25763
|
+
if (REPOSITORY_PLATFORMS.includes(normalized)) {
|
|
25764
|
+
return normalized;
|
|
25765
|
+
}
|
|
25766
|
+
throw new Error(
|
|
25767
|
+
`${source} must be one of: ${REPOSITORY_PLATFORMS.join(", ")}. Received '${value}'.`
|
|
25768
|
+
);
|
|
25769
|
+
}
|
|
25770
|
+
function normalizeStatus(value, source) {
|
|
25771
|
+
const normalized = value.trim().toUpperCase();
|
|
25772
|
+
if (REPOSITORY_STATUSES.includes(normalized)) {
|
|
25773
|
+
return normalized;
|
|
25774
|
+
}
|
|
25775
|
+
throw new Error(
|
|
25776
|
+
`${source} must be one of: ${REPOSITORY_STATUSES.join(", ")}. Received '${value}'.`
|
|
25777
|
+
);
|
|
25778
|
+
}
|
|
25779
|
+
function normalizeFilterField(rawField) {
|
|
25780
|
+
const field = rawField.trim().toLowerCase();
|
|
25781
|
+
switch (field) {
|
|
25782
|
+
case "platform":
|
|
25783
|
+
return "platform";
|
|
25784
|
+
case "status":
|
|
25785
|
+
return "status";
|
|
25786
|
+
case "url":
|
|
25787
|
+
case "repository":
|
|
25788
|
+
case "repositoryurl":
|
|
25789
|
+
case "repository_url":
|
|
25790
|
+
return "repository_url";
|
|
25791
|
+
case "repo":
|
|
25792
|
+
case "name":
|
|
25793
|
+
case "repository_name":
|
|
25794
|
+
return "repo_name";
|
|
25795
|
+
case "branch":
|
|
25796
|
+
case "defaultbranch":
|
|
25797
|
+
case "default_branch":
|
|
25798
|
+
return "default_branch";
|
|
25799
|
+
case "language":
|
|
25800
|
+
case "primarylanguage":
|
|
25801
|
+
case "primary_language":
|
|
25802
|
+
return "language";
|
|
25803
|
+
default:
|
|
25804
|
+
throw new Error(
|
|
25805
|
+
`Unknown repository filter field '${rawField}'. Supported fields: platform, status, url, repo, branch, language.`
|
|
25806
|
+
);
|
|
25807
|
+
}
|
|
25808
|
+
}
|
|
25809
|
+
function parseFilterOperator(rawOperator) {
|
|
25810
|
+
switch (rawOperator) {
|
|
25811
|
+
case "=":
|
|
25812
|
+
return "exact";
|
|
25813
|
+
case "!=":
|
|
25814
|
+
return "not_exact";
|
|
25815
|
+
case ":":
|
|
25816
|
+
return "contains";
|
|
25817
|
+
case "!:":
|
|
25818
|
+
return "not_contains";
|
|
25819
|
+
default:
|
|
25820
|
+
throw new Error(`Unsupported filter operator '${rawOperator}'. Use one of: =, !=, :, !:.`);
|
|
25821
|
+
}
|
|
25822
|
+
}
|
|
25823
|
+
function parseRepositoryFilterExpression(expression) {
|
|
25824
|
+
const trimmed = expression.trim();
|
|
25825
|
+
const match = /^([a-zA-Z][a-zA-Z0-9_-]*)(!=|!:|=|:)(.+)$/.exec(trimmed);
|
|
25826
|
+
if (!match) {
|
|
25827
|
+
throw new Error(
|
|
25828
|
+
`Invalid filter '${expression}'. Expected format: <field><operator><value> (e.g., platform=GITHUB, repo:trace, status!=DISCONNECTED).`
|
|
25755
25829
|
);
|
|
25756
|
-
|
|
25757
|
-
|
|
25758
|
-
|
|
25830
|
+
}
|
|
25831
|
+
const rawField = match[1];
|
|
25832
|
+
const rawOperator = match[2];
|
|
25833
|
+
const rawValue = match[3];
|
|
25834
|
+
if (!rawField || !rawOperator || !rawValue) {
|
|
25835
|
+
throw new Error(`Invalid filter '${expression}'.`);
|
|
25836
|
+
}
|
|
25837
|
+
const value = rawValue.trim();
|
|
25838
|
+
if (!value) {
|
|
25839
|
+
throw new Error(`Invalid filter '${expression}': value cannot be empty.`);
|
|
25840
|
+
}
|
|
25841
|
+
const field = normalizeFilterField(rawField);
|
|
25842
|
+
const operator = parseFilterOperator(rawOperator);
|
|
25843
|
+
if (field === "platform") {
|
|
25844
|
+
normalizePlatform(value, "--filter");
|
|
25845
|
+
}
|
|
25846
|
+
if (field === "status") {
|
|
25847
|
+
normalizeStatus(value, "--filter");
|
|
25848
|
+
}
|
|
25849
|
+
return { field, operator, value };
|
|
25850
|
+
}
|
|
25851
|
+
function doesValueMatchFilter(candidate, filterValue, operator, exactMatchOnly) {
|
|
25852
|
+
const normalizedCandidate = normalizeForMatching(candidate);
|
|
25853
|
+
const normalizedFilter = normalizeForMatching(filterValue);
|
|
25854
|
+
if (exactMatchOnly || operator === "exact" || operator === "not_exact") {
|
|
25855
|
+
const matches2 = normalizedCandidate === normalizedFilter;
|
|
25856
|
+
return operator === "not_exact" ? !matches2 : matches2;
|
|
25857
|
+
}
|
|
25858
|
+
const matches = normalizedCandidate.includes(normalizedFilter);
|
|
25859
|
+
return operator === "not_contains" ? !matches : matches;
|
|
25860
|
+
}
|
|
25861
|
+
function matchParsedFilter(repo, parsedFilter) {
|
|
25862
|
+
switch (parsedFilter.field) {
|
|
25863
|
+
case "platform":
|
|
25864
|
+
return doesValueMatchFilter(repo.platform, parsedFilter.value, parsedFilter.operator, true);
|
|
25865
|
+
case "status":
|
|
25866
|
+
return doesValueMatchFilter(repo.status, parsedFilter.value, parsedFilter.operator, true);
|
|
25867
|
+
case "repository_url":
|
|
25868
|
+
return doesValueMatchFilter(
|
|
25869
|
+
repo.repositoryUrl,
|
|
25870
|
+
parsedFilter.value,
|
|
25871
|
+
parsedFilter.operator,
|
|
25872
|
+
false
|
|
25873
|
+
);
|
|
25874
|
+
case "repo_name":
|
|
25875
|
+
return doesValueMatchFilter(
|
|
25876
|
+
extractRepoName(repo.repositoryUrl),
|
|
25877
|
+
parsedFilter.value,
|
|
25878
|
+
parsedFilter.operator,
|
|
25879
|
+
false
|
|
25880
|
+
);
|
|
25881
|
+
case "default_branch":
|
|
25882
|
+
return doesValueMatchFilter(
|
|
25883
|
+
repo.defaultBranch,
|
|
25884
|
+
parsedFilter.value,
|
|
25885
|
+
parsedFilter.operator,
|
|
25886
|
+
false
|
|
25887
|
+
);
|
|
25888
|
+
case "language": {
|
|
25889
|
+
const language = repo.primaryLanguage ?? "";
|
|
25890
|
+
return doesValueMatchFilter(language, parsedFilter.value, parsedFilter.operator, false);
|
|
25759
25891
|
}
|
|
25760
|
-
|
|
25761
|
-
|
|
25762
|
-
|
|
25763
|
-
|
|
25892
|
+
}
|
|
25893
|
+
}
|
|
25894
|
+
function filterRepositories(repositories, options) {
|
|
25895
|
+
const parsedFilters = (options.filters ?? []).map(
|
|
25896
|
+
(expression) => parseRepositoryFilterExpression(expression)
|
|
25897
|
+
);
|
|
25898
|
+
const normalizedPlatform = options.platform ? normalizePlatform(options.platform, "--platform") : void 0;
|
|
25899
|
+
const normalizedStatus = options.status ? normalizeStatus(options.status, "--status") : void 0;
|
|
25900
|
+
const normalizedQuery = options.query ? normalizeForMatching(options.query) : void 0;
|
|
25901
|
+
const normalizedDefaultBranch = options.defaultBranch ? normalizeForMatching(options.defaultBranch) : void 0;
|
|
25902
|
+
const normalizedLanguage = options.language ? normalizeForMatching(options.language) : void 0;
|
|
25903
|
+
return repositories.filter((repo) => {
|
|
25904
|
+
if (normalizedPlatform && repo.platform.toUpperCase() !== normalizedPlatform) {
|
|
25905
|
+
return false;
|
|
25906
|
+
}
|
|
25907
|
+
if (normalizedStatus && repo.status.toUpperCase() !== normalizedStatus) {
|
|
25908
|
+
return false;
|
|
25909
|
+
}
|
|
25910
|
+
if (normalizedDefaultBranch && !normalizeForMatching(repo.defaultBranch).includes(normalizedDefaultBranch)) {
|
|
25911
|
+
return false;
|
|
25764
25912
|
}
|
|
25765
|
-
|
|
25766
|
-
|
|
25767
|
-
|
|
25768
|
-
const platform = stripAnsi(String(repo.platform)).toLowerCase();
|
|
25769
|
-
console.log(`${url2} (${platform}, ${status})`);
|
|
25913
|
+
const repoLanguage = normalizeForMatching(repo.primaryLanguage ?? "");
|
|
25914
|
+
if (normalizedLanguage && !repoLanguage.includes(normalizedLanguage)) {
|
|
25915
|
+
return false;
|
|
25770
25916
|
}
|
|
25917
|
+
if (normalizedQuery) {
|
|
25918
|
+
const normalizedUrl = normalizeForMatching(repo.repositoryUrl);
|
|
25919
|
+
const normalizedBranch = normalizeForMatching(repo.defaultBranch);
|
|
25920
|
+
const normalizedName = normalizeForMatching(extractRepoName(repo.repositoryUrl));
|
|
25921
|
+
if (!normalizedUrl.includes(normalizedQuery) && !normalizedBranch.includes(normalizedQuery) && !repoLanguage.includes(normalizedQuery) && !normalizedName.includes(normalizedQuery)) {
|
|
25922
|
+
return false;
|
|
25923
|
+
}
|
|
25924
|
+
}
|
|
25925
|
+
for (const parsedFilter of parsedFilters) {
|
|
25926
|
+
if (!matchParsedFilter(repo, parsedFilter)) {
|
|
25927
|
+
return false;
|
|
25928
|
+
}
|
|
25929
|
+
}
|
|
25930
|
+
return true;
|
|
25931
|
+
});
|
|
25932
|
+
}
|
|
25933
|
+
function collectFilterExpression(value, previous) {
|
|
25934
|
+
previous.push(value);
|
|
25935
|
+
return previous;
|
|
25936
|
+
}
|
|
25937
|
+
function toRepositoryListFilterOptions(options) {
|
|
25938
|
+
return {
|
|
25939
|
+
...options.platform ? { platform: options.platform } : {},
|
|
25940
|
+
...options.status ? { status: options.status } : {},
|
|
25941
|
+
...options.query ? { query: options.query } : {},
|
|
25942
|
+
...options.defaultBranch ? { defaultBranch: options.defaultBranch } : {},
|
|
25943
|
+
...options.language ? { language: options.language } : {},
|
|
25944
|
+
...options.filter.length > 0 ? { filters: options.filter } : {}
|
|
25945
|
+
};
|
|
25946
|
+
}
|
|
25947
|
+
function addRepositoryFilterOptions(command) {
|
|
25948
|
+
return command.option(
|
|
25949
|
+
"--platform <platform>",
|
|
25950
|
+
"Filter by platform (GITHUB, GITLAB, BITBUCKET)",
|
|
25951
|
+
(value) => value.toUpperCase()
|
|
25952
|
+
).option(
|
|
25953
|
+
"--status <status>",
|
|
25954
|
+
"Filter by status (CONNECTED, DISCONNECTED)",
|
|
25955
|
+
(value) => value.toUpperCase()
|
|
25956
|
+
).option("--query <query>", "Case-insensitive search across URL, repo, branch, and language").option("--default-branch <branch>", "Filter by default branch (case-insensitive contains)").option("--language <language>", "Filter by primary language (case-insensitive contains)").option(
|
|
25957
|
+
"--filter <expression>",
|
|
25958
|
+
"Advanced filter expression. Repeatable. Examples: platform=GITHUB, repo:trace, status!=DISCONNECTED, language!:typescript",
|
|
25959
|
+
collectFilterExpression,
|
|
25960
|
+
[]
|
|
25961
|
+
).option("--json", "Output as JSON");
|
|
25962
|
+
}
|
|
25963
|
+
async function runRepositoryListCommand(options) {
|
|
25964
|
+
let filteredRepositories;
|
|
25965
|
+
const organizationId = await resolveOrganizationId();
|
|
25966
|
+
const result = await authenticatedRequest(
|
|
25967
|
+
(client) => client.repository.listRepositories({
|
|
25968
|
+
params: { organizationId }
|
|
25969
|
+
})
|
|
25970
|
+
);
|
|
25971
|
+
if (result.status !== 200) {
|
|
25972
|
+
console.error(`Failed to list repositories (status ${result.status}).`);
|
|
25973
|
+
process.exit(1);
|
|
25974
|
+
}
|
|
25975
|
+
try {
|
|
25976
|
+
filteredRepositories = filterRepositories(result.body, toRepositoryListFilterOptions(options));
|
|
25977
|
+
} catch (error48) {
|
|
25978
|
+
const message = error48 instanceof Error ? error48.message : "Unknown filter parsing error.";
|
|
25979
|
+
console.error(`Invalid repository filter: ${message}`);
|
|
25980
|
+
process.exit(1);
|
|
25981
|
+
}
|
|
25982
|
+
if (filteredRepositories.length === 0) {
|
|
25983
|
+
if (options.platform || options.status || options.query || options.defaultBranch || options.language || options.filter.length > 0) {
|
|
25984
|
+
console.log("No repositories found matching the provided filters.");
|
|
25985
|
+
return;
|
|
25986
|
+
}
|
|
25987
|
+
console.log("No repositories found.");
|
|
25988
|
+
return;
|
|
25989
|
+
}
|
|
25990
|
+
if (options.json) {
|
|
25991
|
+
console.log(JSON.stringify(filteredRepositories, null, 2));
|
|
25992
|
+
return;
|
|
25993
|
+
}
|
|
25994
|
+
for (const repo of filteredRepositories) {
|
|
25995
|
+
const status = repo.status === "CONNECTED" ? "connected" : "disconnected";
|
|
25996
|
+
const url2 = stripAnsi(String(repo.repositoryUrl));
|
|
25997
|
+
const platform = stripAnsi(String(repo.platform)).toLowerCase();
|
|
25998
|
+
console.log(`${url2} (${platform}, ${status})`);
|
|
25999
|
+
}
|
|
26000
|
+
}
|
|
26001
|
+
function createListCommand2() {
|
|
26002
|
+
return addRepositoryFilterOptions(
|
|
26003
|
+
new Command("list").description("List repositories in your organization")
|
|
26004
|
+
).action(async (options) => {
|
|
26005
|
+
await runRepositoryListCommand(options);
|
|
26006
|
+
});
|
|
26007
|
+
}
|
|
26008
|
+
function createFilterCommand() {
|
|
26009
|
+
return addRepositoryFilterOptions(
|
|
26010
|
+
new Command("filter").description("Filter repositories in your organization")
|
|
26011
|
+
).action(async (options) => {
|
|
26012
|
+
await runRepositoryListCommand(options);
|
|
25771
26013
|
});
|
|
25772
26014
|
}
|
|
25773
26015
|
function urlWithUsername(httpsUrl) {
|
|
@@ -25776,14 +26018,21 @@ function urlWithUsername(httpsUrl) {
|
|
|
25776
26018
|
url2.password = "";
|
|
25777
26019
|
return url2.toString();
|
|
25778
26020
|
}
|
|
25779
|
-
function createAskPassScript(
|
|
26021
|
+
function createAskPassScript() {
|
|
25780
26022
|
const dir = mkdtempSync(join3(tmpdir(), "trace-askpass-"));
|
|
25781
26023
|
const scriptPath = join3(dir, "askpass.sh");
|
|
25782
|
-
writeFileSync3(
|
|
25783
|
-
|
|
25784
|
-
|
|
25785
|
-
|
|
25786
|
-
|
|
26024
|
+
writeFileSync3(
|
|
26025
|
+
scriptPath,
|
|
26026
|
+
`#!/bin/sh
|
|
26027
|
+
if [ -z "\${${TRACE_GIT_ASKPASS_TOKEN_ENV}:-}" ]; then
|
|
26028
|
+
exit 1
|
|
26029
|
+
fi
|
|
26030
|
+
printf '%s\\n' "$${TRACE_GIT_ASKPASS_TOKEN_ENV}"
|
|
26031
|
+
`,
|
|
26032
|
+
{
|
|
26033
|
+
mode: 448
|
|
26034
|
+
}
|
|
26035
|
+
);
|
|
25787
26036
|
chmodSync(scriptPath, 448);
|
|
25788
26037
|
return scriptPath;
|
|
25789
26038
|
}
|
|
@@ -25803,7 +26052,8 @@ async function fetchCloneToken(repoUrl) {
|
|
|
25803
26052
|
return null;
|
|
25804
26053
|
}
|
|
25805
26054
|
if (result.status === 200) {
|
|
25806
|
-
|
|
26055
|
+
const token = result.body.token.trim();
|
|
26056
|
+
return token.length > 0 ? token : null;
|
|
25807
26057
|
}
|
|
25808
26058
|
return null;
|
|
25809
26059
|
} catch {
|
|
@@ -25811,7 +26061,7 @@ async function fetchCloneToken(repoUrl) {
|
|
|
25811
26061
|
}
|
|
25812
26062
|
}
|
|
25813
26063
|
function createCloneCommand() {
|
|
25814
|
-
return new Command("clone").description("Clone a repository").argument("<repository>", "Repository URL or owner/repo shorthand").argument("[directory]", "Directory to clone into").option("--branch <branch>", "Branch to clone").option("--depth <depth>", "Shallow clone depth",
|
|
26064
|
+
return new Command("clone").description("Clone a repository").argument("<repository>", "Repository URL or owner/repo shorthand").argument("[directory]", "Directory to clone into").option("--branch <branch>", "Branch to clone").option("--depth <depth>", "Shallow clone depth", parseCloneDepth).action(
|
|
25815
26065
|
async (repository, directory, options) => {
|
|
25816
26066
|
const cloneUrl = resolveRepoUrl(repository);
|
|
25817
26067
|
const targetDir = directory ?? extractRepoName(repository);
|
|
@@ -25833,8 +26083,9 @@ function createCloneCommand() {
|
|
|
25833
26083
|
console.log(`Cloning ${cloneUrl} into ${targetDir}...`);
|
|
25834
26084
|
const extraEnv = {};
|
|
25835
26085
|
if (installationToken) {
|
|
25836
|
-
askPassScript = createAskPassScript(
|
|
26086
|
+
askPassScript = createAskPassScript();
|
|
25837
26087
|
extraEnv["GIT_ASKPASS"] = askPassScript;
|
|
26088
|
+
extraEnv[TRACE_GIT_ASKPASS_TOKEN_ENV] = installationToken;
|
|
25838
26089
|
extraEnv["GIT_TERMINAL_PROMPT"] = "0";
|
|
25839
26090
|
}
|
|
25840
26091
|
try {
|
|
@@ -25845,7 +26096,7 @@ function createCloneCommand() {
|
|
|
25845
26096
|
});
|
|
25846
26097
|
const exitCode = await proc.exited;
|
|
25847
26098
|
if (exitCode !== 0) {
|
|
25848
|
-
process.exit(
|
|
26099
|
+
process.exit(1);
|
|
25849
26100
|
}
|
|
25850
26101
|
if (installationToken) {
|
|
25851
26102
|
const resetProc = Bun.spawn(["git", "remote", "set-url", "origin", cloneUrl], {
|
|
@@ -25872,6 +26123,7 @@ function createCloneCommand() {
|
|
|
25872
26123
|
function createRepoCommand() {
|
|
25873
26124
|
const repo = new Command("repo").description("Manage repositories");
|
|
25874
26125
|
repo.addCommand(createListCommand2());
|
|
26126
|
+
repo.addCommand(createFilterCommand());
|
|
25875
26127
|
repo.addCommand(createCloneCommand());
|
|
25876
26128
|
return repo;
|
|
25877
26129
|
}
|