-
Notifications
You must be signed in to change notification settings - Fork 37
/
getDefaultRemote.ts
111 lines (99 loc) · 4.27 KB
/
getDefaultRemote.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import fs from "fs";
import path from "path";
import { findGitRoot } from "../paths";
import { PackageInfo } from "../types/PackageInfo";
import { getRepositoryName } from "./getRepositoryName";
import { git } from "./git";
export type GetDefaultRemoteOptions = {
/** Get repository info relative to this directory. */
cwd: string;
/**
* If true, throw an error if remote info can't be found, or if a `repository` is not specified
* in package.json and no matching remote is found.
*/
strict?: boolean;
/** If true, log debug messages about how the remote was chosen */
verbose?: boolean;
};
/**
* Get the name of the default remote: the one matching the `repository` field in package.json.
* Throws if `options.cwd` is not in a git repo or there's no package.json at the repo root.
*
* The order of preference for returned remotes is:
* 1. If `repository` is defined in package.json, the remote with a matching URL (if `options.strict`
* is true, throws an error if no matching remote exists)
* 2. `upstream` if defined
* 3. `origin` if defined
* 4. The first defined remote
* 5. If there are no defined remotes: throws an error if `options.strict` is true; otherwise returns `origin`
*
* @returns The name of the inferred default remote.
*/
export function getDefaultRemote(options: GetDefaultRemoteOptions): string;
/** @deprecated Use the object param version */
export function getDefaultRemote(cwd: string): string;
export function getDefaultRemote(cwdOrOptions: string | GetDefaultRemoteOptions) {
const options = typeof cwdOrOptions === "string" ? { cwd: cwdOrOptions } : cwdOrOptions;
const { cwd, strict, verbose } = options;
const log = (message: string) => verbose && console.log(message);
const logOrThrow = (message: string) => {
if (strict) {
throw new Error(message);
}
log(message);
};
const gitRoot = findGitRoot(cwd);
let packageJson: Partial<PackageInfo> = {};
const packageJsonPath = path.join(gitRoot, "package.json");
try {
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8").trim());
} catch (e) {
logOrThrow(`Could not read "${packageJsonPath}"`);
}
const { repository } = packageJson;
const repositoryUrl = typeof repository === "string" ? repository : (repository && repository.url) || "";
if (!repositoryUrl) {
// This is always logged because it's strongly recommended to fix
console.log(
`Valid "repository" key not found in "${packageJsonPath}". Consider adding this info for more accurate git remote detection.`
);
}
/** Repository full name (owner and repo name) specified in package.json */
const repositoryName = getRepositoryName(repositoryUrl);
const remotesResult = git(["remote", "-v"], { cwd });
if (!remotesResult.success) {
logOrThrow(`Could not determine available git remotes under "${cwd}"`);
}
/** Mapping from remote URL to full name (owner and repo name) */
const remotes: { [remoteRepoUrl: string]: string } = {};
remotesResult.stdout.split("\n").forEach((line) => {
const [remoteName, remoteUrl] = line.split(/\s+/);
const remoteRepoName = getRepositoryName(remoteUrl);
if (remoteRepoName) {
remotes[remoteRepoName] = remoteName;
}
});
if (repositoryName) {
// If the repository name was found in package.json, check for a matching remote
if (remotes[repositoryName]) {
return remotes[repositoryName];
}
// If `strict` is true, and repositoryName is found, there MUST be a matching remote
logOrThrow(`Could not find remote pointing to repository "${repositoryName}".`);
}
// Default to upstream or origin if available, or the first remote otherwise
const allRemoteNames = Object.values(remotes);
const fallbacks = ["upstream", "origin", ...allRemoteNames];
for (const fallback of fallbacks) {
if (allRemoteNames.includes(fallback)) {
log(`Default to remote "${fallback}"`);
return fallback;
}
}
// If we get here, no git remotes were found. This should probably always be an error (since
// subsequent operations which require a remote likely won't work), but to match old behavior,
// still default to "origin" unless `strict` is true.
logOrThrow(`Could not find any remotes in git repo at "${gitRoot}".`);
log(`Assuming default remote "origin".`);
return "origin";
}