diff --git a/README.md b/README.md index f7b9d97..6d48bf3 100644 --- a/README.md +++ b/README.md @@ -215,26 +215,6 @@ In this example `.compilerc`, JavaScript files won't be compiled: By *far*, the easiest way to do this is via using [electron-forge](https://github.com/electron-userland/electron-forge/). electron-forge handles every aspect of packaging your app on all platforms and helping you publish it. Unless you have a very good reason, you should be using it! -## How can I precompile my code for release-time? (the hard way) - -electron-compile comes with a command-line application to pre-create a cache for you. - -```sh -Usage: electron-compile --appdir [root-app-dir] paths... - -Options: - -a, --appdir The top-level application directory (i.e. where your - package.json is) - -v, --verbose Print verbose information - -h, --help Show help -``` - -Run `electron-compile` on all of your application assets, even if they aren't strictly code (i.e. your static assets like PNGs). electron-compile will recursively walk the given directories. - -```sh -electron-compile --appDir /path/to/my/app ./src ./static -``` - ### But I use Grunt / Gulp / I want to do Something Interesting Compilation also has its own API, check out the [documentation](http://electron.github.io/electron-compile/docs/) for more information. diff --git a/package.json b/package.json index 613a24f..6f41a86 100644 --- a/package.json +++ b/package.json @@ -35,15 +35,13 @@ "dependencies": { "@paulcbetts/mime-types": "^2.1.10", "@types/node": "^7.0.12", - "btoa": "^1.1.2", "debug": "^2.5.1", "lru-cache": "^4.0.1", "mkdirp": "^0.5.1", "pify": "^2.3.0", "rimraf": "^2.5.4", "rxjs": "^5.1.1", - "spawn-rx": "^2.0.3", - "yargs": "^4.8.1" + "spawn-rx": "^2.0.3" }, "devDependencies": { "asar": "^0.12.1", diff --git a/src/cli.js b/src/cli.js deleted file mode 100644 index 061cba9..0000000 --- a/src/cli.js +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env node - -import path from 'path'; -import mkdirp from 'mkdirp'; - -import {createCompilerHostFromProjectRoot} from './config-parser'; -import {forAllFiles} from './for-all-files'; - -process.on('unhandledRejection', (e) => { - d(e.message || e); - d(e.stack || ''); -}); - -process.on('uncaughtException', (e) => { - d(e.message || e); - d(e.stack || ''); -}); - -export async function main(appDir, sourceDirs, cacheDir, sourceMapDir) { - let compilerHost = null; - if (!cacheDir || cacheDir.length < 1) { - cacheDir = '.cache'; - } - - let rootCacheDir = path.join(appDir, cacheDir); - mkdirp.sync(rootCacheDir); - let mapDir = rootCacheDir; - - if (sourceMapDir) { - mapDir = path.join(appDir, sourceMapDir); - d(`specifed separate source map dir at ${mapDir}, creating it`); - mkdirp.sync(mapDir); - } - - if (process.env.NODE_ENV !== 'production') { - console.log(`Using NODE_ENV = ${process.env.NODE_ENV || 'development'}`); - } - - d(`main: ${appDir}, ${JSON.stringify(sourceDirs)}`); - try { - compilerHost = await createCompilerHostFromProjectRoot(appDir, rootCacheDir, sourceMapDir); - } catch (e) { - console.error(`Couldn't set up compilers: ${e.message}`); - d(e.stack); - - throw e; - } - - await Promise.all(sourceDirs.map((dir) => forAllFiles(dir, async (f) => { - try { - d(`Starting compilation for ${f}`); - await compilerHost.compile(f); - } catch (e) { - console.error(`Failed to compile file: ${f}`); - console.error(e.message); - - d(e.stack); - } - }))); - - d('Saving out configuration'); - await compilerHost.saveConfiguration(); -} - -const d = require('debug')('electron-compile'); - -const yargs = require('yargs') - .usage('Usage: electron-compile --appdir [root-app-dir] paths...') - .alias('a', 'appdir') - .describe('a', 'The top-level application directory (i.e. where your package.json is)') - .default('a', process.cwd()) - .alias('c', 'cachedir') - .describe('c', 'The directory to put the cache') - .alias('s', 'sourcemapdir') - .describe('s', 'The directory to store sourcemap if compiler configured to have sourcemap file. Default to cachedir if not specified.') - .help('h') - .alias('h', 'help') - .epilog('Copyright 2015'); - -if (process.mainModule === module) { - const argv = yargs.argv; - - if (!argv._ || argv._.length < 1) { - yargs.showHelp(); - process.exit(-1); - } - - const sourceDirs = argv._; - const appDir = argv.a; - const cacheDir = argv.c; - const sourceMapDir = argv.s; - - main(appDir, sourceDirs, cacheDir, sourceMapDir) - .then(() => process.exit(0)) - .catch((e) => { - console.error(e.message || e); - d(e.stack); - - console.error("Compilation failed!\nFor extra information, set the DEBUG environment variable to '*'"); - process.exit(-1); - }); -} diff --git a/src/packager-cli.js b/src/packager-cli.js deleted file mode 100644 index a8b2a04..0000000 --- a/src/packager-cli.js +++ /dev/null @@ -1,163 +0,0 @@ -#!/usr/bin/env node - -import path from 'path'; -import rimraf from 'rimraf'; - -import {pfs} from './promise'; -import {main} from './cli'; - -import {spawnPromise, findActualExecutable} from 'spawn-rx'; - -const d = require('debug')('electron-compile:packager'); -const electronPackager = 'electron-packager'; - -export async function packageDirToResourcesDir(packageDir) { - let appDir = (await pfs.readdir(packageDir)).find((x) => x.match(/\.app$/i)); - if (appDir) { - return path.join(packageDir, appDir, 'Contents', 'Resources', 'app'); - } else { - return path.join(packageDir, 'resources', 'app'); - } -} - -async function copySmallFile(from, to) { - d(`Copying ${from} => ${to}`); - - let buf = await pfs.readFile(from); - await pfs.writeFile(to, buf); -} - -export function splitOutAsarArguments(argv) { - if (argv.find((x) => x.match(/^--asar-unpack$/))) { - throw new Error("electron-compile doesn't support --asar-unpack at the moment, use asar-unpack-dir"); - } - - // Strip --asar altogether - let ret = argv.filter((x) => !x.match(/^--asar/)); - - if (ret.length === argv.length) { return { packagerArgs: ret, asarArgs: null }; } - - let indexOfUnpack = ret.findIndex((x) => x.match(/^--asar-unpack-dir$/)); - if (indexOfUnpack < 0) { - return { packagerArgs: ret, asarArgs: [] }; - } - - let unpackArgs = ret.slice(indexOfUnpack, indexOfUnpack+1); - let notUnpackArgs = ret.slice(0, indexOfUnpack).concat(ret.slice(indexOfUnpack+2)); - - return { packagerArgs: notUnpackArgs, asarArgs: unpackArgs }; -} - -export function parsePackagerOutput(output) { - // NB: Yes, this is fragile as fuck. :-/ - console.log(output); - let lines = output.split('\n'); - - let idx = lines.findIndex((x) => x.match(/Wrote new app/i)); - if (idx < 1) throw new Error(`Packager output is invalid: ${output}`); - lines = lines.splice(idx); - - // Multi-platform case - if (lines[0].match(/Wrote new apps/)) { - return lines.splice(1).filter((x) => x.length > 1); - } else { - return [lines[0].replace(/^.*new app to /, '')]; - } -} - -async function compileAndShim(packageDir) { - let appDir = await packageDirToResourcesDir(packageDir); - - d(`Looking in ${appDir}`); - for (let entry of await pfs.readdir(appDir)) { - if (entry.match(/^(node_modules|bower_components)$/)) continue; - - let fullPath = path.join(appDir, entry); - let stat = await pfs.stat(fullPath); - - if (!stat.isDirectory()) continue; - - d(`Executing electron-compile: ${appDir} => ${entry}`); - await main(appDir, [fullPath]); - } - - d('Copying in es6-shim'); - let packageJson = JSON.parse( - await pfs.readFile(path.join(appDir, 'package.json'), 'utf8')); - - let index = packageJson.main || 'index.js'; - packageJson.originalMain = index; - packageJson.main = 'es6-shim.js'; - - await copySmallFile( - path.join(__dirname, 'es6-shim.js'), - path.join(appDir, 'es6-shim.js')); - - await pfs.writeFile( - path.join(appDir, 'package.json'), - JSON.stringify(packageJson, null, 2)); -} - -export async function runAsarArchive(packageDir, asarUnpackDir) { - let appDir = await packageDirToResourcesDir(packageDir); - - let asarArgs = ['pack', 'app', 'app.asar']; - if (asarUnpackDir) { - asarArgs.push('--unpack-dir', asarUnpackDir); - } - - let { cmd, args } = findExecutableOrGuess('asar', asarArgs); - - d(`Running ${cmd} ${JSON.stringify(args)}`); - await spawnPromise(cmd, args, { cwd: path.join(appDir, '..') }); - rimraf.sync(path.join(appDir)); -} - -export function findExecutableOrGuess(cmdToFind, argsToUse) { - let { cmd, args } = findActualExecutable(cmdToFind, argsToUse); - if (cmd === electronPackager) { - d(`Can't find ${cmdToFind}, falling back to where it should be as a guess!`); - let cmdSuffix = process.platform === 'win32' ? '.cmd' : ''; - return findActualExecutable(path.resolve(__dirname, '..', '..', '.bin', `${cmdToFind}${cmdSuffix}`), argsToUse); - } - - return { cmd, args }; -} - -export async function packagerMain(argv) { - d(`argv: ${JSON.stringify(argv)}`); - argv = argv.splice(2); - - let { packagerArgs, asarArgs } = splitOutAsarArguments(argv); - let { cmd, args } = findExecutableOrGuess(electronPackager, packagerArgs); - - d(`Spawning electron-packager: ${JSON.stringify(args)}`); - let packagerOutput = await spawnPromise(cmd, args); - let packageDirs = parsePackagerOutput(packagerOutput); - - d(`Starting compilation for ${JSON.stringify(packageDirs)}`); - for (let packageDir of packageDirs) { - await compileAndShim(packageDir); - - if (!asarArgs) continue; - - d('Starting ASAR packaging'); - let asarUnpackDir = null; - if (asarArgs.length === 2) { - asarUnpackDir = asarArgs[1]; - } - - await runAsarArchive(packageDir, asarUnpackDir); - } -} - -if (process.mainModule === module) { - packagerMain(process.argv) - .then(() => process.exit(0)) - .catch((e) => { - console.error(e.message || e); - d(e.stack); - - process.exit(-1); - }); -} diff --git a/test/packager-cli.js b/test/packager-cli.js deleted file mode 100644 index b1fdd85..0000000 --- a/test/packager-cli.js +++ /dev/null @@ -1,123 +0,0 @@ -import './support.js'; - -import sfs from 'fs'; -import path from 'path'; -import rimraf from 'rimraf'; -import mkdirp from 'mkdirp'; - -import {packagerMain} from '../src/packager-cli'; - -const d = require('debug')('test:packager-cli'); - -let testCount = 0; - -function statSyncNoException(fsPath) { - if ('statSyncNoException' in sfs) { - return sfs.statSyncNoException(fsPath); - } - - try { - return sfs.statSync(fsPath); - } catch (e) { - return null; - } -} - -describe.skip('the packager CLI', function() { - this.timeout(120 * 1000); - - beforeEach(function() { - this.tempCacheDir = path.join(__dirname, `__packager_cli_${testCount++}`); - mkdirp.sync(this.tempCacheDir); - }); - - afterEach(function() { - rimraf.sync(this.tempCacheDir); - }); - - it('should do the basics of electron-packager', async function() { - let inputApp = path.resolve(__dirname, 'electron-app'); - - // NB: The first two elements are dummies to fake out what would normally - // be the path to node and the path to the script - await packagerMain(['', '', '--platform', 'win32', '--version', '1.3.2', '--arch', 'all', '--out', this.tempCacheDir, inputApp]); - - const toFind = ['node.dll', 'resources', 'resources/app/src/main.coffee']; - let cacheDir = this.tempCacheDir; - - toFind.forEach((name) => { - let file = path.resolve(cacheDir, 'mp3-encoder-demo-win32-ia32', name); - - d(`Looking for ${file}`); - expect(sfs.statSync(file)).to.be.ok; - }); - }); - - it('should run electron-compile', async function() { - let inputApp = path.resolve(__dirname, 'electron-app'); - - // NB: The first two elements are dummies to fake out what would normally - // be the path to node and the path to the script - await packagerMain(['', '', '--platform', 'win32', '--version', '1.3.2', '--arch', 'x64', '--out', this.tempCacheDir, inputApp]); - - const toFind = ['resources/app/.cache', 'resources/app/.compilerc']; - let cacheDir = this.tempCacheDir; - - toFind.forEach((name) => { - let file = path.resolve(cacheDir, 'mp3-encoder-demo-win32-x64', name); - - d(`Looking for ${file}`); - expect(sfs.statSync(file)).to.be.ok; - }); - }); - - it('should replace the init script with es6-shim', async function() { - let inputApp = path.resolve(__dirname, 'electron-app'); - - // NB: The first two elements are dummies to fake out what would normally - // be the path to node and the path to the script - await packagerMain(['', '', '--platform', 'win32', '--version', '1.3.2', '--arch', 'x64', '--out', this.tempCacheDir, inputApp]); - - const toFind = ['resources/app/package.json', 'resources/app/es6-shim.js']; - let cacheDir = this.tempCacheDir; - - toFind.forEach((name) => { - let file = path.resolve(cacheDir, 'mp3-encoder-demo-win32-x64', name); - - d(`Looking for ${file}`); - expect(sfs.statSync(file)).to.be.ok; - }); - - let packageJson = require( - path.join(cacheDir, 'mp3-encoder-demo-win32-x64', 'resources', 'app', 'package.json')); - - expect(packageJson.originalMain).to.equal('main.js'); - expect(packageJson.main).to.equal('es6-shim.js'); - }); - - it('should ASAR archive', async function() { - let inputApp = path.resolve(__dirname, 'electron-app'); - - // NB: The first two elements are dummies to fake out what would normally - // be the path to node and the path to the script - await packagerMain(['', '', '--platform', 'win32', '--version', '1.3.2', '--arch', 'x64', '--asar', '--out', this.tempCacheDir, inputApp]); - - const toFind = ['resources/app.asar']; - let cacheDir = this.tempCacheDir; - - toFind.forEach((name) => { - let file = path.resolve(cacheDir, 'mp3-encoder-demo-win32-x64', name); - - d(`Looking for ${file}`); - expect(statSyncNoException(file)).to.be.ok; - }); - - const toNotFind = ['resources/app']; - toNotFind.forEach((name) => { - let file = path.resolve(cacheDir, 'mp3-encoder-demo-win32-x64', name); - - d(`Looking for ${file}`); - expect(statSyncNoException(file)).not.to.be.ok; - }); - }); -});