7 Commits

Author SHA1 Message Date
Samuel Attard
621083fe1f fix: add debug logging 2020-11-19 10:01:20 -08:00
Samuel Attard
0770238718 chore: cleanup files and split into modules 2020-11-19 09:49:17 -08:00
Samuel Attard
c01deb5576 build: fix lockfile 2020-11-19 09:39:03 -08:00
Niels Leenheer
82acb6fc72 fix: no asar support (#4), renamed directories (#5) and check if we need to duplicate asar's (#2) (#8)
* fix: no asar support (#4), renamed directories (#5) and check if we need to duplicate asar's (#1)

* Add missing newline
2020-11-19 09:34:14 -08:00
Niels Leenheer
8bb61593b2 fix: add "entry-asar/*" to the files section of package.json (fixes #3) (#7) 2020-11-19 09:33:42 -08:00
dependabot[bot]
3ebf924651 build(deps-dev): bump semantic-release from 17.2.2 to 17.2.3
Bumps [semantic-release](https://github.com/semantic-release/semantic-release) from 17.2.2 to 17.2.3.
- [Release notes](https://github.com/semantic-release/semantic-release/releases)
- [Commits](https://github.com/semantic-release/semantic-release/compare/v17.2.2...v17.2.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-18 15:14:48 -08:00
Samuel Attard
46a3b7e94d docs: update README 2020-11-13 14:30:01 -08:00
11 changed files with 329 additions and 120 deletions

View File

@@ -30,3 +30,7 @@ because it contains two apps in one.
The way `@electron/universal` works today means you don't need to worry about The way `@electron/universal` works today means you don't need to worry about
things like building universal versions of your native modules. As long as things like building universal versions of your native modules. As long as
your x64 and arm64 apps work in isolation the Universal app will work as well. your x64 and arm64 apps work in isolation the Universal app will work as well.
#### How do I build my app for Apple silicon in the first place?
Check out the [Electron Apple silicon blog post](https://www.electronjs.org/blog/apple-silicon)

7
entry-asar/has-asar.js Normal file
View File

@@ -0,0 +1,7 @@
if (process.arch === 'arm64') {
process._archPath = require.resolve('../app-arm64.asar');
} else {
process._archPath = require.resolve('../app-x64.asar');
}
require(process._archPath);

View File

@@ -1,7 +0,0 @@
if (process.arch === 'arm64') {
process._asarPath = require.resolve('../arm64.app.asar');
} else {
process._asarPath = require.resolve('../x64.app.asar');
}
require(process._asarPath);

7
entry-asar/no-asar.js Normal file
View File

@@ -0,0 +1,7 @@
if (process.arch === 'arm64') {
process._archPath = require.resolve('../app-arm64');
} else {
process._archPath = require.resolve('../app-x64');
}
require(process._archPath);

View File

@@ -15,6 +15,7 @@
}, },
"files": [ "files": [
"dist/*", "dist/*",
"entry-asar/*",
"README.md" "README.md"
], ],
"author": "Samuel Attard", "author": "Samuel Attard",
@@ -26,6 +27,7 @@
}, },
"devDependencies": { "devDependencies": {
"@continuous-auth/semantic-release-npm": "^2.0.0", "@continuous-auth/semantic-release-npm": "^2.0.0",
"@types/debug": "^4.1.5",
"@types/fs-extra": "^9.0.4", "@types/fs-extra": "^9.0.4",
"@types/node": "^14.14.7", "@types/node": "^14.14.7",
"husky": "^4.3.0", "husky": "^4.3.0",
@@ -37,6 +39,18 @@
"dependencies": { "dependencies": {
"@malept/cross-spawn-promise": "^1.1.0", "@malept/cross-spawn-promise": "^1.1.0",
"asar": "^3.0.3", "asar": "^3.0.3",
"debug": "^4.3.1",
"dir-compare": "^2.4.0",
"fs-extra": "^9.0.1" "fs-extra": "^9.0.1"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.ts": [
"prettier --write"
]
} }
} }

21
src/asar-utils.ts Normal file
View File

@@ -0,0 +1,21 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import { d } from './debug';
export enum AsarMode {
NO_ASAR,
HAS_ASAR,
}
export const detectAsarMode = async (appPath: string) => {
d('checking asar mode of', appPath);
const asarPath = path.resolve(appPath, 'Contents', 'Resources', 'app.asar');
if (!(await fs.pathExists(asarPath))) {
d('determined no asar');
return AsarMode.NO_ASAR;
}
d('determined has asar');
return AsarMode.HAS_ASAR;
};

3
src/debug.ts Normal file
View File

@@ -0,0 +1,3 @@
import * as debug from 'debug';
export const d = debug('electron-universal');

61
src/file-utils.ts Normal file
View File

@@ -0,0 +1,61 @@
import { spawn } from '@malept/cross-spawn-promise';
import * as fs from 'fs-extra';
import * as path from 'path';
const MACHO_PREFIX = 'Mach-O ';
export enum AppFileType {
MACHO,
PLAIN,
SNAPSHOT,
APP_CODE,
}
export type AppFile = {
relativePath: string;
type: AppFileType;
};
/**
*
* @param appPath Path to the application
*/
export const getAllAppFiles = async (appPath: string): Promise<AppFile[]> => {
const files: AppFile[] = [];
const visited = new Set<string>();
const traverse = async (p: string) => {
p = await fs.realpath(p);
if (visited.has(p)) return;
visited.add(p);
const info = await fs.stat(p);
if (info.isSymbolicLink()) return;
if (info.isFile()) {
let fileType = AppFileType.PLAIN;
const fileOutput = await spawn('file', ['--brief', '--no-pad', p]);
if (p.includes('app.asar')) {
fileType = AppFileType.APP_CODE;
} else if (fileOutput.startsWith(MACHO_PREFIX)) {
fileType = AppFileType.MACHO;
} else if (p.endsWith('.bin')) {
fileType = AppFileType.SNAPSHOT;
}
files.push({
relativePath: path.relative(appPath, p),
type: fileType,
});
}
if (info.isDirectory()) {
for (const child of await fs.readdir(p)) {
await traverse(path.resolve(p, child));
}
}
};
await traverse(appPath);
return files;
};

View File

@@ -1,11 +1,13 @@
import { spawn } from '@malept/cross-spawn-promise'; import { spawn } from '@malept/cross-spawn-promise';
import * as asar from 'asar'; import * as asar from 'asar';
import * as crypto from 'crypto';
import * as fs from 'fs-extra'; import * as fs from 'fs-extra';
import * as os from 'os'; import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import * as dircompare from 'dir-compare';
const MACHO_PREFIX = 'Mach-O '; import { AppFile, AppFileType, getAllAppFiles } from './file-utils';
import { AsarMode, detectAsarMode } from './asar-utils';
import { sha } from './sha';
import { d } from './debug';
type MakeUniversalOpts = { type MakeUniversalOpts = {
/** /**
@@ -28,78 +30,12 @@ type MakeUniversalOpts = {
force: boolean; force: boolean;
}; };
enum AsarMode { const dupedFiles = (files: AppFile[]) =>
NO_ASAR, files.filter((f) => f.type !== AppFileType.SNAPSHOT && f.type !== AppFileType.APP_CODE);
HAS_ASAR,
}
export const detectAsarMode = async (appPath: string) => {
const asarPath = path.resolve(appPath, 'Contents', 'Resources', 'app.asar');
if (!(await fs.pathExists(asarPath))) return AsarMode.NO_ASAR;
return AsarMode.HAS_ASAR;
};
enum AppFileType {
MACHO,
PLAIN,
SNAPSHOT,
APP_CODE,
}
type AppFile = {
relativePath: string;
type: AppFileType;
}
const getAllFiles = async (appPath: string): Promise<AppFile[]> => {
const files: AppFile[] = [];
const visited = new Set<string>();
const traverse = async (p: string) => {
p = await fs.realpath(p);
if (visited.has(p)) return;
visited.add(p);
const info = await fs.stat(p);
if (info.isSymbolicLink()) return;
if (info.isFile()) {
let fileType = AppFileType.PLAIN;
const fileOutput = await spawn('file', ['--brief', '--no-pad', p]);
if (p.includes('app.asar')) {
fileType = AppFileType.APP_CODE;
} else if (fileOutput.startsWith(MACHO_PREFIX)) {
fileType = AppFileType.MACHO;
} else if (p.endsWith('.bin')) {
fileType = AppFileType.SNAPSHOT;
}
files.push({
relativePath: path.relative(appPath, p),
type: fileType,
});
}
if (info.isDirectory()) {
for (const child of await fs.readdir(p)) {
await traverse(path.resolve(p, child));
}
}
};
await traverse(appPath);
return files;
};
const dupedFiles = (files: AppFile[]) => files.filter(f => f.type !== AppFileType.SNAPSHOT && f.type !== AppFileType.APP_CODE);
const sha = async (filePath: string) => {
return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
}
export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> => { export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> => {
d('making a universal app with options', opts);
if (process.platform !== 'darwin') if (process.platform !== 'darwin')
throw new Error('@electron/universal is only supported on darwin platforms'); throw new Error('@electron/universal is only supported on darwin platforms');
if (!opts.x64AppPath || !path.isAbsolute(opts.x64AppPath)) if (!opts.x64AppPath || !path.isAbsolute(opts.x64AppPath))
@@ -110,17 +46,21 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
throw new Error('Expected opts.outAppPath to be an absolute path but it was not'); throw new Error('Expected opts.outAppPath to be an absolute path but it was not');
if (await fs.pathExists(opts.outAppPath)) { if (await fs.pathExists(opts.outAppPath)) {
d('output path exists already');
if (!opts.force) { if (!opts.force) {
throw new Error( throw new Error(
`The out path "${opts.outAppPath}" already exists and force is not set to true`, `The out path "${opts.outAppPath}" already exists and force is not set to true`,
); );
} else { } else {
d('overwriting existing application because force == true');
await fs.remove(opts.outAppPath); await fs.remove(opts.outAppPath);
} }
} }
const x64AsarMode = await detectAsarMode(opts.x64AppPath); const x64AsarMode = await detectAsarMode(opts.x64AppPath);
const arm64AsarMode = await detectAsarMode(opts.arm64AppPath); const arm64AsarMode = await detectAsarMode(opts.arm64AppPath);
d('detected x64AsarMode =', x64AsarMode);
d('detected arm64AsarMode =', arm64AsarMode);
if (x64AsarMode !== arm64AsarMode) if (x64AsarMode !== arm64AsarMode)
throw new Error( throw new Error(
@@ -128,23 +68,28 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
); );
const tmpDir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'electron-universal-')); const tmpDir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'electron-universal-'));
d('building universal app in', tmpDir);
try { try {
d('copying x64 app as starter template');
const tmpApp = path.resolve(tmpDir, 'Tmp.app'); const tmpApp = path.resolve(tmpDir, 'Tmp.app');
await spawn('cp', ['-R', opts.x64AppPath, tmpApp]); await spawn('cp', ['-R', opts.x64AppPath, tmpApp]);
const uniqueToX64: string[] = []; const uniqueToX64: string[] = [];
const uniqueToArm64: string[] = []; const uniqueToArm64: string[] = [];
const x64Files = await getAllFiles(await fs.realpath(tmpApp)); const x64Files = await getAllAppFiles(await fs.realpath(tmpApp));
const arm64Files = await getAllFiles(opts.arm64AppPath); const arm64Files = await getAllAppFiles(opts.arm64AppPath);
for (const file of dupedFiles(x64Files)) { for (const file of dupedFiles(x64Files)) {
if (!arm64Files.some(f => f.relativePath === file.relativePath)) uniqueToX64.push(file.relativePath); if (!arm64Files.some((f) => f.relativePath === file.relativePath))
uniqueToX64.push(file.relativePath);
} }
for (const file of dupedFiles(arm64Files)) { for (const file of dupedFiles(arm64Files)) {
if (!x64Files.some(f => f.relativePath === file.relativePath)) uniqueToArm64.push(file.relativePath); if (!x64Files.some((f) => f.relativePath === file.relativePath))
uniqueToArm64.push(file.relativePath);
} }
if (uniqueToX64.length !== 0 || uniqueToArm64.length !== 0) { if (uniqueToX64.length !== 0 || uniqueToArm64.length !== 0) {
d('some files were not in both builds, aborting');
console.error({ console.error({
uniqueToX64, uniqueToX64,
uniqueToArm64, uniqueToArm64,
@@ -154,59 +99,159 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise<void> =
); );
} }
for (const file of x64Files.filter(f => f.type === AppFileType.PLAIN)) { for (const file of x64Files.filter((f) => f.type === AppFileType.PLAIN)) {
const x64Sha = await sha(path.resolve(opts.x64AppPath, file.relativePath)); const x64Sha = await sha(path.resolve(opts.x64AppPath, file.relativePath));
const arm64Sha = await sha(path.resolve(opts.arm64AppPath, file.relativePath)); const arm64Sha = await sha(path.resolve(opts.arm64AppPath, file.relativePath));
if (x64Sha !== arm64Sha) { if (x64Sha !== arm64Sha) {
console.error(`${x64Sha} !== ${arm64Sha}`); d('SHA for file', file.relativePath, `does not match across builds ${x64Sha}!=${arm64Sha}`);
throw new Error(`Expected all non-binary files to have identical SHAs when creating a universal build but "${file.relativePath}" did not`); throw new Error(
`Expected all non-binary files to have identical SHAs when creating a universal build but "${file.relativePath}" did not`,
);
} }
} }
for (const machOFile of x64Files.filter(f => f.type === AppFileType.MACHO)) { for (const machOFile of x64Files.filter((f) => f.type === AppFileType.MACHO)) {
const first = await fs.realpath(path.resolve(tmpApp, machOFile.relativePath));
const second = await fs.realpath(path.resolve(opts.arm64AppPath, machOFile.relativePath));
d('joining two MachO files with lipo', {
first,
second,
});
await spawn('lipo', [ await spawn('lipo', [
await fs.realpath(path.resolve(tmpApp, machOFile.relativePath)), first,
await fs.realpath(path.resolve(opts.arm64AppPath, machOFile.relativePath)), second,
'-create', '-create',
'-output', '-output',
await fs.realpath(path.resolve(tmpApp, machOFile.relativePath)), await fs.realpath(path.resolve(tmpApp, machOFile.relativePath)),
]); ]);
} }
/**
* If we don't have an ASAR we need to check if the two "app" folders are identical, if
* they are then we can just leave one there and call it a day. If the app folders for x64
* and arm64 are different though we need to rename each folder and create a new fake "app"
* entrypoint to dynamically load the correct app folder
*/
if (x64AsarMode === AsarMode.NO_ASAR) { if (x64AsarMode === AsarMode.NO_ASAR) {
await fs.move(path.resolve(tmpApp, 'Contents', 'Resources', 'app'), path.resolve(tmpApp, 'Contents', 'Resources', 'x64.app')); d('checking if the x64 and arm64 app folders are identical');
await fs.copy(path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app'), path.resolve(tmpApp, 'Contents', 'Resources', 'arm64.app')); const comparison = await dircompare.compare(
path.resolve(tmpApp, 'Contents', 'Resources', 'app'),
path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app'),
{ compareSize: true, compareContent: true },
);
if (!comparison.same) {
d('x64 and arm64 app folders are different, creating dynamic entry ASAR');
await fs.move(
path.resolve(tmpApp, 'Contents', 'Resources', 'app'),
path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64'),
);
await fs.copy(
path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app'),
path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64'),
);
const entryAsar = path.resolve(tmpDir, 'entry-asar');
await fs.mkdir(entryAsar);
await fs.copy(
path.resolve(__dirname, '..', '..', 'entry-asar', 'no-asar.js'),
path.resolve(entryAsar, 'index.js'),
);
let pj = await fs.readJson(
path.resolve(opts.x64AppPath, 'Contents', 'Resources', 'app', 'package.json'),
);
pj.main = 'index.js';
await fs.writeJson(path.resolve(entryAsar, 'package.json'), pj);
await asar.createPackage(
entryAsar,
path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'),
);
} else { } else {
await fs.move(path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'), path.resolve(tmpApp, 'Contents', 'Resources', 'x64.app.asar')); d('x64 and arm64 app folders are the same');
const x64Unpacked = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar.unpacked'); }
if (await fs.pathExists(x64Unpacked)) {
await fs.move(x64Unpacked, path.resolve(tmpApp, 'Contents', 'Resources', 'x64.app.asar.unpacked'));
} }
await fs.copy(path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'), path.resolve(tmpApp, 'Contents', 'Resources', 'arm64.app.asar')); /**
const arm64Unpacked = path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar.unpacked'); * If we have an ASAR we just need to check if the two "app.asar" files have the same hash,
if (await fs.pathExists(arm64Unpacked)) { * if they are, same as above, we can leave one there and call it a day. If they're different
await fs.copy(arm64Unpacked, path.resolve(tmpApp, 'Contents', 'Resources', 'arm64.app.asar.unpacked')); * we have to make a dynamic entrypoint. There is an assumption made here that every file in
* app.asar.unpacked is a native node module. This assumption _may_ not be true so we should
* look at codifying that assumption as actual logic.
*/
// FIXME: Codify the assumption that app.asar.unpacked only contains native modules
if (x64AsarMode === AsarMode.HAS_ASAR) {
d('checking if the x64 and arm64 asars are identical');
const x64AsarSha = await sha(path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'));
const arm64AsarSha = await sha(
path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'),
);
if (x64AsarSha !== arm64AsarSha) {
d('x64 and arm64 asars are different');
await fs.move(
path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'),
path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar'),
);
const x64Unpacked = path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar.unpacked');
if (await fs.pathExists(x64Unpacked)) {
await fs.move(
x64Unpacked,
path.resolve(tmpApp, 'Contents', 'Resources', 'app-x64.asar.unpacked'),
);
} }
await fs.copy(
path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'),
path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64.asar'),
);
const arm64Unpacked = path.resolve(
opts.arm64AppPath,
'Contents',
'Resources',
'app.asar.unpacked',
);
if (await fs.pathExists(arm64Unpacked)) {
await fs.copy(
arm64Unpacked,
path.resolve(tmpApp, 'Contents', 'Resources', 'app-arm64.asar.unpacked'),
);
} }
const entryAsar = path.resolve(tmpDir, 'entry-asar'); const entryAsar = path.resolve(tmpDir, 'entry-asar');
await fs.mkdir(entryAsar); await fs.mkdir(entryAsar);
await fs.copy(path.resolve(__dirname, '..', '..', 'entry-asar', 'index.js'), path.resolve(entryAsar, 'index.js')); await fs.copy(
let pj: any; path.resolve(__dirname, '..', '..', 'entry-asar', 'has-asar.js'),
if (x64AsarMode === AsarMode.NO_ASAR) { path.resolve(entryAsar, 'index.js'),
pj = await fs.readJson(path.resolve(opts.x64AppPath, 'Contents', 'Resources', 'app', 'package.json')); );
} else { let pj = JSON.parse(
pj = JSON.parse((await asar.extractFile(path.resolve(opts.x64AppPath, 'Contents', 'Resources', 'app.asar'), 'package.json')).toString('utf8')); (
} await asar.extractFile(
path.resolve(opts.x64AppPath, 'Contents', 'Resources', 'app.asar'),
'package.json',
)
).toString('utf8'),
);
pj.main = 'index.js'; pj.main = 'index.js';
await fs.writeJson(path.resolve(entryAsar, 'package.json'), pj); await fs.writeJson(path.resolve(entryAsar, 'package.json'), pj);
await asar.createPackage(entryAsar, path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar')); await asar.createPackage(
entryAsar,
for (const snapshotsFile of arm64Files.filter(f => f.type === AppFileType.SNAPSHOT)) { path.resolve(tmpApp, 'Contents', 'Resources', 'app.asar'),
await fs.copy(path.resolve(opts.arm64AppPath, snapshotsFile.relativePath), path.resolve(tmpApp, snapshotsFile.relativePath)); );
} else {
d('x64 and arm64 asars are the same');
}
} }
for (const snapshotsFile of arm64Files.filter((f) => f.type === AppFileType.SNAPSHOT)) {
d('copying snapshot file', snapshotsFile.relativePath, 'to target application');
await fs.copy(
path.resolve(opts.arm64AppPath, snapshotsFile.relativePath),
path.resolve(tmpApp, snapshotsFile.relativePath),
);
}
d('moving final universal app to target destination');
await spawn('mv', [tmpApp, opts.outAppPath]); await spawn('mv', [tmpApp, opts.outAppPath]);
} catch (err) { } catch (err) {
throw err; throw err;

15
src/sha.ts Normal file
View File

@@ -0,0 +1,15 @@
import * as fs from 'fs-extra';
import * as crypto from 'crypto';
import { d } from './debug';
export const sha = async (filePath: string) => {
d('hashing', filePath);
const hash = crypto.createHash('sha256');
const fileStream = fs.createReadStream(filePath);
fileStream.pipe(hash);
await new Promise((resolve, reject) => {
fileStream.on('end', () => resolve());
fileStream.on('error', (err) => reject(err));
});
return hash.digest('hex');
};

View File

@@ -269,6 +269,11 @@
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
"@types/debug@^4.1.5":
version "4.1.5"
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd"
integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==
"@types/fs-extra@^9.0.4": "@types/fs-extra@^9.0.4":
version "9.0.4" version "9.0.4"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.4.tgz#12553138cf0438db9a31cdc8b0a3aa9332eb67aa" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.4.tgz#12553138cf0438db9a31cdc8b0a3aa9332eb67aa"
@@ -616,6 +621,11 @@ braces@^3.0.1:
dependencies: dependencies:
fill-range "^7.0.1" fill-range "^7.0.1"
buffer-equal@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe"
integrity sha1-WWFrSYME1Var1GaWayLu2j7KX74=
buffer-from@^1.0.0: buffer-from@^1.0.0:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
@@ -900,6 +910,13 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies: dependencies:
delayed-stream "~1.0.0" delayed-stream "~1.0.0"
commander@2.9.0:
version "2.9.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
integrity sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=
dependencies:
graceful-readlink ">= 1.0.0"
commander@^5.0.0: commander@^5.0.0:
version "5.1.0" version "5.1.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
@@ -1131,6 +1148,13 @@ debug@^3.1.0:
dependencies: dependencies:
ms "^2.1.1" ms "^2.1.1"
debug@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
dependencies:
ms "2.1.2"
debuglog@^1.0.1: debuglog@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
@@ -1211,6 +1235,16 @@ dezalgo@^1.0.0, dezalgo@~1.0.3:
asap "^2.0.0" asap "^2.0.0"
wrappy "1" wrappy "1"
dir-compare@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/dir-compare/-/dir-compare-2.4.0.tgz#785c41dc5f645b34343a4eafc50b79bac7f11631"
integrity sha512-l9hmu8x/rjVC9Z2zmGzkhOEowZvW7pmYws5CWHutg8u1JgvsKWMx7Q/UODeu4djLZ4FgW5besw5yvMQnBHzuCA==
dependencies:
buffer-equal "1.0.0"
colors "1.0.3"
commander "2.9.0"
minimatch "3.0.4"
dir-glob@^3.0.0, dir-glob@^3.0.1: dir-glob@^3.0.0, dir-glob@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
@@ -1804,6 +1838,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
"graceful-readlink@>= 1.0.0":
version "1.0.1"
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=
handlebars@^4.7.6: handlebars@^4.7.6:
version "4.7.6" version "4.7.6"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.6.tgz#d4c05c1baf90e9945f77aa68a7a219aa4a7df74e" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.6.tgz#d4c05c1baf90e9945f77aa68a7a219aa4a7df74e"
@@ -2864,7 +2903,7 @@ min-indent@^1.0.0:
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
minimatch@^3.0.4: minimatch@3.0.4, minimatch@^3.0.4:
version "3.0.4" version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
@@ -4103,9 +4142,9 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1:
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
semantic-release@^17.2.2: semantic-release@^17.2.2:
version "17.2.2" version "17.2.3"
resolved "https://registry.yarnpkg.com/semantic-release/-/semantic-release-17.2.2.tgz#520dae9cd188c7cdcc5216a7aad131548fc5cec7" resolved "https://registry.yarnpkg.com/semantic-release/-/semantic-release-17.2.3.tgz#11f10b851d4e75b1015b17515c433049b3df994c"
integrity sha512-LNU68ud3a3oU46H11OThXaKAK430jGGGTIF4VsiP843kRmS6s8kVCceLRdi7yWWz/sCCMD0zygPTQV2Jw79J5g== integrity sha512-MY1MlowGQrkOR7+leOD8ICkVOC6i1szbwDODdbJ0UdshtMx8Ms0bhpRQmEEliqYKEb5PLv/dqs6zKKuHT7UxTg==
dependencies: dependencies:
"@semantic-release/commit-analyzer" "^8.0.0" "@semantic-release/commit-analyzer" "^8.0.0"
"@semantic-release/error" "^2.2.0" "@semantic-release/error" "^2.2.0"