Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keytar #259

Merged
merged 3 commits into from
Apr 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 206 additions & 18 deletions gulp/gulpfile.app.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,87 @@
* Thomas Schmid <schmid-thomas@gmx.net>
*/

const { src, dest, watch, parallel } = require('gulp');
const { src, dest, watch, parallel, series } = require('gulp');
const { existsSync } = require('fs');
const { readFile } = require('fs').promises;

const logger = require('gulplog');

const common = require("./gulpfile.common.js");

const path = require('path');
const tar = require('tar');
const { getAbi } = require('node-abi');


const CACHE_DIR_APP = path.join(common.BASE_DIR_BUILD, "electron/cache");
const BUILD_DIR_APP = path.join(common.BASE_DIR_BUILD, "electron/resources");
const OUTPUT_DIR_APP = path.join(common.BASE_DIR_BUILD, "electron/out");
const BASE_DIR_APP = "./src/app/";


const KEYTAR_NAME = "keytar";
const KEYTAR_OUTPUT_DIR = `/libs/${KEYTAR_NAME}`;
const BASE_DIR_KEYTAR = `./node_modules/${KEYTAR_NAME}/`;
const PREBUILT_URL_KEYTAR = `https://github.com/atom/node-keytar/releases/download`;

const WIN_ARCH = "x64";
const WIN_PLATFORM = "win32";
const LINUX_ARCH = "x64";
const LINUX_PLATFORM = "linux";
const MAC_ARCH = "x64";
const MAC_PLATFORM = "mas";

const RUNTIME_ELECTRON = "electron";

/**
* Extracts a tar or tar.gz file to the given destination.
*
* @param {string} filename
* the path to the tar file
* @param {string} destination
* the destination folder into which the tar should be extracted.
*/
async function untar(filename, destination) {
"use strict";

logger.debug(`Extracting ${filename} to ${destination}`);

return await tar.x({
file: filename,
cwd: destination,
strict: true
});
}

/**
* Gets electron's release as well as the abi version.
* Throws in case the electron runtime could not be found.
*
* @param {string} dir
* the path to the electron runtime.
* @returns {{ version : string, abi : string}}
* electrons release as well as the abi version.
*/
async function getElectronVersion(dir) {

"use strict";

const versionFile = path.join(dir + '/version');

if (!existsSync(versionFile))
throw new Error(`Failed to detect version, no electron runtime in ${dir}`);

const version = (await readFile(versionFile)).toString();
const abi = await getAbi(version, RUNTIME_ELECTRON);

return {
"version": version,
"abi": abi
};
}


/**
* Copies and updates the package.json inside the build directory.
* It is typically used by other tools like the electron-packager.
Expand Down Expand Up @@ -141,6 +207,120 @@ function packageCommon() {
]).pipe(dest(BUILD_DIR_APP + '/libs'));
}

/**
* The keytar files need to go into the app/lib directory.
* After packaging electron the you need to add the native
* prebuilt libraries.
*
* @returns {Stream}
* a stream to be consumed by gulp
*/
function packageKeytar() {
"use strict";

return src([
BASE_DIR_KEYTAR + "/**",
// Filter out the rfc documents
"!" + BASE_DIR_KEYTAR + "/*.gyp",
"!" + BASE_DIR_KEYTAR + "/*.ts",
"!" + BASE_DIR_KEYTAR + "/src/**",
"!" + BASE_DIR_KEYTAR + "/build/**",
"!" + BASE_DIR_KEYTAR + "/node_modules/**"
]).pipe(dest(path.join(BUILD_DIR_APP, KEYTAR_OUTPUT_DIR)));
}

/**
* Deploys the native prebuilt node modules into an electron application.
* Typically you first package the module without any prebuilt files.
* Then invoke electron packager with the target operating system and
* architecture. Then call this method to deploy the matching prebuilt modules
* to the electron packager output.
*
* Keep in mind a prebuilt is a binary. It needs to be binary
* compatible and the abi has to match. An Windows Electron requires
* a windows prebuilt, a Linux electron a linux prebuilt, etc
*
* @param {string} electronDest
* the location of the electron framework which should be repackaged
* @param {string} prebuiltDest
* the location (inside the electron application) where the prebuilt
* binaries should be stored.
* @param {string} pkgSrc
* the directory to the source node package, which should be repackaged.
* @param {string} pkgName
* the the package name
* @param {string} platform
* the platform for which the prebuilt packages
* @param {string} arch
* the architecture for the prebuilt packages
*/
async function deployPrebuilt(electronDest, prebuiltDest, pkgName, platform, arch) {
"use strict";

logger.debug(`Packaging Prebuilt ${pkgName} for ${platform}-${arch}`);

// Step one, extract the electron version
electronDest = path.resolve(path.join(electronDest, `/sieve-${platform}-${arch}`));

if (!existsSync(electronDest))
throw new Error(`Could not find a compatible electron release in ${electronDest}`);

const abi = (await getElectronVersion(electronDest)).abi;

// Step two, extract the package version

// Prebuilt and electron packager use a different naming for mac.
if (platform.toLowerCase() === "mas")
platform = "darwin";

if (platform === "darwin")
prebuiltDest = path.join(electronDest, "sieve.app/Contents/Resources/app", prebuiltDest);
else
prebuiltDest = path.join(electronDest, "/resources/app/", prebuiltDest);

const pkg = JSON.parse(
await readFile(path.join(prebuiltDest, '/package.json')));

// Step three, download the package,

const filename = `${pkgName}-v${pkg.version}-${RUNTIME_ELECTRON}-v${abi}-${platform}-${arch}.tar.gz`;
const prebuiltSrc = path.join(CACHE_DIR_APP, filename);

if (!existsSync(prebuiltSrc)) {
const url = `${PREBUILT_URL_KEYTAR}/v${pkg.version}/${filename}`;
await common.download(url, prebuiltSrc);
}

// Step four, deploy the prebuilt.
await untar(prebuiltSrc, prebuiltDest);

return;
}

/**
* Packages the Keytar prebuilt modules into the win32 build output
*/
async function packageKeytarWin32() {
"use strict";
return await deployPrebuilt(OUTPUT_DIR_APP, KEYTAR_OUTPUT_DIR, KEYTAR_NAME, WIN_PLATFORM, WIN_ARCH);
}

/**
* Packages the Keytar prebuilt modules into the linux build output
*/
async function packageKeytarLinux() {
"use strict";
return await deployPrebuilt(OUTPUT_DIR_APP, KEYTAR_OUTPUT_DIR, KEYTAR_NAME, LINUX_PLATFORM, LINUX_ARCH);
}

/**
* Packages the Keytar prebuilt modules into the macOS build output
*/
async function packageKeytarMacOS() {
"use strict";
return await deployPrebuilt(OUTPUT_DIR_APP, KEYTAR_OUTPUT_DIR, KEYTAR_NAME, MAC_PLATFORM, MAC_ARCH);
}

/**
* Packages the build directory and electron for windows.
*/
Expand All @@ -152,12 +332,11 @@ async function packageWin32() {
arch: WIN_ARCH,
platform: WIN_PLATFORM,
download: {
cacheRoot: path.join(common.BASE_DIR_BUILD, "/electron/cache")
cacheRoot: CACHE_DIR_APP
},
out: path.join(common.BASE_DIR_BUILD, "/electron/out"),
out: OUTPUT_DIR_APP,
overwrite: true,
icon: path.join(common.BASE_DIR_COMMON, "icons/win.ico"),
prune: true
icon: path.join(common.BASE_DIR_COMMON, "icons/win.ico")
};

const packager = require('electron-packager');
Expand All @@ -175,12 +354,10 @@ async function packageLinux() {
arch: LINUX_ARCH,
platform: LINUX_PLATFORM,
download: {
cache: path.join(common.BASE_DIR_BUILD, "/electron/cache")
cacheRoot: CACHE_DIR_APP
},
out: path.join(common.BASE_DIR_BUILD, "/electron/out"),
out: OUTPUT_DIR_APP,
overwrite: true,
// packageManager : "yarn"
// packageManager : false,
prune: true
};

Expand All @@ -199,9 +376,9 @@ async function packageMacOS() {
arch: MAC_ARCH,
platform: MAC_PLATFORM,
download: {
cache: path.join(common.BASE_DIR_BUILD, "/electron/cache")
cacheRoot: CACHE_DIR_APP
},
out: path.join(common.BASE_DIR_BUILD, "/electron/out"),
out: OUTPUT_DIR_APP,
overwrite: true,
icon: path.join(common.BASE_DIR_COMMON, "icons/mac.icns"),
prune: true
Expand Down Expand Up @@ -253,7 +430,7 @@ async function zipWin32() {

const version = (await common.getPackageVersion()).join(".");

const source = path.resolve(path.join(common.BASE_DIR_BUILD, `/electron/out/sieve-${WIN_PLATFORM}-${WIN_ARCH}`));
const source = path.resolve(path.join(OUTPUT_DIR_APP, `sieve-${WIN_PLATFORM}-${WIN_ARCH}`));
const destination = path.join(common.BASE_DIR_BUILD, `sieve-${version}-${WIN_PLATFORM}-${WIN_ARCH}.zip`);

await common.compress(source, destination);
Expand All @@ -267,7 +444,7 @@ async function zipLinux() {

const version = (await common.getPackageVersion()).join(".");

const source = path.resolve(path.join(common.BASE_DIR_BUILD, `/electron/out/sieve-${LINUX_PLATFORM}-${LINUX_ARCH}`));
const source = path.resolve(path.join(OUTPUT_DIR_APP, `sieve-${LINUX_PLATFORM}-${LINUX_ARCH}`));
const destination = path.join(common.BASE_DIR_BUILD, `sieve-${version}-${LINUX_PLATFORM}-${LINUX_ARCH}.zip`);

const options = {
Expand All @@ -293,9 +470,20 @@ exports["packageLicense"] = packageLicense;
exports["packageSrc"] = packageSrc;
exports["packageCommon"] = packageCommon;

exports["packageWin32"] = packageWin32;
exports["packageLinux"] = packageLinux;
exports["packageMacOS"] = packageMacOS;
exports["packageWin32"] = series(
packageWin32,
packageKeytarWin32
);

exports["packageLinux"] = series(
packageLinux,
packageKeytarLinux
);

exports["packageMacOS"] = series(
packageMacOS,
packageKeytarMacOS
);

exports["zipWin32"] = zipWin32;
exports["zipLinux"] = zipLinux;
Expand All @@ -308,6 +496,6 @@ exports['package'] = parallel(
packageMaterialIcons,
packageLicense,
packageSrc,
packageCommon
packageCommon,
packageKeytar
);

54 changes: 52 additions & 2 deletions gulp/gulpfile.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ const { createWriteStream, existsSync } = require('fs');
const path = require('path');
const yazl = require('yazl');



const BASE_DIR_BOOTSTRAP = "./node_modules/bootstrap/dist";
const BASE_DIR_MATERIALICONS = "./node_modules/material-design-icons-iconfont/dist";
const BASE_DIR_JQUERY = "./node_modules/jquery/dist";
Expand Down Expand Up @@ -352,8 +350,60 @@ async function compress(source, destination, options) {
});
}

/**
* Downloads a file from an https url and stores the data into the given file.
* It follows redirects. Upon a non 200 status code an error is thrown.
*
* @param {string} url
* the download url.
* @param {string} destination
* the file into which the downloaded data should be stored.
* In case the file exists it will be silently overwritten.
*/
async function download(url, destination) {

"use strict";

const https = require('https');
const fs = require('fs');

logger.debug(`Downloading ${url} to ${destination}`);

return await new Promise((resolve, reject) => {
https.get(url, async function (response) {

try {
if (response.statusCode >= 300 && response.statusCode <= 308) {
logger.debug(`Following redirect to ${response.headers.location}`);
await download(response.headers.location, destination);

resolve();
return;
}

if (response.statusCode !== 200)
throw Error(`Response failed with status code ${response.statusCode}.`);

const file = fs.createWriteStream(destination);

response.pipe(file);

file.on('finish', function () {
file.close(function () {
resolve();
});
});
} catch (ex) {
reject(new Error(ex));
}
});
});
}


exports["clean"] = clean;
exports["compress"] = compress;
exports["download"] = download;

exports["packageJQuery"] = packageJQuery;
exports["packageCodeMirror"] = packageCodeMirror;
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
"eslint-plugin-jsdoc": "^24.0.0",
"gulp": "^4.0.0",
"jquery": "~3.4.1",
"keytar": "^5.4.0",
"material-design-icons-iconfont": "^5.0.1",
"tar": "^6.0.1",
"yazl": "^2.5.1"
},
"scripts": {
Expand All @@ -35,4 +37,4 @@
"url": "https://github.com/thsmi/sieve/issues"
},
"homepage": "https://github.com/thsmi/sieve#readme"
}
}
Loading