├── .gitignore ├── .travis.yml ├── downloaded └── readme.md ├── collaborators.md ├── cli.js ├── package.json ├── readme.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | downloaded/*.zip 2 | node_modules 3 | .DS_Store -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.12' 4 | -------------------------------------------------------------------------------- /downloaded/readme.md: -------------------------------------------------------------------------------- 1 | downloaded atom shell zips get cached here -------------------------------------------------------------------------------- /collaborators.md: -------------------------------------------------------------------------------- 1 | ## Collaborators 2 | 3 | atom-shell-packager is only possible due to the excellent work of the following collaborators: 4 | 5 | 6 | 7 | 8 |
maxogdenGitHub/maxogden
mafintoshGitHub/mafintosh
remixzGitHub/remixz
9 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var args = require('minimist')(process.argv.slice(2)) 3 | var packager = require('./') 4 | 5 | args.dir = args._[0] 6 | args.name = args._[1] 7 | 8 | var protocolSchemes = [].concat(args.protocol || []) 9 | var protocolNames = [].concat(args['protocol-name'] || []) 10 | 11 | if (protocolSchemes && protocolNames && protocolNames.length === protocolSchemes.length) { 12 | args.protocols = protocolSchemes.map(function (scheme, i) { 13 | return {schemes: [scheme], name: protocolNames[i]} 14 | }) 15 | } 16 | 17 | if (!args.dir || !args.name) { 18 | console.error('Usage: atom-shell-packager ') 19 | process.exit(1) 20 | } 21 | 22 | packager(args, function done (err, appPath) { 23 | if (err) { 24 | console.error(err) 25 | process.exit(1) 26 | } 27 | 28 | console.error('Wrote new app to', appPath) 29 | }) 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "atom-shell-packager", 3 | "version": "2.1.1", 4 | "description": "package and distribute your atom-shell app in OS executables (.app, .exe, etc) via JS or CLI", 5 | "main": "index.js", 6 | "bin": { 7 | "atom-shell-packager": "cli.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/maxogden/atom-shell-packager.git" 12 | }, 13 | "author": "max ogden", 14 | "license": "BSD", 15 | "bugs": { 16 | "url": "https://github.com/maxogden/atom-shell-packager/issues" 17 | }, 18 | "homepage": "https://github.com/maxogden/atom-shell-packager", 19 | "dependencies": { 20 | "minimist": "^1.1.1", 21 | "mkdirp": "^0.5.0", 22 | "ncp": "^2.0.0", 23 | "plist": "^1.1.0", 24 | "rimraf": "^2.3.2" 25 | }, 26 | "devDependencies": { 27 | "standard": "^3.3.2" 28 | }, 29 | "scripts": { 30 | "test": "standard" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # atom-shell-packager 2 | 3 | Build a distributable app from an atom-shell app source code directory. **Currently only Mac OS is implemented** but you can send PRs to implement windows/linux :) 4 | 5 | [![NPM](https://nodei.co/npm/atom-shell-packager.png)](https://nodei.co/npm/atom-shell-packager/) 6 | 7 | [![Build Status](https://travis-ci.org/maxogden/atom-shell-packager.svg?branch=master)](https://travis-ci.org/maxogden/atom-shell-packager) 8 | 9 | For an example project using this, check out [Monu](https://github.com/maxogden/monu) 10 | 11 | ### installation 12 | 13 | ``` 14 | # for use in npm scripts 15 | npm i atom-shell-packager --save-dev 16 | 17 | # for use from cli 18 | npm i atom-shell-packager -g 19 | 20 | # you also need atom-shell installed 21 | npm i atom-shell 22 | ``` 23 | 24 | ### usage 25 | 26 | ``` 27 | $ atom-shell-packager my-app-source-dir AppName 28 | ``` 29 | 30 | This will: 31 | 32 | - Find the closest local version of `atom-shell` installed (using `require.resolve`) 33 | - Use that version of atom-shell to create a Mac app in `cwd` called `AppName.app` 34 | 35 | You should be able to double-click `AppName.app` to launch the app. If not, check your settings and try again. 36 | 37 | **Be careful** not to include node_modules you don't want into your final app. For example, do not include the `node_modules/atom-shell-packager` folder or `node_modules/atom-shell`. You can use `--ignore=node_modules/atom-shell` to ignore of these 38 | 39 | ### options 40 | 41 | these are optional CLI options you can pass in 42 | 43 | - `out` (default current working dir) - the dir to put the app into at the end 44 | - `version` (default hardcoded in source) - atom-shell version to use 45 | - `app-bundle-id` - bundle identifier to use in the app plist 46 | - `helper-bundle-id` - bundle identifier to use in the app helper plist 47 | - `ignore` (default none) - do not copy files into App whose filenames regex .match this string 48 | - `prune` - runs `npm prune --production` on the app 49 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var os = require('os') 2 | var path = require('path') 3 | var fs = require('fs') 4 | var child = require('child_process') 5 | 6 | var plist = require('plist') 7 | var mkdirp = require('mkdirp') 8 | var rimraf = require('rimraf') 9 | var ncp = require('ncp').ncp 10 | 11 | module.exports = function packager (opts, cb) { 12 | var atomShellPath 13 | 14 | try { 15 | atomShellPath = require.resolve('atom-shell') 16 | atomShellPath = path.join(atomShellPath, '..') 17 | } catch (e) { 18 | try { 19 | atomShellPath = require.resolve(path.join(process.execPath, '../../lib/node_modules/atom-shell')) 20 | atomShellPath = path.join(atomShellPath, '..') 21 | } catch (e) { 22 | cb(new Error('Cannot find atom-shell from here, please install it from npm')) 23 | } 24 | } 25 | 26 | var atomPkg = require(path.join(atomShellPath, 'package.json')) 27 | console.error('Using atom-shell version', atomPkg.version, 'from', atomShellPath) 28 | 29 | var atomShellApp = path.join(atomShellPath, 'dist', 'Atom.app') 30 | var tmpDir = path.join(os.tmpdir(), 'atom-shell-packager-mac') 31 | 32 | var newApp = path.join(tmpDir, opts.name + '.app') 33 | 34 | // reset build folders + copy template app 35 | rimraf(tmpDir, function rmrfd () { 36 | // ignore errors 37 | mkdirp(newApp, function mkdirpd () { 38 | // ignore errors 39 | // copy .app folder and use as template (this is exactly what Atom editor does) 40 | ncp(atomShellApp, newApp, function copied (err) { 41 | if (err) return cb(err) 42 | buildMacApp() 43 | }) 44 | }) 45 | }) 46 | 47 | function buildMacApp () { 48 | var paths = { 49 | info1: path.join(newApp, 'Contents', 'Info.plist'), 50 | info2: path.join(newApp, 'Contents', 'Frameworks', 'Atom Helper.app', 'Contents', 'Info.plist'), 51 | app: path.join(newApp, 'Contents', 'Resources', 'app') 52 | } 53 | 54 | // update plist files 55 | var pl1 = plist.parse(fs.readFileSync(paths.info1).toString()) 56 | var pl2 = plist.parse(fs.readFileSync(paths.info2).toString()) 57 | 58 | var bundleId = opts['app-bundle-id'] || 'com.atom-shell.' + opts.name.toLowerCase() 59 | var bundleHelperId = opts['helper-bundle-id'] || 'com.atom-shell.' + opts.name.toLowerCase() + '.helper' 60 | 61 | pl1.CFBundleDisplayName = opts.name 62 | pl1.CFBundleIdentifier = bundleId 63 | pl1.CFBundleName = opts.name 64 | pl2.CFBundleIdentifier = bundleHelperId 65 | pl2.CFBundleName = opts.name 66 | 67 | if (opts.protocols) { 68 | pl2.CFBundleURLTypes = pl1.CFBundleURLTypes = opts.protocols.map(function (protocol) { 69 | return { 70 | CFBundleURLName: protocol.name, 71 | CFBundleURLSchemes: [].concat(protocol.schemes) 72 | } 73 | }) 74 | } 75 | 76 | fs.writeFileSync(paths.info1, plist.build(pl1)) 77 | fs.writeFileSync(paths.info2, plist.build(pl2)) 78 | 79 | function filter (file) { 80 | var ignore = opts.ignore || [] 81 | if (!Array.isArray(ignore)) ignore = [ignore] 82 | for (var i = 0; i < ignore.length; i++) { 83 | if (file.match(ignore[i])) { 84 | return false 85 | } 86 | } 87 | return true 88 | } 89 | 90 | // copy users app into .app 91 | ncp(opts.dir, paths.app, {filter: filter}, function copied (err) { 92 | if (err) return cb(err) 93 | 94 | if (opts.prune) { 95 | prune(function pruned (err) { 96 | if (err) return cb(err) 97 | moveApp() 98 | }) 99 | } else { 100 | moveApp() 101 | } 102 | 103 | function prune (cb) { 104 | child.exec('npm prune --production', { cwd: paths.app }, cb) 105 | } 106 | 107 | function moveApp () { 108 | // finally, move app into cwd 109 | var finalPath = path.join(opts.out || process.cwd(), opts.name + '.app') 110 | 111 | fs.rename(newApp, finalPath, function moved (err) { 112 | cb(err, finalPath) 113 | }) 114 | } 115 | }) 116 | } 117 | } 118 | --------------------------------------------------------------------------------