dugite 3.0.0-rc3 → 3.0.0-rc5

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.
Files changed (39) hide show
  1. package/.node-version +1 -0
  2. package/build/lib/env-map.d.ts +20 -0
  3. package/build/lib/env-map.js +65 -0
  4. package/build/lib/env-map.js.map +1 -0
  5. package/build/lib/errors.d.ts +24 -7
  6. package/build/lib/errors.js +42 -8
  7. package/build/lib/errors.js.map +1 -1
  8. package/build/lib/exec.d.ts +102 -0
  9. package/build/lib/exec.js +53 -0
  10. package/build/lib/exec.js.map +1 -0
  11. package/build/lib/git-environment.d.ts +5 -4
  12. package/build/lib/git-environment.js +29 -40
  13. package/build/lib/git-environment.js.map +1 -1
  14. package/build/lib/ignore-closed-input-stream.d.ts +30 -0
  15. package/build/lib/ignore-closed-input-stream.js +65 -0
  16. package/build/lib/ignore-closed-input-stream.js.map +1 -0
  17. package/build/lib/index.d.ts +6 -2
  18. package/build/lib/index.js +7 -6
  19. package/build/lib/index.js.map +1 -1
  20. package/build/lib/parse-bad-config-value-error-info.d.ts +4 -0
  21. package/build/lib/parse-bad-config-value-error-info.js +20 -0
  22. package/build/lib/parse-bad-config-value-error-info.js.map +1 -0
  23. package/build/lib/parse-error.d.ts +2 -0
  24. package/build/lib/parse-error.js +8 -0
  25. package/build/lib/parse-error.js.map +1 -0
  26. package/build/lib/spawn.d.ts +21 -0
  27. package/build/lib/spawn.js +21 -0
  28. package/build/lib/spawn.js.map +1 -0
  29. package/package.json +13 -19
  30. package/script/config.js +9 -7
  31. package/script/download-git.js +104 -105
  32. package/script/test.mjs +43 -0
  33. package/build/lib/git-process.d.ts +0 -121
  34. package/build/lib/git-process.js +0 -297
  35. package/build/lib/git-process.js.map +0 -1
  36. package/jest.external.config.js +0 -13
  37. package/jest.fast.config.js +0 -13
  38. package/jest.slow.config.js +0 -13
  39. package/script/utils.js +0 -27
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ignoreClosedInputStream = void 0;
4
+ /**
5
+ * Prevent errors originating from the stdin stream related
6
+ * to the child process closing the pipe from bubbling up and
7
+ * causing an unhandled exception when no error handler is
8
+ * attached to the input stream.
9
+ *
10
+ * The common scenario where this happens is if the consumer
11
+ * is writing data to the stdin stream of a child process and
12
+ * the child process for one reason or another decides to either
13
+ * terminate or simply close its standard input. Imagine this
14
+ * scenario
15
+ *
16
+ * cat /dev/zero | head -c 1
17
+ *
18
+ * The 'head' command would close its standard input (by terminating)
19
+ * the moment it has read one byte. In the case of Git this could
20
+ * happen if you for example pass badly formed input to apply-patch.
21
+ *
22
+ * Since consumers of dugite using the `exec` api are unable to get
23
+ * a hold of the stream until after we've written data to it they're
24
+ * unable to fix it themselves so we'll just go ahead and ignore the
25
+ * error for them. By supressing the stream error we can pick up on
26
+ * the real error when the process exits when we parse the exit code
27
+ * and the standard error.
28
+ *
29
+ * See https://github.com/desktop/desktop/pull/4027#issuecomment-366213276
30
+ */
31
+ function ignoreClosedInputStream({ stdin }) {
32
+ // If Node fails to spawn due to a runtime error (EACCESS, EAGAIN, etc)
33
+ // it will not setup the stdio streams, see
34
+ // https://github.com/nodejs/node/blob/v10.16.0/lib/internal/child_process.js#L342-L354
35
+ // The error itself will be emitted asynchronously but we're still in
36
+ // the synchronous path so if we attempts to call `.on` on `.stdin`
37
+ // (which is undefined) that error would be thrown before the underlying
38
+ // error.
39
+ if (!stdin) {
40
+ return;
41
+ }
42
+ stdin.on('error', err => {
43
+ const code = 'code' in err && typeof err.code === 'string' ? err.code : undefined;
44
+ // Is the error one that we'd expect from the input stream being
45
+ // closed, i.e. EPIPE on macOS and EOF on Windows. We've also
46
+ // seen ECONNRESET failures on Linux hosts so let's throw that in
47
+ // there for good measure.
48
+ if (code === 'EPIPE' || code === 'EOF' || code === 'ECONNRESET') {
49
+ return;
50
+ }
51
+ // Nope, this is something else. Are there any other error listeners
52
+ // attached than us? If not we'll have to mimic the behavior of
53
+ // EventEmitter.
54
+ //
55
+ // See https://nodejs.org/api/errors.html#errors_error_propagation_and_interception
56
+ //
57
+ // "For all EventEmitter objects, if an 'error' event handler is not
58
+ // provided, the error will be thrown"
59
+ if (stdin.listeners('error').length <= 1) {
60
+ throw err;
61
+ }
62
+ });
63
+ }
64
+ exports.ignoreClosedInputStream = ignoreClosedInputStream;
65
+ //# sourceMappingURL=ignore-closed-input-stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ignore-closed-input-stream.js","sourceRoot":"","sources":["../../lib/ignore-closed-input-stream.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,SAAgB,uBAAuB,CAAC,EAAE,KAAK,EAAgB;IAC7D,uEAAuE;IACvE,2CAA2C;IAC3C,uFAAuF;IACvF,qEAAqE;IACrE,mEAAmE;IACnE,wEAAwE;IACxE,SAAS;IACT,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAM;IACR,CAAC;IAED,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE;QACtB,MAAM,IAAI,GACR,MAAM,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAA;QAEtE,gEAAgE;QAChE,6DAA6D;QAC7D,iEAAiE;QACjE,0BAA0B;QAC1B,IAAI,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,YAAY,EAAE,CAAC;YAChE,OAAM;QACR,CAAC;QAED,oEAAoE;QACpE,+DAA+D;QAC/D,gBAAgB;QAChB,EAAE;QACF,mFAAmF;QACnF,EAAE;QACF,oEAAoE;QACpE,uCAAuC;QACvC,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YACzC,MAAM,GAAG,CAAA;QACX,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AApCD,0DAoCC"}
@@ -1,3 +1,7 @@
1
- export { GitProcess, IGitResult, IGitExecutionOptions, IGitTask, GitTaskCancelResult, } from './git-process';
2
- export { GitError, RepositoryDoesNotExistErrorCode, GitNotFoundErrorCode, } from './errors';
1
+ export * from './exec';
2
+ export * from './spawn';
3
+ export * from './parse-error';
4
+ export * from './parse-bad-config-value-error-info';
5
+ export { GitError, ExecError } from './errors';
3
6
  export * from './git-environment';
7
+ export * from './ignore-closed-input-stream';
@@ -14,13 +14,14 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.GitNotFoundErrorCode = exports.RepositoryDoesNotExistErrorCode = exports.GitError = exports.GitTaskCancelResult = exports.GitProcess = void 0;
18
- var git_process_1 = require("./git-process");
19
- Object.defineProperty(exports, "GitProcess", { enumerable: true, get: function () { return git_process_1.GitProcess; } });
20
- Object.defineProperty(exports, "GitTaskCancelResult", { enumerable: true, get: function () { return git_process_1.GitTaskCancelResult; } });
17
+ exports.ExecError = exports.GitError = void 0;
18
+ __exportStar(require("./exec"), exports);
19
+ __exportStar(require("./spawn"), exports);
20
+ __exportStar(require("./parse-error"), exports);
21
+ __exportStar(require("./parse-bad-config-value-error-info"), exports);
21
22
  var errors_1 = require("./errors");
22
23
  Object.defineProperty(exports, "GitError", { enumerable: true, get: function () { return errors_1.GitError; } });
23
- Object.defineProperty(exports, "RepositoryDoesNotExistErrorCode", { enumerable: true, get: function () { return errors_1.RepositoryDoesNotExistErrorCode; } });
24
- Object.defineProperty(exports, "GitNotFoundErrorCode", { enumerable: true, get: function () { return errors_1.GitNotFoundErrorCode; } });
24
+ Object.defineProperty(exports, "ExecError", { enumerable: true, get: function () { return errors_1.ExecError; } });
25
25
  __exportStar(require("./git-environment"), exports);
26
+ __exportStar(require("./ignore-closed-input-stream"), exports);
26
27
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../lib/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,6CAMsB;AALpB,yGAAA,UAAU,OAAA;AAIV,kHAAA,mBAAmB,OAAA;AAErB,mCAIiB;AAHf,kGAAA,QAAQ,OAAA;AACR,yHAAA,+BAA+B,OAAA;AAC/B,8GAAA,oBAAoB,OAAA;AAEtB,oDAAiC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../lib/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,yCAAsB;AACtB,0CAAuB;AACvB,gDAA6B;AAC7B,sEAAmD;AACnD,mCAA8C;AAArC,kGAAA,QAAQ,OAAA;AAAE,mGAAA,SAAS,OAAA;AAC5B,oDAAiC;AACjC,+DAA4C"}
@@ -0,0 +1,4 @@
1
+ export declare function parseBadConfigValueErrorInfo(stderr: string): {
2
+ key: string;
3
+ value: string;
4
+ } | null;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseBadConfigValueErrorInfo = void 0;
4
+ const errors_1 = require("./errors");
5
+ function parseBadConfigValueErrorInfo(stderr) {
6
+ const errorEntry = Object.entries(errors_1.GitErrorRegexes).find(([_, v]) => v === errors_1.GitError.BadConfigValue);
7
+ if (errorEntry === undefined) {
8
+ return null;
9
+ }
10
+ const m = stderr.match(errorEntry[0]);
11
+ if (m === null) {
12
+ return null;
13
+ }
14
+ if (!m[1] || !m[2]) {
15
+ return null;
16
+ }
17
+ return { key: m[2], value: m[1] };
18
+ }
19
+ exports.parseBadConfigValueErrorInfo = parseBadConfigValueErrorInfo;
20
+ //# sourceMappingURL=parse-bad-config-value-error-info.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-bad-config-value-error-info.js","sourceRoot":"","sources":["../../lib/parse-bad-config-value-error-info.ts"],"names":[],"mappings":";;;AAAA,qCAAoD;AAEpD,SAAgB,4BAA4B,CAC1C,MAAc;IAEd,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,wBAAe,CAAC,CAAC,IAAI,CACrD,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,iBAAQ,CAAC,cAAc,CAC1C,CAAA;IAED,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAA;IACb,CAAC;IAED,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAA;IAErC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;QACf,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACnB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;AACnC,CAAC;AAtBD,oEAsBC"}
@@ -0,0 +1,2 @@
1
+ /** Try to parse an error type from stderr. */
2
+ export declare const parseError: (stderr: string) => import("./errors").GitError | null;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseError = void 0;
4
+ const errors_1 = require("./errors");
5
+ /** Try to parse an error type from stderr. */
6
+ const parseError = (stderr) => Object.entries(errors_1.GitErrorRegexes).find(([re]) => stderr.match(re))?.[1] ?? null;
7
+ exports.parseError = parseError;
8
+ //# sourceMappingURL=parse-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-error.js","sourceRoot":"","sources":["../../lib/parse-error.ts"],"names":[],"mappings":";;;AAAA,qCAA0C;AAE1C,8CAA8C;AACvC,MAAM,UAAU,GAAG,CAAC,MAAc,EAAE,EAAE,CAC3C,MAAM,CAAC,OAAO,CAAC,wBAAe,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAA;AADlE,QAAA,UAAU,cACwD"}
@@ -0,0 +1,21 @@
1
+ /// <reference types="node" />
2
+ /**
3
+ * A set of configuration options that can be passed when
4
+ * executing a streaming Git command.
5
+ */
6
+ export interface IGitSpawnOptions {
7
+ /**
8
+ * An optional collection of key-value pairs which will be
9
+ * set as environment variables before executing the git
10
+ * process.
11
+ */
12
+ readonly env?: Record<string, string | undefined>;
13
+ }
14
+ /**
15
+ * Execute a command and interact with the process outputs directly.
16
+ *
17
+ * The returned promise will reject when the git executable fails to launch,
18
+ * in which case the thrown Error will have a string `code` property. See
19
+ * `errors.ts` for some of the known error codes.
20
+ */
21
+ export declare function spawn(args: string[], path: string, opts?: IGitSpawnOptions): import("child_process").ChildProcessWithoutNullStreams;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.spawn = void 0;
4
+ const ignore_closed_input_stream_1 = require("./ignore-closed-input-stream");
5
+ const git_environment_1 = require("./git-environment");
6
+ const child_process_1 = require("child_process");
7
+ /**
8
+ * Execute a command and interact with the process outputs directly.
9
+ *
10
+ * The returned promise will reject when the git executable fails to launch,
11
+ * in which case the thrown Error will have a string `code` property. See
12
+ * `errors.ts` for some of the known error codes.
13
+ */
14
+ function spawn(args, path, opts) {
15
+ const { env, gitLocation } = (0, git_environment_1.setupEnvironment)(opts?.env ?? {});
16
+ const spawnedProcess = (0, child_process_1.spawn)(gitLocation, args, { env, cwd: path });
17
+ (0, ignore_closed_input_stream_1.ignoreClosedInputStream)(spawnedProcess);
18
+ return spawnedProcess;
19
+ }
20
+ exports.spawn = spawn;
21
+ //# sourceMappingURL=spawn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn.js","sourceRoot":"","sources":["../../lib/spawn.ts"],"names":[],"mappings":";;;AAAA,6EAAsE;AACtE,uDAAoD;AACpD,iDAA+C;AAe/C;;;;;;GAMG;AACH,SAAgB,KAAK,CAAC,IAAc,EAAE,IAAY,EAAE,IAAuB;IACzE,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,IAAA,kCAAgB,EAAC,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC,CAAA;IAC9D,MAAM,cAAc,GAAG,IAAA,qBAAM,EAAC,WAAW,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAA;IAEpE,IAAA,oDAAuB,EAAC,cAAc,CAAC,CAAA;IAEvC,OAAO,cAAc,CAAA;AACvB,CAAC;AAPD,sBAOC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dugite",
3
- "version": "3.0.0-rc3",
3
+ "version": "3.0.0-rc5",
4
4
  "description": "Elegant bindings for Git",
5
5
  "main": "./build/lib/index.js",
6
6
  "typings": "./build/lib/index.d.ts",
@@ -9,18 +9,15 @@
9
9
  "build": "yarn clean && tsc -p ./tsconfig.json && tsc -p ./examples/tsconfig.json",
10
10
  "prepack": "yarn build && yarn test",
11
11
  "postpublish": "git push --follow-tags",
12
- "test": "yarn test:fast && yarn test:slow && yarn test:external",
13
- "test:fast": "cross-env LOCAL_GIT_DIRECTORY=./git/ jest --runInBand --silent --config ./jest.fast.config.js",
14
- "test:slow": "cross-env LOCAL_GIT_DIRECTORY=./git/ jest --runInBand --silent --config ./jest.slow.config.js",
15
- "test:external": "jest --runInBand --silent --config ./jest.external.config.js",
12
+ "test": "node script/test.mjs",
16
13
  "download-git": "node ./script/download-git.js",
17
14
  "postinstall": "node ./script/download-git.js",
18
- "prettify": "prettier \"{examples,lib,script,test}/**/*.ts\" --write",
19
- "is-it-pretty": "prettier --check \"{examples,lib,script,test}/**/*.ts\"",
15
+ "prettify": "prettier \"{examples,lib,script,test}/**/*.{ts,js,mjs}\" --write",
16
+ "is-it-pretty": "prettier \"{examples,lib,script,test}/**/*.{ts,js,mjs}\" --check",
20
17
  "update-embedded-git": "node ./script/update-embedded-git.js"
21
18
  },
22
19
  "engines": {
23
- "node": ">= 18"
20
+ "node": ">= 20"
24
21
  },
25
22
  "repository": {
26
23
  "type": "git",
@@ -34,21 +31,18 @@
34
31
  "homepage": "https://github.com/desktop/dugite#readme",
35
32
  "dependencies": {
36
33
  "progress": "^2.0.3",
37
- "tar": "^6.1.11"
34
+ "tar-stream": "^3.1.7"
38
35
  },
39
36
  "devDependencies": {
40
- "@types/jest": "^28.1.7",
41
- "@types/node": "18",
37
+ "@types/node": "20",
42
38
  "@types/progress": "^2.0.1",
43
39
  "@types/rimraf": "2.0.2",
44
- "@types/tar": "^6.1.2",
45
- "cross-env": "^5.2.0",
46
- "find-git-exec": "^0.0.4",
47
- "jest": "^28.1.3",
48
- "prettier": "^2.7.1",
49
- "rimraf": "^2.5.4",
50
- "temp": "^0.9.0",
51
- "ts-jest": "^28.0.8",
40
+ "@types/temp": "^0.9.4",
41
+ "node-test-github-reporter": "^1.2.0",
42
+ "prettier": "^3.3.1",
43
+ "rimraf": "^5.0.7",
44
+ "temp": "^0.9.4",
45
+ "tsx": "^4.10.5",
52
46
  "typescript": "^5.4.5"
53
47
  }
54
48
  }
package/script/config.js CHANGED
@@ -11,22 +11,22 @@ function getConfig() {
11
11
  source: '',
12
12
  checksum: '',
13
13
  fileName: '',
14
- tempFile: ''
14
+ tempFile: '',
15
15
  }
16
16
 
17
17
  // Possible values are ‘x64’, ‘arm’, ‘arm64’, ‘s390’, ‘s390x’, ‘mipsel’, ‘ia32’, ‘mips’, ‘ppc’ and ‘ppc64’
18
- let arch = os.arch();
18
+ let arch = os.arch()
19
19
 
20
20
  if (process.env.npm_config_arch) {
21
21
  // If a specific npm_config_arch is set, we use that one instead of the OS arch (to support cross compilation)
22
- console.log('npm_config_arch detected: ' + process.env.npm_config_arch);
23
- arch = process.env.npm_config_arch;
22
+ console.log('npm_config_arch detected: ' + process.env.npm_config_arch)
23
+ arch = process.env.npm_config_arch
24
24
  }
25
25
 
26
26
  if (process.platform === 'win32' && arch === 'arm64') {
27
27
  // Use the Dugite Native ia32 package for Windows arm64 (arm64 can run 32-bit code through emulation)
28
- console.log('Downloading 32-bit Dugite Native for Windows arm64');
29
- arch = 'ia32';
28
+ console.log('Downloading 32-bit Dugite Native for Windows arm64')
29
+ arch = 'ia32'
30
30
  }
31
31
 
32
32
  const key = `${process.platform}-${arch}`
@@ -37,7 +37,9 @@ function getConfig() {
37
37
  config.checksum = entry.checksum
38
38
  config.source = entry.url
39
39
  } else {
40
- console.log(`No embedded Git found for ${process.platform} and architecture ${arch}`)
40
+ console.log(
41
+ `No embedded Git found for ${process.platform} and architecture ${arch}`
42
+ )
41
43
  }
42
44
 
43
45
  if (config.source !== '') {
@@ -1,132 +1,131 @@
1
- const fs = require('fs')
2
-
3
1
  const ProgressBar = require('progress')
4
- const tar = require('tar')
5
- const https = require('https')
6
2
  const { createHash } = require('crypto')
7
- const { rm, rmSync, mkdir, createReadStream, createWriteStream, existsSync } = require('fs')
3
+ const { createReadStream, createWriteStream } = require('fs')
4
+ const { rm, mkdir, access, symlink } = require('fs/promises')
5
+ const { extract } = require('tar-stream')
6
+ const { createGunzip } = require('zlib')
7
+ const { join } = require('path')
8
+ const assert = require('node:assert')
9
+ /**
10
+ * Returns a value indicating whether or not the provided path exists (as in
11
+ * whether it's visible to the current process or not).
12
+ */
13
+ const pathExists = path =>
14
+ access(path).then(
15
+ () => true,
16
+ () => false
17
+ )
8
18
 
9
- const config = require('./config')()
19
+ const { Readable } = require('stream')
10
20
 
11
- const verifyFile = function(file, callback) {
12
- const h = createHash('sha256').on('finish', () => {
13
- const hash = h.digest('hex')
14
- const match = hash === config.checksum
15
- if (!match) {
16
- console.log(`Validation failed. Expected '${config.checksum}' but got '${hash}'`)
17
- }
18
- callback(match)
19
- })
21
+ const config = require('./config')()
20
22
 
21
- createReadStream(file).pipe(h)
22
- }
23
+ const verifyFile = function (file, callback) {
24
+ return new Promise((resolve, reject) => {
25
+ const h = createHash('sha256')
26
+ .on('error', reject)
27
+ .on('finish', () => {
28
+ const hash = h.digest('hex')
29
+ if (hash !== config.checksum) {
30
+ reject(
31
+ new Error(
32
+ `Validation failed. Expected '${config.checksum}' but got '${hash}'`
33
+ )
34
+ )
35
+ } else {
36
+ resolve()
37
+ }
38
+ })
23
39
 
24
- const unpackFile = function(file) {
25
- tar.x({ cwd: config.outputPath, file }).catch(e => {
26
- console.log('Unable to extract archive, aborting...', error)
27
- process.exit(1)
40
+ createReadStream(file).pipe(h).on('error', reject)
28
41
  })
29
42
  }
30
43
 
31
- const downloadAndUnpack = (url, isFollowingRedirect) => {
32
- if (!isFollowingRedirect) {
33
- console.log(`Downloading Git from: ${url}`)
34
- }
44
+ const unpackFile = file =>
45
+ new Promise((resolve, reject) => {
46
+ createReadStream(file)
47
+ .pipe(new createGunzip())
48
+ .pipe(extract())
49
+ .on('entry', ({ type, name, mode, linkname }, stream, next) => {
50
+ if (name.includes('..')) {
51
+ throw new Error(`invalid file name: ${name}`)
52
+ }
53
+ const p = join(config.outputPath, name)
54
+ if (type === 'file') {
55
+ stream.pipe(createWriteStream(p, { mode })).on('finish', next)
56
+ } else if (type === 'directory') {
57
+ mkdir(p).then(next)
58
+ } else if (type === 'symlink') {
59
+ symlink(linkname, p).then(next)
60
+ } else {
61
+ throw new Error(`unknown file type: ${type}`)
62
+ }
63
+ })
64
+ .on('error', reject)
65
+ .on('finish', resolve)
66
+ })
35
67
 
36
- const options = {
68
+ const downloadAndUnpack = async url => {
69
+ const res = await fetch(url, {
37
70
  headers: {
38
71
  Accept: 'application/octet-stream',
39
- 'User-Agent': 'dugite'
72
+ 'User-Agent': 'dugite',
40
73
  },
41
- secureProtocol: 'TLSv1_2_method'
42
- }
43
-
44
- const req = https.get(url, options)
45
-
46
- req.on('error', function(error) {
47
- if (error.code === 'ETIMEDOUT') {
48
- console.log(
49
- `A timeout has occurred while downloading '${url}' - check ` +
50
- `your internet connection and try again. If you are using a proxy, ` +
51
- `make sure that the HTTP_PROXY and HTTPS_PROXY environment variables are set.`,
52
- error
53
- )
54
- } else {
55
- console.log(`Error raised while downloading ${url}`, error)
56
- }
74
+ }).catch(e => {
75
+ console.log('Unable to download archive, aborting...', e)
57
76
  process.exit(1)
58
77
  })
59
78
 
60
- req.on('response', function(res) {
61
- if ([301, 302].includes(res.statusCode) && res.headers['location']) {
62
- downloadAndUnpack(res.headers.location, true)
63
- return
64
- }
65
-
66
- if (res.statusCode !== 200) {
67
- console.log(`Non-200 response returned from ${url} - (${res.statusCode})`)
68
- process.exit(1)
69
- }
70
-
71
- const len = parseInt(res.headers['content-length'], 10)
79
+ if (!res.ok) {
80
+ console.log(`Got ${res.status} trying to download archive, aborting...`)
81
+ process.exit(1)
82
+ }
72
83
 
73
- const bar = new ProgressBar('Downloading Git [:bar] :percent :etas', {
74
- complete: '=',
75
- incomplete: ' ',
76
- width: 50,
77
- total: len
78
- })
84
+ const len = parseInt(res.headers.get('content-length'), 10)
79
85
 
80
- res.pipe(createWriteStream(config.tempFile))
86
+ const bar = new ProgressBar('Downloading Git [:bar] :percent :etas', {
87
+ complete: '=',
88
+ incomplete: ' ',
89
+ width: 50,
90
+ total: len,
91
+ })
81
92
 
82
- res.on('data', c => bar.tick(c.length))
83
- res.on('end', function() {
84
- verifyFile(config.tempFile, valid => {
85
- if (valid) {
86
- unpackFile(config.tempFile)
87
- } else {
88
- console.log(`checksum verification failed, refusing to unpack...`)
89
- process.exit(1)
90
- }
91
- })
92
- })
93
+ await new Promise((resolve, reject) => {
94
+ Readable.fromWeb(res.body)
95
+ .on('data', c => bar.tick(c.length))
96
+ .pipe(createWriteStream(config.tempFile))
97
+ .on('error', reject)
98
+ .on('finish', resolve)
93
99
  })
100
+ await verifyFile(config.tempFile)
101
+ await unpackFile(config.tempFile)
94
102
  }
95
103
 
96
- if (config.source === '') {
97
- console.log(
98
- `Skipping downloading embedded Git as platform '${process.platform}' is not supported.`
99
- )
100
- console.log(`To learn more about using dugite with a system Git: https://git.io/vF5oj`)
101
- process.exit(0)
102
- }
104
+ ;(async function run() {
105
+ if (config.source === '') {
106
+ console.log(
107
+ `Skipping downloading embedded Git as platform '${process.platform}' is not supported.`
108
+ )
109
+ console.log(
110
+ `To learn more about using dugite with a system Git: https://git.io/vF5oj`
111
+ )
112
+ process.exit(0)
113
+ }
103
114
 
104
- rm(config.outputPath, { recursive: true, force: true }, error => {
105
- if (error) {
115
+ await rm(config.outputPath, { recursive: true, force: true }).catch(error => {
106
116
  console.log(`Unable to clean directory at ${config.outputPath}`, error)
107
117
  process.exit(1)
108
- }
109
-
110
- mkdir(config.outputPath, { recursive: true }, function(error) {
111
- if (error) {
112
- console.log(`Unable to create directory at ${config.outputPath}`, error)
113
- process.exit(1)
114
- }
115
-
116
- const tempFile = config.tempFile
118
+ })
117
119
 
118
- if (existsSync(tempFile)) {
119
- verifyFile(tempFile, valid => {
120
- if (valid) {
121
- unpackFile(tempFile)
122
- } else {
123
- rmSync(tempFile)
124
- downloadAndUnpack(config.source)
125
- }
126
- })
127
- return
128
- }
120
+ const tempFile = config.tempFile
129
121
 
130
- downloadAndUnpack(config.source)
131
- })
132
- })
122
+ if (await pathExists(tempFile)) {
123
+ await verifyFile(tempFile).catch(e => {
124
+ console.log('Unable to verify cached archive, removing...', e)
125
+ return rm(tempFile)
126
+ })
127
+ await unpackFile(tempFile)
128
+ } else {
129
+ await downloadAndUnpack(config.source)
130
+ }
131
+ })()
@@ -0,0 +1,43 @@
1
+ import { spawn } from 'child_process'
2
+ import { glob } from 'glob'
3
+ import { dirname, resolve } from 'path'
4
+ import { fileURLToPath } from 'url'
5
+
6
+ if (process.argv.some(arg => ['-h', '--help'].includes(arg))) {
7
+ console.log(`Usage: ${process.argv0} [kind]`)
8
+ console.log(
9
+ ' kind: The kind of tests to run (e.g. "fast", "slow", "external", "all")'
10
+ )
11
+ process.exit(0)
12
+ }
13
+
14
+ ;(async function (kind) {
15
+ const wildcard = kind && kind !== 'all' ? `${kind}/**` : '**'
16
+ const files = await glob(`test/${wildcard}/*-test.ts`)
17
+ const reporterDestinationArgs = ['--test-reporter-destination', 'stdout']
18
+ const specTestReporterArgs = [
19
+ '--test-reporter',
20
+ 'spec',
21
+ ...reporterDestinationArgs,
22
+ ]
23
+
24
+ const testReporterArgs = process.env.GITHUB_ACTIONS
25
+ ? [
26
+ '--test-reporter',
27
+ 'node-test-github-reporter',
28
+ ...reporterDestinationArgs,
29
+ ...specTestReporterArgs,
30
+ ]
31
+ : specTestReporterArgs
32
+
33
+ spawn('node', ['--import', 'tsx', ...testReporterArgs, '--test', ...files], {
34
+ stdio: 'inherit',
35
+ env: {
36
+ ...process.env,
37
+ LOCAL_GIT_DIRECTORY: resolve(
38
+ dirname(fileURLToPath(import.meta.url)),
39
+ '../git/'
40
+ ),
41
+ },
42
+ }).on('exit', process.exit)
43
+ })(process.argv[2])