securewithtrace 0.10.0 → 0.12.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 +39 -2
- package/dist/index.js +353 -28
- 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,8 @@ 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`)
|
|
56
|
+
- **Find repositories by dependency** with optional version filtering (`repo list --dependency`)
|
|
53
57
|
- **Clone repositories** with secure token handling (`repo clone`)
|
|
54
58
|
- **Filter vulnerabilities** by repo, severity, and status (`vulnerabilities list`)
|
|
55
59
|
- **Export results as JSON or TSV** for automation (`vulnerabilities list --output json`)
|
|
@@ -70,6 +74,27 @@ tracecli auth logout
|
|
|
70
74
|
# List org repositories known to Trace
|
|
71
75
|
tracecli repo list
|
|
72
76
|
|
|
77
|
+
# List only connected GitHub repositories that match "trace"
|
|
78
|
+
tracecli repo list --platform GITHUB --status CONNECTED --query trace
|
|
79
|
+
|
|
80
|
+
# Advanced filtering syntax (repeatable)
|
|
81
|
+
tracecli repo list --filter "platform=GITHUB" --filter "repo:trace" --filter "status!=DISCONNECTED"
|
|
82
|
+
|
|
83
|
+
# Alias command for filtering
|
|
84
|
+
tracecli repo filter --filter "language:typescript" --filter "branch=main"
|
|
85
|
+
|
|
86
|
+
# Find repositories that use a specific dependency
|
|
87
|
+
tracecli repo list --dependency lodash
|
|
88
|
+
|
|
89
|
+
# Filter by dependency at a specific version
|
|
90
|
+
tracecli repo list --dependency lodash@4.17.21
|
|
91
|
+
|
|
92
|
+
# Filter by scoped package with partial version match
|
|
93
|
+
tracecli repo list --dependency @types/node@20
|
|
94
|
+
|
|
95
|
+
# Dependency results as JSON
|
|
96
|
+
tracecli repo list --dependency react --json | jq '.[].fullName'
|
|
97
|
+
|
|
73
98
|
# Clone by owner/repo shorthand
|
|
74
99
|
tracecli repo clone securewithtrace/trace
|
|
75
100
|
|
|
@@ -114,6 +139,18 @@ Supported filter values:
|
|
|
114
139
|
- `--severity`: `CRITICAL`, `HIGH`, `MEDIUM`, `LOW`
|
|
115
140
|
- `--status`: `OPEN`, `IN_PROGRESS`, `FIXED`, `IGNORED`, `FALSE_POSITIVE`
|
|
116
141
|
|
|
142
|
+
Repository filter options:
|
|
143
|
+
|
|
144
|
+
- `--platform`: `GITHUB`, `GITLAB`, `BITBUCKET`
|
|
145
|
+
- `--status`: `CONNECTED`, `DISCONNECTED`
|
|
146
|
+
- `--query`: case-insensitive contains match across URL, repo name, default branch, and language
|
|
147
|
+
- `--default-branch`: case-insensitive contains match for default branch
|
|
148
|
+
- `--language`: case-insensitive contains match for primary language
|
|
149
|
+
- `--filter`: repeatable advanced expression in the form `<field><operator><value>`
|
|
150
|
+
- Supported fields: `platform`, `status`, `url`, `repo`, `branch`, `language`
|
|
151
|
+
- Supported operators: `=` (exact), `!=` (not exact), `:` (contains), `!:` (not contains)
|
|
152
|
+
- `--dependency`: filter by dependency name with optional `@version` (e.g. `lodash`, `lodash@4.17`, `@types/node@20`)
|
|
153
|
+
|
|
117
154
|
### Pentests
|
|
118
155
|
|
|
119
156
|
```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,341 @@ function validateBranchName(branch) {
|
|
|
25745
25748
|
}
|
|
25746
25749
|
return branch;
|
|
25747
25750
|
}
|
|
25748
|
-
function
|
|
25749
|
-
|
|
25750
|
-
|
|
25751
|
-
|
|
25752
|
-
|
|
25753
|
-
|
|
25754
|
-
|
|
25751
|
+
function parseDependencyFilter(filter) {
|
|
25752
|
+
const isScoped = filter.startsWith("@");
|
|
25753
|
+
const separatorIndex = isScoped ? filter.indexOf("@", 1) : filter.indexOf("@");
|
|
25754
|
+
if (separatorIndex === -1) {
|
|
25755
|
+
return { name: filter };
|
|
25756
|
+
}
|
|
25757
|
+
const name = filter.slice(0, separatorIndex);
|
|
25758
|
+
const version2 = filter.slice(separatorIndex + 1);
|
|
25759
|
+
if (!name || !version2) {
|
|
25760
|
+
return { name: filter };
|
|
25761
|
+
}
|
|
25762
|
+
return { name, version: version2 };
|
|
25763
|
+
}
|
|
25764
|
+
function parseCloneDepth(value) {
|
|
25765
|
+
const depth = Number.parseInt(value, 10);
|
|
25766
|
+
if (!Number.isInteger(depth) || depth < 1) {
|
|
25767
|
+
throw new Error("--depth must be a positive integer");
|
|
25768
|
+
}
|
|
25769
|
+
return depth;
|
|
25770
|
+
}
|
|
25771
|
+
function normalizeForMatching(value) {
|
|
25772
|
+
return value.trim().toLowerCase();
|
|
25773
|
+
}
|
|
25774
|
+
function normalizePlatform(value, source) {
|
|
25775
|
+
const normalized = value.trim().toUpperCase();
|
|
25776
|
+
if (REPOSITORY_PLATFORMS.includes(normalized)) {
|
|
25777
|
+
return normalized;
|
|
25778
|
+
}
|
|
25779
|
+
throw new Error(
|
|
25780
|
+
`${source} must be one of: ${REPOSITORY_PLATFORMS.join(", ")}. Received '${value}'.`
|
|
25781
|
+
);
|
|
25782
|
+
}
|
|
25783
|
+
function normalizeStatus(value, source) {
|
|
25784
|
+
const normalized = value.trim().toUpperCase();
|
|
25785
|
+
if (REPOSITORY_STATUSES.includes(normalized)) {
|
|
25786
|
+
return normalized;
|
|
25787
|
+
}
|
|
25788
|
+
throw new Error(
|
|
25789
|
+
`${source} must be one of: ${REPOSITORY_STATUSES.join(", ")}. Received '${value}'.`
|
|
25790
|
+
);
|
|
25791
|
+
}
|
|
25792
|
+
function normalizeFilterField(rawField) {
|
|
25793
|
+
const field = rawField.trim().toLowerCase();
|
|
25794
|
+
switch (field) {
|
|
25795
|
+
case "platform":
|
|
25796
|
+
return "platform";
|
|
25797
|
+
case "status":
|
|
25798
|
+
return "status";
|
|
25799
|
+
case "url":
|
|
25800
|
+
case "repository":
|
|
25801
|
+
case "repositoryurl":
|
|
25802
|
+
case "repository_url":
|
|
25803
|
+
return "repository_url";
|
|
25804
|
+
case "repo":
|
|
25805
|
+
case "name":
|
|
25806
|
+
case "repository_name":
|
|
25807
|
+
return "repo_name";
|
|
25808
|
+
case "branch":
|
|
25809
|
+
case "defaultbranch":
|
|
25810
|
+
case "default_branch":
|
|
25811
|
+
return "default_branch";
|
|
25812
|
+
case "language":
|
|
25813
|
+
case "primarylanguage":
|
|
25814
|
+
case "primary_language":
|
|
25815
|
+
return "language";
|
|
25816
|
+
default:
|
|
25817
|
+
throw new Error(
|
|
25818
|
+
`Unknown repository filter field '${rawField}'. Supported fields: platform, status, url, repo, branch, language.`
|
|
25819
|
+
);
|
|
25820
|
+
}
|
|
25821
|
+
}
|
|
25822
|
+
function parseFilterOperator(rawOperator) {
|
|
25823
|
+
switch (rawOperator) {
|
|
25824
|
+
case "=":
|
|
25825
|
+
return "exact";
|
|
25826
|
+
case "!=":
|
|
25827
|
+
return "not_exact";
|
|
25828
|
+
case ":":
|
|
25829
|
+
return "contains";
|
|
25830
|
+
case "!:":
|
|
25831
|
+
return "not_contains";
|
|
25832
|
+
default:
|
|
25833
|
+
throw new Error(`Unsupported filter operator '${rawOperator}'. Use one of: =, !=, :, !:.`);
|
|
25834
|
+
}
|
|
25835
|
+
}
|
|
25836
|
+
function parseRepositoryFilterExpression(expression) {
|
|
25837
|
+
const trimmed = expression.trim();
|
|
25838
|
+
const match = /^([a-zA-Z][a-zA-Z0-9_-]*)(!=|!:|=|:)(.+)$/.exec(trimmed);
|
|
25839
|
+
if (!match) {
|
|
25840
|
+
throw new Error(
|
|
25841
|
+
`Invalid filter '${expression}'. Expected format: <field><operator><value> (e.g., platform=GITHUB, repo:trace, status!=DISCONNECTED).`
|
|
25755
25842
|
);
|
|
25756
|
-
|
|
25757
|
-
|
|
25758
|
-
|
|
25843
|
+
}
|
|
25844
|
+
const rawField = match[1];
|
|
25845
|
+
const rawOperator = match[2];
|
|
25846
|
+
const rawValue = match[3];
|
|
25847
|
+
if (!rawField || !rawOperator || !rawValue) {
|
|
25848
|
+
throw new Error(`Invalid filter '${expression}'.`);
|
|
25849
|
+
}
|
|
25850
|
+
const value = rawValue.trim();
|
|
25851
|
+
if (!value) {
|
|
25852
|
+
throw new Error(`Invalid filter '${expression}': value cannot be empty.`);
|
|
25853
|
+
}
|
|
25854
|
+
const field = normalizeFilterField(rawField);
|
|
25855
|
+
const operator = parseFilterOperator(rawOperator);
|
|
25856
|
+
if (field === "platform") {
|
|
25857
|
+
normalizePlatform(value, "--filter");
|
|
25858
|
+
}
|
|
25859
|
+
if (field === "status") {
|
|
25860
|
+
normalizeStatus(value, "--filter");
|
|
25861
|
+
}
|
|
25862
|
+
return { field, operator, value };
|
|
25863
|
+
}
|
|
25864
|
+
function doesValueMatchFilter(candidate, filterValue, operator, exactMatchOnly) {
|
|
25865
|
+
const normalizedCandidate = normalizeForMatching(candidate);
|
|
25866
|
+
const normalizedFilter = normalizeForMatching(filterValue);
|
|
25867
|
+
if (exactMatchOnly || operator === "exact" || operator === "not_exact") {
|
|
25868
|
+
const matches2 = normalizedCandidate === normalizedFilter;
|
|
25869
|
+
return operator === "not_exact" ? !matches2 : matches2;
|
|
25870
|
+
}
|
|
25871
|
+
const matches = normalizedCandidate.includes(normalizedFilter);
|
|
25872
|
+
return operator === "not_contains" ? !matches : matches;
|
|
25873
|
+
}
|
|
25874
|
+
function matchParsedFilter(repo, parsedFilter) {
|
|
25875
|
+
switch (parsedFilter.field) {
|
|
25876
|
+
case "platform":
|
|
25877
|
+
return doesValueMatchFilter(repo.platform, parsedFilter.value, parsedFilter.operator, true);
|
|
25878
|
+
case "status":
|
|
25879
|
+
return doesValueMatchFilter(repo.status, parsedFilter.value, parsedFilter.operator, true);
|
|
25880
|
+
case "repository_url":
|
|
25881
|
+
return doesValueMatchFilter(
|
|
25882
|
+
repo.repositoryUrl,
|
|
25883
|
+
parsedFilter.value,
|
|
25884
|
+
parsedFilter.operator,
|
|
25885
|
+
false
|
|
25886
|
+
);
|
|
25887
|
+
case "repo_name":
|
|
25888
|
+
return doesValueMatchFilter(
|
|
25889
|
+
extractRepoName(repo.repositoryUrl),
|
|
25890
|
+
parsedFilter.value,
|
|
25891
|
+
parsedFilter.operator,
|
|
25892
|
+
false
|
|
25893
|
+
);
|
|
25894
|
+
case "default_branch":
|
|
25895
|
+
return doesValueMatchFilter(
|
|
25896
|
+
repo.defaultBranch,
|
|
25897
|
+
parsedFilter.value,
|
|
25898
|
+
parsedFilter.operator,
|
|
25899
|
+
false
|
|
25900
|
+
);
|
|
25901
|
+
case "language": {
|
|
25902
|
+
const language = repo.primaryLanguage ?? "";
|
|
25903
|
+
return doesValueMatchFilter(language, parsedFilter.value, parsedFilter.operator, false);
|
|
25904
|
+
}
|
|
25905
|
+
}
|
|
25906
|
+
}
|
|
25907
|
+
function filterRepositories(repositories, options) {
|
|
25908
|
+
const parsedFilters = (options.filters ?? []).map(
|
|
25909
|
+
(expression) => parseRepositoryFilterExpression(expression)
|
|
25910
|
+
);
|
|
25911
|
+
const normalizedPlatform = options.platform ? normalizePlatform(options.platform, "--platform") : void 0;
|
|
25912
|
+
const normalizedStatus = options.status ? normalizeStatus(options.status, "--status") : void 0;
|
|
25913
|
+
const normalizedQuery = options.query ? normalizeForMatching(options.query) : void 0;
|
|
25914
|
+
const normalizedDefaultBranch = options.defaultBranch ? normalizeForMatching(options.defaultBranch) : void 0;
|
|
25915
|
+
const normalizedLanguage = options.language ? normalizeForMatching(options.language) : void 0;
|
|
25916
|
+
return repositories.filter((repo) => {
|
|
25917
|
+
if (normalizedPlatform && repo.platform.toUpperCase() !== normalizedPlatform) {
|
|
25918
|
+
return false;
|
|
25919
|
+
}
|
|
25920
|
+
if (normalizedStatus && repo.status.toUpperCase() !== normalizedStatus) {
|
|
25921
|
+
return false;
|
|
25922
|
+
}
|
|
25923
|
+
if (normalizedDefaultBranch && !normalizeForMatching(repo.defaultBranch).includes(normalizedDefaultBranch)) {
|
|
25924
|
+
return false;
|
|
25925
|
+
}
|
|
25926
|
+
const repoLanguage = normalizeForMatching(repo.primaryLanguage ?? "");
|
|
25927
|
+
if (normalizedLanguage && !repoLanguage.includes(normalizedLanguage)) {
|
|
25928
|
+
return false;
|
|
25929
|
+
}
|
|
25930
|
+
if (normalizedQuery) {
|
|
25931
|
+
const normalizedUrl = normalizeForMatching(repo.repositoryUrl);
|
|
25932
|
+
const normalizedBranch = normalizeForMatching(repo.defaultBranch);
|
|
25933
|
+
const normalizedName = normalizeForMatching(extractRepoName(repo.repositoryUrl));
|
|
25934
|
+
if (!normalizedUrl.includes(normalizedQuery) && !normalizedBranch.includes(normalizedQuery) && !repoLanguage.includes(normalizedQuery) && !normalizedName.includes(normalizedQuery)) {
|
|
25935
|
+
return false;
|
|
25936
|
+
}
|
|
25937
|
+
}
|
|
25938
|
+
for (const parsedFilter of parsedFilters) {
|
|
25939
|
+
if (!matchParsedFilter(repo, parsedFilter)) {
|
|
25940
|
+
return false;
|
|
25941
|
+
}
|
|
25759
25942
|
}
|
|
25760
|
-
|
|
25761
|
-
|
|
25762
|
-
|
|
25943
|
+
return true;
|
|
25944
|
+
});
|
|
25945
|
+
}
|
|
25946
|
+
function collectFilterExpression(value, previous) {
|
|
25947
|
+
previous.push(value);
|
|
25948
|
+
return previous;
|
|
25949
|
+
}
|
|
25950
|
+
function toRepositoryListFilterOptions(options) {
|
|
25951
|
+
return {
|
|
25952
|
+
...options.platform ? { platform: options.platform } : {},
|
|
25953
|
+
...options.status ? { status: options.status } : {},
|
|
25954
|
+
...options.query ? { query: options.query } : {},
|
|
25955
|
+
...options.defaultBranch ? { defaultBranch: options.defaultBranch } : {},
|
|
25956
|
+
...options.language ? { language: options.language } : {},
|
|
25957
|
+
...options.filter.length > 0 ? { filters: options.filter } : {}
|
|
25958
|
+
};
|
|
25959
|
+
}
|
|
25960
|
+
function addRepositoryFilterOptions(command) {
|
|
25961
|
+
return command.option(
|
|
25962
|
+
"--platform <platform>",
|
|
25963
|
+
"Filter by platform (GITHUB, GITLAB, BITBUCKET)",
|
|
25964
|
+
(value) => value.toUpperCase()
|
|
25965
|
+
).option(
|
|
25966
|
+
"--status <status>",
|
|
25967
|
+
"Filter by status (CONNECTED, DISCONNECTED)",
|
|
25968
|
+
(value) => value.toUpperCase()
|
|
25969
|
+
).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(
|
|
25970
|
+
"--filter <expression>",
|
|
25971
|
+
"Advanced filter expression. Repeatable. Examples: platform=GITHUB, repo:trace, status!=DISCONNECTED, language!:typescript",
|
|
25972
|
+
collectFilterExpression,
|
|
25973
|
+
[]
|
|
25974
|
+
).option("--json", "Output as JSON").option(
|
|
25975
|
+
"--dependency <pkg>",
|
|
25976
|
+
"Filter repositories that use a dependency (e.g. lodash, lodash@4.17.21, @types/node@20)"
|
|
25977
|
+
);
|
|
25978
|
+
}
|
|
25979
|
+
async function runRepositoryListCommand(options) {
|
|
25980
|
+
const organizationId = await resolveOrganizationId();
|
|
25981
|
+
if (options.dependency) {
|
|
25982
|
+
await runDependencyFilter(organizationId, options);
|
|
25983
|
+
return;
|
|
25984
|
+
}
|
|
25985
|
+
let filteredRepositories;
|
|
25986
|
+
const result = await authenticatedRequest(
|
|
25987
|
+
(client) => client.repository.listRepositories({
|
|
25988
|
+
params: { organizationId }
|
|
25989
|
+
})
|
|
25990
|
+
);
|
|
25991
|
+
if (result.status !== 200) {
|
|
25992
|
+
console.error(`Failed to list repositories (status ${result.status}).`);
|
|
25993
|
+
process.exit(1);
|
|
25994
|
+
}
|
|
25995
|
+
try {
|
|
25996
|
+
filteredRepositories = filterRepositories(result.body, toRepositoryListFilterOptions(options));
|
|
25997
|
+
} catch (error48) {
|
|
25998
|
+
const message = error48 instanceof Error ? error48.message : "Unknown filter parsing error.";
|
|
25999
|
+
console.error(`Invalid repository filter: ${message}`);
|
|
26000
|
+
process.exit(1);
|
|
26001
|
+
}
|
|
26002
|
+
if (filteredRepositories.length === 0) {
|
|
26003
|
+
if (options.platform || options.status || options.query || options.defaultBranch || options.language || options.filter.length > 0) {
|
|
26004
|
+
console.log("No repositories found matching the provided filters.");
|
|
25763
26005
|
return;
|
|
25764
26006
|
}
|
|
25765
|
-
|
|
25766
|
-
|
|
25767
|
-
|
|
25768
|
-
|
|
25769
|
-
|
|
26007
|
+
console.log("No repositories found.");
|
|
26008
|
+
return;
|
|
26009
|
+
}
|
|
26010
|
+
if (options.json) {
|
|
26011
|
+
console.log(JSON.stringify(filteredRepositories, null, 2));
|
|
26012
|
+
return;
|
|
26013
|
+
}
|
|
26014
|
+
for (const repo of filteredRepositories) {
|
|
26015
|
+
const status = repo.status === "CONNECTED" ? "connected" : "disconnected";
|
|
26016
|
+
const url2 = stripAnsi(String(repo.repositoryUrl));
|
|
26017
|
+
const platform = stripAnsi(String(repo.platform)).toLowerCase();
|
|
26018
|
+
console.log(`${url2} (${platform}, ${status})`);
|
|
26019
|
+
}
|
|
26020
|
+
}
|
|
26021
|
+
async function runDependencyFilter(organizationId, options) {
|
|
26022
|
+
const { name, version: version2 } = parseDependencyFilter(options.dependency);
|
|
26023
|
+
const result = await authenticatedRequest(
|
|
26024
|
+
(client) => client.dependency.searchDependencies({
|
|
26025
|
+
params: { organizationId },
|
|
26026
|
+
body: {
|
|
26027
|
+
query: name,
|
|
26028
|
+
...version2 ? { versionPrefix: version2 } : {},
|
|
26029
|
+
pageSize: 100
|
|
26030
|
+
}
|
|
26031
|
+
})
|
|
26032
|
+
);
|
|
26033
|
+
if (result.status !== 200) {
|
|
26034
|
+
console.error(`Failed to search dependencies (status ${result.status}).`);
|
|
26035
|
+
process.exit(1);
|
|
26036
|
+
}
|
|
26037
|
+
const repoMap = /* @__PURE__ */ new Map();
|
|
26038
|
+
for (const pkg of result.body.packages) {
|
|
26039
|
+
if (pkg.name.toLowerCase() !== name.toLowerCase()) {
|
|
26040
|
+
continue;
|
|
26041
|
+
}
|
|
26042
|
+
for (const repo of pkg.repositories) {
|
|
26043
|
+
const existing = repoMap.get(repo.repositoryId);
|
|
26044
|
+
const versionStrings = repo.versions.map((v) => v.version);
|
|
26045
|
+
if (existing) {
|
|
26046
|
+
for (const v of versionStrings) {
|
|
26047
|
+
if (!existing.versions.includes(v)) {
|
|
26048
|
+
existing.versions.push(v);
|
|
26049
|
+
}
|
|
26050
|
+
}
|
|
26051
|
+
} else {
|
|
26052
|
+
repoMap.set(repo.repositoryId, {
|
|
26053
|
+
fullName: repo.fullName,
|
|
26054
|
+
versions: versionStrings
|
|
26055
|
+
});
|
|
26056
|
+
}
|
|
25770
26057
|
}
|
|
26058
|
+
}
|
|
26059
|
+
const repos = Array.from(repoMap.values());
|
|
26060
|
+
if (repos.length === 0) {
|
|
26061
|
+
const label = version2 ? `${name}@${version2}` : name;
|
|
26062
|
+
console.log(`No repositories found using dependency '${label}'.`);
|
|
26063
|
+
return;
|
|
26064
|
+
}
|
|
26065
|
+
if (options.json) {
|
|
26066
|
+
console.log(JSON.stringify(repos, null, 2));
|
|
26067
|
+
return;
|
|
26068
|
+
}
|
|
26069
|
+
for (const repo of repos) {
|
|
26070
|
+
const versions = repo.versions.join(", ");
|
|
26071
|
+
console.log(`${repo.fullName} (${versions})`);
|
|
26072
|
+
}
|
|
26073
|
+
}
|
|
26074
|
+
function createListCommand2() {
|
|
26075
|
+
return addRepositoryFilterOptions(
|
|
26076
|
+
new Command("list").description("List repositories in your organization")
|
|
26077
|
+
).action(async (options) => {
|
|
26078
|
+
await runRepositoryListCommand(options);
|
|
26079
|
+
});
|
|
26080
|
+
}
|
|
26081
|
+
function createFilterCommand() {
|
|
26082
|
+
return addRepositoryFilterOptions(
|
|
26083
|
+
new Command("filter").description("Filter repositories in your organization")
|
|
26084
|
+
).action(async (options) => {
|
|
26085
|
+
await runRepositoryListCommand(options);
|
|
25771
26086
|
});
|
|
25772
26087
|
}
|
|
25773
26088
|
function urlWithUsername(httpsUrl) {
|
|
@@ -25776,14 +26091,21 @@ function urlWithUsername(httpsUrl) {
|
|
|
25776
26091
|
url2.password = "";
|
|
25777
26092
|
return url2.toString();
|
|
25778
26093
|
}
|
|
25779
|
-
function createAskPassScript(
|
|
26094
|
+
function createAskPassScript() {
|
|
25780
26095
|
const dir = mkdtempSync(join3(tmpdir(), "trace-askpass-"));
|
|
25781
26096
|
const scriptPath = join3(dir, "askpass.sh");
|
|
25782
|
-
writeFileSync3(
|
|
25783
|
-
|
|
25784
|
-
|
|
25785
|
-
|
|
25786
|
-
|
|
26097
|
+
writeFileSync3(
|
|
26098
|
+
scriptPath,
|
|
26099
|
+
`#!/bin/sh
|
|
26100
|
+
if [ -z "\${${TRACE_GIT_ASKPASS_TOKEN_ENV}:-}" ]; then
|
|
26101
|
+
exit 1
|
|
26102
|
+
fi
|
|
26103
|
+
printf '%s\\n' "$${TRACE_GIT_ASKPASS_TOKEN_ENV}"
|
|
26104
|
+
`,
|
|
26105
|
+
{
|
|
26106
|
+
mode: 448
|
|
26107
|
+
}
|
|
26108
|
+
);
|
|
25787
26109
|
chmodSync(scriptPath, 448);
|
|
25788
26110
|
return scriptPath;
|
|
25789
26111
|
}
|
|
@@ -25803,7 +26125,8 @@ async function fetchCloneToken(repoUrl) {
|
|
|
25803
26125
|
return null;
|
|
25804
26126
|
}
|
|
25805
26127
|
if (result.status === 200) {
|
|
25806
|
-
|
|
26128
|
+
const token = result.body.token.trim();
|
|
26129
|
+
return token.length > 0 ? token : null;
|
|
25807
26130
|
}
|
|
25808
26131
|
return null;
|
|
25809
26132
|
} catch {
|
|
@@ -25811,7 +26134,7 @@ async function fetchCloneToken(repoUrl) {
|
|
|
25811
26134
|
}
|
|
25812
26135
|
}
|
|
25813
26136
|
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",
|
|
26137
|
+
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
26138
|
async (repository, directory, options) => {
|
|
25816
26139
|
const cloneUrl = resolveRepoUrl(repository);
|
|
25817
26140
|
const targetDir = directory ?? extractRepoName(repository);
|
|
@@ -25833,8 +26156,9 @@ function createCloneCommand() {
|
|
|
25833
26156
|
console.log(`Cloning ${cloneUrl} into ${targetDir}...`);
|
|
25834
26157
|
const extraEnv = {};
|
|
25835
26158
|
if (installationToken) {
|
|
25836
|
-
askPassScript = createAskPassScript(
|
|
26159
|
+
askPassScript = createAskPassScript();
|
|
25837
26160
|
extraEnv["GIT_ASKPASS"] = askPassScript;
|
|
26161
|
+
extraEnv[TRACE_GIT_ASKPASS_TOKEN_ENV] = installationToken;
|
|
25838
26162
|
extraEnv["GIT_TERMINAL_PROMPT"] = "0";
|
|
25839
26163
|
}
|
|
25840
26164
|
try {
|
|
@@ -25845,7 +26169,7 @@ function createCloneCommand() {
|
|
|
25845
26169
|
});
|
|
25846
26170
|
const exitCode = await proc.exited;
|
|
25847
26171
|
if (exitCode !== 0) {
|
|
25848
|
-
process.exit(
|
|
26172
|
+
process.exit(1);
|
|
25849
26173
|
}
|
|
25850
26174
|
if (installationToken) {
|
|
25851
26175
|
const resetProc = Bun.spawn(["git", "remote", "set-url", "origin", cloneUrl], {
|
|
@@ -25872,6 +26196,7 @@ function createCloneCommand() {
|
|
|
25872
26196
|
function createRepoCommand() {
|
|
25873
26197
|
const repo = new Command("repo").description("Manage repositories");
|
|
25874
26198
|
repo.addCommand(createListCommand2());
|
|
26199
|
+
repo.addCommand(createFilterCommand());
|
|
25875
26200
|
repo.addCommand(createCloneCommand());
|
|
25876
26201
|
return repo;
|
|
25877
26202
|
}
|