├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── TODO.md ├── bin └── aspm ├── index.js ├── lib ├── cli.js └── index.js ├── package.json ├── src ├── cli.coffee └── index.coffee └── test ├── basic-test.coffee └── node-pre-gyp ├── app1.tar.gz ├── app3.tar.gz └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | tmp 4 | atom-shell 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.11" 4 | # disabled because of CERT_UNTRUSTED error while running node-gyp 5 | #- "0.10" 6 | before_install: 7 | - "sudo apt-get update" 8 | # we need these packages to run atom-shell 9 | - "sudo apt-get install -y libgtk2.0-0:i386 libgconf2-4:i386 libnss3:i386 libasound2-dev:i386 libxtst6:i386 libcap2:i386" 10 | # we need these packages to compile for ia32 11 | # also, we need to install these last for some reason 12 | - "sudo apt-get install -y gcc-multilib g++-multilib" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Benjamin Winkler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aspm - Atom-Shell package manager 2 | [![build status](http://img.shields.io/travis/bwin/aspm/master.svg?style=flat-square)](https://travis-ci.org/bwin/aspm) 3 | [![dependencies](http://img.shields.io/david/bwin/aspm.svg?style=flat-square)](https://david-dm.org/bwin/aspm) 4 | [![npm version](http://img.shields.io/npm/v/aspm.svg?style=flat-square)](https://npmjs.org/package/aspm) 5 | 6 | > A node CLI script like npm but for Atom-Shell. Install and build npm-modules for Atom-Shell. 7 | 8 | `aspm` is designed as a replacement for `npm` if you're working on an Atom-Shell project. 9 | 10 | **Warning:** *May be unreliable at the moment.* 11 | 12 | **Table of Contents** 13 | - [Prequisities](#prequisities) 14 | - [Installation](#installation) 15 | - [Quick-Start](#quick-start) 16 | - [Usage](#usage) 17 | - [Configuration (optional)](#configuration-optional) 18 | - [Overriding configuration](#overriding-configuration) 19 | - [Without configuration](#without-configuration) 20 | - [Examples](#examples) 21 | - [How it works](#how-it-works) 22 | - [Under the hood](#under-the-hood) 23 | - [Support for modules that use `node-pre-gyp`](#support-for-modules-that-use-node-pre-gyp) 24 | - [BTW](#btw) 25 | 26 | ## Motivation 27 | There are several ways to build modules for atom-shell, but none of them felt quite right for my needs. Also, since `node-pre-gyp` doesn't yet support atom-shell, it gets even worse for modules using that. 28 | This tries to be as convenient as possible for a small project. Just use it for everything in your project as you would use `npm`. Most of the time it does just pass-through to `npm` anyway. 29 | If you're compiling for several platforms/atom-shell-versions on the same machine, it probably makes more sense to use Grunt build tasks (see [grunt-build-atom-shell](https://github.com/paulcbetts/grunt-build-atom-shell). This also allows renaming the executable file on Windows. Probably even more.). 30 | 31 | Maybe this is unnecessary, does it the wrong way, is generally a stupid idea or whatever. (But at the time it works for me and makes updating atom-shell easier for me. Atom-shell-starter and grunt-build-atom-shell came after I started my current project.) 32 | 33 | ## Prequisities 34 | Since you're using Atom-Shell you most likely have those installed already. 35 | - `node` & `npm`(global) 36 | - `node-gyp`(global), also fulfill it's [requirements](https://github.com/TooTallNate/node-gyp#installation). 37 | 38 | ## Installation 39 | Install (preferred globally) with `npm install aspm -g`. 40 | 41 | ## Quick-Start 42 | - Install globally with npm. `npm install -g aspm` 43 | - Add some configuration to your package.json. (See Configuration. This is optional but highly recommended.) 44 | - Now use `aspm` in place of `npm` in your project. 45 | 46 | ## Usage 47 | ``` 48 | Usage: aspm [options] [command] 49 | 50 | Commands: 51 | 52 | install|i [module] install module (fetch & build) 53 | fetch|f [module] fetch module 54 | build|b build module 55 | 56 | Options: 57 | 58 | -h, --help output usage information 59 | -V, --version output the version number 60 | -t, --target Atom-Shell version 61 | -a, --arch target architecture 62 | -p, --target-platform target platform 63 | -s, --save save as dependency to package.json 64 | -s, --save-dev save as devDependency to package.json 65 | -g install globally with normal npm 66 | --tarball [url/path] install from [remote] tarball 67 | --quiet don't say anything 68 | ``` 69 | 70 | Most npm commands are just passed trough to npm (dedupe, shrinkwrap, etc. should all work normally). 71 | 72 | Unlike npm which takes its parameters in the form `--target=1.2.3`, aspm expects `--target 1.2.3`. 73 | 74 | Also if you try to install globally with `-g` we bail out and just pretend you used npm in the first place, since it wouldn't make much sense to install for atom-shell globally. 75 | To do that anyways, you can use the `--g-yes-install-for-atom-shell-globally` flag. (No, you can't.) 76 | 77 | ## Configuration (optional) 78 | You can (and should to make things more convenient) configure default values for target, arch and platform in your `package.json`. 79 | ```js 80 | { 81 | "config": { 82 | "atom-shell": { 83 | "version": "0.19.5", 84 | "arch": "ia32", 85 | "platform": "win32" 86 | } 87 | } 88 | } 89 | ``` 90 | This way you can use it just like npm without additional parameters (for basic tasks as shown in usage). 91 | 92 | ### Overriding configuration 93 | You can always set/override some or all configuration values. For example: `aspm install --target 0.19.5 --arch ia32`. 94 | 95 | ### Without configuration 96 | **Important:** If you don't specify default values, you'll always have to provide at least a target and arch. 97 | 98 | ## Examples 99 | ``` 100 | # Install all modules from package.json 101 | aspm install 102 | 103 | # Install specific module and save as dependency in package.json 104 | aspm install serialport --save 105 | 106 | # Install specific module in a specific version and save as dependency in package.json 107 | aspm install sqlite3@3.0.4 --save 108 | 109 | # Install multiple module and save as dependency in package.json 110 | aspm install pathwatcher v8-profiler --save 111 | 112 | # Install module from tarball 113 | # In contrast to npm you have to specify the module name here too. 114 | aspm install sqlite3 --tarball https://github.com/mapbox/node-sqlite3/archive/master.tar.gz --target 0.19.5 --arch ia32 115 | 116 | # Build a specific module for a specific target 117 | aspm build leveldown --target 0.19.5 --arch ia32 118 | # or shorter 119 | aspm b leveldown -t 0.19.5 -a ia32 120 | 121 | # fetch all modules from package.json, then build all in a separate step 122 | aspm fetch 123 | aspm build 124 | ``` 125 | 126 | ## How it works 127 | 128 | ### Under the hood 129 | To fetch the modules we just call out to `npm` with `--ignore-scripts`. To build we use `node-gyp` with some additional arguments. 130 | 131 | ### Support for modules that use `node-pre-gyp` 132 | We have basic support for compiling modules that use `node-pre-gyp` (i.e. `sqlite3`) by faking some stuff. 133 | 134 | ## Hints 135 | - [sqlite3]() in version 3.0.4 (current npm) won't compile for atom-shell >= 0.18.0. We can't change that. But it's possible to build with the current github master. (And it will when 3.0.5 or whatever comes next is released.) 136 | - The [test suite](https://travis-ci.org/bwin/aspm) compiles and requires a few modules for a few atom-shell versions on ia32 and x64. 137 | - Modules/Packages: `time@0.11.0`, `leveldown@1.0.0`, `nslog@1.0.1`, `pathwatcher@2.3.5`, `serialport@1.4.9`, `zipfile@0.5.4`, `v8-profiler@5.2.1`, `sqlite3@3.0.4` & `sqlite3@master`(see above) 138 | - Atom-Shell versions: 0.17.2, 0.19.5, current 139 | - Platforms: linux ia32 & x64 on travis-ci and locally tested in a vm on Windows 7 140 | 141 | - I don't really know about Mac compability, since I don't have one here at the moment. 142 | - Modules with native dependencies won't get compiled at the moment. Well, their dependencies won't be. This makes using something like `nano` (which depends on `serialport`) impossible at the moment. 143 | 144 | ## BTW 145 | There may or may not be several (maybe better?) alternatives to this. 146 | - https://github.com/atom/atom-shell/blob/master/docs/tutorial/using-native-node-modules.md 147 | 148 | I haven't looked into these, yet. 149 | - https://github.com/paulcbetts/grunt-build-atom-shell 150 | - https://github.com/atom/atom-shell-starter in scripts/ 151 | - https://github.com/probablycorey/atom-node-module-installer -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | ## sooner 4 | - [ ] readme: add motivation 5 | - [ ] add tests for more modules 6 | - [ ] add commands: rebuild, update 7 | - [ ] tests: "modules /w yyy": "moduleName": "moduleVersion"(?): "for atom-shell...": "build"/"require" 8 | - [ ] pass trough whole argv to npm install plus(maybe) --ignore-scripts 9 | - [ ] build modules recursively !! 10 | - [ ] add examples directory 11 | - [ ] test using `--build-from-source` for better compability 12 | - [ ] set npm_home to ~/.aspm 13 | - [ ] 14 | 15 | ## next 16 | - [x] meta[test]: install and build in tmp/ 17 | - [ ] investigate npm-only build way 18 | - [ ] fix building `node-sass` 19 | - [ ] fix building `nodegit` 20 | - [ ] readme: better explain under the hood 21 | - [ ] readme: better explain node-pre-gyp support (you get the "node" and the "gyp", without the "pre") 22 | - [ ] allow overriding of `--dist-url` (also from config) 23 | - [ ] error handling/reporting 24 | - [ ] gist with atom-shell-require-sqlite3-test 25 | - [ ] add ~/.aspm/config.json 26 | - [ ] 27 | 28 | ## later 29 | - [ ] test cross-compilation with `--target-platform` 30 | - [ ] ask someone else to test on darwin (lago1283?) 31 | - [ ] PR node-pre-gyp: patch versioning.js and republish to npm 32 | - [ ] PR node-sqlite3: update node-pre-gyp and republish to npm 33 | - [ ] `--no-colors` 34 | - [ ] 35 | 36 | # MAYBE's 37 | - [ ] simplify installModule? 38 | - [ ] new branch: use `minimist` 39 | - [ ] add `aspm init`? 40 | - [ ] add `aspm config`? 41 | - [ ] 42 | -------------------------------------------------------------------------------- /bin/aspm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('../lib/cli'); 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('./lib'); -------------------------------------------------------------------------------- /lib/cli.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.8.0 2 | var aspm, cmd, exec, fail, pkg, program, _ref, 3 | __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; 4 | 5 | exec = require('child_process').exec; 6 | 7 | program = require('commander'); 8 | 9 | aspm = require('../lib'); 10 | 11 | pkg = require('../package.json'); 12 | 13 | fail = function(err) { 14 | console.error(err.message); 15 | console.error("not ok".red); 16 | process.exit(1); 17 | }; 18 | 19 | program.version("v" + pkg.version).description('Install and build npm modules for Atom-Shell').option('-t, --target ', 'Atom-Shell version').option('-a, --arch ', 'target architecture').option('-p, --target-platform ', 'target platform').option('-s, --save', 'save as dependency to package.json').option('-s, --save-dev', 'save as devDependency to package.json').option('-g', 'install globally with normal npm').option('--tarball [url/path]', 'install from [remote] tarball').option('--quiet', "don't say anything").option('--run-scripts', "do not use --ignore-scripts flag").option('--compatibility', "try for max compability"); 20 | 21 | program.command('install [module]').alias('i').description('install module (fetch & build)').action(function(module) { 22 | var cmd; 23 | if (program.G) { 24 | cmd = "npm " + (process.argv.slice(2).join(' ')); 25 | return aspm.runCmd(cmd, {}, false, function(err) { 26 | if (err) { 27 | fail(err); 28 | } 29 | return console.log("ok".green); 30 | }); 31 | } else { 32 | return aspm.installModule(module, program, function(err) { 33 | if (err) { 34 | fail(err); 35 | } 36 | return console.log("ok".green); 37 | }); 38 | } 39 | }); 40 | 41 | program.command('fetch [module]').alias('f').description('fetch module').action(function(module) { 42 | return aspm.fetchModule(module, program, function(err) { 43 | if (err) { 44 | fail(err); 45 | } 46 | return console.log("ok".green); 47 | }); 48 | }); 49 | 50 | program.command('build ').alias('b').description('build module').action(function(module) { 51 | return aspm.buildModule(module, program, function(err) { 52 | if (err) { 53 | fail(err); 54 | } 55 | return console.log("ok".green); 56 | }); 57 | }); 58 | 59 | if (_ref = process.argv[2], __indexOf.call('adduser bin bugs bundle cache completion config dedupe deprecate docs edit explore help help-search init info link ls npm outdated owner pack prefix prune publish repo restart rm root run run-script search shrinkwrap star stars start stop tag test uninstall unpublish version view whoami'.split(' '), _ref) >= 0) { 60 | cmd = "npm " + (process.argv.slice(2).join(' ')); 61 | aspm.runCmd(cmd, {}, false, function(err) { 62 | if (err) { 63 | fail(err); 64 | } 65 | return console.log("ok".green); 66 | }); 67 | } else { 68 | program.parse(process.argv); 69 | if (program.args.length === 0) { 70 | program.help(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.8.0 2 | var buildModule, configureModule, exec, fetchModule, fs, os, path, queue, rmDirRecursiveSync, runCmd, semver; 3 | 4 | fs = require('fs'); 5 | 6 | path = require('path'); 7 | 8 | os = require('os'); 9 | 10 | exec = require('child_process').exec; 11 | 12 | queue = require('queue-async'); 13 | 14 | semver = require('semver'); 15 | 16 | require('terminal-colors'); 17 | 18 | rmDirRecursiveSync = function(dirPath) { 19 | var e, filePath, files, i; 20 | try { 21 | files = fs.readdirSync(dirPath); 22 | } catch (_error) { 23 | e = _error; 24 | return; 25 | } 26 | if (files.length > 0) { 27 | i = 0; 28 | while (i < files.length) { 29 | filePath = dirPath + "/" + files[i]; 30 | if (fs.statSync(filePath).isFile()) { 31 | fs.unlinkSync(filePath); 32 | } else { 33 | rmDirRecursiveSync(filePath); 34 | } 35 | i++; 36 | } 37 | } 38 | try { 39 | fs.rmdirSync(dirPath); 40 | } catch (_error) {} 41 | }; 42 | 43 | module.exports.runCmd = runCmd = function(cmd, opts, quiet, cb) { 44 | var child, errMsg; 45 | if (!quiet) { 46 | console.log(("" + (opts.cwd || '') + "> " + cmd).lightBlue); 47 | } 48 | errMsg = ''; 49 | child = exec(cmd, opts); 50 | if (!quiet) { 51 | child.stdout.pipe(process.stdout); 52 | } 53 | if (!quiet) { 54 | child.stderr.pipe(process.stderr); 55 | } else { 56 | child.stderr.on('data', function(chunk) { 57 | errMsg += chunk; 58 | }); 59 | } 60 | child.on('exit', function(code) { 61 | if (code !== 0) { 62 | return typeof cb === "function" ? cb(new Error("command failed: " + cmd + "\n" + errMsg)) : void 0; 63 | } 64 | return typeof cb === "function" ? cb() : void 0; 65 | }); 66 | }; 67 | 68 | configureModule = function(moduleName, opts, nodePreGypParams, cb) { 69 | var cmd, cmdOpts; 70 | cmd = "node-gyp configure " + nodePreGypParams; 71 | cmdOpts = { 72 | cwd: path.join('node_modules', moduleName) 73 | }; 74 | if (opts.cwd) { 75 | cmdOpts.cwd = path.join(opts.cwd, cmdOpts.cwd); 76 | } 77 | runCmd(cmd, cmdOpts, opts.quiet, cb); 78 | }; 79 | 80 | module.exports.fetchModule = fetchModule = function(moduleName, opts, cb) { 81 | var cmd, cmdOpts; 82 | if (!opts.quiet) { 83 | console.log("fetching " + (moduleName || 'all')); 84 | } 85 | if (moduleName == null) { 86 | moduleName = ''; 87 | } 88 | if (opts.tarball) { 89 | moduleName = opts.tarball; 90 | } 91 | cmd = "npm install " + moduleName; 92 | if (!opts.runScripts) { 93 | cmd += ' --ignore-scripts'; 94 | } 95 | if (opts.save) { 96 | cmd += ' --save'; 97 | } 98 | if (opts.saveDev) { 99 | cmd += ' --save-dev'; 100 | } 101 | cmdOpts = {}; 102 | if (opts.cwd) { 103 | cmdOpts.cwd = opts.cwd; 104 | } 105 | runCmd(cmd, cmdOpts, opts.quiet, cb); 106 | }; 107 | 108 | module.exports.buildModule = buildModule = function(moduleName, opts, cb) { 109 | var arch, buildPkg, cmd, cmdOpts, config, cwd, fakeNodePreGyp, modulePath, module_path, modules, nodePreGypParams, nodePreGypPkg, nodePreGypVersion, node_abi, platform, preGyp, preScripts, projectPkg, q, scriptName, target, _i, _j, _len, _len1, _ref, _ref1, _ref2; 110 | projectPkg = require(path.join(process.cwd(), 'package.json')); 111 | config = (_ref = projectPkg.config) != null ? _ref['atom-shell'] : void 0; 112 | target = opts.target || (config != null ? config.version : void 0); 113 | arch = opts.arch || (config != null ? config.arch : void 0); 114 | platform = opts['target-platform'] || (config != null ? config['platform'] : void 0) || os.platform(); 115 | modules = []; 116 | nodePreGypParams = ''; 117 | if (moduleName.indexOf(' ') !== -1) { 118 | modules = moduleName.split(' '); 119 | } 120 | if (!moduleName) { 121 | modules = Object.keys(projectPkg.dependencies); 122 | } 123 | if (modules.length !== 0) { 124 | q = queue(1); 125 | for (_i = 0, _len = modules.length; _i < _len; _i++) { 126 | moduleName = modules[_i]; 127 | q.defer(buildModule, moduleName); 128 | q.awaitAll(function(err) { 129 | return cb(); 130 | }); 131 | } 132 | return; 133 | } 134 | moduleName = moduleName.split('@')[0]; 135 | cwd = process.cwd(); 136 | if (opts.cwd) { 137 | cwd = path.join(cwd, opts.cwd); 138 | } 139 | modulePath = path.join(cwd, 'node_modules', moduleName); 140 | if (!fs.existsSync(path.join(modulePath, 'package.json'))) { 141 | return typeof cb === "function" ? cb(new Error("aspm: module not found '" + moduleName + "'")) : void 0; 142 | } 143 | if (!fs.existsSync(path.join(modulePath, 'binding.gyp'))) { 144 | return cb(); 145 | } 146 | if (!target) { 147 | return typeof cb === "function" ? cb(new Error("aspm: no atom-shell version specified.")) : void 0; 148 | } 149 | if (!arch) { 150 | return typeof cb === "function" ? cb(new Error("aspm: no target architecture specified.")) : void 0; 151 | } 152 | buildPkg = require(path.join(modulePath, 'package.json')); 153 | fakeNodePreGyp = (((_ref1 = buildPkg.dependencies) != null ? _ref1['node-pre-gyp'] : void 0) != null) && (buildPkg.binary != null); 154 | if (fakeNodePreGyp) { 155 | nodePreGypPkg = require(path.join(modulePath, 'node_modules', 'node-pre-gyp', 'package.json')); 156 | nodePreGypVersion = nodePreGypPkg.version; 157 | node_abi = "atom-shell-v" + target; 158 | if (semver.lte(nodePreGypVersion, '999.0.0')) { 159 | node_abi = (function() { 160 | var atomshellToModulesVersion, atomshellToNodeVersion, lookupTable, targetParts, targetSimplified; 161 | atomshellToModulesVersion = { 162 | '0.21.x': 41, 163 | '0.20.x': 17, 164 | "0.19.x": 16, 165 | "0.18.x": 16, 166 | "0.17.x": 15, 167 | "0.16.x": 14 168 | }; 169 | atomshellToNodeVersion = { 170 | '0.21.x': '1.0.0-pre', 171 | '0.20.x': '0.13.0-pre', 172 | '0.19.x': '0.11.14', 173 | '0.18.x': '0.11.14', 174 | '0.17.x': '0.11.14', 175 | '0.16.x': '0.11.13' 176 | }; 177 | lookupTable = (function() { 178 | if (semver.lt(nodePreGypVersion, '0.6.0')) { 179 | return atomshellToModulesVersion; 180 | } 181 | return atomshellToNodeVersion; 182 | })(); 183 | targetParts = target.split('.'); 184 | targetParts[2] = 'x'; 185 | targetSimplified = targetParts.join('.'); 186 | return "node-v" + lookupTable[targetSimplified]; 187 | })(); 188 | } 189 | module_path = buildPkg.binary.module_path; 190 | module_path = module_path.replace('{node_abi}', node_abi).replace('{platform}', os.platform()).replace('{arch}', arch).replace('{module_name}', buildPkg.binary.module_name).replace('{configuration}', 'Release').replace('{version}', buildPkg.version); 191 | preGyp = { 192 | module_name: buildPkg.binary.module_name, 193 | module_path: path.join('..', module_path) 194 | }; 195 | } 196 | if (fakeNodePreGyp) { 197 | nodePreGypParams += " --module_name=" + preGyp.module_name; 198 | nodePreGypParams += " --module_path=" + preGyp.module_path; 199 | } 200 | q = queue(1); 201 | preScripts = 'prepublish preinstall'.split(' '); 202 | if (opts.compatibility) { 203 | preScripts.push('install'); 204 | } 205 | cmdOpts = { 206 | cwd: path.join('node_modules', moduleName) 207 | }; 208 | if (opts.cwd) { 209 | cmdOpts.cwd = path.join(opts.cwd, cmdOpts.cwd); 210 | } 211 | for (_j = 0, _len1 = preScripts.length; _j < _len1; _j++) { 212 | scriptName = preScripts[_j]; 213 | if (((_ref2 = buildPkg.scripts) != null ? _ref2[scriptName] : void 0) != null) { 214 | cmd = "npm run " + scriptName; 215 | q.defer(runCmd, cmd, cmdOpts, opts.quiet); 216 | } 217 | } 218 | q.awaitAll(function(err) { 219 | configureModule(moduleName, opts, nodePreGypParams, function(err) { 220 | if (!opts.quiet) { 221 | console.log("building " + moduleName + " for Atom-Shell v" + target + " " + (os.platform()) + " " + arch); 222 | } 223 | cmd = "node-gyp rebuild --target=" + target + " --arch=" + arch + " --target_platform=" + platform + " --dist-url=https://gh-contractor-zcbenz.s3.amazonaws.com/atom-shell/dist " + nodePreGypParams; 224 | runCmd(cmd, cmdOpts, opts.quiet, function(err) { 225 | var _k, _len2, _ref3, _ref4; 226 | if (err) { 227 | return typeof cb === "function" ? cb(err) : void 0; 228 | } 229 | 230 | /* 231 | unless fakeNodePreGyp 232 | * we move the node_module.node file to lib/binding 233 | try fs.mkdirSync "node_modules/#{moduleName}/lib/binding" 234 | fs.renameSync "node_modules/#{moduleName}/build/Release/node_#{moduleName}.node", "node_modules/#{moduleName}/lib/binding/node_#{moduleName}.node" 235 | rmDirRecursiveSync "node_modules/#{moduleName}/build/" 236 | */ 237 | q = queue(1); 238 | _ref3 = 'postinstall'.split(' '); 239 | for (_k = 0, _len2 = _ref3.length; _k < _len2; _k++) { 240 | scriptName = _ref3[_k]; 241 | if (((_ref4 = buildPkg.scripts) != null ? _ref4[scriptName] : void 0) != null) { 242 | cmd = "npm run " + scriptName; 243 | q.defer(runCmd, cmd, cmdOpts, opts.quiet); 244 | } 245 | } 246 | return q.awaitAll(function(err) { 247 | return typeof cb === "function" ? cb() : void 0; 248 | }); 249 | }); 250 | }); 251 | }); 252 | }; 253 | 254 | module.exports.installModule = function(moduleName, opts, cb) { 255 | if (!opts.quiet) { 256 | console.log("installing " + (moduleName || 'all')); 257 | } 258 | fetchModule(moduleName, opts, function(err) { 259 | if (err) { 260 | return typeof cb === "function" ? cb(err) : void 0; 261 | } 262 | buildModule(moduleName, opts, function(err) { 263 | return typeof cb === "function" ? cb(err) : void 0; 264 | }); 265 | }); 266 | }; 267 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aspm", 3 | "version": "0.1.3", 4 | "description": "Atom-Shell Package Manager", 5 | "main": "index.js", 6 | "bin": { 7 | "aspm": "bin/aspm" 8 | }, 9 | "scripts": { 10 | "pretest": "npm run clean", 11 | "test": "mocha --reporter spec --require=coffee-script/register test/*.coffee", 12 | "test-verbose": "mocha --reporter spec --require=coffee-script/register test/*.coffee --verbose", 13 | "clean": "rm -rf tmp/", 14 | "build": "coffee --bare --output lib --compile src" 15 | }, 16 | "engines": { 17 | "node": ">= 0.10.0" 18 | }, 19 | "keywords": [ 20 | "atom-shell", 21 | "package manager" 22 | ], 23 | "homepage": "https://github.com/bwin/aspm", 24 | "author": { 25 | "name": "Benjamin Winkler (bwin)" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "git://github.com/bwin/aspm.git" 30 | }, 31 | "bugs": { 32 | "url": "https://github.com/bwin/aspm/issues" 33 | }, 34 | "license": "MIT", 35 | "dependencies": { 36 | "commander": "^2.5.0", 37 | "node-gyp": "^1.0.2", 38 | "queue-async": "^1.0.7", 39 | "request": "^2.51.0", 40 | "semver": "^4.1.0", 41 | "terminal-colors": "^0.1.3" 42 | }, 43 | "devDependencies": { 44 | "chai": "^1.10.0", 45 | "coffee-script": "^1.8.0", 46 | "mocha": "^2.0.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/cli.coffee: -------------------------------------------------------------------------------- 1 | 2 | exec = require('child_process').exec 3 | 4 | program = require 'commander' 5 | 6 | aspm = require '../lib' 7 | 8 | pkg = require '../package.json' 9 | 10 | fail = (err) -> 11 | console.error err.message 12 | console.error "not ok".red 13 | process.exit 1 14 | return 15 | 16 | program 17 | .version "v#{pkg.version}" 18 | .description 'Install and build npm modules for Atom-Shell' 19 | .option '-t, --target ', 'Atom-Shell version' 20 | .option '-a, --arch ', 'target architecture' 21 | .option '-p, --target-platform ', 'target platform' 22 | .option '-s, --save', 'save as dependency to package.json' 23 | .option '-s, --save-dev', 'save as devDependency to package.json' 24 | .option '-g', 'install globally with normal npm' 25 | .option '--tarball [url/path]', 'install from [remote] tarball' 26 | .option '--quiet', "don't say anything" 27 | .option '--run-scripts', "do not use --ignore-scripts flag" 28 | .option '--compatibility', "try for max compability" 29 | 30 | program 31 | .command 'install [module]' 32 | .alias 'i' 33 | .description 'install module (fetch & build)' 34 | .action (module) -> 35 | if program.G 36 | # with -g flag we use npm directly 37 | cmd = "npm #{process.argv.slice(2).join(' ')}" 38 | aspm.runCmd cmd, {}, no, (err) -> fail err if err; console.log "ok".green 39 | else 40 | aspm.installModule module, program, (err) -> fail err if err; console.log "ok".green 41 | 42 | program 43 | .command 'fetch [module]' 44 | .alias 'f' 45 | .description 'fetch module' 46 | .action (module) -> 47 | aspm.fetchModule module, program, (err) -> fail err if err; console.log "ok".green 48 | 49 | program 50 | .command 'build ' 51 | .alias 'b' 52 | .description 'build module' 53 | .action (module) -> 54 | aspm.buildModule module, program, (err) -> fail err if err; console.log "ok".green 55 | 56 | 57 | # pass-through some npm commands directly 58 | # we only do build, install, rebuild, update 59 | if process.argv[2] in 'adduser bin bugs bundle cache completion config dedupe deprecate docs edit explore help help-search init info link ls npm outdated owner pack prefix prune publish repo restart rm root run run-script search shrinkwrap star stars start stop tag test uninstall unpublish version view whoami'.split ' ' 60 | cmd = "npm #{process.argv.slice(2).join(' ')}" 61 | aspm.runCmd cmd, {}, no, (err) -> fail err if err; console.log "ok".green 62 | else 63 | program.parse process.argv 64 | program.help() if program.args.length is 0 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/index.coffee: -------------------------------------------------------------------------------- 1 | 2 | fs = require 'fs' 3 | path = require 'path' 4 | os = require 'os' 5 | exec = require('child_process').exec 6 | 7 | queue = require 'queue-async' 8 | semver = require 'semver' 9 | require 'terminal-colors' 10 | 11 | 12 | # modified from https://gist.github.com/liangzan/807712#comment-337828 13 | rmDirRecursiveSync = (dirPath) -> 14 | try 15 | files = fs.readdirSync(dirPath) 16 | catch e 17 | return 18 | if files.length > 0 19 | i = 0 20 | while i < files.length 21 | filePath = dirPath + "/" + files[i] 22 | if fs.statSync(filePath).isFile() 23 | fs.unlinkSync(filePath) 24 | else 25 | rmDirRecursiveSync filePath 26 | i++ 27 | try fs.rmdirSync dirPath 28 | return 29 | 30 | module.exports.runCmd = runCmd = (cmd, opts, quiet, cb) -> 31 | console.log "#{opts.cwd or ''}> #{cmd}".lightBlue unless quiet 32 | errMsg = '' 33 | child = exec cmd, opts 34 | child.stdout.pipe process.stdout unless quiet 35 | unless quiet 36 | child.stderr.pipe process.stderr 37 | else 38 | child.stderr.on 'data', (chunk) -> errMsg += chunk; return 39 | 40 | child.on 'exit', (code) -> 41 | return cb?(new Error "command failed: #{cmd}\n#{errMsg}") if code isnt 0 42 | cb?() 43 | return 44 | 45 | configureModule = (moduleName, opts, nodePreGypParams, cb) -> 46 | cmd = "node-gyp configure #{nodePreGypParams}" 47 | 48 | cmdOpts = 49 | cwd: path.join 'node_modules', moduleName 50 | cmdOpts.cwd = path.join opts.cwd, cmdOpts.cwd if opts.cwd 51 | runCmd cmd, cmdOpts, opts.quiet, cb 52 | return 53 | 54 | module.exports.fetchModule = fetchModule = (moduleName, opts, cb) -> 55 | console.log "fetching #{moduleName or 'all'}" unless opts.quiet 56 | moduleName ?= '' 57 | moduleName = opts.tarball if opts.tarball 58 | cmd = "npm install #{moduleName}" 59 | cmd += ' --ignore-scripts' unless opts.runScripts 60 | cmd += ' --save' if opts.save 61 | cmd += ' --save-dev' if opts.saveDev 62 | cmdOpts = {} 63 | cmdOpts.cwd = opts.cwd if opts.cwd 64 | runCmd cmd, cmdOpts, opts.quiet, cb 65 | return 66 | 67 | module.exports.buildModule = buildModule = (moduleName, opts, cb) -> 68 | projectPkg = require path.join process.cwd(), 'package.json' 69 | config = projectPkg.config?['atom-shell'] 70 | target = opts.target or config?.version 71 | arch = opts.arch or config?.arch 72 | platform = opts['target-platform'] or config?['platform'] or os.platform() 73 | modules = [] 74 | nodePreGypParams = '' 75 | 76 | modules = moduleName.split ' ' if moduleName.indexOf(' ') isnt -1 77 | modules = Object.keys projectPkg.dependencies unless moduleName 78 | 79 | if modules.length isnt 0 80 | # build multiple modules serially and return 81 | q = queue(1) 82 | for moduleName in modules 83 | q.defer buildModule, moduleName 84 | q.awaitAll (err) -> 85 | return cb() 86 | return 87 | 88 | [moduleName] = moduleName.split '@' # get rid of version 89 | 90 | cwd = process.cwd() 91 | cwd = path.join cwd, opts.cwd if opts.cwd 92 | modulePath = path.join cwd, 'node_modules', moduleName 93 | 94 | # error if module is not found 95 | return cb?(new Error("aspm: module not found '#{moduleName}'")) unless fs.existsSync path.join modulePath, 'package.json' 96 | # skip if module has no bynding.gyp 97 | return cb() unless fs.existsSync path.join modulePath, 'binding.gyp' 98 | 99 | return cb?(new Error "aspm: no atom-shell version specified.") unless target 100 | return cb?(new Error "aspm: no target architecture specified.") unless arch 101 | 102 | buildPkg = require path.join modulePath, 'package.json' 103 | 104 | fakeNodePreGyp = buildPkg.dependencies?['node-pre-gyp']? and buildPkg.binary? 105 | if fakeNodePreGyp 106 | nodePreGypPkg = require path.join modulePath, 'node_modules', 'node-pre-gyp', 'package.json' 107 | nodePreGypVersion = nodePreGypPkg.version 108 | 109 | node_abi = "atom-shell-v#{target}" 110 | if semver.lte nodePreGypVersion, '999.0.0' # some future version with atom-shell support 111 | node_abi = do -> 112 | atomshellToModulesVersion = 113 | '0.21.x': 41 114 | '0.20.x': 17 115 | "0.19.x": 16 116 | "0.18.x": 16 117 | "0.17.x": 15 118 | "0.16.x": 14 119 | atomshellToNodeVersion = 120 | '0.21.x': '1.0.0-pre' 121 | '0.20.x': '0.13.0-pre' 122 | '0.19.x': '0.11.14' 123 | '0.18.x': '0.11.14' 124 | '0.17.x': '0.11.14' 125 | '0.16.x': '0.11.13' 126 | #'0.15.x': '0.11.13' 127 | #'0.14.x': '0.11.13' 128 | #'0.13.x': '0.11.10' 129 | #'0.12.x': '0.11.10' 130 | #'0.11.x': '0.11.10' 131 | #'0.10.x': '0.11.10' 132 | #'0.9.x': '0.11.10' 133 | #'0.8.x': '0.11.10' 134 | #'0.7.x': '0.10.18' 135 | #'0.6.x': '0.10.18' 136 | 137 | lookupTable = do -> 138 | return atomshellToModulesVersion if semver.lt nodePreGypVersion, '0.6.0' 139 | return atomshellToNodeVersion 140 | 141 | targetParts = target.split '.' 142 | targetParts[2] = 'x' 143 | targetSimplified = targetParts.join '.' 144 | return "node-v#{lookupTable[targetSimplified]}" 145 | 146 | module_path = buildPkg.binary.module_path 147 | # fake node-pre-gyp 148 | module_path = module_path 149 | .replace '{node_abi}', node_abi 150 | .replace '{platform}', os.platform() 151 | .replace '{arch}', arch 152 | .replace '{module_name}', buildPkg.binary.module_name 153 | .replace '{configuration}', 'Release' 154 | .replace '{version}', buildPkg.version 155 | 156 | preGyp = 157 | module_name: buildPkg.binary.module_name 158 | module_path: path.join '..', module_path 159 | 160 | if fakeNodePreGyp 161 | nodePreGypParams += " --module_name=#{preGyp.module_name}" 162 | nodePreGypParams += " --module_path=#{preGyp.module_path}" 163 | 164 | # run pre-scripts from package.json 165 | q = queue(1) 166 | preScripts = 'prepublish preinstall'.split ' ' 167 | preScripts.push 'install' if opts.compatibility 168 | cmdOpts = 169 | cwd: path.join 'node_modules', moduleName 170 | cmdOpts.cwd = path.join opts.cwd, cmdOpts.cwd if opts.cwd 171 | for scriptName in preScripts 172 | if buildPkg.scripts?[scriptName]? 173 | cmd = "npm run #{scriptName}" 174 | q.defer runCmd, cmd, cmdOpts, opts.quiet 175 | q.awaitAll (err) -> 176 | configureModule moduleName, opts, nodePreGypParams, (err) -> 177 | console.log "building #{moduleName} for Atom-Shell v#{target} #{os.platform()} #{arch}" unless opts.quiet 178 | 179 | cmd = "node-gyp rebuild --target=#{target} --arch=#{arch} --target_platform=#{platform} --dist-url=https://gh-contractor-zcbenz.s3.amazonaws.com/atom-shell/dist #{nodePreGypParams}" 180 | 181 | runCmd cmd, cmdOpts, opts.quiet, (err) -> 182 | return cb?(err) if err 183 | ### 184 | unless fakeNodePreGyp 185 | # we move the node_module.node file to lib/binding 186 | try fs.mkdirSync "node_modules/#{moduleName}/lib/binding" 187 | fs.renameSync "node_modules/#{moduleName}/build/Release/node_#{moduleName}.node", "node_modules/#{moduleName}/lib/binding/node_#{moduleName}.node" 188 | rmDirRecursiveSync "node_modules/#{moduleName}/build/" 189 | ### 190 | 191 | # run post-scripts from package.json 192 | q = queue(1) 193 | for scriptName in 'postinstall'.split ' ' # also 'install'? 194 | if buildPkg.scripts?[scriptName]? 195 | cmd = "npm run #{scriptName}" 196 | q.defer runCmd, cmd, cmdOpts, opts.quiet 197 | q.awaitAll (err) -> 198 | return cb?() 199 | return 200 | return 201 | return 202 | 203 | module.exports.installModule = (moduleName, opts, cb) -> 204 | console.log "installing #{moduleName or 'all'}" unless opts.quiet 205 | fetchModule moduleName, opts, (err) -> 206 | return cb?(err) if err 207 | buildModule moduleName, opts, (err) -> 208 | return cb?(err) 209 | return 210 | return 211 | -------------------------------------------------------------------------------- /test/basic-test.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | try these: 3 | * snappy@3.0.4 4 | * node-expat 5 | * ffi 6 | * midi 7 | maybe: 8 | * airtunes 9 | * canvas 10 | try (with npg): 11 | * mapnik 12 | * osmium 13 | * osrm 14 | never worked: 15 | * node-sass 16 | * nodegit 17 | ### 18 | 19 | os = require 'os' 20 | fs = require 'fs' 21 | path = require 'path' 22 | exec = require('child_process').exec 23 | 24 | chai = require 'chai' 25 | expect = chai.expect 26 | request = require 'request' 27 | 28 | aspm = require '../lib' 29 | 30 | platform = os.platform() 31 | 32 | defaultTargets = '0.17.2 0.19.5 0.20.1 0.20.8 0.21.0'.split ' ' 33 | defaultArchs = 'ia32 x64'.split ' ' 34 | defaultArchs = ['ia32'] if platform is 'win32' 35 | 36 | 37 | testModule = (moduleName, opts, cb) -> 38 | [moduleName] = moduleName.split '@' # get rid of version 39 | quiet = opts.quiet 40 | atomShellExe = path.join 'atom-shell', "atom-shell-v#{opts.target}-#{platform}-#{opts.arch}", 'atom' 41 | atomShellExe += '.exe' if platform is 'win32' 42 | testDir = path.join 'tmp', moduleName 43 | cmd = """ 44 | env ATOM_SHELL_INTERNAL_RUN_AS_NODE=1 #{atomShellExe} -e "require('./tmp/node_modules/#{moduleName}'); console.log('OK: required #{moduleName}')" 45 | """ 46 | errMsg = '' 47 | child = exec cmd 48 | child.stdout.pipe process.stdout unless quiet 49 | unless quiet 50 | child.stderr.pipe process.stderr 51 | else 52 | child.stderr.on 'data', (chunk) -> errMsg += chunk; return 53 | child.on 'exit', (code) -> 54 | return cb?(new Error "command failed: #{cmd}\n#{errMsg}") if code isnt 0 55 | cb?() 56 | return 57 | 58 | testInstallMulti = (moduleName, targets=defaultTargets, archs=defaultArchs, opts={}) -> 59 | for target in targets 60 | for arch in archs 61 | currentOpts = {} 62 | currentOpts[key] = val for key, val of opts # copy opts 63 | currentOpts.target = target 64 | currentOpts.arch = arch 65 | 66 | testInstall moduleName, currentOpts 67 | return 68 | 69 | testInstall = (moduleName, opts={}) -> 70 | opts.quiet = yes unless '--verbose' in process.argv 71 | opts.cwd = 'tmp' 72 | msg = moduleName 73 | msg += " for Atom-Shell@#{opts.target} on #{platform} #{opts.arch}" if opts.target and opts.arch 74 | msg += " from #{opts.tarball}" if opts.tarball 75 | it msg, (done) -> 76 | #process.chdir opts.cwd if opts.cwd 77 | aspm.installModule moduleName, opts, (err) -> 78 | return done err if err 79 | return done err unless opts.target and opts.arch 80 | testModule moduleName, opts, (err) -> 81 | return done err 82 | return 83 | return 84 | return 85 | 86 | downloadAtomShellMulti = (targets=defaultTargets, archs=defaultArchs, opts={}) -> 87 | for target in targets 88 | for arch in archs 89 | do (target, arch) -> 90 | it "#{target} #{platform} #{arch}", (done) -> downloadAtomShell target, arch, done 91 | return 92 | 93 | downloadAtomShell = (target, arch, cb) -> 94 | try fs.mkdirSync 'atom-shell' 95 | releaseName = "atom-shell-v#{target}-#{platform}-#{arch}" 96 | dir = path.join 'atom-shell', releaseName 97 | return cb() if fs.existsSync dir # skip if we already have it 98 | url = "https://github.com/atom/atom-shell/releases/download/v#{target}/#{releaseName}.zip" 99 | zipfileName = path.join 'atom-shell', "#{releaseName}.zip" 100 | zipfile = fs.createWriteStream zipfileName 101 | request(url).pipe(zipfile).on 'finish', -> 102 | zipfile.close -> 103 | exec "unzip #{zipfileName} -d #{dir}", (err) -> 104 | fs.unlink zipfileName, cb 105 | return 106 | return 107 | return 108 | return 109 | 110 | 111 | describe 'download atom-shell', -> 112 | @timeout 1000*60 * 5 # minutes 113 | 114 | downloadAtomShellMulti() 115 | 116 | describe 'build', -> 117 | @timeout 1000*60 * 2 # minutes 118 | 119 | try fs.mkdirSync 'tmp' 120 | # create stub package.json in ./tmp/ 121 | fs.writeFileSync path.join('tmp', 'package.json'), """ 122 | { 123 | "name": "aspm-test", 124 | "description": "...", 125 | "version": "0.0.1", 126 | "private": true 127 | } 128 | """ 129 | 130 | describe 'js-only module', -> 131 | testInstall 'async' 132 | 133 | # !!! first enable recursive building 134 | #describe 'js-only module /w native dependency', -> 135 | # testInstallMulti 'nino@0.1.2' 136 | 137 | describe 'native module', -> 138 | testInstallMulti 'time@0.11.0' 139 | testInstallMulti 'leveldown@1.0.0' 140 | testInstallMulti 'nslog@1.0.1' 141 | testInstallMulti 'pathwatcher@2.3.5' 142 | ## testInstallMulti 'node-sass@1.2.3' 143 | 144 | describe 'native module /w node-pre-gyp', -> 145 | ## testInstallMulti 'nodegit@0.2.4', null, null, compatibility: yes 146 | ## testInstallMulti 'node-expat@2.3.3' 147 | ## testInstallMulti 'ffi@1.2.7' 148 | ## testInstallMulti 'midi@0.9.0' 149 | 150 | testInstallMulti 'serialport@1.4.9' 151 | testInstallMulti 'zipfile@0.5.4' 152 | testInstallMulti 'v8-profiler@5.2.1' 153 | testInstallMulti 'sqlite3@3.0.4', ['0.17.2'] 154 | testInstallMulti 'sqlite3@master', ['0.19.5', '0.20.1'], null, tarball: 'https://github.com/mapbox/node-sqlite3/archive/master.tar.gz' 155 | 156 | describe 'node-pre-gyp test app', -> 157 | testInstallMulti 'node-pre-gyp-test-app1', null, null, tarball: '../test/node-pre-gyp/app1.tar.gz' 158 | testInstallMulti 'node-pre-gyp-test-app3', null, null, tarball: '../test/node-pre-gyp/app3.tar.gz' 159 | -------------------------------------------------------------------------------- /test/node-pre-gyp/app1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwin/aspm/1d3dd660690a8a053ea3c686bb693477cadc680b/test/node-pre-gyp/app1.tar.gz -------------------------------------------------------------------------------- /test/node-pre-gyp/app3.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwin/aspm/1d3dd660690a8a053ea3c686bb693477cadc680b/test/node-pre-gyp/app3.tar.gz -------------------------------------------------------------------------------- /test/node-pre-gyp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-pre-gyp-tests", 3 | "version": "0.0.1", 4 | "private": true 5 | } --------------------------------------------------------------------------------