insomnia/packages/insomnia-app/scripts/build.ts
2021-07-23 11:04:56 +12:00

258 lines
7.9 KiB
TypeScript

import childProcess from 'child_process';
import { promises, readFileSync, writeFileSync } from 'fs';
import licenseChecker from 'license-checker';
import mkdirp from 'mkdirp';
import { ncp } from 'ncp';
import path from 'path';
import rimraf from 'rimraf';
import webpack from 'webpack';
import appConfig from '../config/config.json';
import electronWebpackConfig from '../webpack/webpack.config.electron';
import productionWebpackConfig from '../webpack/webpack.config.production';
import { getBuildContext } from './getBuildContext';
const { readFile, writeFile } = promises;
// Start build if ran from CLI
if (require.main === module) {
process.nextTick(async () => {
try {
await module.exports.start(false);
} catch (err) {
console.log('[build] ERROR:', err);
process.exit(1);
}
});
}
const buildWebpack = (config: webpack.Configuration) => new Promise<void>((resolve, reject) => {
webpack(config).run((err, stats) => {
if (err) {
reject(err);
return;
}
if (stats?.hasErrors()) {
reject(new Error('Failed to build webpack'));
console.log(stats.toJson().errors);
return;
}
resolve();
});
});
const emptyDir = (relPath: string) => new Promise<void>((resolve, reject) => {
const dir = path.resolve(__dirname, relPath);
rimraf(dir, err => {
if (err) {
reject(err);
} else {
mkdirp.sync(dir);
resolve();
}
});
});
const copyFiles = (relSource: string, relDest: string) => new Promise<void>((resolve, reject) => {
const source = path.resolve(__dirname, relSource);
const dest = path.resolve(__dirname, relDest);
console.log(`[build] copy "${relSource}" to "${relDest}"`);
ncp(source, dest, err => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
const buildLicenseList = (relSource: string, relDest: string) => new Promise<void>((resolve, reject) => {
const source = path.resolve(__dirname, relSource);
const dest = path.resolve(__dirname, relDest);
mkdirp.sync(path.dirname(dest));
licenseChecker.init(
{
start: source,
production: true,
},
(err, packages) => {
if (err) {
return reject(err);
}
const header = [
'This application bundles the following third-party packages in ',
'accordance with the following licenses:',
'-------------------------------------------------------------------------',
'',
'',
].join('\n');
const out = Object.keys(packages).sort().map(packageName => {
const { licenses, repository, publisher, email, licenseFile: lf } = packages[packageName];
const licenseFile = (lf || '').includes('README') ? null : lf;
return [
'-------------------------------------------------------------------------',
'',
`PACKAGE: ${packageName}`,
licenses ? `LICENSES: ${licenses}` : null,
repository ? `REPOSITORY: ${repository}` : null,
publisher ? `PUBLISHER: ${publisher}` : null,
email ? `EMAIL: ${email}` : null,
'',
licenseFile ? readFileSync(licenseFile) : '[no license file]',
'',
'',
].filter(v => v !== null).join('\n');
}).join('\n');
writeFileSync(dest, `${header}${out}`);
resolve();
},
);
});
const install = (relDir: string) => new Promise<void>((resolve, reject) => {
const prefix = path.resolve(__dirname, relDir);
const p = childProcess.spawn('npm', ['install', '--production', '--no-optional'], {
cwd: prefix,
shell: true,
});
p.stdout.on('data', data => {
console.log(data.toString());
});
p.stderr.on('data', data => {
console.log(data.toString());
});
p.on('exit', code => {
console.log(`child process exited with code ${code}`);
if (code === 0) {
resolve();
} else {
reject(new Error('[build] failed to install dependencies'));
}
});
});
const generatePackageJson = async (relBasePkg: string, relOutPkg: string) => {
// Read package.json's
const basePath = path.resolve(__dirname, relBasePkg);
const outPath = path.resolve(__dirname, relOutPkg);
const inputFile = String(await readFile(basePath));
const basePkg = JSON.parse(inputFile);
const appPkg = {
name: appConfig.name,
version: appConfig.version,
productName: appConfig.productName,
longName: appConfig.longName,
description: basePkg.description,
license: basePkg.license,
homepage: basePkg.homepage,
author: basePkg.author,
copyright: `Copyright © ${new Date().getFullYear()} ${basePkg.author}`,
main: 'main.min.js',
dependencies: {},
};
console.log(`[build] Generated build config for ${appPkg.name} ${appPkg.version}`);
for (const key of Object.keys(appPkg)) {
if (key === undefined) {
throw new Error(`[build] missing "app.${key}" from package.json`);
}
}
// Figure out which dependencies to pack
const allDependencies = Object.keys(basePkg.dependencies);
const packedDependencies = basePkg.packedDependencies;
const unpackedDependencies = allDependencies.filter(name => !packedDependencies.includes(name));
// Add dependencies
console.log(`[build] Adding ${unpackedDependencies.length} node dependencies`);
for (const name of unpackedDependencies) {
const version = basePkg.dependencies[name];
if (!version) {
throw new Error(`Failed to find packed dep "${name}" in dependencies`);
}
appPkg.dependencies[name] = version;
}
const outputFile = JSON.stringify(appPkg, null, 2);
await writeFile(outPath, outputFile);
};
export const start = async ({ forceFromGitRef }: { forceFromGitRef: boolean }) => {
const buildContext = getBuildContext(forceFromGitRef);
const { gitRef, smokeTest, version } = buildContext;
if (smokeTest) {
console.log('[build] Starting build to smoke test');
} else {
if (!version) {
if (!gitRef) {
console.log('[build] No git ref found. Check for the presence of a `GIT_TAG`, `GITHUB_REF`, `TRAVIS_TAG`, or `TRAVIS_CURRENT_BRANCH` environment variable');
} else {
console.log(`[build] git ref \`${gitRef}\` found`);
}
console.log('[build] Skipping build because no version was found (the version is derived from the git ref)');
process.exit(1);
}
if (appConfig.version !== version) {
const tags = `${appConfig.version} != ${version}`;
console.log(`[build] App version mismatch with Git tag ${tags}`);
process.exit(1);
}
console.log(`[build] Starting build for ref "${gitRef}"`);
}
console.log(`[build] npm: ${childProcess.spawnSync('npm', ['--version']).stdout}`.trim());
console.log(`[build] node: ${childProcess.spawnSync('node', ['--version']).stdout}`.trim());
if (process.version.indexOf('v12.') !== 0) {
console.log('[build] Node v12.x.x is required to build');
process.exit(1);
}
const buildFolder = path.join('../build');
// Remove folders first
console.log('[build] Removing existing directories');
await emptyDir(buildFolder);
// Build the things
console.log('[build] Building license list');
await buildLicenseList('../', path.join(buildFolder, 'opensource-licenses.txt'));
console.log('[build] Building Webpack renderer');
await buildWebpack(productionWebpackConfig as webpack.Configuration);
console.log('[build] Building Webpack main');
await buildWebpack(electronWebpackConfig as webpack.Configuration);
// Copy necessary files
console.log('[build] Copying files');
await copyFiles('../bin', buildFolder);
await copyFiles('../app/static', path.join(buildFolder, 'static'));
await copyFiles('../app/icons', buildFolder);
// Generate necessary files needed by `electron-builder`
await generatePackageJson('../package.json', path.join(buildFolder, 'package.json'));
// Install Node modules
console.log('[build] Installing dependencies');
await install(buildFolder);
console.log('[build] Complete!');
return buildContext;
};