Cleanup app build script and add more detailed documentation

This commit is contained in:
hensm
2019-03-05 05:26:42 +00:00
parent 95eb3ad9b5
commit 45908a09e1
2 changed files with 146 additions and 53 deletions

View File

@@ -7,9 +7,9 @@ const minimist = require("minimist");
const glob = require("glob"); const glob = require("glob");
const mustache = require("mustache"); const mustache = require("mustache");
const makensis = require("makensis"); const makensis = require("makensis");
const pkg = require("pkg");
const { spawnSync } = require("child_process"); const { spawnSync } = require("child_process");
const { exec: pkgExec } = require("pkg");
const { __applicationName: applicationName const { __applicationName: applicationName
, __applicationVersion: applicationVersion } = require("../package.json"); , __applicationVersion: applicationVersion } = require("../package.json");
@@ -25,6 +25,7 @@ const { executableName
, WIN_REGISTRY_KEY } = require("./lib/paths"); , WIN_REGISTRY_KEY } = require("./lib/paths");
// Command line args
const argv = minimist(process.argv.slice(2), { const argv = minimist(process.argv.slice(2), {
boolean: [ "package" ] boolean: [ "package" ]
, string: [ "platform", "arch", "packageType" ] , string: [ "platform", "arch", "packageType" ]
@@ -42,11 +43,12 @@ const SRC_PATH = path.join(ROOT_PATH, "src");
const BUILD_PATH = path.join(ROOT_PATH, "build"); const BUILD_PATH = path.join(ROOT_PATH, "build");
// Clean /**
* Shouldn't exist, but cleanup and re-create any existing
* build directories, just in case.
*/
fs.removeSync(BUILD_PATH); fs.removeSync(BUILD_PATH);
fs.removeSync(DIST_PATH); fs.removeSync(DIST_PATH);
// Make directories
fs.ensureDirSync(BUILD_PATH); fs.ensureDirSync(BUILD_PATH);
fs.ensureDirSync(DIST_PATH, { recursive: true }); fs.ensureDirSync(DIST_PATH, { recursive: true });
@@ -72,66 +74,91 @@ async function build () {
}); });
// Create app manifest /**
* Native app manifest
* https://mdn.io/Native_manifests#Native_messaging_manifests
*/
const manifest = { const manifest = {
"name": applicationName "name": applicationName
, "description": "" , "description": ""
, "type": "stdio" , "type": "stdio"
, "allowed_extensions": [ extensionId ] , "allowed_extensions": [ extensionId ]
, "path": argv.package
// Add either installed path or dist path
? (argv.platform === "win32" ? path.win32 : path)
.join(executablePath[argv.platform]
, executableName[argv.platform])
: path.join(DIST_PATH, executableName[argv.platform])
}; };
// Write manifest /**
* If packaging, add the installed executable path, otherwise
* add the path to the built executable in the dist folder.
*/
if (argv.package) {
// If packaging for windows, use win32 path helpers.
manifest.path = (argv.platform === "win32" ? path.win32 : path)
.join(executablePath[argv.platform]
, executableName[argv.platform]);
} else {
manifest.path = path.join(DIST_PATH, executableName[argv.platform]);
}
// Write app manifest
fs.writeFileSync(path.join(BUILD_PATH, manifestName) fs.writeFileSync(path.join(BUILD_PATH, manifestName)
, JSON.stringify(manifest, null, 4)); , JSON.stringify(manifest, null, 4));
// File permissions // Ensure file permissions are correct
for (const file of fs.readdirSync(BUILD_PATH)) { for (const file of fs.readdirSync(BUILD_PATH)) {
fs.chmodSync(path.resolve(BUILD_PATH, file), 0o755); fs.chmodSync(path.resolve(BUILD_PATH, file), 0o755);
} }
// Need a minimal package.json for pkg.
const pkgManifest = { const pkgManifest = {
bin: "main.js" bin: "main.js"
, pkg: { , pkg: {
// Workaround for pkg asset detection /**
// https://github.com/thibauts/node-castv2/issues/46 * Workaround for pkg asset detection
* https://github.com/thibauts/node-castv2/issues/46
*/
"assets": "../node_modules/castv2/lib/cast_channel.proto" "assets": "../node_modules/castv2/lib/cast_channel.proto"
} }
}; };
// Write pkg manifest
fs.writeFileSync(path.join(BUILD_PATH, "package.json") fs.writeFileSync(path.join(BUILD_PATH, "package.json")
, JSON.stringify(pkgManifest)) , JSON.stringify(pkgManifest))
// Package executable // Run pkg to create a single executable
await pkgExec([ await pkg.exec([
BUILD_PATH BUILD_PATH
, "--target", `${pkgPlatform[argv.platform]}-${argv.arch}` , "--target", `${pkgPlatform[argv.platform]}-${argv.arch}`
, "--output", path.join(BUILD_PATH, executableName[argv.platform]) , "--output", path.join(BUILD_PATH, executableName[argv.platform])
]); ]);
/**
* If packaging, create an installer package and move it to
* dist, otherwise move the built executable and app manifest
* to dist.
*/
if (argv.package) { if (argv.package) {
const installerName = await packageApp(argv.platform); const installerName = await packageApp(argv.platform);
if (installerName) { if (installerName) {
// Move installer to dist // Move installer to dist
fs.moveSync(path.join(BUILD_PATH, installerName) fs.moveSync(
path.join(BUILD_PATH, installerName)
, path.join(DIST_PATH, path.basename(installerName)) , path.join(DIST_PATH, path.basename(installerName))
, { overwrite: true }); , { overwrite: true });
} }
} else { } else {
// Move binary / app manifest const builtExecutableName = executableName[argv.platform];
fs.moveSync(path.join(BUILD_PATH, manifestName)
// Move executable and app manifest to dist
fs.moveSync(
path.join(BUILD_PATH, manifestName)
, path.join(DIST_PATH, manifestName) , path.join(DIST_PATH, manifestName)
, { overwrite: true }); , { overwrite: true });
fs.moveSync(path.join(BUILD_PATH, executableName[argv.platform]) fs.moveSync(
, path.join(DIST_PATH, executableName[argv.platform]) path.join(BUILD_PATH, builtExecutableName)
, path.join(DIST_PATH, builtExecutableName)
, { overwrite: true }); , { overwrite: true });
} }
@@ -139,29 +166,60 @@ async function build () {
fs.removeSync(BUILD_PATH); fs.removeSync(BUILD_PATH);
} }
/**
* Takes a platform and returns the path of the created
* install package.
*/
function packageApp (platform) { function packageApp (platform) {
const packageFunctionArgs = [
executableName[platform] // platformExecutableName
, executablePath[platform] // platformExecutablePath
, manifestPath[platform] // platformManifestPath
];
switch (platform) { switch (platform) {
case "darwin": case "win32": return packageWin32(...packageFunctionArgs);
return packageDarwin(platform); case "darwin": return packageDarwin(...packageFunctionArgs);
case "linux": case "linux":
/**
* Get manifest path from package type sub key for Linux
* platforms.
*/
packageFunctionArgs.push(
packageFunctionArgs.pop()[argv.packageType]);
switch (argv.packageType) { switch (argv.packageType) {
case "deb": case "deb": return packageLinuxDeb(...packageFunctionArgs);
return packageLinuxDeb(platform, argv.packageType); case "rpm": return packageLinuxRpm(...packageFunctionArgs);
case "rpm":
return packageLinuxRpm(platform, argv.packageType);
} }
case "win32": break;
return packageWin32(platform);
default: default:
console.log("Cannot build installer package for this platform"); console.log("Cannot build installer package for this platform");
} }
} }
function packageDarwin (platform) { /**
* Builds a macOS Installer package.
*
* Creates a root directory with the installed file system
* structure for package files, bundles the postinstall
* script (packaging/mac/scripts/postinstall), then creates
* a component package.
* Distribution package is built from the component package
* and distribution file (packaging/mac/distribution.xml).
*
* Requires the pkgbuild and productbuild command line
* utilities. Only possible on macOS.
*/
function packageDarwin (
platformExecutableName
, platformExecutablePath
, platformManifestPath) {
const installerName = `${applicationName}.pkg`; const installerName = `${applicationName}.pkg`;
const componentName = `${applicationName}_component.pkg`; const componentName = `${applicationName}_component.pkg`;
@@ -170,16 +228,16 @@ function packageDarwin (platform) {
// Create pkgbuild root // Create pkgbuild root
const rootPath = path.join(BUILD_PATH, "root"); const rootPath = path.join(BUILD_PATH, "root");
const rootExecutablePath = path.join(rootPath, executablePath[platform]); const rootExecutablePath = path.join(rootPath, platformExecutablePath);
const rootManifestPath = path.join(rootPath, manifestPath[platform]); const rootManifestPath = path.join(rootPath, platformManifestPath);
// Create install locations // Create install locations
fs.ensureDirSync(rootExecutablePath, { recursive: true }); fs.ensureDirSync(rootExecutablePath, { recursive: true });
fs.ensureDirSync(rootManifestPath, { recursive: true }); fs.ensureDirSync(rootManifestPath, { recursive: true });
// Move files to root // Move files to root
fs.moveSync(path.join(BUILD_PATH, executableName[platform]) fs.moveSync(path.join(BUILD_PATH, platformExecutableName)
, path.join(rootExecutablePath, executableName[platform])); , path.join(rootExecutablePath, platformExecutableName));
fs.moveSync(path.join(BUILD_PATH, manifestName) fs.moveSync(path.join(BUILD_PATH, manifestName)
, path.join(rootManifestPath, manifestName)); , path.join(rootManifestPath, manifestName));
@@ -192,8 +250,8 @@ function packageDarwin (platform) {
, manifestName , manifestName
, componentName , componentName
, packageId: `tf.matt.${applicationName}` , packageId: `tf.matt.${applicationName}`
, executablePath: executablePath[platform] , executablePath: platformExecutablePath
, manifestPath: manifestPath[platform] , manifestPath: platformManifestPath
}; };
// Template paths // Template paths
@@ -229,24 +287,36 @@ function packageDarwin (platform) {
, { shell: true }); , { shell: true });
return installerName; return installerName;
} }
function packageLinuxDeb (platform, packageType) { /**
* Builds a DEB package for Debian, Ubuntu, Mint, etc...
*
* Creates a root directory with the installed file system
* structure for package files, copies control file
* (packaging/linux/deb/DEBIAN/control) to root, then builds
* package from root.
* Requires the dpkg-deb command line utility.
*/
function packageLinuxDeb (
platformExecutableName
, platformExecutablePath
, platformManifestPath) {
const installerName = `${applicationName}.deb`; const installerName = `${applicationName}.deb`;
// Create root // Create root
const rootPath = path.join(BUILD_PATH, "root"); const rootPath = path.join(BUILD_PATH, "root");
const rootExecutablePath = path.join(rootPath, executablePath[platform]); const rootExecutablePath = path.join(rootPath, platformExecutablePath);
const rootManifestPath = path.join(rootPath const rootManifestPath = path.join(rootPath
, manifestPath[platform][packageType]); , platformManifestPath);
fs.ensureDirSync(rootExecutablePath, { recursive: true }); fs.ensureDirSync(rootExecutablePath, { recursive: true });
fs.ensureDirSync(rootManifestPath, { recursive: true }); fs.ensureDirSync(rootManifestPath, { recursive: true });
// Move files to root // Move files to root
fs.moveSync(path.join(BUILD_PATH, executableName[platform]) fs.moveSync(path.join(BUILD_PATH, platformExecutableName)
, path.join(rootExecutablePath, executableName[platform])); , path.join(rootExecutablePath, platformExecutableName));
fs.moveSync(path.join(BUILD_PATH, manifestName) fs.moveSync(path.join(BUILD_PATH, manifestName)
, path.join(rootManifestPath, manifestName)); , path.join(rootManifestPath, manifestName));
@@ -280,7 +350,18 @@ function packageLinuxDeb (platform, packageType) {
return installerName; return installerName;
} }
function packageLinuxRpm (platform, packageType) { /**
* Builds an RPM package for Fedora, openSUSE, etc...
*
* Templates and uses the spec file
* (packaging/linux/rpm/package.spec) to build the package.
* Requires the rpmbuild command line utility.
*/
function packageLinuxRpm (
platformExecutableName
, platformExecutablePath
, platformManifestPath) {
const specPath = path.join(__dirname const specPath = path.join(__dirname
, "../packaging/linux/rpm/package.spec"); , "../packaging/linux/rpm/package.spec");
@@ -290,9 +371,9 @@ function packageLinuxRpm (platform, packageType) {
packageName: applicationName packageName: applicationName
, applicationName , applicationName
, applicationVersion , applicationVersion
, executablePath: executablePath[platform] , executablePath: platformExecutablePath
, manifestPath: manifestPath[platform][packageType] , manifestPath: platformManifestPath
, executableName: executableName[platform] , executableName: platformExecutableName
, manifestName , manifestName
}; };
@@ -312,7 +393,17 @@ function packageLinuxRpm (platform, packageType) {
return glob.sync("**/*.rpm", { cwd: BUILD_PATH })[0]; return glob.sync("**/*.rpm", { cwd: BUILD_PATH })[0];
} }
function packageWin32 (platform) { /**
* Builds a Windows installer.
*
* Uses NSIS to create a GUI installer with an installer
* script (packaging/win/installer.nsi). Requires the
* makensis command line utility.
*/
function packageWin32 (
platformExecutableName
, platformExecutablePath) {
const scriptPath = path.join(__dirname, "../packaging/win/installer.nsi"); const scriptPath = path.join(__dirname, "../packaging/win/installer.nsi");
const scriptOutputPath = path.join(BUILD_PATH, path.basename(scriptPath)); const scriptOutputPath = path.join(BUILD_PATH, path.basename(scriptPath));
@@ -321,18 +412,20 @@ function packageWin32 (platform) {
const view = { const view = {
applicationName applicationName
, applicationVersion , applicationVersion
, executableName: executableName[platform] , executableName: platformExecutableName
, executablePath: executablePath[platform] , executablePath: platformExecutablePath
, manifestName , manifestName
, winRegistryKey: WIN_REGISTRY_KEY , winRegistryKey: WIN_REGISTRY_KEY
, outFile , outFile
}; };
// Write templated script to build dir
fs.writeFileSync(scriptOutputPath fs.writeFileSync(scriptOutputPath
, mustache.render( , mustache.render(
fs.readFileSync(scriptPath).toString() fs.readFileSync(scriptPath).toString()
, view)); , view));
const output = makensis.compileSync(scriptOutputPath); const output = makensis.compileSync(scriptOutputPath);
if (output.status === 0) { if (output.status === 0) {

2
ext/package-lock.json generated
View File

@@ -6915,7 +6915,7 @@
}, },
"pretty-format": { "pretty-format": {
"version": "3.8.0", "version": "3.8.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", "resolved": "http://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
"integrity": "sha1-v77VbV6ad2ZF9LH/eqGjrE+jw4U=", "integrity": "sha1-v77VbV6ad2ZF9LH/eqGjrE+jw4U=",
"dev": true "dev": true
}, },