Upload via GUI (7543 Dateien)

This commit is contained in:
2026-10-22 06:56:19 +00:00
parent e4585e50ee
commit d6518698b1
50 changed files with 1914 additions and 0 deletions
+20
View File
@@ -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
View File
@@ -0,0 +1,208 @@
# @electron/asar - Electron Archive
[![Test](https://github.com/electron/asar/actions/workflows/test.yml/badge.svg)](https://github.com/electron/asar/actions/workflows/test.yml)
[![npm version](http://img.shields.io/npm/v/@electron/asar.svg)](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
View File
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=glob.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"glob.js","sourceRoot":"","sources":["../../src/types/glob.ts"],"names":[],"mappings":""}
+13
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
[![CircleCI](https://circleci.com/gh/electron/fuses.svg?style=shield)](https://circleci.com/gh/electron/fuses)
[![npm version](http://img.shields.io/npm/v/@electron/fuses.svg)](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
View File
@@ -0,0 +1,2 @@
#!/usr/bin/env node
export {};
+146
View File
@@ -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
View File
File diff suppressed because one or more lines are too long
+34
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,7 @@
export declare const SENTINEL = "dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX";
export declare enum FuseState {
DISABLE = 48,
ENABLE = 49,
REMOVED = 114,
INHERIT = 144
}
+12
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+58
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1 @@
{"version":3,"file":"Cache.js","sourceRoot":"","sources":["../src/Cache.ts"],"names":[],"mappings":"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"}
+18
View File
@@ -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
View File
@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=Downloader.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"Downloader.js","sourceRoot":"","sources":["../src/Downloader.ts"],"names":[],"mappings":""}
+43
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1 @@
{"version":3,"file":"FetchDownloader.js","sourceRoot":"","sources":["../src/FetchDownloader.ts"],"names":[],"mappings":"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"}
+4
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1 @@
{"version":3,"file":"artifact-utils.js","sourceRoot":"","sources":["../src/artifact-utils.ts"],"names":[],"mappings":"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"}
+3
View File
@@ -0,0 +1,3 @@
import { DownloadOptions } from './types.js';
import { Downloader } from './Downloader.js';
export declare function getDownloaderForSystem(): Promise<Downloader<DownloadOptions>>;
+5
View File
@@ -0,0 +1,5 @@
import { FetchDownloader } from './FetchDownloader.js';
export async function getDownloaderForSystem() {
return new FetchDownloader();
}
//# sourceMappingURL=downloader-resolver.js.map
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because one or more lines are too long
+14
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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