workspace-tools 0.23.3 → 0.24.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/CHANGELOG.json CHANGED
@@ -2,7 +2,22 @@
2
2
  "name": "workspace-tools",
3
3
  "entries": [
4
4
  {
5
- "date": "Fri, 15 Jul 2022 07:09:46 GMT",
5
+ "date": "Wed, 20 Jul 2022 22:31:28 GMT",
6
+ "tag": "workspace-tools_v0.24.0",
7
+ "version": "0.24.0",
8
+ "comments": {
9
+ "minor": [
10
+ {
11
+ "author": "elcraig@microsoft.com",
12
+ "package": "workspace-tools",
13
+ "comment": "Make getDefaultRemote properly handle more combinations of URL formats, and add more logging to encourage defining the `repository` property in package.json for more accurate detection",
14
+ "commit": "4bffdc45ceb49ca49f28ebc788e5c2cbe71e3a9f"
15
+ }
16
+ ]
17
+ }
18
+ },
19
+ {
20
+ "date": "Fri, 15 Jul 2022 07:09:50 GMT",
6
21
  "tag": "workspace-tools_v0.23.3",
7
22
  "version": "0.23.3",
8
23
  "comments": {
package/CHANGELOG.md CHANGED
@@ -1,12 +1,20 @@
1
1
  # Change Log - workspace-tools
2
2
 
3
- This log was last generated on Fri, 15 Jul 2022 07:09:46 GMT and should not be manually modified.
3
+ This log was last generated on Wed, 20 Jul 2022 22:31:28 GMT and should not be manually modified.
4
4
 
5
5
  <!-- Start content -->
6
6
 
7
+ ## 0.24.0
8
+
9
+ Wed, 20 Jul 2022 22:31:28 GMT
10
+
11
+ ### Minor changes
12
+
13
+ - Make getDefaultRemote properly handle more combinations of URL formats, and add more logging to encourage defining the `repository` property in package.json for more accurate detection (elcraig@microsoft.com)
14
+
7
15
  ## 0.23.3
8
16
 
9
- Fri, 15 Jul 2022 07:09:46 GMT
17
+ Fri, 15 Jul 2022 07:09:50 GMT
10
18
 
11
19
  ### Patches
12
20
 
@@ -0,0 +1,28 @@
1
+ export declare type GetDefaultRemoteOptions = {
2
+ /** Get repository info relative to this directory. */
3
+ cwd: string;
4
+ /**
5
+ * If true, throw an error if remote info can't be found, or if a `repository` is not specified
6
+ * in package.json and no matching remote is found.
7
+ */
8
+ strict?: boolean;
9
+ /** If true, log debug messages about how the remote was chosen */
10
+ verbose?: boolean;
11
+ };
12
+ /**
13
+ * Get the name of the default remote: the one matching the `repository` field in package.json.
14
+ * Throws if `options.cwd` is not in a git repo or there's no package.json at the repo root.
15
+ *
16
+ * The order of preference for returned remotes is:
17
+ * 1. If `repository` is defined in package.json, the remote with a matching URL (if `options.strict`
18
+ * is true, throws an error if no matching remote exists)
19
+ * 2. `upstream` if defined
20
+ * 3. `origin` if defined
21
+ * 4. The first defined remote
22
+ * 5. If there are no defined remotes: throws an error if `options.strict` is true; otherwise returns `origin`
23
+ *
24
+ * @returns The name of the inferred default remote.
25
+ */
26
+ export declare function getDefaultRemote(options: GetDefaultRemoteOptions): string;
27
+ /** @deprecated Use the object param version */
28
+ export declare function getDefaultRemote(cwd: string): string;
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getDefaultRemote = void 0;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const paths_1 = require("../paths");
10
+ const getRepositoryName_1 = require("./getRepositoryName");
11
+ const git_1 = require("./git");
12
+ function getDefaultRemote(cwdOrOptions) {
13
+ const options = typeof cwdOrOptions === "string" ? { cwd: cwdOrOptions } : cwdOrOptions;
14
+ const { cwd, strict, verbose } = options;
15
+ const log = (message) => verbose && console.log(message);
16
+ const logOrThrow = (message) => {
17
+ if (strict) {
18
+ throw new Error(message);
19
+ }
20
+ log(message);
21
+ };
22
+ const gitRoot = (0, paths_1.findGitRoot)(cwd);
23
+ if (!gitRoot) {
24
+ throw new Error(`Directory "${cwd}" does not appear to be in a git repository`);
25
+ }
26
+ let packageJson = {};
27
+ const packageJsonPath = path_1.default.join(gitRoot, "package.json");
28
+ try {
29
+ packageJson = fs_extra_1.default.readJSONSync(packageJsonPath);
30
+ }
31
+ catch (e) {
32
+ throw new Error(`Could not read "${packageJsonPath}"`);
33
+ }
34
+ const { repository } = packageJson;
35
+ const repositoryUrl = typeof repository === "string" ? repository : (repository && repository.url) || "";
36
+ if (!repositoryUrl) {
37
+ // This is always logged because it's strongly recommended to fix
38
+ console.log(`Valid "repository" key not found in "${packageJsonPath}". Consider adding this info for more accurate git remote detection.`);
39
+ }
40
+ /** Repository full name (owner and repo name) specified in package.json */
41
+ const repositoryName = (0, getRepositoryName_1.getRepositoryName)(repositoryUrl);
42
+ const remotesResult = (0, git_1.git)(["remote", "-v"], { cwd });
43
+ if (!remotesResult.success) {
44
+ logOrThrow(`Could not determine available git remotes under "${cwd}"`);
45
+ }
46
+ /** Mapping from remote URL to full name (owner and repo name) */
47
+ const remotes = {};
48
+ remotesResult.stdout.split("\n").forEach((line) => {
49
+ const [remoteName, remoteUrl] = line.split(/\s+/);
50
+ const remoteRepoName = (0, getRepositoryName_1.getRepositoryName)(remoteUrl);
51
+ if (remoteRepoName) {
52
+ remotes[remoteRepoName] = remoteName;
53
+ }
54
+ });
55
+ if (repositoryName) {
56
+ // If the repository name was found in package.json, check for a matching remote
57
+ if (remotes[repositoryName]) {
58
+ return remotes[repositoryName];
59
+ }
60
+ // If `strict` is true, and repositoryName is found, there MUST be a matching remote
61
+ logOrThrow(`Could not find remote pointing to repository "${repositoryName}".`);
62
+ }
63
+ // Default to upstream or origin if available, or the first remote otherwise
64
+ const allRemoteNames = Object.values(remotes);
65
+ const fallbacks = ["upstream", "origin", ...allRemoteNames];
66
+ for (const fallback of fallbacks) {
67
+ if (allRemoteNames.includes(fallback)) {
68
+ log(`Default to remote "${fallback}"`);
69
+ return fallback;
70
+ }
71
+ }
72
+ // If we get here, no git remotes were found. This should probably always be an error (since
73
+ // subsequent operations which require a remote likely won't work), but to match old behavior,
74
+ // still default to "origin" unless `strict` is true.
75
+ logOrThrow(`Could not find any remotes in git repo at "${gitRoot}".`);
76
+ log(`Assuming default remote "origin".`);
77
+ return "origin";
78
+ }
79
+ exports.getDefaultRemote = getDefaultRemote;
@@ -0,0 +1,18 @@
1
+ import { GetDefaultRemoteOptions } from "./getDefaultRemote";
2
+ export declare type GetDefaultRemoteBranchOptions = GetDefaultRemoteOptions & {
3
+ /** Name of branch to use. If undefined, uses the default branch name (falling back to `master`). */
4
+ branch?: string;
5
+ };
6
+ /**
7
+ * Gets a reference to `options.branch` or the default branch relative to the default remote.
8
+ * (See {@link getDefaultRemote} for how the default remote is determined.)
9
+ * Throws if `options.cwd` is not in a git repo or there's no package.json at the repo root.
10
+ * @returns A branch reference like `upstream/master` or `origin/master`.
11
+ */
12
+ export declare function getDefaultRemoteBranch(options: GetDefaultRemoteBranchOptions): string;
13
+ /**
14
+ * First param: `branch`. Second param: `cwd`. See {@link GetDefaultRemoteBranchOptions} for more info.
15
+ * (This had to be changed to `...args` to avoid a conflict with the object param version.)
16
+ * @deprecated Use the object param version
17
+ */
18
+ export declare function getDefaultRemoteBranch(...args: string[]): string;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getDefaultRemoteBranch = void 0;
4
+ const getDefaultRemote_1 = require("./getDefaultRemote");
5
+ const git_1 = require("./git");
6
+ const gitUtilities_1 = require("./gitUtilities");
7
+ function getDefaultRemoteBranch(...args) {
8
+ var _a;
9
+ const [branchOrOptions, argsCwd] = args;
10
+ const options = typeof branchOrOptions === "string"
11
+ ? { branch: branchOrOptions, cwd: argsCwd }
12
+ : branchOrOptions;
13
+ const { cwd, branch } = options;
14
+ const defaultRemote = (0, getDefaultRemote_1.getDefaultRemote)(options);
15
+ if (branch) {
16
+ return `${defaultRemote}/${branch}`;
17
+ }
18
+ const showRemote = (0, git_1.git)(["remote", "show", defaultRemote], { cwd });
19
+ let remoteDefaultBranch;
20
+ if (showRemote.success) {
21
+ /**
22
+ * `showRemote.stdout` is something like this:
23
+ *
24
+ * * remote origin
25
+ * Fetch URL: .../monorepo-upstream
26
+ * Push URL: .../monorepo-upstream
27
+ * HEAD branch: main
28
+ */
29
+ remoteDefaultBranch = (_a = showRemote.stdout
30
+ .split(/\n/)
31
+ .find((line) => line.includes("HEAD branch"))) === null || _a === void 0 ? void 0 : _a.replace(/^\s*HEAD branch:\s+/, "");
32
+ }
33
+ return `${defaultRemote}/${remoteDefaultBranch || (0, gitUtilities_1.getDefaultBranch)(cwd)}`;
34
+ }
35
+ exports.getDefaultRemoteBranch = getDefaultRemoteBranch;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Get a repository full name (owner and repo, plus organization for ADO/VSO) from a repository URL,
3
+ * including special handling for the many ADO/VSO URL formats.
4
+ *
5
+ * Examples:
6
+ * - returns `microsoft/workspace-tools` for `https://github.com/microsoft/workspace-tools.git`
7
+ * - returns `foo/bar/some-repo` for `https://dev.azure.com/foo/bar/_git/some-repo`
8
+ */
9
+ export declare function getRepositoryName(url: string): string;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getRepositoryName = void 0;
7
+ const git_url_parse_1 = __importDefault(require("git-url-parse"));
8
+ /**
9
+ * Get a repository full name (owner and repo, plus organization for ADO/VSO) from a repository URL,
10
+ * including special handling for the many ADO/VSO URL formats.
11
+ *
12
+ * Examples:
13
+ * - returns `microsoft/workspace-tools` for `https://github.com/microsoft/workspace-tools.git`
14
+ * - returns `foo/bar/some-repo` for `https://dev.azure.com/foo/bar/_git/some-repo`
15
+ */
16
+ function getRepositoryName(url) {
17
+ var _a;
18
+ try {
19
+ // Mostly use this standard library, but fix some VSO/ADO-specific quirks to account for the
20
+ // fact that all of the following URLs should be considered to point to the same repo:
21
+ // https://foo.visualstudio.com/bar/_git/some-repo
22
+ // https://foo.visualstudio.com/DefaultCollection/bar/_git/some-repo
23
+ // https://user:token@foo.visualstudio.com/DefaultCollection/bar/_git/some-repo
24
+ // https://foo.visualstudio.com/DefaultCollection/bar/_git/_optimized/some-repo
25
+ // foo@vs-ssh.visualstudio.com:v3/foo/bar/some-repo
26
+ // https://dev.azure.com/foo/bar/_git/some-repo
27
+ // https://dev.azure.com/foo/bar/_git/_optimized/some-repo
28
+ // https://user@dev.azure.com/foo/bar/_git/some-repo
29
+ // git@ssh.dev.azure.com:v3/foo/bar/some-repo
30
+ let fixedUrl = url.replace("/_optimized/", "/").replace("/DefaultCollection/", "/");
31
+ const parsedUrl = (0, git_url_parse_1.default)(fixedUrl);
32
+ const isVSO = fixedUrl.includes(".visualstudio.com");
33
+ const isADO = fixedUrl.includes("dev.azure.com");
34
+ if (!isVSO && !isADO) {
35
+ return parsedUrl.full_name;
36
+ }
37
+ // As of writing, ADO and VSO SSH URLs are parsed completely wrong
38
+ const sshMatch = parsedUrl.full_name.match(/(vs-ssh\.visualstudio\.com|ssh\.dev\.azure\.com):v\d+\/([^/]+)\/([^/]+)/);
39
+ if (sshMatch) {
40
+ return `${sshMatch[2]}/${sshMatch[3]}/${parsedUrl.name}`;
41
+ }
42
+ // As of writing, full_name is wrong for enough variants of ADO and VSO URLs that it
43
+ // makes more sense to just build it manually.
44
+ let organization = parsedUrl.organization;
45
+ if (!organization && isVSO) {
46
+ // organization is missing or wrong for VSO
47
+ organization = (_a = parsedUrl.resource.match(/([^.@]+)\.visualstudio\.com/)) === null || _a === void 0 ? void 0 : _a[1];
48
+ }
49
+ return `${organization}/${parsedUrl.owner}/${parsedUrl.name}`;
50
+ }
51
+ catch (err) {
52
+ return "";
53
+ }
54
+ }
55
+ exports.getRepositoryName = getRepositoryName;
@@ -0,0 +1,29 @@
1
+ /// <reference types="node" />
2
+ import { SpawnSyncOptions } from "child_process";
3
+ export declare class GitError extends Error {
4
+ originalError: unknown;
5
+ constructor(message: string, originalError?: unknown);
6
+ }
7
+ export declare type GitProcessOutput = {
8
+ stderr: string;
9
+ stdout: string;
10
+ success: boolean;
11
+ };
12
+ /** Observes the git operations called from `git()` or `gitFailFast()` */
13
+ declare type GitObserver = (args: string[], output: GitProcessOutput) => void;
14
+ /**
15
+ * Adds an observer for the git operations, e.g. for testing
16
+ * @param observer
17
+ */
18
+ export declare function addGitObserver(observer: GitObserver): void;
19
+ /**
20
+ * Runs git command - use this for read-only commands
21
+ */
22
+ export declare function git(args: string[], options?: SpawnSyncOptions): GitProcessOutput;
23
+ /**
24
+ * Runs git command - use this for commands that make changes to the filesystem
25
+ */
26
+ export declare function gitFailFast(args: string[], options?: SpawnSyncOptions & {
27
+ noExitCode?: boolean;
28
+ }): void;
29
+ export {};
package/lib/git/git.js ADDED
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ //
3
+ // Basic git wrappers
4
+ //
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.gitFailFast = exports.git = exports.addGitObserver = exports.GitError = void 0;
7
+ const child_process_1 = require("child_process");
8
+ class GitError extends Error {
9
+ constructor(message, originalError) {
10
+ if (originalError instanceof Error) {
11
+ super(`${message}: ${originalError.message}`);
12
+ }
13
+ else {
14
+ super(message);
15
+ }
16
+ this.originalError = originalError;
17
+ }
18
+ }
19
+ exports.GitError = GitError;
20
+ /**
21
+ * A global maxBuffer override for all git operations.
22
+ * Bumps up the default to 500MB instead of 1MB.
23
+ * Override this value with the `GIT_MAX_BUFFER` environment variable.
24
+ */
25
+ const defaultMaxBuffer = process.env.GIT_MAX_BUFFER ? parseInt(process.env.GIT_MAX_BUFFER) : 500 * 1024 * 1024;
26
+ const observers = [];
27
+ let observing;
28
+ /**
29
+ * Adds an observer for the git operations, e.g. for testing
30
+ * @param observer
31
+ */
32
+ function addGitObserver(observer) {
33
+ observers.push(observer);
34
+ }
35
+ exports.addGitObserver = addGitObserver;
36
+ /**
37
+ * Runs git command - use this for read-only commands
38
+ */
39
+ function git(args, options) {
40
+ const results = (0, child_process_1.spawnSync)("git", args, Object.assign({ maxBuffer: defaultMaxBuffer }, options));
41
+ const output = {
42
+ stderr: results.stderr.toString().trimRight(),
43
+ stdout: results.stdout.toString().trimRight(),
44
+ success: results.status === 0,
45
+ };
46
+ // notify observers, flipping the observing bit to prevent infinite loops
47
+ if (!observing) {
48
+ observing = true;
49
+ for (const observer of observers) {
50
+ observer(args, output);
51
+ }
52
+ observing = false;
53
+ }
54
+ return output;
55
+ }
56
+ exports.git = git;
57
+ /**
58
+ * Runs git command - use this for commands that make changes to the filesystem
59
+ */
60
+ function gitFailFast(args, options) {
61
+ var _a, _b;
62
+ const gitResult = git(args, options);
63
+ if (!gitResult.success) {
64
+ if (!(options === null || options === void 0 ? void 0 : options.noExitCode)) {
65
+ process.exitCode = 1;
66
+ }
67
+ throw new GitError(`CRITICAL ERROR: running git command: git ${args.join(" ")}!
68
+ ${(_a = gitResult.stdout) === null || _a === void 0 ? void 0 : _a.toString().trimRight()}
69
+ ${(_b = gitResult.stderr) === null || _b === void 0 ? void 0 : _b.toString().trimRight()}`);
70
+ }
71
+ }
72
+ exports.gitFailFast = gitFailFast;
@@ -1,25 +1,3 @@
1
- /// <reference types="node" />
2
- import { SpawnSyncOptions } from "child_process";
3
- declare type ProcessOutput = {
4
- stderr: string;
5
- stdout: string;
6
- success: boolean;
7
- };
8
- /** Observes the git operations called from `git()` or `gitFailFast()` */
9
- declare type GitObserver = (args: string[], output: ProcessOutput) => void;
10
- /**
11
- * Adds an observer for the git operations, e.g. for testing
12
- * @param observer
13
- */
14
- export declare function addGitObserver(observer: GitObserver): void;
15
- /**
16
- * Runs git command - use this for read-only commands
17
- */
18
- export declare function git(args: string[], options?: SpawnSyncOptions): ProcessOutput;
19
- /**
20
- * Runs git command - use this for commands that make changes to the filesystem
21
- */
22
- export declare function gitFailFast(args: string[], options?: SpawnSyncOptions): void;
23
1
  export declare function getUntrackedChanges(cwd: string): string[];
24
2
  export declare function fetchRemote(remote: string, cwd: string): void;
25
3
  export declare function fetchRemoteBranch(remote: string, remoteBranch: string, cwd: string): void;
@@ -31,8 +9,6 @@ export declare function getUnstagedChanges(cwd: string): string[];
31
9
  export declare function getChanges(branch: string, cwd: string): string[];
32
10
  /**
33
11
  * Gets all the changes between the branch and the merge-base
34
- * @param branch
35
- * @param cwd
36
12
  */
37
13
  export declare function getBranchChanges(branch: string, cwd: string): string[];
38
14
  export declare function getChangesBetweenRefs(fromRef: string, toRef: string, options: string[], pattern: string, cwd: string): string[];
@@ -58,8 +34,8 @@ export declare function parseRemoteBranch(branch: string): {
58
34
  remote: string;
59
35
  remoteBranch: string;
60
36
  };
61
- export declare function getDefaultRemoteBranch(branch: string | undefined, cwd: string): string;
37
+ /**
38
+ * Gets the default branch based on `git config init.defaultBranch`, falling back to `master`.
39
+ */
62
40
  export declare function getDefaultBranch(cwd: string): string;
63
- export declare function getDefaultRemote(cwd: string): string;
64
41
  export declare function listAllTrackedFiles(patterns: string[], cwd: string): string[];
65
- export {};
@@ -0,0 +1,286 @@
1
+ "use strict";
2
+ //
3
+ // Assorted other git utilities
4
+ // (could be split into separate files later if desired)
5
+ //
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.listAllTrackedFiles = exports.getDefaultBranch = exports.parseRemoteBranch = exports.getRemoteBranch = exports.getParentBranch = exports.revertLocalChanges = exports.stageAndCommit = exports.commit = exports.stage = exports.init = exports.getFileAddedHash = exports.getCurrentHash = exports.getShortBranchName = exports.getFullBranchRef = exports.getBranchName = exports.getUserEmail = exports.getRecentCommitMessages = exports.getStagedChanges = exports.getChangesBetweenRefs = exports.getBranchChanges = exports.getChanges = exports.getUnstagedChanges = exports.fetchRemoteBranch = exports.fetchRemote = exports.getUntrackedChanges = void 0;
8
+ const git_1 = require("./git");
9
+ function getUntrackedChanges(cwd) {
10
+ try {
11
+ const results = (0, git_1.git)(["status", "--short"], { cwd });
12
+ if (!results.success || !results.stdout) {
13
+ return [];
14
+ }
15
+ const lines = results.stdout.split(/[\r\n]+/).filter((line) => line);
16
+ const untracked = [];
17
+ for (let i = 0; i < lines.length; i++) {
18
+ const line = lines[i];
19
+ if (line[0] === " " || line[0] === "?") {
20
+ untracked.push(line.substring(3));
21
+ }
22
+ else if (line[0] === "R") {
23
+ i++;
24
+ }
25
+ }
26
+ return untracked;
27
+ }
28
+ catch (e) {
29
+ throw new git_1.GitError(`Cannot gather information about untracked changes`, e);
30
+ }
31
+ }
32
+ exports.getUntrackedChanges = getUntrackedChanges;
33
+ function fetchRemote(remote, cwd) {
34
+ const results = (0, git_1.git)(["fetch", "--", remote], { cwd });
35
+ if (!results.success) {
36
+ throw new git_1.GitError(`Cannot fetch remote "${remote}"`);
37
+ }
38
+ }
39
+ exports.fetchRemote = fetchRemote;
40
+ function fetchRemoteBranch(remote, remoteBranch, cwd) {
41
+ const results = (0, git_1.git)(["fetch", "--", remote, remoteBranch], { cwd });
42
+ if (!results.success) {
43
+ throw new git_1.GitError(`Cannot fetch branch "${remoteBranch}" from remote "${remote}"`);
44
+ }
45
+ }
46
+ exports.fetchRemoteBranch = fetchRemoteBranch;
47
+ /**
48
+ * Gets all the changes that have not been staged yet
49
+ * @param cwd
50
+ */
51
+ function getUnstagedChanges(cwd) {
52
+ try {
53
+ return processGitOutput((0, git_1.git)(["--no-pager", "diff", "--name-only", "--relative"], { cwd }));
54
+ }
55
+ catch (e) {
56
+ throw new git_1.GitError(`Cannot gather information about unstaged changes`, e);
57
+ }
58
+ }
59
+ exports.getUnstagedChanges = getUnstagedChanges;
60
+ function getChanges(branch, cwd) {
61
+ try {
62
+ return processGitOutput((0, git_1.git)(["--no-pager", "diff", "--relative", "--name-only", branch + "..."], { cwd }));
63
+ }
64
+ catch (e) {
65
+ throw new git_1.GitError(`Cannot gather information about changes`, e);
66
+ }
67
+ }
68
+ exports.getChanges = getChanges;
69
+ /**
70
+ * Gets all the changes between the branch and the merge-base
71
+ */
72
+ function getBranchChanges(branch, cwd) {
73
+ return getChangesBetweenRefs(branch, "", [], "", cwd);
74
+ }
75
+ exports.getBranchChanges = getBranchChanges;
76
+ function getChangesBetweenRefs(fromRef, toRef, options, pattern, cwd) {
77
+ try {
78
+ return processGitOutput((0, git_1.git)([
79
+ "--no-pager",
80
+ "diff",
81
+ "--name-only",
82
+ "--relative",
83
+ ...options,
84
+ `${fromRef}...${toRef}`,
85
+ ...(pattern ? ["--", pattern] : []),
86
+ ], { cwd }));
87
+ }
88
+ catch (e) {
89
+ throw new git_1.GitError(`Cannot gather information about change between refs changes (${fromRef} to ${toRef})`, e);
90
+ }
91
+ }
92
+ exports.getChangesBetweenRefs = getChangesBetweenRefs;
93
+ function getStagedChanges(cwd) {
94
+ try {
95
+ return processGitOutput((0, git_1.git)(["--no-pager", "diff", "--relative", "--staged", "--name-only"], { cwd }));
96
+ }
97
+ catch (e) {
98
+ throw new git_1.GitError(`Cannot gather information about staged changes`, e);
99
+ }
100
+ }
101
+ exports.getStagedChanges = getStagedChanges;
102
+ function getRecentCommitMessages(branch, cwd) {
103
+ try {
104
+ const results = (0, git_1.git)(["log", "--decorate", "--pretty=format:%s", `${branch}..HEAD`], { cwd });
105
+ if (!results.success) {
106
+ return [];
107
+ }
108
+ return results.stdout.split(/\n/).map((line) => line.trim());
109
+ }
110
+ catch (e) {
111
+ throw new git_1.GitError(`Cannot gather information about recent commits`, e);
112
+ }
113
+ }
114
+ exports.getRecentCommitMessages = getRecentCommitMessages;
115
+ function getUserEmail(cwd) {
116
+ try {
117
+ const results = (0, git_1.git)(["config", "user.email"], { cwd });
118
+ return results.success ? results.stdout : null;
119
+ }
120
+ catch (e) {
121
+ throw new git_1.GitError(`Cannot gather information about user.email`, e);
122
+ }
123
+ }
124
+ exports.getUserEmail = getUserEmail;
125
+ function getBranchName(cwd) {
126
+ try {
127
+ const results = (0, git_1.git)(["rev-parse", "--abbrev-ref", "HEAD"], { cwd });
128
+ return results.success ? results.stdout : null;
129
+ }
130
+ catch (e) {
131
+ throw new git_1.GitError(`Cannot get branch name`, e);
132
+ }
133
+ }
134
+ exports.getBranchName = getBranchName;
135
+ function getFullBranchRef(branch, cwd) {
136
+ const showRefResults = (0, git_1.git)(["show-ref", "--heads", branch], { cwd });
137
+ return showRefResults.success ? showRefResults.stdout.split(" ")[1] : null;
138
+ }
139
+ exports.getFullBranchRef = getFullBranchRef;
140
+ function getShortBranchName(fullBranchRef, cwd) {
141
+ const showRefResults = (0, git_1.git)(["name-rev", "--name-only", fullBranchRef], {
142
+ cwd,
143
+ });
144
+ return showRefResults.success ? showRefResults.stdout : null;
145
+ }
146
+ exports.getShortBranchName = getShortBranchName;
147
+ function getCurrentHash(cwd) {
148
+ try {
149
+ const results = (0, git_1.git)(["rev-parse", "HEAD"], { cwd });
150
+ return results.success ? results.stdout : null;
151
+ }
152
+ catch (e) {
153
+ throw new git_1.GitError(`Cannot get current git hash`, e);
154
+ }
155
+ }
156
+ exports.getCurrentHash = getCurrentHash;
157
+ /**
158
+ * Get the commit hash in which the file was first added.
159
+ */
160
+ function getFileAddedHash(filename, cwd) {
161
+ const results = (0, git_1.git)(["rev-list", "HEAD", filename], { cwd });
162
+ if (results.success) {
163
+ return results.stdout.trim().split("\n").slice(-1)[0];
164
+ }
165
+ return undefined;
166
+ }
167
+ exports.getFileAddedHash = getFileAddedHash;
168
+ function init(cwd, email, username) {
169
+ (0, git_1.git)(["init"], { cwd });
170
+ const configLines = (0, git_1.git)(["config", "--list"], { cwd }).stdout.split("\n");
171
+ if (!configLines.find((line) => line.includes("user.name"))) {
172
+ if (!username) {
173
+ throw new git_1.GitError("must include a username when initializing git repo");
174
+ }
175
+ (0, git_1.git)(["config", "user.name", username], { cwd });
176
+ }
177
+ if (!configLines.find((line) => line.includes("user.email"))) {
178
+ if (!email) {
179
+ throw new Error("must include a email when initializing git repo");
180
+ }
181
+ (0, git_1.git)(["config", "user.email", email], { cwd });
182
+ }
183
+ }
184
+ exports.init = init;
185
+ function stage(patterns, cwd) {
186
+ try {
187
+ patterns.forEach((pattern) => {
188
+ (0, git_1.git)(["add", pattern], { cwd });
189
+ });
190
+ }
191
+ catch (e) {
192
+ throw new git_1.GitError(`Cannot stage changes`, e);
193
+ }
194
+ }
195
+ exports.stage = stage;
196
+ function commit(message, cwd, options = []) {
197
+ try {
198
+ const commitResults = (0, git_1.git)(["commit", "-m", message, ...options], { cwd });
199
+ if (!commitResults.success) {
200
+ throw new Error(`Cannot commit changes: ${commitResults.stdout} ${commitResults.stderr}`);
201
+ }
202
+ }
203
+ catch (e) {
204
+ throw new git_1.GitError(`Cannot commit changes`, e);
205
+ }
206
+ }
207
+ exports.commit = commit;
208
+ function stageAndCommit(patterns, message, cwd, commitOptions = []) {
209
+ stage(patterns, cwd);
210
+ commit(message, cwd, commitOptions);
211
+ }
212
+ exports.stageAndCommit = stageAndCommit;
213
+ function revertLocalChanges(cwd) {
214
+ const stash = `workspace-tools_${new Date().getTime()}`;
215
+ (0, git_1.git)(["stash", "push", "-u", "-m", stash], { cwd });
216
+ const results = (0, git_1.git)(["stash", "list"]);
217
+ if (results.success) {
218
+ const lines = results.stdout.split(/\n/);
219
+ const foundLine = lines.find((line) => line.includes(stash));
220
+ if (foundLine) {
221
+ const matched = foundLine.match(/^[^:]+/);
222
+ if (matched) {
223
+ (0, git_1.git)(["stash", "drop", matched[0]]);
224
+ return true;
225
+ }
226
+ }
227
+ }
228
+ return false;
229
+ }
230
+ exports.revertLocalChanges = revertLocalChanges;
231
+ function getParentBranch(cwd) {
232
+ const branchName = getBranchName(cwd);
233
+ if (!branchName || branchName === "HEAD") {
234
+ return null;
235
+ }
236
+ const showBranchResult = (0, git_1.git)(["show-branch", "-a"], { cwd });
237
+ if (showBranchResult.success) {
238
+ const showBranchLines = showBranchResult.stdout.split(/\n/);
239
+ const parentLine = showBranchLines.find((line) => line.includes("*") && !line.includes(branchName) && !line.includes("publish_"));
240
+ const matched = parentLine === null || parentLine === void 0 ? void 0 : parentLine.match(/\[(.*)\]/);
241
+ return matched ? matched[1] : null;
242
+ }
243
+ return null;
244
+ }
245
+ exports.getParentBranch = getParentBranch;
246
+ function getRemoteBranch(branch, cwd) {
247
+ const results = (0, git_1.git)(["rev-parse", "--abbrev-ref", "--symbolic-full-name", `${branch}@\{u\}`], { cwd });
248
+ if (results.success) {
249
+ return results.stdout.trim();
250
+ }
251
+ return null;
252
+ }
253
+ exports.getRemoteBranch = getRemoteBranch;
254
+ function parseRemoteBranch(branch) {
255
+ const firstSlashPos = branch.indexOf("/", 0);
256
+ const remote = branch.substring(0, firstSlashPos);
257
+ const remoteBranch = branch.substring(firstSlashPos + 1);
258
+ return {
259
+ remote,
260
+ remoteBranch,
261
+ };
262
+ }
263
+ exports.parseRemoteBranch = parseRemoteBranch;
264
+ /**
265
+ * Gets the default branch based on `git config init.defaultBranch`, falling back to `master`.
266
+ */
267
+ function getDefaultBranch(cwd) {
268
+ const result = (0, git_1.git)(["config", "init.defaultBranch"], { cwd });
269
+ // Default to the legacy 'master' for backwards compat and old git clients
270
+ return result.success ? result.stdout.trim() : "master";
271
+ }
272
+ exports.getDefaultBranch = getDefaultBranch;
273
+ function listAllTrackedFiles(patterns, cwd) {
274
+ const results = (0, git_1.git)(["ls-files", ...patterns], { cwd });
275
+ return results.success ? results.stdout.split(/\n/) : [];
276
+ }
277
+ exports.listAllTrackedFiles = listAllTrackedFiles;
278
+ function processGitOutput(output) {
279
+ if (!output.success) {
280
+ return [];
281
+ }
282
+ return output.stdout
283
+ .split(/\n/)
284
+ .map((line) => line.trim())
285
+ .filter((line) => !!line && !line.includes("node_modules"));
286
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./git";
2
+ export * from "./getDefaultRemote";
3
+ export * from "./getDefaultRemoteBranch";
4
+ export * from "./gitUtilities";
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
10
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ __exportStar(require("./git"), exports);
14
+ __exportStar(require("./getDefaultRemote"), exports);
15
+ __exportStar(require("./getDefaultRemoteBranch"), exports);
16
+ __exportStar(require("./gitUtilities"), exports);
17
+ // getRepositoryName is not currently exported; could be changed if it would be useful externally
@@ -1,7 +1,13 @@
1
+ import { PackageInfo } from "../types/PackageInfo";
1
2
  /**
2
3
  * Create a temp directory containing the given fixture name in a git repo.
3
4
  * Be sure to call `cleanupFixtures()` after all tests to clean up temp directories.
4
5
  */
5
- export declare function setupFixture(fixtureName: string): string;
6
+ export declare function setupFixture(fixtureName?: string): string;
7
+ /**
8
+ * `tmp` is not always reliable about cleanup even with appropriate options, so it's recommended to
9
+ * call this function in `afterAll`.
10
+ */
6
11
  export declare function cleanupFixtures(): void;
7
- export declare function setupLocalRemote(cwd: string, remoteName: string, fixtureName: string): void;
12
+ export declare function setupPackageJson(cwd: string, packageJson?: Partial<PackageInfo>): void;
13
+ export declare function setupLocalRemote(cwd: string, remoteName: string, fixtureName?: string): void;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.setupLocalRemote = exports.cleanupFixtures = exports.setupFixture = void 0;
6
+ exports.setupLocalRemote = exports.setupPackageJson = exports.cleanupFixtures = exports.setupFixture = void 0;
7
7
  const path_1 = __importDefault(require("path"));
8
8
  const find_up_1 = __importDefault(require("find-up"));
9
9
  const fs_extra_1 = __importDefault(require("fs-extra"));
@@ -13,6 +13,7 @@ const git_1 = require("../git");
13
13
  // So we attempt to use its built-in cleanup mechanisms, but tests should ideally do their own cleanup too.
14
14
  tmp_1.default.setGracefulCleanup();
15
15
  let fixturesRoot;
16
+ // Temp directories are created under tempRoot.name with incrementing numeric sub-directories
16
17
  let tempRoot;
17
18
  let tempNumber = 0;
18
19
  /**
@@ -20,20 +21,23 @@ let tempNumber = 0;
20
21
  * Be sure to call `cleanupFixtures()` after all tests to clean up temp directories.
21
22
  */
22
23
  function setupFixture(fixtureName) {
23
- if (!fixturesRoot) {
24
- fixturesRoot = find_up_1.default.sync("__fixtures__", { cwd: __dirname, type: "directory" });
25
- }
26
- const fixturePath = path_1.default.join(fixturesRoot, fixtureName);
27
- if (!fs_extra_1.default.existsSync(fixturePath)) {
28
- throw new Error(`Couldn't find fixture "${fixtureName}" under "${fixturesRoot}"`);
24
+ let fixturePath;
25
+ if (fixtureName) {
26
+ if (!fixturesRoot) {
27
+ fixturesRoot = find_up_1.default.sync("__fixtures__", { cwd: __dirname, type: "directory" });
28
+ }
29
+ fixturePath = path_1.default.join(fixturesRoot, fixtureName);
30
+ if (!fs_extra_1.default.existsSync(fixturePath)) {
31
+ throw new Error(`Couldn't find fixture "${fixtureName}" under "${fixturesRoot}"`);
32
+ }
29
33
  }
30
34
  if (!tempRoot) {
31
35
  // Create a shared root temp directory for fixture files
32
36
  tempRoot = tmp_1.default.dirSync({ unsafeCleanup: true }); // clean up even if files are left
33
37
  }
34
- const cwd = path_1.default.join(tempRoot.name, String(tempNumber++), fixtureName);
38
+ // Make the directory and git init
39
+ const cwd = path_1.default.join(tempRoot.name, String(tempNumber++), fixtureName || "");
35
40
  fs_extra_1.default.mkdirpSync(cwd);
36
- fs_extra_1.default.copySync(fixturePath, cwd);
37
41
  (0, git_1.init)(cwd, "test@test.email", "test user");
38
42
  // Ensure GPG signing doesn't interfere with tests
39
43
  (0, git_1.gitFailFast)(["config", "commit.gpgsign", "false"], { cwd });
@@ -43,10 +47,18 @@ function setupFixture(fixtureName) {
43
47
  // a 'fixed' value for our tests, regardless of user configuration
44
48
  (0, git_1.gitFailFast)(["symbolic-ref", "HEAD", "refs/heads/main"], { cwd });
45
49
  (0, git_1.gitFailFast)(["config", "init.defaultBranch", "main"], { cwd });
46
- (0, git_1.stageAndCommit)(["."], "test", cwd);
50
+ // Copy and commit the fixture if requested
51
+ if (fixturePath) {
52
+ fs_extra_1.default.copySync(fixturePath, cwd);
53
+ (0, git_1.stageAndCommit)(["."], "test", cwd);
54
+ }
47
55
  return cwd;
48
56
  }
49
57
  exports.setupFixture = setupFixture;
58
+ /**
59
+ * `tmp` is not always reliable about cleanup even with appropriate options, so it's recommended to
60
+ * call this function in `afterAll`.
61
+ */
50
62
  function cleanupFixtures() {
51
63
  if (tempRoot) {
52
64
  tempRoot.removeCallback();
@@ -54,16 +66,21 @@ function cleanupFixtures() {
54
66
  }
55
67
  }
56
68
  exports.cleanupFixtures = cleanupFixtures;
69
+ function setupPackageJson(cwd, packageJson = {}) {
70
+ const pkgJsonPath = path_1.default.join(cwd, "package.json");
71
+ let oldPackageJson;
72
+ if (fs_extra_1.default.existsSync(pkgJsonPath)) {
73
+ oldPackageJson = JSON.parse(fs_extra_1.default.readFileSync(pkgJsonPath, "utf-8"));
74
+ }
75
+ fs_extra_1.default.writeFileSync(pkgJsonPath, JSON.stringify(Object.assign(Object.assign({}, oldPackageJson), packageJson), null, 2));
76
+ }
77
+ exports.setupPackageJson = setupPackageJson;
57
78
  function setupLocalRemote(cwd, remoteName, fixtureName) {
58
79
  // Create a seperate repo and configure it as a remote
59
80
  const remoteCwd = setupFixture(fixtureName);
60
81
  const remoteUrl = remoteCwd.replace(/\\/g, "/");
61
82
  (0, git_1.gitFailFast)(["remote", "add", remoteName, remoteUrl], { cwd });
62
83
  // Configure url in package.json
63
- const pkgJsonPath = path_1.default.join(cwd, "package.json");
64
- const pkgJson = JSON.parse(fs_extra_1.default.readFileSync(pkgJsonPath, "utf-8"));
65
- fs_extra_1.default.writeFileSync(pkgJsonPath, JSON.stringify(Object.assign(Object.assign({}, pkgJson), { repository: {
66
- url: remoteUrl,
67
- } }), null, 2));
84
+ setupPackageJson(cwd, { repository: { url: remoteUrl, type: "git" } });
68
85
  }
69
86
  exports.setupLocalRemote = setupLocalRemote;
@@ -14,11 +14,14 @@ export interface PackageInfo {
14
14
  private?: boolean;
15
15
  group?: string;
16
16
  scripts?: {
17
- [dep: string]: string;
17
+ [scriptName: string]: string;
18
18
  };
19
- [key: string]: string | boolean | string[] | {
20
- [dep: string]: string;
21
- } | undefined;
19
+ repository?: string | {
20
+ type: string;
21
+ url: string;
22
+ directory?: string;
23
+ };
24
+ [key: string]: any;
22
25
  }
23
26
  export interface PackageInfos {
24
27
  [pkgName: string]: PackageInfo;
@@ -50,7 +50,7 @@ exports.getChangedPackagesBetweenRefs = getChangedPackagesBetweenRefs;
50
50
  * @returns string[] of package names that have changed
51
51
  */
52
52
  function getChangedPackages(cwd, target, ignoreGlobs = []) {
53
- const targetBranch = target || (0, git_1.getDefaultRemoteBranch)(undefined, cwd);
53
+ const targetBranch = target || (0, git_1.getDefaultRemoteBranch)({ cwd });
54
54
  let changes = [
55
55
  ...new Set([
56
56
  ...((0, git_1.getUntrackedChanges)(cwd) || []),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workspace-tools",
3
- "version": "0.23.3",
3
+ "version": "0.24.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
package/lib/git.js DELETED
@@ -1,442 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.listAllTrackedFiles = exports.getDefaultRemote = exports.getDefaultBranch = exports.getDefaultRemoteBranch = exports.parseRemoteBranch = exports.getRemoteBranch = exports.getParentBranch = exports.revertLocalChanges = exports.stageAndCommit = exports.commit = exports.stage = exports.init = exports.getFileAddedHash = exports.getCurrentHash = exports.getShortBranchName = exports.getFullBranchRef = exports.getBranchName = exports.getUserEmail = exports.getRecentCommitMessages = exports.getStagedChanges = exports.getChangesBetweenRefs = exports.getBranchChanges = exports.getChanges = exports.getUnstagedChanges = exports.fetchRemoteBranch = exports.fetchRemote = exports.getUntrackedChanges = exports.gitFailFast = exports.git = exports.addGitObserver = void 0;
7
- const child_process_1 = require("child_process");
8
- const fs_1 = __importDefault(require("fs"));
9
- const path_1 = __importDefault(require("path"));
10
- const paths_1 = require("./paths");
11
- const git_url_parse_1 = __importDefault(require("git-url-parse"));
12
- function gitError(message, e) {
13
- if (e && e instanceof Error) {
14
- return new Error(`${message}: ${e.message}`);
15
- }
16
- return new Error(message);
17
- }
18
- /**
19
- * A global maxBuffer override for all git operations.
20
- * Bumps up the default to 500MB instead of 1MB.
21
- * Override this value with the `GIT_MAX_BUFFER` environment variable.
22
- */
23
- const defaultMaxBuffer = process.env.GIT_MAX_BUFFER ? parseInt(process.env.GIT_MAX_BUFFER) : 500 * 1024 * 1024;
24
- const observers = [];
25
- let observing;
26
- /**
27
- * Adds an observer for the git operations, e.g. for testing
28
- * @param observer
29
- */
30
- function addGitObserver(observer) {
31
- observers.push(observer);
32
- }
33
- exports.addGitObserver = addGitObserver;
34
- /**
35
- * Runs git command - use this for read-only commands
36
- */
37
- function git(args, options) {
38
- const results = (0, child_process_1.spawnSync)("git", args, Object.assign({ maxBuffer: defaultMaxBuffer }, options));
39
- const output = {
40
- stderr: results.stderr.toString().trimRight(),
41
- stdout: results.stdout.toString().trimRight(),
42
- success: results.status === 0
43
- };
44
- // notify observers, flipping the observing bit to prevent infinite loops
45
- if (!observing) {
46
- observing = true;
47
- for (const observer of observers) {
48
- observer(args, output);
49
- }
50
- observing = false;
51
- }
52
- return output;
53
- }
54
- exports.git = git;
55
- /**
56
- * Runs git command - use this for commands that make changes to the filesystem
57
- */
58
- function gitFailFast(args, options) {
59
- const gitResult = git(args, options);
60
- if (!gitResult.success) {
61
- process.exitCode = 1;
62
- throw gitError(`CRITICAL ERROR: running git command: git ${args.join(" ")}!
63
- ${gitResult.stdout && gitResult.stdout.toString().trimRight()}
64
- ${gitResult.stderr && gitResult.stderr.toString().trimRight()}`);
65
- }
66
- }
67
- exports.gitFailFast = gitFailFast;
68
- function getUntrackedChanges(cwd) {
69
- try {
70
- const results = git(["status", "--short"], { cwd });
71
- if (!results.success) {
72
- return [];
73
- }
74
- const changes = results.stdout;
75
- if (changes.length == 0) {
76
- return [];
77
- }
78
- const lines = changes.split(/[\r\n]+/).filter((line) => line) || [];
79
- const untracked = [];
80
- for (let i = 0; i < lines.length; i++) {
81
- const line = lines[i];
82
- if (line[0] === " " || line[0] === "?") {
83
- untracked.push(line.substr(3));
84
- }
85
- else if (line[0] === "R") {
86
- i++;
87
- }
88
- }
89
- return untracked;
90
- }
91
- catch (e) {
92
- throw gitError(`Cannot gather information about untracked changes`, e);
93
- }
94
- }
95
- exports.getUntrackedChanges = getUntrackedChanges;
96
- function fetchRemote(remote, cwd) {
97
- const results = git(["fetch", "--", remote], { cwd });
98
- if (!results.success) {
99
- throw gitError(`Cannot fetch remote: ${remote}`);
100
- }
101
- }
102
- exports.fetchRemote = fetchRemote;
103
- function fetchRemoteBranch(remote, remoteBranch, cwd) {
104
- const results = git(["fetch", "--", remote, remoteBranch], { cwd });
105
- if (!results.success) {
106
- throw gitError(`Cannot fetch remote: ${remote} ${remoteBranch}`);
107
- }
108
- }
109
- exports.fetchRemoteBranch = fetchRemoteBranch;
110
- /**
111
- * Gets all the changes that have not been staged yet
112
- * @param cwd
113
- */
114
- function getUnstagedChanges(cwd) {
115
- try {
116
- return processGitOutput(git(["--no-pager", "diff", "--name-only", "--relative"], { cwd }));
117
- }
118
- catch (e) {
119
- throw gitError(`Cannot gather information about unstaged changes`, e);
120
- }
121
- }
122
- exports.getUnstagedChanges = getUnstagedChanges;
123
- function getChanges(branch, cwd) {
124
- try {
125
- return processGitOutput(git(["--no-pager", "diff", "--relative", "--name-only", branch + "..."], { cwd }));
126
- }
127
- catch (e) {
128
- throw gitError(`Cannot gather information about changes`, e);
129
- }
130
- }
131
- exports.getChanges = getChanges;
132
- /**
133
- * Gets all the changes between the branch and the merge-base
134
- * @param branch
135
- * @param cwd
136
- */
137
- function getBranchChanges(branch, cwd) {
138
- return getChangesBetweenRefs(branch, "", [], "", cwd);
139
- }
140
- exports.getBranchChanges = getBranchChanges;
141
- function getChangesBetweenRefs(fromRef, toRef, options, pattern, cwd) {
142
- try {
143
- return processGitOutput(git(["--no-pager", "diff", "--name-only", "--relative", ...options, `${fromRef}...${toRef}`, ...(pattern ? ["--", pattern] : [])], {
144
- cwd,
145
- }));
146
- }
147
- catch (e) {
148
- throw gitError(`Cannot gather information about change between refs changes (${fromRef} to ${toRef})`, e);
149
- }
150
- }
151
- exports.getChangesBetweenRefs = getChangesBetweenRefs;
152
- function getStagedChanges(cwd) {
153
- try {
154
- return processGitOutput(git(["--no-pager", "diff", "--relative", "--staged", "--name-only"], { cwd }));
155
- }
156
- catch (e) {
157
- throw gitError(`Cannot gather information about staged changes`, e);
158
- }
159
- }
160
- exports.getStagedChanges = getStagedChanges;
161
- function getRecentCommitMessages(branch, cwd) {
162
- try {
163
- const results = git(["log", "--decorate", "--pretty=format:%s", `${branch}..HEAD`], { cwd });
164
- if (!results.success) {
165
- return [];
166
- }
167
- let changes = results.stdout;
168
- let lines = changes.split(/\n/) || [];
169
- return lines.map((line) => line.trim());
170
- }
171
- catch (e) {
172
- throw gitError(`Cannot gather information about recent commits`, e);
173
- }
174
- }
175
- exports.getRecentCommitMessages = getRecentCommitMessages;
176
- function getUserEmail(cwd) {
177
- try {
178
- const results = git(["config", "user.email"], { cwd });
179
- if (!results.success) {
180
- return null;
181
- }
182
- return results.stdout;
183
- }
184
- catch (e) {
185
- throw gitError(`Cannot gather information about user.email`, e);
186
- }
187
- }
188
- exports.getUserEmail = getUserEmail;
189
- function getBranchName(cwd) {
190
- try {
191
- const results = git(["rev-parse", "--abbrev-ref", "HEAD"], { cwd });
192
- if (results.success) {
193
- return results.stdout;
194
- }
195
- }
196
- catch (e) {
197
- throw gitError(`Cannot get branch name`, e);
198
- }
199
- return null;
200
- }
201
- exports.getBranchName = getBranchName;
202
- function getFullBranchRef(branch, cwd) {
203
- const showRefResults = git(["show-ref", "--heads", branch], { cwd });
204
- if (showRefResults.success) {
205
- return showRefResults.stdout.split(" ")[1];
206
- }
207
- return null;
208
- }
209
- exports.getFullBranchRef = getFullBranchRef;
210
- function getShortBranchName(fullBranchRef, cwd) {
211
- const showRefResults = git(["name-rev", "--name-only", fullBranchRef], {
212
- cwd,
213
- });
214
- if (showRefResults.success) {
215
- return showRefResults.stdout;
216
- }
217
- return null;
218
- }
219
- exports.getShortBranchName = getShortBranchName;
220
- function getCurrentHash(cwd) {
221
- try {
222
- const results = git(["rev-parse", "HEAD"], { cwd });
223
- if (results.success) {
224
- return results.stdout;
225
- }
226
- }
227
- catch (e) {
228
- throw gitError(`Cannot get current git hash`, e);
229
- }
230
- return null;
231
- }
232
- exports.getCurrentHash = getCurrentHash;
233
- /**
234
- * Get the commit hash in which the file was first added.
235
- */
236
- function getFileAddedHash(filename, cwd) {
237
- const results = git(["rev-list", "HEAD", filename], { cwd });
238
- if (results.success) {
239
- return results.stdout.trim().split("\n").slice(-1)[0];
240
- }
241
- return undefined;
242
- }
243
- exports.getFileAddedHash = getFileAddedHash;
244
- function init(cwd, email, username) {
245
- git(["init"], { cwd });
246
- const configLines = git(["config", "--list"], { cwd }).stdout.split("\n");
247
- if (!configLines.find((line) => line.includes("user.name"))) {
248
- if (!username) {
249
- throw gitError("must include a username when initializing git repo");
250
- }
251
- git(["config", "user.name", username], { cwd });
252
- }
253
- if (!configLines.find((line) => line.includes("user.email"))) {
254
- if (!email) {
255
- throw new Error("must include a email when initializing git repo");
256
- }
257
- git(["config", "user.email", email], { cwd });
258
- }
259
- }
260
- exports.init = init;
261
- function stage(patterns, cwd) {
262
- try {
263
- patterns.forEach((pattern) => {
264
- git(["add", pattern], { cwd });
265
- });
266
- }
267
- catch (e) {
268
- throw gitError(`Cannot stage changes`, e);
269
- }
270
- }
271
- exports.stage = stage;
272
- function commit(message, cwd, options = []) {
273
- try {
274
- const commitResults = git(["commit", "-m", message, ...options], { cwd });
275
- if (!commitResults.success) {
276
- throw new Error(`Cannot commit changes: ${commitResults.stdout} ${commitResults.stderr}`);
277
- }
278
- }
279
- catch (e) {
280
- throw gitError(`Cannot commit changes`, e);
281
- }
282
- }
283
- exports.commit = commit;
284
- function stageAndCommit(patterns, message, cwd, commitOptions = []) {
285
- stage(patterns, cwd);
286
- commit(message, cwd, commitOptions);
287
- }
288
- exports.stageAndCommit = stageAndCommit;
289
- function revertLocalChanges(cwd) {
290
- const stash = `beachball_${new Date().getTime()}`;
291
- git(["stash", "push", "-u", "-m", stash], { cwd });
292
- const results = git(["stash", "list"]);
293
- if (results.success) {
294
- const lines = results.stdout.split(/\n/);
295
- const foundLine = lines.find((line) => line.includes(stash));
296
- if (foundLine) {
297
- const matched = foundLine.match(/^[^:]+/);
298
- if (matched) {
299
- git(["stash", "drop", matched[0]]);
300
- return true;
301
- }
302
- }
303
- }
304
- return false;
305
- }
306
- exports.revertLocalChanges = revertLocalChanges;
307
- function getParentBranch(cwd) {
308
- const branchName = getBranchName(cwd);
309
- if (!branchName || branchName === "HEAD") {
310
- return null;
311
- }
312
- const showBranchResult = git(["show-branch", "-a"], { cwd });
313
- if (showBranchResult.success) {
314
- const showBranchLines = showBranchResult.stdout.split(/\n/);
315
- const parentLine = showBranchLines.find((line) => line.indexOf("*") > -1 && line.indexOf(branchName) < 0 && line.indexOf("publish_") < 0);
316
- if (!parentLine) {
317
- return null;
318
- }
319
- const matched = parentLine.match(/\[(.*)\]/);
320
- if (!matched) {
321
- return null;
322
- }
323
- return matched[1];
324
- }
325
- return null;
326
- }
327
- exports.getParentBranch = getParentBranch;
328
- function getRemoteBranch(branch, cwd) {
329
- const results = git(["rev-parse", "--abbrev-ref", "--symbolic-full-name", `${branch}@\{u\}`], { cwd });
330
- if (results.success) {
331
- return results.stdout.trim();
332
- }
333
- return null;
334
- }
335
- exports.getRemoteBranch = getRemoteBranch;
336
- function parseRemoteBranch(branch) {
337
- const firstSlashPos = branch.indexOf("/", 0);
338
- const remote = branch.substring(0, firstSlashPos);
339
- const remoteBranch = branch.substring(firstSlashPos + 1);
340
- return {
341
- remote,
342
- remoteBranch,
343
- };
344
- }
345
- exports.parseRemoteBranch = parseRemoteBranch;
346
- function normalizeRepoUrl(repositoryUrl) {
347
- try {
348
- const parsed = (0, git_url_parse_1.default)(repositoryUrl);
349
- return parsed
350
- .toString("https")
351
- .replace(/\.git$/, "")
352
- .toLowerCase();
353
- }
354
- catch (e) {
355
- return "";
356
- }
357
- }
358
- function getDefaultRemoteBranch(branch, cwd) {
359
- const defaultRemote = getDefaultRemote(cwd);
360
- const showRemote = git(["remote", "show", defaultRemote], { cwd });
361
- /**
362
- * The `showRemote` returns something like this in stdout:
363
- *
364
- * * remote origin
365
- * Fetch URL: ../monorepo-upstream/
366
- * Push URL: ../monorepo-upstream/
367
- * HEAD branch: main
368
- *
369
- */
370
- const headBranchLine = showRemote.stdout.split(/\n/).find((line) => line.includes("HEAD branch"));
371
- let remoteDefaultBranch;
372
- if (headBranchLine) {
373
- remoteDefaultBranch = headBranchLine.replace(/^\s*HEAD branch:\s+/, "");
374
- }
375
- branch = branch || remoteDefaultBranch || getDefaultBranch(cwd);
376
- return `${defaultRemote}/${branch}`;
377
- }
378
- exports.getDefaultRemoteBranch = getDefaultRemoteBranch;
379
- function getDefaultBranch(cwd) {
380
- const result = git(["config", "init.defaultBranch"], { cwd });
381
- if (!result.success) {
382
- // Default to the legacy 'master' for backwards compat and old git clients
383
- return "master";
384
- }
385
- return result.stdout.trim();
386
- }
387
- exports.getDefaultBranch = getDefaultBranch;
388
- function getDefaultRemote(cwd) {
389
- let packageJson;
390
- try {
391
- packageJson = JSON.parse(fs_1.default.readFileSync(path_1.default.join((0, paths_1.findGitRoot)(cwd), "package.json")).toString());
392
- }
393
- catch (e) {
394
- throw new Error("invalid package.json detected");
395
- }
396
- const { repository } = packageJson;
397
- let repositoryUrl = "";
398
- if (typeof repository === "string") {
399
- repositoryUrl = repository;
400
- }
401
- else if (repository && repository.url) {
402
- repositoryUrl = repository.url;
403
- }
404
- const normalizedUrl = normalizeRepoUrl(repositoryUrl);
405
- const remotesResult = git(["remote", "-v"], { cwd });
406
- if (remotesResult.success) {
407
- const allRemotes = {};
408
- remotesResult.stdout.split("\n").forEach((line) => {
409
- const parts = line.split(/\s+/);
410
- allRemotes[normalizeRepoUrl(parts[1])] = parts[0];
411
- });
412
- if (Object.keys(allRemotes).length > 0) {
413
- const remote = allRemotes[normalizedUrl];
414
- if (remote) {
415
- return remote;
416
- }
417
- }
418
- }
419
- return "origin";
420
- }
421
- exports.getDefaultRemote = getDefaultRemote;
422
- function listAllTrackedFiles(patterns, cwd) {
423
- if (patterns) {
424
- const results = git(["ls-files", ...patterns], { cwd });
425
- if (results.success) {
426
- return results.stdout.split(/\n/);
427
- }
428
- }
429
- return [];
430
- }
431
- exports.listAllTrackedFiles = listAllTrackedFiles;
432
- function processGitOutput(output) {
433
- if (!output.success) {
434
- return [];
435
- }
436
- let stdout = output.stdout;
437
- let lines = stdout.split(/\n/) || [];
438
- return lines
439
- .filter((line) => line.trim() !== "")
440
- .map((line) => line.trim())
441
- .filter((line) => !line.includes("node_modules"));
442
- }