Upload via GUI (7543 Dateien)
This commit is contained in:
+20
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2014 GitHub Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
# @electron/asar - Electron Archive
|
||||
|
||||
[](https://github.com/electron/asar/actions/workflows/test.yml)
|
||||
[](https://npmjs.org/package/@electron/asar)
|
||||
|
||||
Asar is a simple extensive archive format, it works like `tar` that concatenates
|
||||
all files together without compression, while having random access support.
|
||||
|
||||
## Features
|
||||
|
||||
* Support random access
|
||||
* Use JSON to store files' information
|
||||
* Very easy to write a parser
|
||||
|
||||
## Command line utility
|
||||
|
||||
### Install
|
||||
|
||||
This module requires Node 10 or later.
|
||||
|
||||
```bash
|
||||
$ npm install --engine-strict @electron/asar
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
$ asar --help
|
||||
|
||||
Usage: asar [options] [command]
|
||||
|
||||
Commands:
|
||||
|
||||
pack|p <dir> <output>
|
||||
create asar archive
|
||||
|
||||
list|l <archive>
|
||||
list files of asar archive
|
||||
|
||||
extract-file|ef <archive> <filename>
|
||||
extract one file from archive
|
||||
|
||||
extract|e <archive> <dest>
|
||||
extract archive
|
||||
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help output usage information
|
||||
-V, --version output the version number
|
||||
|
||||
```
|
||||
|
||||
#### Excluding multiple resources from being packed
|
||||
|
||||
Given:
|
||||
```
|
||||
app
|
||||
(a) ├── x1
|
||||
(b) ├── x2
|
||||
(c) ├── y3
|
||||
(d) │ ├── x1
|
||||
(e) │ └── z1
|
||||
(f) │ └── x2
|
||||
(g) └── z4
|
||||
(h) └── w1
|
||||
```
|
||||
|
||||
Exclude: a, b
|
||||
```bash
|
||||
$ asar pack app app.asar --unpack-dir "{x1,x2}"
|
||||
```
|
||||
|
||||
Exclude: a, b, d, f
|
||||
```bash
|
||||
$ asar pack app app.asar --unpack-dir "**/{x1,x2}"
|
||||
```
|
||||
|
||||
Exclude: a, b, d, f, h
|
||||
```bash
|
||||
$ asar pack app app.asar --unpack-dir "{**/x1,**/x2,z4/w1}"
|
||||
```
|
||||
|
||||
## Using programmatically
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
const asar = require('@electron/asar');
|
||||
|
||||
const src = 'some/path/';
|
||||
const dest = 'name.asar';
|
||||
|
||||
await asar.createPackage(src, dest);
|
||||
console.log('done.');
|
||||
```
|
||||
|
||||
Please note that there is currently **no** error handling provided!
|
||||
|
||||
### Transform
|
||||
You can pass in a `transform` option, that is a function, which either returns
|
||||
nothing, or a `stream.Transform`. The latter will be used on files that will be
|
||||
in the `.asar` file to transform them (e.g. compress).
|
||||
|
||||
```javascript
|
||||
const asar = require('@electron/asar');
|
||||
|
||||
const src = 'some/path/';
|
||||
const dest = 'name.asar';
|
||||
|
||||
function transform (filename) {
|
||||
return new CustomTransformStream()
|
||||
}
|
||||
|
||||
await asar.createPackageWithOptions(src, dest, { transform: transform });
|
||||
console.log('done.');
|
||||
```
|
||||
|
||||
## Format
|
||||
|
||||
Asar uses [Pickle][pickle] to safely serialize binary value to file.
|
||||
|
||||
The format of asar is very flat:
|
||||
|
||||
```
|
||||
| UInt32: header_size | String: header | Bytes: file1 | ... | Bytes: file42 |
|
||||
```
|
||||
|
||||
The `header_size` and `header` are serialized with [Pickle][pickle] class, and
|
||||
`header_size`'s [Pickle][pickle] object is 8 bytes.
|
||||
|
||||
The `header` is a JSON string, and the `header_size` is the size of `header`'s
|
||||
`Pickle` object.
|
||||
|
||||
Structure of `header` is something like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"files": {
|
||||
"tmp": {
|
||||
"files": {}
|
||||
},
|
||||
"usr" : {
|
||||
"files": {
|
||||
"bin": {
|
||||
"files": {
|
||||
"ls": {
|
||||
"offset": "0",
|
||||
"size": 100,
|
||||
"executable": true,
|
||||
"integrity": {
|
||||
"algorithm": "SHA256",
|
||||
"hash": "...",
|
||||
"blockSize": 1024,
|
||||
"blocks": ["...", "..."]
|
||||
}
|
||||
},
|
||||
"cd": {
|
||||
"offset": "100",
|
||||
"size": 100,
|
||||
"executable": true,
|
||||
"integrity": {
|
||||
"algorithm": "SHA256",
|
||||
"hash": "...",
|
||||
"blockSize": 1024,
|
||||
"blocks": ["...", "..."]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"etc": {
|
||||
"files": {
|
||||
"hosts": {
|
||||
"offset": "200",
|
||||
"size": 32,
|
||||
"integrity": {
|
||||
"algorithm": "SHA256",
|
||||
"hash": "...",
|
||||
"blockSize": 1024,
|
||||
"blocks": ["...", "..."]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`offset` and `size` records the information to read the file from archive, the
|
||||
`offset` starts from 0 so you have to manually add the size of `header_size` and
|
||||
`header` to the `offset` to get the real offset of the file.
|
||||
|
||||
`offset` is a UINT64 number represented in string, because there is no way to
|
||||
precisely represent UINT64 in JavaScript `Number`. `size` is a JavaScript
|
||||
`Number` that is no larger than `Number.MAX_SAFE_INTEGER`, which has a value of
|
||||
`9007199254740991` and is about 8PB in size. We didn't store `size` in UINT64
|
||||
because file size in Node.js is represented as `Number` and it is not safe to
|
||||
convert `Number` to UINT64.
|
||||
|
||||
`integrity` is an object consisting of a few keys:
|
||||
* A hashing `algorithm`, currently only `SHA256` is supported.
|
||||
* A hex encoded `hash` value representing the hash of the entire file.
|
||||
* An array of hex encoded hashes for the `blocks` of the file. i.e. for a blockSize of 4KB this array contains the hash of every block if you split the file into N 4KB blocks.
|
||||
* A integer value `blockSize` representing the size in bytes of each block in the `blocks` hashes above
|
||||
|
||||
[pickle]: https://chromium.googlesource.com/chromium/src/+/main/base/pickle.h
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
//# sourceMappingURL=glob.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"glob.js","sourceRoot":"","sources":["../../src/types/glob.ts"],"names":[],"mappings":""}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
type AsarFS = typeof import('fs') & {
|
||||
mkdirp(dir: string): Promise<void>;
|
||||
mkdirpSync(dir: string): void;
|
||||
lstat: (typeof import('fs'))['promises']['lstat'];
|
||||
mkdtemp: (typeof import('fs'))['promises']['mkdtemp'];
|
||||
readFile: (typeof import('fs'))['promises']['readFile'];
|
||||
stat: (typeof import('fs'))['promises']['stat'];
|
||||
writeFile: (typeof import('fs'))['promises']['writeFile'];
|
||||
symlink: (typeof import('fs'))['promises']['symlink'];
|
||||
readlink: (typeof import('fs'))['promises']['readlink'];
|
||||
};
|
||||
declare const promisified: AsarFS;
|
||||
export default promisified;
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const fs = 'electron' in process.versions ? require('original-fs') : require('fs');
|
||||
const promisifiedMethods = [
|
||||
'lstat',
|
||||
'mkdtemp',
|
||||
'readFile',
|
||||
'stat',
|
||||
'writeFile',
|
||||
'symlink',
|
||||
'readlink',
|
||||
];
|
||||
const promisified = {};
|
||||
for (const method of Object.keys(fs)) {
|
||||
if (promisifiedMethods.includes(method)) {
|
||||
promisified[method] = fs.promises[method];
|
||||
}
|
||||
else {
|
||||
promisified[method] = fs[method];
|
||||
}
|
||||
}
|
||||
// To make it more like fs-extra
|
||||
promisified.mkdirp = (dir) => fs.promises.mkdir(dir, { recursive: true });
|
||||
promisified.mkdirpSync = (dir) => fs.mkdirSync(dir, { recursive: true });
|
||||
exports.default = promisified;
|
||||
//# sourceMappingURL=wrapped-fs.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"wrapped-fs.js","sourceRoot":"","sources":["../src/wrapped-fs.ts"],"names":[],"mappings":";;AAAA,MAAM,EAAE,GAAG,UAAU,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnF,MAAM,kBAAkB,GAAG;IACzB,OAAO;IACP,SAAS;IACT,UAAU;IACV,MAAM;IACN,WAAW;IACX,SAAS;IACT,UAAU;CACX,CAAC;AAcF,MAAM,WAAW,GAAG,EAAY,CAAC;AAEjC,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;IACrC,IAAI,kBAAkB,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACvC,WAAmB,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACrD,CAAC;SAAM,CAAC;QACL,WAAmB,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC;IAC5C,CAAC;AACH,CAAC;AACD,gCAAgC;AAChC,WAAW,CAAC,MAAM,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC1E,WAAW,CAAC,UAAU,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAEzE,kBAAe,WAAW,CAAC"}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "@electron/asar",
|
||||
"description": "Creating Electron app packages",
|
||||
"version": "3.4.1",
|
||||
"main": "./lib/asar.js",
|
||||
"types": "./lib/asar.d.ts",
|
||||
"bin": {
|
||||
"asar": "./bin/asar.js"
|
||||
},
|
||||
"files": [
|
||||
"bin",
|
||||
"lib"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10.12.0"
|
||||
},
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/electron/asar",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/electron/asar.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/electron/asar/issues"
|
||||
},
|
||||
"publishConfig": {
|
||||
"provenance": true
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"mocha": "xvfb-maybe electron-mocha && mocha",
|
||||
"mocha:update": "mocha --update",
|
||||
"mocha:watch": "mocha --watch",
|
||||
"test": "yarn lint && yarn mocha",
|
||||
"lint": "yarn prettier:check",
|
||||
"prettier": "prettier \"src/**/*.ts\" \"test/**/*.js\" \"*.js\"",
|
||||
"prettier:check": "yarn prettier --check",
|
||||
"prettier:write": "yarn prettier --write",
|
||||
"prepare": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": "^5.0.0",
|
||||
"glob": "^7.1.6",
|
||||
"minimatch": "^3.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/minimatch": "^3.0.5",
|
||||
"@types/node": "^12.0.0",
|
||||
"chai": "^4.5.0",
|
||||
"electron": "^22.0.0",
|
||||
"electron-mocha": "^13.0.1",
|
||||
"lodash": "^4.17.15",
|
||||
"mocha": "^10.1.0",
|
||||
"mocha-chai-jest-snapshot": "^1.1.6",
|
||||
"prettier": "^3.3.3",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^5.5.4",
|
||||
"xvfb-maybe": "^0.2.1"
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2020 Electron Maintainers.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
# @electron/fuses
|
||||
|
||||
> Flip [Electron Fuses](https://github.com/electron/electron/blob/main/docs/tutorial/fuses.md) and customize your packaged build of Electron
|
||||
|
||||
[](https://circleci.com/gh/electron/fuses)
|
||||
[](https://npmjs.org/package/@electron/fuses)
|
||||
|
||||
## Usage
|
||||
|
||||
### Via JavaScript
|
||||
|
||||
```typescript
|
||||
import { flipFuses, FuseVersion, FuseV1Options } from '@electron/fuses';
|
||||
|
||||
// During your build / package process
|
||||
await flipFuses(
|
||||
require('electron'), // Returns the path to the electron binary
|
||||
{
|
||||
version: FuseVersion.V1,
|
||||
[FuseV1Options.RunAsNode]: false, // Disables ELECTRON_RUN_AS_NODE
|
||||
[FuseV1Options.EnableCookieEncryption]: true, // Enables cookie encryption
|
||||
[FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false, // Disables the NODE_OPTIONS environment variable
|
||||
[FuseV1Options.EnableNodeCliInspectArguments]: false, // Disables the --inspect and --inspect-brk family of CLI options
|
||||
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true, // Enables validation of the app.asar archive on macOS
|
||||
[FuseV1Options.OnlyLoadAppFromAsar]: true, // Enforces that Electron will only load your app from "app.asar" instead of its normal search paths
|
||||
[FuseV1Options.LoadBrowserProcessSpecificV8Snapshot]: true, // Loads V8 Snapshot from `browser_v8_context_snapshot.bin` for the browser process
|
||||
[FuseV1Options.GrantFileProtocolExtraPrivileges]: true, // Grants the file protocol extra privileges
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
### From the command line
|
||||
|
||||
```sh
|
||||
$ npx @electron/fuses read --app /Applications/Foo.app
|
||||
$ npx @electron/fuses write --app /Applications/Foo.app <...key=on/off>
|
||||
```
|
||||
|
||||
### Apple Silicon
|
||||
|
||||
For `arm64` macOS builds of your app if you are not immediately codesigning your app after flipping
|
||||
the fuses you will need to pass `resetAdHocDarwinSignature: true` to the `flipFuses` method. Otherwise
|
||||
the app will refuse to launch with code signature validation errors. This is a new security measure on
|
||||
Apple Silicon devices.
|
||||
|
||||
```typescript
|
||||
import { flipFuses, FuseVersion, FuseV1Options } from '@electron/fuses';
|
||||
|
||||
await flipFuses(
|
||||
require('electron'),
|
||||
{
|
||||
version: FuseVersion.V1,
|
||||
resetAdHocDarwinSignature: targetPlatform === 'darwin' && targetArch === 'arm64',
|
||||
[FuseV1Options.RunAsNode]: false,
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
### New Fuses
|
||||
|
||||
If you want to ensure you provide a config for every fuse, even newly added fuses during Electron upgrades
|
||||
you can set the `strictlyRequireAllFuses` option to `true`. This will hard fail the build if you are on
|
||||
a version of `@electron/fuses` that doesn't have configuration options for every fuse in the Electron binary
|
||||
you are targetting or if you don't provide a configuration for a specific fuse present in the Electron binary
|
||||
you are targetting.
|
||||
|
||||
```typescript
|
||||
import { flipFuses, FuseVersion, FuseV1Options } from '@electron/fuses';
|
||||
|
||||
await flipFuses(
|
||||
require('electron'),
|
||||
{
|
||||
version: FuseVersion.V1,
|
||||
strictlyRequireAllFuses: true,
|
||||
[FuseV1Options.RunAsNode]: false,
|
||||
},
|
||||
);
|
||||
```
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env node
|
||||
export {};
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
var __rest = (this && this.__rest) || function (s, e) {
|
||||
var t = {};
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
||||
t[p] = s[p];
|
||||
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
||||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
||||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
||||
t[p[i]] = s[p[i]];
|
||||
}
|
||||
return t;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const chalk = require("chalk");
|
||||
const minimist = require("minimist");
|
||||
const path = require("path");
|
||||
const _1 = require(".");
|
||||
const config_1 = require("./config");
|
||||
const constants_1 = require("./constants");
|
||||
const mode = process.argv[2];
|
||||
const readHelpText = `electron-fuses read --app [path-to-app]`;
|
||||
const writeHelpText = `electron-fuses write --app [path-to-app] <...key=on/off>`;
|
||||
if (mode !== 'read' && mode !== 'write') {
|
||||
console.error('Invalid mode, check the usage below:');
|
||||
console.info(readHelpText);
|
||||
console.info(writeHelpText);
|
||||
process.exit(0);
|
||||
}
|
||||
function stringForState(state) {
|
||||
switch (state) {
|
||||
case constants_1.FuseState.ENABLE:
|
||||
return chalk.green('Enabled');
|
||||
case constants_1.FuseState.DISABLE:
|
||||
return chalk.red('Disabled');
|
||||
case constants_1.FuseState.INHERIT:
|
||||
return chalk.yellow('Inherited');
|
||||
case constants_1.FuseState.REMOVED:
|
||||
return chalk.strikethrough(chalk.red('Removed'));
|
||||
}
|
||||
}
|
||||
if (mode === 'read') {
|
||||
const argv = minimist(process.argv.slice(3), {
|
||||
string: ['app'],
|
||||
boolean: ['help'],
|
||||
});
|
||||
if (argv.help) {
|
||||
console.log(readHelpText);
|
||||
process.exit(0);
|
||||
}
|
||||
if (!argv.app) {
|
||||
console.error('--app argument is required');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('Analyzing app:', chalk.cyan(path.basename(argv.app)));
|
||||
(0, _1.getCurrentFuseWire)(argv.app)
|
||||
.then((config) => {
|
||||
const { version, resetAdHocDarwinSignature, strictlyRequireAllFuses } = config, rest = __rest(config, ["version", "resetAdHocDarwinSignature", "strictlyRequireAllFuses"]);
|
||||
console.log(`Fuse Version: ${chalk.cyan(`v${version}`)}`);
|
||||
switch (config.version) {
|
||||
case config_1.FuseVersion.V1:
|
||||
for (const key of Object.keys(rest)) {
|
||||
console.log(` ${chalk.yellow(config_1.FuseV1Options[key])} is ${stringForState(rest[key])}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
else {
|
||||
const argv = minimist(process.argv.slice(3), {
|
||||
string: ['app'],
|
||||
boolean: ['help'],
|
||||
});
|
||||
if (argv.help) {
|
||||
console.log(writeHelpText);
|
||||
process.exit(0);
|
||||
}
|
||||
if (!argv.app) {
|
||||
console.error('--app argument is required');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('Analyzing app:', chalk.cyan(path.basename(argv.app)));
|
||||
(0, _1.getCurrentFuseWire)(argv.app)
|
||||
.then((config) => {
|
||||
const { version, resetAdHocDarwinSignature } = config, rest = __rest(config, ["version", "resetAdHocDarwinSignature"]);
|
||||
console.log(`Fuse Version: ${chalk.cyan(`v${version}`)}`);
|
||||
const keyPairs = argv._ || [];
|
||||
for (const keyPair of keyPairs) {
|
||||
const [key, state] = keyPair.split('=');
|
||||
if (!key || !state) {
|
||||
console.error('Invalid fuse:', keyPair);
|
||||
console.error('Must be in the format FuseName=on/off');
|
||||
process.exit(1);
|
||||
}
|
||||
if (state !== 'on' && state !== 'off') {
|
||||
console.error('Invalid fuse state:', chalk.yellow(keyPair));
|
||||
console.error(`Fuses can only be set to the "${chalk.green('on')}" or "${chalk.red('off')}" state`);
|
||||
process.exit(1);
|
||||
}
|
||||
switch (config.version) {
|
||||
case config_1.FuseVersion.V1:
|
||||
const validFuseNames = Object.keys(config_1.FuseV1Options).filter((k) => !/^[0-9]+$/.test(k));
|
||||
if (!validFuseNames.includes(key)) {
|
||||
console.error('Invalid fuse name', chalk.yellow(key));
|
||||
console.error('Expected name to be one of', chalk.yellow(JSON.stringify(validFuseNames)));
|
||||
process.exit(1);
|
||||
}
|
||||
const currentState = config[config_1.FuseV1Options[key]];
|
||||
const newState = state === 'on' ? constants_1.FuseState.ENABLE : constants_1.FuseState.DISABLE;
|
||||
if (currentState === newState) {
|
||||
console.log(` ${chalk.yellow(key)} is already ${stringForState(currentState)} and will not be changed`);
|
||||
}
|
||||
else {
|
||||
console.log(` ${chalk.yellow(key)} is ${stringForState(currentState)} and will become ${stringForState(newState)}`);
|
||||
}
|
||||
config[config_1.FuseV1Options[key]] = newState;
|
||||
break;
|
||||
}
|
||||
}
|
||||
console.log('Writing to app:', chalk.cyan(path.basename(argv.app)));
|
||||
function adaptConfig(config) {
|
||||
const { version, resetAdHocDarwinSignature } = config, rest = __rest(config, ["version", "resetAdHocDarwinSignature"]);
|
||||
const fuseConfig = {
|
||||
version,
|
||||
resetAdHocDarwinSignature,
|
||||
};
|
||||
for (const key of Object.keys(rest)) {
|
||||
fuseConfig[key] = rest[key] === constants_1.FuseState.ENABLE;
|
||||
}
|
||||
return fuseConfig;
|
||||
}
|
||||
return (0, _1.flipFuses)(argv.app, adaptConfig(config));
|
||||
})
|
||||
.then(() => {
|
||||
console.log(chalk.green('Fuses written to disk'));
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=bin.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+34
@@ -0,0 +1,34 @@
|
||||
export declare enum FuseVersion {
|
||||
V1 = "1"
|
||||
}
|
||||
/**
|
||||
* Maps config keys to their index in the fuse wire
|
||||
*/
|
||||
export declare enum FuseV1Options {
|
||||
RunAsNode = 0,
|
||||
EnableCookieEncryption = 1,
|
||||
EnableNodeOptionsEnvironmentVariable = 2,
|
||||
EnableNodeCliInspectArguments = 3,
|
||||
EnableEmbeddedAsarIntegrityValidation = 4,
|
||||
OnlyLoadAppFromAsar = 5,
|
||||
LoadBrowserProcessSpecificV8Snapshot = 6,
|
||||
GrantFileProtocolExtraPrivileges = 7
|
||||
}
|
||||
export declare type FuseV1Config<T = boolean> = {
|
||||
version: FuseVersion.V1;
|
||||
resetAdHocDarwinSignature?: boolean;
|
||||
/**
|
||||
* Ensures that all fuses in the fuse wire being set have been defined to a set value
|
||||
* by the provided config. Set this to true to ensure you don't accidentally miss a
|
||||
* fuse being added in future Electron upgrades.
|
||||
*
|
||||
* This option may default to "true" in a future version of @electron/fuses but currently
|
||||
* defaults to "false"
|
||||
*/
|
||||
strictlyRequireAllFuses?: boolean;
|
||||
} & ((Partial<Record<FuseV1Options, T>> & {
|
||||
strictlyRequireAllFuses?: false | undefined;
|
||||
}) | (Record<FuseV1Options, T> & {
|
||||
strictlyRequireAllFuses: true;
|
||||
}));
|
||||
export declare type FuseConfig<T = boolean> = FuseV1Config<T>;
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.FuseV1Options = exports.FuseVersion = void 0;
|
||||
var FuseVersion;
|
||||
(function (FuseVersion) {
|
||||
FuseVersion["V1"] = "1";
|
||||
})(FuseVersion = exports.FuseVersion || (exports.FuseVersion = {}));
|
||||
/**
|
||||
* Maps config keys to their index in the fuse wire
|
||||
*/
|
||||
var FuseV1Options;
|
||||
(function (FuseV1Options) {
|
||||
FuseV1Options[FuseV1Options["RunAsNode"] = 0] = "RunAsNode";
|
||||
FuseV1Options[FuseV1Options["EnableCookieEncryption"] = 1] = "EnableCookieEncryption";
|
||||
FuseV1Options[FuseV1Options["EnableNodeOptionsEnvironmentVariable"] = 2] = "EnableNodeOptionsEnvironmentVariable";
|
||||
FuseV1Options[FuseV1Options["EnableNodeCliInspectArguments"] = 3] = "EnableNodeCliInspectArguments";
|
||||
FuseV1Options[FuseV1Options["EnableEmbeddedAsarIntegrityValidation"] = 4] = "EnableEmbeddedAsarIntegrityValidation";
|
||||
FuseV1Options[FuseV1Options["OnlyLoadAppFromAsar"] = 5] = "OnlyLoadAppFromAsar";
|
||||
FuseV1Options[FuseV1Options["LoadBrowserProcessSpecificV8Snapshot"] = 6] = "LoadBrowserProcessSpecificV8Snapshot";
|
||||
FuseV1Options[FuseV1Options["GrantFileProtocolExtraPrivileges"] = 7] = "GrantFileProtocolExtraPrivileges";
|
||||
})(FuseV1Options = exports.FuseV1Options || (exports.FuseV1Options = {}));
|
||||
//# sourceMappingURL=config.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":";;;AAAA,IAAY,WAEX;AAFD,WAAY,WAAW;IACrB,uBAAQ,CAAA;AACV,CAAC,EAFW,WAAW,GAAX,mBAAW,KAAX,mBAAW,QAEtB;AAED;;GAEG;AACH,IAAY,aASX;AATD,WAAY,aAAa;IACvB,2DAAa,CAAA;IACb,qFAA0B,CAAA;IAC1B,iHAAwC,CAAA;IACxC,mGAAiC,CAAA;IACjC,mHAAyC,CAAA;IACzC,+EAAuB,CAAA;IACvB,iHAAwC,CAAA;IACxC,yGAAoC,CAAA;AACtC,CAAC,EATW,aAAa,GAAb,qBAAa,KAAb,qBAAa,QASxB"}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
export declare const SENTINEL = "dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX";
|
||||
export declare enum FuseState {
|
||||
DISABLE = 48,
|
||||
ENABLE = 49,
|
||||
REMOVED = 114,
|
||||
INHERIT = 144
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.FuseState = exports.SENTINEL = void 0;
|
||||
exports.SENTINEL = 'dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX';
|
||||
var FuseState;
|
||||
(function (FuseState) {
|
||||
FuseState[FuseState["DISABLE"] = 48] = "DISABLE";
|
||||
FuseState[FuseState["ENABLE"] = 49] = "ENABLE";
|
||||
FuseState[FuseState["REMOVED"] = 114] = "REMOVED";
|
||||
FuseState[FuseState["INHERIT"] = 144] = "INHERIT";
|
||||
})(FuseState = exports.FuseState || (exports.FuseState = {}));
|
||||
//# sourceMappingURL=constants.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":";;;AAAa,QAAA,QAAQ,GAAG,kCAAkC,CAAC;AAE3D,IAAY,SAKX;AALD,WAAY,SAAS;IACnB,gDAAc,CAAA;IACd,8CAAa,CAAA;IACb,iDAAc,CAAA;IACd,iDAAc,CAAA;AAChB,CAAC,EALW,SAAS,GAAT,iBAAS,KAAT,iBAAS,QAKpB"}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { FuseConfig } from './config';
|
||||
import { FuseState } from './constants';
|
||||
export * from './config';
|
||||
export declare const getCurrentFuseWire: (pathToElectron: string) => Promise<FuseConfig<FuseState>>;
|
||||
export declare const flipFuses: (pathToElectron: string, fuseConfig: FuseConfig) => Promise<number>;
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
||||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
||||
};
|
||||
var __rest = (this && this.__rest) || function (s, e) {
|
||||
var t = {};
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
||||
t[p] = s[p];
|
||||
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
||||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
||||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
||||
t[p[i]] = s[p[i]];
|
||||
}
|
||||
return t;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.flipFuses = exports.getCurrentFuseWire = void 0;
|
||||
const cp = require("child_process");
|
||||
const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
const config_1 = require("./config");
|
||||
const constants_1 = require("./constants");
|
||||
__exportStar(require("./config"), exports);
|
||||
const state = (b) => b === undefined ? constants_1.FuseState.INHERIT : b ? constants_1.FuseState.ENABLE : constants_1.FuseState.DISABLE;
|
||||
const buildFuseV1Wire = (config, wireLength) => {
|
||||
const { version } = config, nonVersionConfig = __rest(config, ["version"]);
|
||||
const badFuseOption = Object.keys(nonVersionConfig).find((fuseOption) => parseInt(fuseOption, 10) >= wireLength);
|
||||
if (badFuseOption !== undefined) {
|
||||
throw new Error(`Trying to configure ${config_1.FuseV1Options[badFuseOption]} but the fuse wire in this version of Electron is not long enough`);
|
||||
}
|
||||
return [
|
||||
state(config[config_1.FuseV1Options.RunAsNode]),
|
||||
state(config[config_1.FuseV1Options.EnableCookieEncryption]),
|
||||
state(config[config_1.FuseV1Options.EnableNodeOptionsEnvironmentVariable]),
|
||||
state(config[config_1.FuseV1Options.EnableNodeCliInspectArguments]),
|
||||
state(config[config_1.FuseV1Options.EnableEmbeddedAsarIntegrityValidation]),
|
||||
state(config[config_1.FuseV1Options.OnlyLoadAppFromAsar]),
|
||||
state(config[config_1.FuseV1Options.LoadBrowserProcessSpecificV8Snapshot]),
|
||||
state(config[config_1.FuseV1Options.GrantFileProtocolExtraPrivileges]),
|
||||
];
|
||||
};
|
||||
const pathToFuseFile = (pathToElectron) => {
|
||||
if (pathToElectron.endsWith('.app')) {
|
||||
return path.resolve(pathToElectron, 'Contents', 'Frameworks', 'Electron Framework.framework', 'Electron Framework');
|
||||
}
|
||||
if (pathToElectron.includes('.app')) {
|
||||
return path.resolve(pathToElectron, '..', '..', 'Frameworks', 'Electron Framework.framework', 'Electron Framework');
|
||||
}
|
||||
return pathToElectron;
|
||||
};
|
||||
const setFuseWire = async (pathToElectron, fuseVersion, strictlyRequireAllFuses, fuseWireBuilder, fuseNamer) => {
|
||||
const fuseFilePath = pathToFuseFile(pathToElectron);
|
||||
const electron = await fs.readFile(fuseFilePath);
|
||||
const firstSentinel = electron.indexOf(constants_1.SENTINEL);
|
||||
const lastSentinel = electron.lastIndexOf(constants_1.SENTINEL);
|
||||
// If the last sentinel is different to the first sentinel we are probably in a universal build
|
||||
// We should flip the fuses in both sentinels to affect both slices of the universal binary
|
||||
const sentinels = firstSentinel === lastSentinel ? [firstSentinel] : [firstSentinel, lastSentinel];
|
||||
for (const indexOfSentinel of sentinels) {
|
||||
if (indexOfSentinel === -1) {
|
||||
throw new Error('Could not find sentinel in the provided Electron binary, fuses are only supported in Electron 12 and higher');
|
||||
}
|
||||
const fuseWirePosition = indexOfSentinel + constants_1.SENTINEL.length;
|
||||
const fuseWireVersion = electron[fuseWirePosition];
|
||||
if (parseInt(fuseVersion, 10) !== fuseWireVersion) {
|
||||
throw new Error(`Provided fuse wire version "${parseInt(fuseVersion, 10)}" does not match watch was found in the binary "${fuseWireVersion}". You should update your usage of @electron/fuses.`);
|
||||
}
|
||||
const fuseWireLength = electron[fuseWirePosition + 1];
|
||||
const wire = fuseWireBuilder(fuseWireLength).slice(0, fuseWireLength);
|
||||
if (wire.length < fuseWireLength && strictlyRequireAllFuses) {
|
||||
throw new Error(`strictlyRequireAllFuses: The fuse wire in the Electron binary has ${fuseWireLength} fuses but you only provided a config for ${wire.length} fuses, you may need to update @electron/fuses or provide additional fuse settings`);
|
||||
}
|
||||
for (let i = 0; i < wire.length; i++) {
|
||||
const idx = fuseWirePosition + 2 + i;
|
||||
const currentState = electron[idx];
|
||||
const newState = wire[i];
|
||||
if (currentState === constants_1.FuseState.REMOVED && newState !== constants_1.FuseState.INHERIT) {
|
||||
console.warn(`Overriding fuse "${fuseNamer(i)}" that has been marked as removed, setting this fuse is a noop`);
|
||||
}
|
||||
if (newState === constants_1.FuseState.INHERIT) {
|
||||
if (strictlyRequireAllFuses) {
|
||||
throw new Error(`strictlyRequireAllFuses: Missing explicit configuration for fuse ${fuseNamer(i)}`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
electron[idx] = newState;
|
||||
}
|
||||
}
|
||||
await fs.writeFile(fuseFilePath, electron);
|
||||
return sentinels.length;
|
||||
};
|
||||
const getCurrentFuseWire = async (pathToElectron) => {
|
||||
const fuseFilePath = pathToFuseFile(pathToElectron);
|
||||
const electron = await fs.readFile(fuseFilePath);
|
||||
const fuseWirePosition = electron.indexOf(constants_1.SENTINEL) + constants_1.SENTINEL.length;
|
||||
if (fuseWirePosition - constants_1.SENTINEL.length === -1) {
|
||||
throw new Error('Could not find sentinel in the provided Electron binary, fuses are only supported in Electron 12 and higher');
|
||||
}
|
||||
const fuseWireVersion = electron[fuseWirePosition];
|
||||
const fuseWireLength = electron[fuseWirePosition + 1];
|
||||
const fuseConfig = {
|
||||
version: `${fuseWireVersion}`,
|
||||
};
|
||||
for (let i = 0; i < fuseWireLength; i++) {
|
||||
const idx = fuseWirePosition + 2 + i;
|
||||
const currentState = electron[idx];
|
||||
switch (fuseConfig.version) {
|
||||
case config_1.FuseVersion.V1:
|
||||
fuseConfig[i] = currentState;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return fuseConfig;
|
||||
};
|
||||
exports.getCurrentFuseWire = getCurrentFuseWire;
|
||||
const flipFuses = async (pathToElectron, fuseConfig) => {
|
||||
let numSentinels;
|
||||
switch (fuseConfig.version) {
|
||||
case config_1.FuseVersion.V1:
|
||||
numSentinels = await setFuseWire(pathToElectron, fuseConfig.version, fuseConfig.strictlyRequireAllFuses || false, buildFuseV1Wire.bind(null, fuseConfig), (i) => config_1.FuseV1Options[i]);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported fuse version number: ${fuseConfig.version}`);
|
||||
}
|
||||
// Reset the ad-hoc signature on macOS, should only be done for arm64 apps
|
||||
if (fuseConfig.resetAdHocDarwinSignature && pathToElectron.includes('.app')) {
|
||||
const pathToApp = `${pathToElectron.split('.app')[0]}.app`;
|
||||
const result = cp.spawnSync('codesign', [
|
||||
'--sign',
|
||||
'-',
|
||||
'--force',
|
||||
'--preserve-metadata=entitlements,requirements,flags,runtime',
|
||||
'--deep',
|
||||
pathToApp,
|
||||
]);
|
||||
if (result.status !== 0) {
|
||||
console.error(result.stderr.toString());
|
||||
throw new Error(`Ad-hoc codesign failed with status: ${result.status}`);
|
||||
}
|
||||
}
|
||||
return numSentinels;
|
||||
};
|
||||
exports.flipFuses = flipFuses;
|
||||
//# sourceMappingURL=index.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+58
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "@electron/fuses",
|
||||
"version": "1.8.0",
|
||||
"main": "dist/index.js",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"electron-fuses": "dist/bin.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "jest",
|
||||
"prepublishOnly": "tsc",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron/get": "^2.0.1",
|
||||
"@electron/universal": "^1.3.0",
|
||||
"@types/fs-extra": "^9.0.1",
|
||||
"@types/jest": "^29.0.3",
|
||||
"@types/minimist": "^1.2.1",
|
||||
"@types/node": "^14.11.2",
|
||||
"extract-zip": "^2.0.1",
|
||||
"husky": "^8.0.0",
|
||||
"jest": "^29.0.3",
|
||||
"lint-staged": "^10.4.0",
|
||||
"prettier": "^2.1.2",
|
||||
"ts-jest": "^29.0.1",
|
||||
"typescript": "^4.3.3"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.ts": [
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.1",
|
||||
"fs-extra": "^9.0.1",
|
||||
"minimist": "^1.2.5"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"description": "Flip Electron Fuses and customize your packaged build of Electron",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/electron/fuses.git"
|
||||
},
|
||||
"keywords": [
|
||||
"electron",
|
||||
"fuses"
|
||||
],
|
||||
"author": "Electron Community",
|
||||
"bugs": {
|
||||
"url": "https://github.com/electron/fuses/issues"
|
||||
},
|
||||
"homepage": "https://github.com/electron/fuses#readme"
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
export declare class Cache {
|
||||
private cacheRoot;
|
||||
constructor(cacheRoot?: string);
|
||||
static getCacheDirectory(downloadUrl: string): string;
|
||||
getCachePath(downloadUrl: string, fileName: string): string;
|
||||
getPathForFileInCache(url: string, fileName: string): string | null;
|
||||
private hashFile;
|
||||
putFileInCache(url: string, currentPath: string, fileName: string): Promise<string>;
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
import debug from 'debug';
|
||||
import envPaths from 'env-paths';
|
||||
import fs from 'graceful-fs';
|
||||
import crypto from 'node:crypto';
|
||||
import path from 'node:path';
|
||||
import { pipeline } from 'node:stream/promises';
|
||||
const d = debug('@electron/get:cache');
|
||||
const defaultCacheRoot = envPaths('electron', {
|
||||
suffix: '',
|
||||
}).cache;
|
||||
export class Cache {
|
||||
cacheRoot;
|
||||
constructor(cacheRoot = defaultCacheRoot) {
|
||||
this.cacheRoot = cacheRoot;
|
||||
}
|
||||
static getCacheDirectory(downloadUrl) {
|
||||
const parsed = new URL(downloadUrl);
|
||||
parsed.hash = '';
|
||||
parsed.search = '';
|
||||
parsed.pathname = path.posix.dirname(parsed.pathname);
|
||||
return crypto.createHash('sha256').update(parsed.toString()).digest('hex');
|
||||
}
|
||||
getCachePath(downloadUrl, fileName) {
|
||||
return path.resolve(this.cacheRoot, Cache.getCacheDirectory(downloadUrl), fileName);
|
||||
}
|
||||
getPathForFileInCache(url, fileName) {
|
||||
const cachePath = this.getCachePath(url, fileName);
|
||||
if (fs.existsSync(cachePath)) {
|
||||
return cachePath;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
async hashFile(path) {
|
||||
const hasher = crypto.createHash('sha256');
|
||||
await pipeline(fs.createReadStream(path), hasher);
|
||||
return hasher.digest('hex');
|
||||
}
|
||||
async putFileInCache(url, currentPath, fileName) {
|
||||
const attempt = async (attemptsLeft = 3) => {
|
||||
try {
|
||||
const cachePath = this.getCachePath(url, fileName);
|
||||
d(`Moving ${currentPath} to ${cachePath}`);
|
||||
if (!fs.existsSync(path.dirname(cachePath))) {
|
||||
await fs.promises.mkdir(path.dirname(cachePath), { recursive: true });
|
||||
}
|
||||
if (fs.existsSync(cachePath)) {
|
||||
const [existingHash, currentHash] = await Promise.all([
|
||||
this.hashFile(cachePath),
|
||||
this.hashFile(currentPath),
|
||||
]);
|
||||
if (existingHash !== currentHash) {
|
||||
d('* Replacing existing file as it does not match our inbound file');
|
||||
await fs.promises.rm(cachePath, { recursive: true, force: true });
|
||||
}
|
||||
else {
|
||||
d('* Using existing file as the hash matches our inbound file, no need to replace');
|
||||
return cachePath;
|
||||
}
|
||||
}
|
||||
try {
|
||||
await fs.promises.rename(currentPath, cachePath);
|
||||
}
|
||||
catch (err) {
|
||||
if (err.code === 'EXDEV') {
|
||||
// Cross-device link, fallback to copy and delete
|
||||
await fs.promises.cp(currentPath, cachePath, {
|
||||
force: true,
|
||||
recursive: true,
|
||||
verbatimSymlinks: true,
|
||||
});
|
||||
await fs.promises.rm(currentPath, { force: true, recursive: true });
|
||||
}
|
||||
else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
return cachePath;
|
||||
}
|
||||
catch (err) {
|
||||
if (process.platform === 'win32' && err.code === 'EPERM') {
|
||||
// On windows this normally means we're fighting another instance of @electron/get
|
||||
// also trying to write this file to the cache
|
||||
d('Experienced error putting thing in cache', err);
|
||||
if (attemptsLeft > 0) {
|
||||
d('Trying again in a few seconds');
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 2000);
|
||||
});
|
||||
return await attempt(attemptsLeft - 1);
|
||||
}
|
||||
d('We have already tried too many times, giving up...');
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
return await attempt();
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=Cache.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"Cache.js","sourceRoot":"","sources":["../src/Cache.ts"],"names":[],"mappings":"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"}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Generic interface for the artifact downloader library.
|
||||
* The default implementation is {@link FetchDownloader},
|
||||
* but any custom downloader can be passed to `@electron/get` via
|
||||
* the {@link ElectronDownloadRequestOptions.downloader} option.
|
||||
*
|
||||
* @typeParam T - Options to pass to the downloader
|
||||
* @category Downloader
|
||||
*/
|
||||
export interface Downloader<T> {
|
||||
/**
|
||||
* Download an artifact from an arbitrary URL to a file path on system
|
||||
* @param url URL of the file to download
|
||||
* @param targetFilePath Filesystem path to download the artifact to (including the file name)
|
||||
* @param options Options to pass to the downloader
|
||||
*/
|
||||
download(url: string, targetFilePath: string, options: T): Promise<void>;
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=Downloader.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"Downloader.js","sourceRoot":"","sources":["../src/Downloader.ts"],"names":[],"mappings":""}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import { Downloader } from './Downloader.js';
|
||||
/**
|
||||
* @category Downloader
|
||||
*/
|
||||
export interface Progress {
|
||||
/** Bytes downloaded so far. */
|
||||
transferred: number;
|
||||
/** Total bytes to download, or `null` if the response had no `Content-Length` header. */
|
||||
total: number | null;
|
||||
/**
|
||||
* Ratio of `transferred` to `total` between 0 and 1.
|
||||
* If `total` is unknown, this is 0 until the download completes, then 1.
|
||||
*/
|
||||
percent: number;
|
||||
}
|
||||
/**
|
||||
* @category Downloader
|
||||
*/
|
||||
export declare class HTTPError extends Error {
|
||||
readonly response: Response;
|
||||
constructor(response: Response);
|
||||
}
|
||||
/**
|
||||
* @category Downloader
|
||||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/RequestInit | `RequestInit`} for possible keys/values.
|
||||
*/
|
||||
export type FetchDownloaderOptions = RequestInit & {
|
||||
/** Called on each chunk with the current download {@link Progress}. */
|
||||
getProgressCallback?: (progress: Progress) => Promise<void>;
|
||||
/**
|
||||
* Disables the console progress bar. Setting the `ELECTRON_GET_NO_PROGRESS`
|
||||
* environment variable to a non-empty value also does this.
|
||||
*/
|
||||
quiet?: boolean;
|
||||
};
|
||||
/**
|
||||
* Default {@link Downloader} implemented with the built-in
|
||||
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API | Fetch API}.
|
||||
* @category Downloader
|
||||
*/
|
||||
export declare class FetchDownloader implements Downloader<FetchDownloaderOptions> {
|
||||
download(url: string, targetFilePath: string, options?: FetchDownloaderOptions): Promise<void>;
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
import fs from 'graceful-fs';
|
||||
import path from 'node:path';
|
||||
import ProgressBar from 'progress';
|
||||
import { pipeline } from 'node:stream/promises';
|
||||
import { Readable } from 'node:stream';
|
||||
const PROGRESS_BAR_DELAY_IN_SECONDS = 30;
|
||||
/**
|
||||
* @category Downloader
|
||||
*/
|
||||
export class HTTPError extends Error {
|
||||
response;
|
||||
constructor(response) {
|
||||
super(`Response code ${response.status} (${response.statusText}) for ${response.url}`);
|
||||
this.response = response;
|
||||
this.name = 'HTTPError';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Default {@link Downloader} implemented with the built-in
|
||||
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API | Fetch API}.
|
||||
* @category Downloader
|
||||
*/
|
||||
export class FetchDownloader {
|
||||
async download(url, targetFilePath, options = {}) {
|
||||
const { quiet, getProgressCallback, ...fetchOptions } = options;
|
||||
let downloadCompleted = false;
|
||||
let bar;
|
||||
let progressPercent;
|
||||
let timeout = undefined;
|
||||
await fs.promises.mkdir(path.dirname(targetFilePath), { recursive: true });
|
||||
if (!quiet && !process.env.ELECTRON_GET_NO_PROGRESS) {
|
||||
const start = new Date();
|
||||
timeout = setTimeout(() => {
|
||||
if (!downloadCompleted) {
|
||||
bar = new ProgressBar(`Downloading ${path.basename(url)}: [:bar] :percent ETA: :eta seconds `, {
|
||||
curr: progressPercent,
|
||||
total: 100,
|
||||
});
|
||||
// https://github.com/visionmedia/node-progress/issues/159
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
bar.start = start;
|
||||
}
|
||||
}, PROGRESS_BAR_DELAY_IN_SECONDS * 1000);
|
||||
}
|
||||
try {
|
||||
const response = await fetch(url, fetchOptions);
|
||||
if (!response.ok) {
|
||||
throw new HTTPError(response);
|
||||
}
|
||||
if (!response.body) {
|
||||
throw new Error('Response body is empty');
|
||||
}
|
||||
const contentLength = response.headers.get('content-length');
|
||||
const total = contentLength ? parseInt(contentLength, 10) : null;
|
||||
let transferred = 0;
|
||||
const onProgress = (percent) => {
|
||||
progressPercent = percent;
|
||||
if (bar) {
|
||||
bar.update(percent);
|
||||
}
|
||||
if (getProgressCallback) {
|
||||
void getProgressCallback({ transferred, total, percent });
|
||||
}
|
||||
};
|
||||
await pipeline(Readable.fromWeb(response.body), async function* (source) {
|
||||
for await (const chunk of source) {
|
||||
transferred += chunk.length;
|
||||
onProgress(total ? transferred / total : 0);
|
||||
yield chunk;
|
||||
}
|
||||
}, fs.createWriteStream(targetFilePath));
|
||||
onProgress(1);
|
||||
}
|
||||
finally {
|
||||
downloadCompleted = true;
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=FetchDownloader.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"FetchDownloader.js","sourceRoot":"","sources":["../src/FetchDownloader.ts"],"names":[],"mappings":"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"}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import { ElectronArtifactDetails } from './types.js';
|
||||
export declare function getArtifactFileName(details: ElectronArtifactDetails): string;
|
||||
export declare function getArtifactRemoteURL(details: ElectronArtifactDetails): Promise<string>;
|
||||
export declare function getArtifactVersion(details: ElectronArtifactDetails): string;
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import { ensureIsTruthyString, normalizeVersion } from './utils.js';
|
||||
const BASE_URL = 'https://github.com/electron/electron/releases/download/';
|
||||
const NIGHTLY_BASE_URL = 'https://github.com/electron/nightlies/releases/download/';
|
||||
export function getArtifactFileName(details) {
|
||||
ensureIsTruthyString(details, 'artifactName');
|
||||
if (details.isGeneric) {
|
||||
return details.artifactName;
|
||||
}
|
||||
ensureIsTruthyString(details, 'arch');
|
||||
ensureIsTruthyString(details, 'platform');
|
||||
ensureIsTruthyString(details, 'version');
|
||||
return `${[
|
||||
details.artifactName,
|
||||
details.version,
|
||||
details.platform,
|
||||
details.arch,
|
||||
...(details.artifactSuffix ? [details.artifactSuffix] : []),
|
||||
].join('-')}.zip`;
|
||||
}
|
||||
function mirrorVar(name, options, defaultValue) {
|
||||
// Convert camelCase to camel_case for env var reading
|
||||
const snakeName = name.replace(/([a-z])([A-Z])/g, (_, a, b) => `${a}_${b}`).toLowerCase();
|
||||
return (
|
||||
// .npmrc
|
||||
process.env[`npm_config_electron_${name.toLowerCase()}`] ||
|
||||
process.env[`NPM_CONFIG_ELECTRON_${snakeName.toUpperCase()}`] ||
|
||||
process.env[`npm_config_electron_${snakeName}`] ||
|
||||
// package.json
|
||||
process.env[`npm_package_config_electron_${name}`] ||
|
||||
process.env[`npm_package_config_electron_${snakeName.toLowerCase()}`] ||
|
||||
// env
|
||||
process.env[`ELECTRON_${snakeName.toUpperCase()}`] ||
|
||||
options[name] ||
|
||||
defaultValue);
|
||||
}
|
||||
export async function getArtifactRemoteURL(details) {
|
||||
const opts = details.mirrorOptions || {};
|
||||
let base = mirrorVar('mirror', opts, BASE_URL);
|
||||
if (details.version.includes('nightly')) {
|
||||
base = mirrorVar('nightlyMirror', opts, NIGHTLY_BASE_URL);
|
||||
}
|
||||
const path = mirrorVar('customDir', opts, details.version).replace('{{ version }}', details.version.replace(/^v/, ''));
|
||||
const file = mirrorVar('customFilename', opts, getArtifactFileName(details));
|
||||
// Allow customized download URL resolution.
|
||||
if (opts.resolveAssetURL) {
|
||||
const url = await opts.resolveAssetURL(details);
|
||||
return url;
|
||||
}
|
||||
return `${base}${path}/${file}`;
|
||||
}
|
||||
export function getArtifactVersion(details) {
|
||||
return normalizeVersion(mirrorVar('customVersion', details.mirrorOptions || {}, details.version));
|
||||
}
|
||||
//# sourceMappingURL=artifact-utils.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"artifact-utils.js","sourceRoot":"","sources":["../src/artifact-utils.ts"],"names":[],"mappings":"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"}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import { DownloadOptions } from './types.js';
|
||||
import { Downloader } from './Downloader.js';
|
||||
export declare function getDownloaderForSystem(): Promise<Downloader<DownloadOptions>>;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { FetchDownloader } from './FetchDownloader.js';
|
||||
export async function getDownloaderForSystem() {
|
||||
return new FetchDownloader();
|
||||
}
|
||||
//# sourceMappingURL=downloader-resolver.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"downloader-resolver.js","sourceRoot":"","sources":["../src/downloader-resolver.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,MAAM,CAAC,KAAK,UAAU,sBAAsB;IAC1C,OAAO,IAAI,eAAe,EAAE,CAAC;AAC/B,CAAC"}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import { ElectronDownloadRequestOptions, ElectronGenericArtifactDetails, ElectronPlatformArtifactDetailsWithDefaults } from './types.js';
|
||||
export { getHostArch } from './utils.js';
|
||||
export { initializeProxy } from './proxy.js';
|
||||
export * from './types.js';
|
||||
/**
|
||||
* Downloads an artifact from an Electron release and returns an absolute path
|
||||
* to the downloaded file.
|
||||
*
|
||||
* Each release of Electron comes with artifacts, many of which are
|
||||
* platform/arch-specific (e.g. `ffmpeg-v31.0.0-darwin-arm64.zip`) and others that
|
||||
* are generic (e.g. `SHASUMS256.txt`).
|
||||
*
|
||||
*
|
||||
* @param artifactDetails - The information required to download the artifact
|
||||
* @category Download Artifact
|
||||
*/
|
||||
export declare function downloadArtifact(artifactDetails: ElectronPlatformArtifactDetailsWithDefaults | ElectronGenericArtifactDetails): Promise<string>;
|
||||
/**
|
||||
* Downloads the Electron binary for a specific version and returns an absolute path to a
|
||||
* ZIP file.
|
||||
*
|
||||
* @param version - The version of Electron you want to download (e.g. `31.0.0`)
|
||||
* @param options - Options to customize the download behavior
|
||||
* @returns An absolute path to the downloaded ZIP file
|
||||
* @category Download Electron
|
||||
*/
|
||||
export declare function download(version: string, options?: ElectronDownloadRequestOptions): Promise<string>;
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
import debug from 'debug';
|
||||
import fs from 'graceful-fs';
|
||||
import path from 'node:path';
|
||||
import util from 'node:util';
|
||||
import semver from 'semver';
|
||||
import sumchecker from 'sumchecker';
|
||||
import { getArtifactFileName, getArtifactRemoteURL, getArtifactVersion } from './artifact-utils.js';
|
||||
import { ElectronDownloadCacheMode, } from './types.js';
|
||||
import { Cache } from './Cache.js';
|
||||
import { getDownloaderForSystem } from './downloader-resolver.js';
|
||||
import { initializeProxy } from './proxy.js';
|
||||
import { withTempDirectoryIn, getHostArch, getNodeArch, ensureIsTruthyString, isOfficialDropped32BitDownload, isOfficialLinuxIA32Download, mkdtemp, doesCallerOwnTemporaryOutput, effectiveCacheMode, shouldTryReadCache, TempDirCleanUpMode, } from './utils.js';
|
||||
export { getHostArch } from './utils.js';
|
||||
export { initializeProxy } from './proxy.js';
|
||||
export * from './types.js';
|
||||
const d = debug('@electron/get:index');
|
||||
if (process.env.ELECTRON_GET_USE_PROXY) {
|
||||
initializeProxy();
|
||||
}
|
||||
async function validateArtifact(artifactDetails, downloadedAssetPath, _downloadArtifact) {
|
||||
return await withTempDirectoryIn(artifactDetails.tempDirectory, async (tempFolder) => {
|
||||
// Don't try to verify the hash of the hash file itself
|
||||
// and for older versions that don't have a SHASUMS256.txt
|
||||
if (!artifactDetails.artifactName.startsWith('SHASUMS256') &&
|
||||
!artifactDetails.unsafelyDisableChecksums &&
|
||||
semver.gte(artifactDetails.version, '1.3.2')) {
|
||||
let shasumPath;
|
||||
const checksums = artifactDetails.checksums;
|
||||
if (checksums) {
|
||||
shasumPath = path.resolve(tempFolder, 'SHASUMS256.txt');
|
||||
const fileNames = Object.keys(checksums);
|
||||
if (fileNames.length === 0) {
|
||||
throw new Error('Provided "checksums" object is empty, cannot generate a valid SHASUMS256.txt');
|
||||
}
|
||||
const generatedChecksums = fileNames
|
||||
.map((fileName) => `${checksums[fileName]} *${fileName}`)
|
||||
.join('\n');
|
||||
await util.promisify(fs.writeFile)(shasumPath, generatedChecksums);
|
||||
}
|
||||
else {
|
||||
shasumPath = await _downloadArtifact({
|
||||
isGeneric: true,
|
||||
version: artifactDetails.version,
|
||||
artifactName: 'SHASUMS256.txt',
|
||||
downloadOptions: artifactDetails.downloadOptions,
|
||||
cacheRoot: artifactDetails.cacheRoot,
|
||||
downloader: artifactDetails.downloader,
|
||||
mirrorOptions: artifactDetails.mirrorOptions,
|
||||
tempDirectory: artifactDetails.tempDirectory,
|
||||
// Never use the cache for loading checksums, load
|
||||
// them fresh every time
|
||||
cacheMode: ElectronDownloadCacheMode.Bypass,
|
||||
});
|
||||
}
|
||||
try {
|
||||
// For versions 1.3.2 - 1.3.4, need to overwrite the `defaultTextEncoding` option:
|
||||
// https://github.com/electron/electron/pull/6676#discussion_r75332120
|
||||
if (semver.satisfies(artifactDetails.version, '1.3.2 - 1.3.4')) {
|
||||
const validatorOptions = {};
|
||||
validatorOptions.defaultTextEncoding = 'binary';
|
||||
const checker = new sumchecker.ChecksumValidator('sha256', shasumPath, validatorOptions);
|
||||
await checker.validate(path.dirname(downloadedAssetPath), path.basename(downloadedAssetPath));
|
||||
}
|
||||
else {
|
||||
await sumchecker('sha256', shasumPath, path.dirname(downloadedAssetPath), [
|
||||
path.basename(downloadedAssetPath),
|
||||
]);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
// Once we're done make sure we clean up the shasum temp dir
|
||||
await fs.promises.rm(path.dirname(shasumPath), { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
}, TempDirCleanUpMode.CLEAN);
|
||||
}
|
||||
/**
|
||||
* Downloads an artifact from an Electron release and returns an absolute path
|
||||
* to the downloaded file.
|
||||
*
|
||||
* Each release of Electron comes with artifacts, many of which are
|
||||
* platform/arch-specific (e.g. `ffmpeg-v31.0.0-darwin-arm64.zip`) and others that
|
||||
* are generic (e.g. `SHASUMS256.txt`).
|
||||
*
|
||||
*
|
||||
* @param artifactDetails - The information required to download the artifact
|
||||
* @category Download Artifact
|
||||
*/
|
||||
export async function downloadArtifact(artifactDetails) {
|
||||
const details = {
|
||||
...artifactDetails,
|
||||
};
|
||||
if (!artifactDetails.isGeneric) {
|
||||
const platformArtifactDetails = details;
|
||||
if (!platformArtifactDetails.platform) {
|
||||
d('No platform found, defaulting to the host platform');
|
||||
platformArtifactDetails.platform = process.platform;
|
||||
}
|
||||
if (platformArtifactDetails.arch) {
|
||||
platformArtifactDetails.arch = getNodeArch(platformArtifactDetails.arch);
|
||||
}
|
||||
else {
|
||||
d('No arch found, defaulting to the host arch');
|
||||
platformArtifactDetails.arch = getHostArch();
|
||||
}
|
||||
}
|
||||
ensureIsTruthyString(details, 'version');
|
||||
details.version = getArtifactVersion(details);
|
||||
const fileName = getArtifactFileName(details);
|
||||
const url = await getArtifactRemoteURL(details);
|
||||
const cache = new Cache(details.cacheRoot);
|
||||
const cacheMode = effectiveCacheMode(details);
|
||||
// Do not check if the file exists in the cache when force === true
|
||||
if (shouldTryReadCache(cacheMode)) {
|
||||
d(`Checking the cache (${details.cacheRoot}) for ${fileName} (${url})`);
|
||||
const cachedPath = cache.getPathForFileInCache(url, fileName);
|
||||
if (cachedPath === null) {
|
||||
d('Cache miss');
|
||||
}
|
||||
else {
|
||||
d('Cache hit');
|
||||
let artifactPath = cachedPath;
|
||||
if (doesCallerOwnTemporaryOutput(cacheMode)) {
|
||||
// Copy out of cache into temporary directory if readOnly cache so
|
||||
// that the caller can take ownership of the returned file
|
||||
const tempDir = await mkdtemp(artifactDetails.tempDirectory);
|
||||
artifactPath = path.resolve(tempDir, fileName);
|
||||
await util.promisify(fs.copyFile)(cachedPath, artifactPath);
|
||||
}
|
||||
try {
|
||||
await validateArtifact(details, artifactPath, downloadArtifact);
|
||||
return artifactPath;
|
||||
}
|
||||
catch (err) {
|
||||
if (doesCallerOwnTemporaryOutput(cacheMode)) {
|
||||
await fs.promises.rm(path.dirname(artifactPath), { recursive: true, force: true });
|
||||
}
|
||||
d("Artifact in cache didn't match checksums", err);
|
||||
d('falling back to re-download');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!details.isGeneric &&
|
||||
isOfficialLinuxIA32Download(details.platform, details.arch, details.version, details.mirrorOptions)) {
|
||||
console.warn('Official Linux/ia32 support is deprecated.');
|
||||
console.warn('For more info: https://electronjs.org/blog/linux-32bit-support');
|
||||
}
|
||||
if (!details.isGeneric &&
|
||||
isOfficialDropped32BitDownload(details.platform, details.arch, details.version, details.mirrorOptions)) {
|
||||
throw new Error(`Electron ${details.version} does not have a published ${details.platform}/${details.arch} artifact. ` +
|
||||
'Electron 44 and newer no longer publish win32/ia32 or linux/armv7l builds; ' +
|
||||
'the last release line with 32-bit support is Electron 43, which is supported until January 2027.');
|
||||
}
|
||||
return await withTempDirectoryIn(details.tempDirectory, async (tempFolder) => {
|
||||
const tempDownloadPath = path.resolve(tempFolder, getArtifactFileName(details));
|
||||
const downloader = details.downloader || (await getDownloaderForSystem());
|
||||
d(`Downloading ${url} to ${tempDownloadPath} with options: ${JSON.stringify(details.downloadOptions)}`);
|
||||
await downloader.download(url, tempDownloadPath, details.downloadOptions);
|
||||
d('attempting to validate artifact...', { details });
|
||||
await validateArtifact(details, tempDownloadPath, downloadArtifact);
|
||||
d('artifact validated');
|
||||
if (doesCallerOwnTemporaryOutput(cacheMode)) {
|
||||
return tempDownloadPath;
|
||||
}
|
||||
else {
|
||||
return await cache.putFileInCache(url, tempDownloadPath, fileName);
|
||||
}
|
||||
}, doesCallerOwnTemporaryOutput(cacheMode) ? TempDirCleanUpMode.ORPHAN : TempDirCleanUpMode.CLEAN);
|
||||
}
|
||||
/**
|
||||
* Downloads the Electron binary for a specific version and returns an absolute path to a
|
||||
* ZIP file.
|
||||
*
|
||||
* @param version - The version of Electron you want to download (e.g. `31.0.0`)
|
||||
* @param options - Options to customize the download behavior
|
||||
* @returns An absolute path to the downloaded ZIP file
|
||||
* @category Download Electron
|
||||
*/
|
||||
export function download(version, options) {
|
||||
return downloadArtifact({
|
||||
...options,
|
||||
version,
|
||||
platform: process.platform,
|
||||
arch: process.arch,
|
||||
artifactName: 'electron',
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=index.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+14
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Initializes a third-party proxy module for HTTP(S) requests. Call this function before
|
||||
* using the {@link download} and {@link downloadArtifact} APIs if you need proxy support.
|
||||
*
|
||||
* If the `ELECTRON_GET_USE_PROXY` environment variable is set to `true`, this function will be
|
||||
* called automatically for `@electron/get` requests.
|
||||
*
|
||||
* Supported environment variables are `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY`.
|
||||
*
|
||||
* @category Utility
|
||||
* @see {@link https://undici.nodejs.org/#/docs/api/EnvHttpProxyAgent | `EnvHttpProxyAgent`}
|
||||
* documentation for available environment variables.
|
||||
*/
|
||||
export declare function initializeProxy(): void;
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import { createRequire } from 'node:module';
|
||||
import debug from 'debug';
|
||||
const d = debug('@electron/get:proxy');
|
||||
const require = createRequire(import.meta.url);
|
||||
/**
|
||||
* Initializes a third-party proxy module for HTTP(S) requests. Call this function before
|
||||
* using the {@link download} and {@link downloadArtifact} APIs if you need proxy support.
|
||||
*
|
||||
* If the `ELECTRON_GET_USE_PROXY` environment variable is set to `true`, this function will be
|
||||
* called automatically for `@electron/get` requests.
|
||||
*
|
||||
* Supported environment variables are `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY`.
|
||||
*
|
||||
* @category Utility
|
||||
* @see {@link https://undici.nodejs.org/#/docs/api/EnvHttpProxyAgent | `EnvHttpProxyAgent`}
|
||||
* documentation for available environment variables.
|
||||
*/
|
||||
export function initializeProxy() {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const { EnvHttpProxyAgent, setGlobalDispatcher } = require('undici');
|
||||
setGlobalDispatcher(new EnvHttpProxyAgent());
|
||||
}
|
||||
catch (e) {
|
||||
d('Could not load undici, built-in proxy support not available:', e);
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=proxy.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,CAAC,GAAG,KAAK,CAAC,qBAAqB,CAAC,CAAC;AACvC,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,eAAe;IAC7B,IAAI,CAAC;QACH,iEAAiE;QACjE,MAAM,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;QACrE,mBAAmB,CAAC,IAAI,iBAAiB,EAAE,CAAC,CAAC;IAC/C,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,CAAC,CAAC,8DAA8D,EAAE,CAAC,CAAC,CAAC;IACvE,CAAC;AACH,CAAC"}
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
import { Downloader } from './Downloader.js';
|
||||
import { FetchDownloader, FetchDownloaderOptions, HTTPError, Progress } from './FetchDownloader.js';
|
||||
export { Downloader, FetchDownloader, FetchDownloaderOptions, HTTPError, Progress };
|
||||
/**
|
||||
* Custom downloaders can implement any set of options.
|
||||
* @category Downloader
|
||||
*/
|
||||
export type DownloadOptions = any;
|
||||
/**
|
||||
* Options for specifying an alternative download mirror for Electron.
|
||||
*
|
||||
* @category Utility
|
||||
* @example
|
||||
*
|
||||
* To download the Electron v4.0.4 release for x64 Linux from
|
||||
* https://github.com/electron/electron/releases/download/v4.0.4/electron-v4.0.4-linux-x64.zip
|
||||
*
|
||||
* ```js
|
||||
* const opts = {
|
||||
* mirror: 'https://github.com/electron/electron/releases/download',
|
||||
* customDir: 'v4.0.4',
|
||||
* customFilename: 'electron-v4.0.4-linux-x64.zip',
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export interface MirrorOptions {
|
||||
/**
|
||||
* The mirror URL for [`electron-nightly`](https://npmjs.com/package/electron-nightly),
|
||||
* which lives in a separate npm package.
|
||||
*/
|
||||
nightlyMirror?: string;
|
||||
/**
|
||||
* The base URL of the mirror to download from.
|
||||
* e.g https://github.com/electron/electron/releases/download
|
||||
*/
|
||||
mirror?: string;
|
||||
/**
|
||||
* The name of the directory to download from,
|
||||
* often scoped by version number e.g 'v4.0.4'
|
||||
*/
|
||||
customDir?: string;
|
||||
/**
|
||||
* The name of the asset to download,
|
||||
* e.g 'electron-v4.0.4-linux-x64.zip'
|
||||
*/
|
||||
customFilename?: string;
|
||||
/**
|
||||
* The version of the asset to download,
|
||||
* e.g '4.0.4'
|
||||
*/
|
||||
customVersion?: string;
|
||||
/**
|
||||
* A function allowing customization of the url returned
|
||||
* from getArtifactRemoteURL().
|
||||
*/
|
||||
resolveAssetURL?: (opts: DownloadOptions) => Promise<string>;
|
||||
}
|
||||
/**
|
||||
* @category Download Artifact
|
||||
* @internal
|
||||
*/
|
||||
export interface ElectronDownloadRequest {
|
||||
/**
|
||||
* The version of Electron associated with the artifact.
|
||||
*/
|
||||
version: string;
|
||||
/**
|
||||
* The type of artifact. For example:
|
||||
* * `electron`
|
||||
* * `ffmpeg`
|
||||
*/
|
||||
artifactName: string;
|
||||
}
|
||||
export declare enum ElectronDownloadCacheMode {
|
||||
/**
|
||||
* Reads from the cache if present
|
||||
* Writes to the cache after fetch if not present
|
||||
*/
|
||||
ReadWrite = 0,
|
||||
/**
|
||||
* Reads from the cache if present
|
||||
* Will **not** write back to the cache after fetching missing artifact
|
||||
*/
|
||||
ReadOnly = 1,
|
||||
/**
|
||||
* Skips reading from the cache
|
||||
* Will write back into the cache, overwriting anything currently in the cache after fetch
|
||||
*/
|
||||
WriteOnly = 2,
|
||||
/**
|
||||
* Bypasses the cache completely, neither reads from nor writes to the cache
|
||||
*/
|
||||
Bypass = 3
|
||||
}
|
||||
/**
|
||||
* @category Download Electron
|
||||
*/
|
||||
export interface ElectronDownloadRequestOptions {
|
||||
/**
|
||||
* When set to `true`, disables checking that the artifact download completed successfully
|
||||
* with the correct payload.
|
||||
*
|
||||
* @defaultValue `false`
|
||||
*/
|
||||
unsafelyDisableChecksums?: boolean;
|
||||
/**
|
||||
* Provides checksums for the artifact as strings.
|
||||
* Can be used if you already know the checksums of the Electron artifact
|
||||
* you are downloading and want to skip the checksum file download
|
||||
* without skipping the checksum validation.
|
||||
*
|
||||
* This should be an object whose keys are the file names of the artifacts and
|
||||
* the values are their respective SHA256 checksums.
|
||||
*
|
||||
* @example
|
||||
* ```json
|
||||
* {
|
||||
* "electron-v4.0.4-linux-x64.zip": "877617029f4c0f2b24f3805a1c3554ba166fda65c4e88df9480ae7b6ffa26a22"
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
checksums?: Record<string, string>;
|
||||
/**
|
||||
* The directory that caches Electron artifact downloads.
|
||||
*
|
||||
* @defaultValue The default value is dependent upon the host platform:
|
||||
*
|
||||
* * Linux: `$XDG_CACHE_HOME` or `~/.cache/electron/`
|
||||
* * MacOS: `~/Library/Caches/electron/`
|
||||
* * Windows: `%LOCALAPPDATA%/electron/Cache` or `~/AppData/Local/electron/Cache/`
|
||||
*/
|
||||
cacheRoot?: string;
|
||||
/**
|
||||
* Options passed to the downloader module.
|
||||
*
|
||||
* @see {@link FetchDownloaderOptions} for options for the default {@link FetchDownloader}.
|
||||
*/
|
||||
downloadOptions?: DownloadOptions;
|
||||
/**
|
||||
* Options related to specifying an artifact mirror.
|
||||
*/
|
||||
mirrorOptions?: MirrorOptions;
|
||||
/**
|
||||
* A custom {@link Downloader} class used to download artifacts. Defaults to the
|
||||
* built-in {@link FetchDownloader}.
|
||||
*/
|
||||
downloader?: Downloader<DownloadOptions>;
|
||||
/**
|
||||
* A temporary directory for downloads.
|
||||
* It is used before artifacts are put into cache.
|
||||
*
|
||||
* @defaultValue the OS default temporary directory via [`os.tmpdir()`](https://nodejs.org/api/os.html#ostmpdir)
|
||||
*/
|
||||
tempDirectory?: string;
|
||||
/**
|
||||
* Controls the cache read and write behavior.
|
||||
*
|
||||
* When set to either {@link ElectronDownloadCacheMode.ReadOnly | ReadOnly} or
|
||||
* {@link ElectronDownloadCacheMode.Bypass | Bypass}, the caller is responsible
|
||||
* for cleaning up the returned file path once they are done using it
|
||||
* (e.g. via `fs.remove(path.dirname(pathFromElectronGet))`).
|
||||
*
|
||||
* When set to either {@link ElectronDownloadCacheMode.WriteOnly | WriteOnly} or
|
||||
* {@link ElectronDownloadCacheMode.ReadWrite | ReadWrite} (the default), the caller
|
||||
* should not move or delete the file path that is returned as the path
|
||||
* points directly to the disk cache.
|
||||
*
|
||||
* @defaultValue {@link ElectronDownloadCacheMode.ReadWrite}
|
||||
*/
|
||||
cacheMode?: ElectronDownloadCacheMode;
|
||||
}
|
||||
/**
|
||||
* @category Download Artifact
|
||||
* @internal
|
||||
*/
|
||||
export type ElectronPlatformArtifactDetails = {
|
||||
/**
|
||||
* The target artifact platform. These are Node-style platform names, for example:
|
||||
* * `win32`
|
||||
* * `darwin`
|
||||
* * `linux`
|
||||
*
|
||||
* @see Node.js {@link https://nodejs.org/api/process.html#processplatform | process.platform} docs
|
||||
*/
|
||||
platform: string;
|
||||
/**
|
||||
* The target artifact architecture. These are Node-style architecture names, for example:
|
||||
* * `ia32` (Electron 43 and earlier)
|
||||
* * `x64`
|
||||
* * `armv7l` (Electron 43 and earlier)
|
||||
*
|
||||
* @see Node.js {@link https://nodejs.org/api/process.html#processarch | process.arch} docs
|
||||
*/
|
||||
arch: string;
|
||||
artifactSuffix?: string;
|
||||
isGeneric?: false;
|
||||
} & ElectronDownloadRequest & ElectronDownloadRequestOptions;
|
||||
/**
|
||||
* Options to download a generic (i.e. platform and architecture-agnostic)
|
||||
* Electron artifact. Contains all options from {@link ElectronDownloadRequestOptions},
|
||||
* but specifies a `version` and `artifactName` for the artifact to download.
|
||||
*
|
||||
* @category Download Artifact
|
||||
* @interface
|
||||
*/
|
||||
export type ElectronGenericArtifactDetails = {
|
||||
isGeneric: true;
|
||||
} & ElectronDownloadRequest & ElectronDownloadRequestOptions;
|
||||
/**
|
||||
* @category Download Artifact
|
||||
* @internal
|
||||
*/
|
||||
export type ElectronArtifactDetails = ElectronPlatformArtifactDetails | ElectronGenericArtifactDetails;
|
||||
/**
|
||||
* Options to download a platform and architecture-specific Electron artifact.
|
||||
* Contains all options from {@link ElectronDownloadRequestOptions}, but
|
||||
* specifies a `version` and `artifactName` for the artifact to download.
|
||||
*
|
||||
* If `platform` and `arch` are omitted, they will be inferred using the host
|
||||
* system platform and architecture.
|
||||
*
|
||||
* @category Download Artifact
|
||||
* @interface
|
||||
*/
|
||||
export type ElectronPlatformArtifactDetailsWithDefaults = Omit<ElectronPlatformArtifactDetails, 'platform' | 'arch'> & {
|
||||
/**
|
||||
* The target artifact platform. These are Node-style platform names, for example:
|
||||
* * `win32`
|
||||
* * `darwin`
|
||||
* * `linux`
|
||||
*
|
||||
* @see Node.js {@link https://nodejs.org/api/process.html#processplatform | process.platform} docs
|
||||
*/
|
||||
platform?: string;
|
||||
/**
|
||||
* The target artifact architecture. These are Node-style architecture names, for example:
|
||||
* * `ia32` (Electron 43 and earlier)
|
||||
* * `x64`
|
||||
* * `armv7l` (Electron 43 and earlier)
|
||||
*
|
||||
* @see Node.js {@link https://nodejs.org/api/process.html#processarch | process.arch} docs
|
||||
*/
|
||||
arch?: string;
|
||||
};
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { FetchDownloader, HTTPError } from './FetchDownloader.js';
|
||||
export { FetchDownloader, HTTPError };
|
||||
export var ElectronDownloadCacheMode;
|
||||
(function (ElectronDownloadCacheMode) {
|
||||
/**
|
||||
* Reads from the cache if present
|
||||
* Writes to the cache after fetch if not present
|
||||
*/
|
||||
ElectronDownloadCacheMode[ElectronDownloadCacheMode["ReadWrite"] = 0] = "ReadWrite";
|
||||
/**
|
||||
* Reads from the cache if present
|
||||
* Will **not** write back to the cache after fetching missing artifact
|
||||
*/
|
||||
ElectronDownloadCacheMode[ElectronDownloadCacheMode["ReadOnly"] = 1] = "ReadOnly";
|
||||
/**
|
||||
* Skips reading from the cache
|
||||
* Will write back into the cache, overwriting anything currently in the cache after fetch
|
||||
*/
|
||||
ElectronDownloadCacheMode[ElectronDownloadCacheMode["WriteOnly"] = 2] = "WriteOnly";
|
||||
/**
|
||||
* Bypasses the cache completely, neither reads from nor writes to the cache
|
||||
*/
|
||||
ElectronDownloadCacheMode[ElectronDownloadCacheMode["Bypass"] = 3] = "Bypass";
|
||||
})(ElectronDownloadCacheMode || (ElectronDownloadCacheMode = {}));
|
||||
//# sourceMappingURL=types.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAA0B,SAAS,EAAY,MAAM,sBAAsB,CAAC;AAEpG,OAAO,EAAc,eAAe,EAA0B,SAAS,EAAY,CAAC;AA4EpF,MAAM,CAAN,IAAY,yBAoBX;AApBD,WAAY,yBAAyB;IACnC;;;OAGG;IACH,mFAAS,CAAA;IACT;;;OAGG;IACH,iFAAQ,CAAA;IACR;;;OAGG;IACH,mFAAS,CAAA;IACT;;OAEG;IACH,6EAAM,CAAA;AACR,CAAC,EApBW,yBAAyB,KAAzB,yBAAyB,QAoBpC"}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { ElectronDownloadCacheMode, ElectronGenericArtifactDetails, ElectronPlatformArtifactDetailsWithDefaults } from './types.js';
|
||||
export declare function mkdtemp(parentDirectory?: string): Promise<string>;
|
||||
export declare enum TempDirCleanUpMode {
|
||||
CLEAN = 0,
|
||||
ORPHAN = 1
|
||||
}
|
||||
export declare function withTempDirectoryIn<T>(parentDirectory: string | undefined, fn: (directory: string) => Promise<T>, cleanUp: TempDirCleanUpMode): Promise<T>;
|
||||
export declare function withTempDirectory<T>(fn: (directory: string) => Promise<T>, cleanUp: TempDirCleanUpMode): Promise<T>;
|
||||
export declare function normalizeVersion(version: string): string;
|
||||
/**
|
||||
* Runs the `uname` command and returns the trimmed output.
|
||||
*/
|
||||
export declare function uname(): string;
|
||||
/**
|
||||
* Generates an architecture name that would be used in an Electron or Node.js
|
||||
* download file name.
|
||||
*/
|
||||
export declare function getNodeArch(arch: string): string;
|
||||
/**
|
||||
* Generates an architecture name that would be used in an Electron or Node.js
|
||||
* download file name from the `process` module information.
|
||||
*
|
||||
* @category Utility
|
||||
*/
|
||||
export declare function getHostArch(): string;
|
||||
export declare function ensureIsTruthyString<T, K extends keyof T>(obj: T, key: K): void;
|
||||
export declare function isOfficialLinuxIA32Download(platform: string, arch: string, version: string, mirrorOptions?: object): boolean;
|
||||
export declare function isOfficialDropped32BitDownload(platform: string, arch: string, version: string, mirrorOptions?: object): boolean;
|
||||
/**
|
||||
* Find the value of a environment variable which may or may not have the
|
||||
* prefix, in a case-insensitive manner.
|
||||
*/
|
||||
export declare function getEnv(prefix?: string): (name: string) => string | undefined;
|
||||
export declare function setEnv(key: string, value: string | undefined): void;
|
||||
export declare function effectiveCacheMode(artifactDetails: ElectronPlatformArtifactDetailsWithDefaults | ElectronGenericArtifactDetails): ElectronDownloadCacheMode;
|
||||
export declare function shouldTryReadCache(cacheMode: ElectronDownloadCacheMode): boolean;
|
||||
export declare function shouldWriteCache(cacheMode: ElectronDownloadCacheMode): boolean;
|
||||
export declare function doesCallerOwnTemporaryOutput(cacheMode: ElectronDownloadCacheMode): boolean;
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
import childProcess from 'node:child_process';
|
||||
import fs from 'graceful-fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import semver from 'semver';
|
||||
import { ElectronDownloadCacheMode, } from './types.js';
|
||||
async function useAndRemoveDirectory(directory, fn) {
|
||||
let result;
|
||||
try {
|
||||
result = await fn(directory);
|
||||
}
|
||||
finally {
|
||||
await fs.promises.rm(directory, { recursive: true, force: true });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
export async function mkdtemp(parentDirectory = os.tmpdir()) {
|
||||
const tempDirectoryPrefix = 'electron-download-';
|
||||
return await fs.promises.mkdtemp(path.resolve(parentDirectory, tempDirectoryPrefix));
|
||||
}
|
||||
export var TempDirCleanUpMode;
|
||||
(function (TempDirCleanUpMode) {
|
||||
TempDirCleanUpMode[TempDirCleanUpMode["CLEAN"] = 0] = "CLEAN";
|
||||
TempDirCleanUpMode[TempDirCleanUpMode["ORPHAN"] = 1] = "ORPHAN";
|
||||
})(TempDirCleanUpMode || (TempDirCleanUpMode = {}));
|
||||
export async function withTempDirectoryIn(parentDirectory = os.tmpdir(), fn, cleanUp) {
|
||||
const tempDirectory = await mkdtemp(parentDirectory);
|
||||
if (cleanUp === TempDirCleanUpMode.CLEAN) {
|
||||
return useAndRemoveDirectory(tempDirectory, fn);
|
||||
}
|
||||
else {
|
||||
return fn(tempDirectory);
|
||||
}
|
||||
}
|
||||
export async function withTempDirectory(fn, cleanUp) {
|
||||
return withTempDirectoryIn(undefined, fn, cleanUp);
|
||||
}
|
||||
export function normalizeVersion(version) {
|
||||
if (!version.startsWith('v')) {
|
||||
return `v${version}`;
|
||||
}
|
||||
return version;
|
||||
}
|
||||
/**
|
||||
* Runs the `uname` command and returns the trimmed output.
|
||||
*/
|
||||
export function uname() {
|
||||
return childProcess.execSync('uname -m').toString().trim();
|
||||
}
|
||||
/**
|
||||
* Generates an architecture name that would be used in an Electron or Node.js
|
||||
* download file name.
|
||||
*/
|
||||
export function getNodeArch(arch) {
|
||||
if (arch === 'arm') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
switch (process.config.variables.arm_version) {
|
||||
case '6':
|
||||
return uname();
|
||||
case '7':
|
||||
default:
|
||||
return 'armv7l';
|
||||
}
|
||||
}
|
||||
return arch;
|
||||
}
|
||||
/**
|
||||
* Generates an architecture name that would be used in an Electron or Node.js
|
||||
* download file name from the `process` module information.
|
||||
*
|
||||
* @category Utility
|
||||
*/
|
||||
export function getHostArch() {
|
||||
return getNodeArch(process.arch);
|
||||
}
|
||||
export function ensureIsTruthyString(obj, key) {
|
||||
if (!obj[key] || typeof obj[key] !== 'string') {
|
||||
throw new Error(`Expected property "${String(key)}" to be provided as a string but it was not`);
|
||||
}
|
||||
}
|
||||
export function isOfficialLinuxIA32Download(platform, arch, version, mirrorOptions) {
|
||||
return (platform === 'linux' &&
|
||||
arch === 'ia32' &&
|
||||
Number(version.slice(1).split('.')[0]) >= 4 &&
|
||||
typeof mirrorOptions === 'undefined');
|
||||
}
|
||||
// Electron 44 dropped support for 32-bit Windows (win32/ia32) and for
|
||||
// armv7l Linux. The last official releases to ship these artifacts are
|
||||
// v44.0.0-alpha.3 on the release channel and v45.0.0-nightly.20260713 on
|
||||
// the nightly channel.
|
||||
const FIRST_RELEASE_WITHOUT_32_BIT_SUPPORT = '44.0.0-alpha.4';
|
||||
const FIRST_NIGHTLY_WITHOUT_32_BIT_SUPPORT = '45.0.0-nightly.20260714';
|
||||
export function isOfficialDropped32BitDownload(platform, arch, version, mirrorOptions) {
|
||||
// Custom mirrors may legitimately host their own builds for these platforms
|
||||
if (typeof mirrorOptions !== 'undefined') {
|
||||
return false;
|
||||
}
|
||||
if (!((platform === 'win32' && arch === 'ia32') || (platform === 'linux' && arch === 'armv7l'))) {
|
||||
return false;
|
||||
}
|
||||
const parsedVersion = semver.parse(version);
|
||||
if (parsedVersion === null) {
|
||||
return false;
|
||||
}
|
||||
const firstVersionWithoutArtifacts = parsedVersion.prerelease.includes('nightly')
|
||||
? FIRST_NIGHTLY_WITHOUT_32_BIT_SUPPORT
|
||||
: FIRST_RELEASE_WITHOUT_32_BIT_SUPPORT;
|
||||
return semver.gte(parsedVersion, firstVersionWithoutArtifacts);
|
||||
}
|
||||
/**
|
||||
* Find the value of a environment variable which may or may not have the
|
||||
* prefix, in a case-insensitive manner.
|
||||
*/
|
||||
export function getEnv(prefix = '') {
|
||||
const envsLowerCase = {};
|
||||
for (const envKey in process.env) {
|
||||
envsLowerCase[envKey.toLowerCase()] = process.env[envKey];
|
||||
}
|
||||
return (name) => {
|
||||
return (envsLowerCase[`${prefix}${name}`.toLowerCase()] ||
|
||||
envsLowerCase[name.toLowerCase()] ||
|
||||
undefined);
|
||||
};
|
||||
}
|
||||
export function setEnv(key, value) {
|
||||
// The `void` operator always returns `undefined`.
|
||||
// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void
|
||||
if (value !== void 0) {
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
export function effectiveCacheMode(artifactDetails) {
|
||||
return artifactDetails.cacheMode || ElectronDownloadCacheMode.ReadWrite;
|
||||
}
|
||||
export function shouldTryReadCache(cacheMode) {
|
||||
return (cacheMode === ElectronDownloadCacheMode.ReadOnly ||
|
||||
cacheMode === ElectronDownloadCacheMode.ReadWrite);
|
||||
}
|
||||
export function shouldWriteCache(cacheMode) {
|
||||
return (cacheMode === ElectronDownloadCacheMode.WriteOnly ||
|
||||
cacheMode === ElectronDownloadCacheMode.ReadWrite);
|
||||
}
|
||||
export function doesCallerOwnTemporaryOutput(cacheMode) {
|
||||
return (cacheMode === ElectronDownloadCacheMode.Bypass ||
|
||||
cacheMode === ElectronDownloadCacheMode.ReadOnly);
|
||||
}
|
||||
//# sourceMappingURL=utils.js.map
|
||||
Reference in New Issue
Block a user