├── test
├── unit
│ ├── fixtures
│ │ ├── invalid_json
│ │ └── valid_json
│ ├── test-lib-couchdb.js
│ ├── test-lib-versions.js
│ ├── test-lib-utils.js
│ └── test-lib-commands-install.js
├── unit.bat
├── all.bat
├── integration.bat
├── integration
│ ├── fixtures
│ │ ├── package-one
│ │ │ ├── main.js
│ │ │ └── package.json
│ │ ├── package-two-v2
│ │ │ ├── two.js
│ │ │ └── package.json
│ │ ├── package-two
│ │ │ ├── two.js
│ │ │ └── package.json
│ │ ├── package-one-v2
│ │ │ ├── main.js
│ │ │ └── package.json
│ │ ├── package-one-v3
│ │ │ ├── main.js
│ │ │ └── package.json
│ │ ├── package-three-invalid-extjs
│ │ │ ├── main.js
│ │ │ └── package.json
│ │ ├── package-three-invalid-characters
│ │ │ ├── main.js
│ │ │ └── package.json
│ │ ├── project-empty
│ │ │ └── README
│ │ ├── project-rangedeps
│ │ │ └── package.json
│ │ ├── project-packagejson
│ │ │ └── package.json
│ │ └── project-custompaths
│ │ │ └── package.json
│ ├── test-publish-unpublish.js
│ ├── test-emptyproject-install-compile.js
│ ├── test-link.js
│ ├── test-publish.js
│ ├── test-emptyproject-install-rm-rebuild.js
│ ├── test-emptyproject-publish-install-upgrade.js
│ ├── test-packagejson-publish-install-ls-remove.js
│ ├── test-emptyproject-publish-install-ls-remove.js
│ ├── test-custompaths-publish-install-ls-remove.js
│ └── test-rangedeps-publish-install-upgrade.js
├── unit.sh
├── all.sh
├── integration.sh
└── utils.js
├── .gitignore
├── .npmrc
├── lib
├── tmpls
│ └── require.config.js
├── schema
│ ├── package.json
│ └── package-full.json
├── commands
│ ├── index.js
│ ├── help.js
│ ├── clear-cache.js
│ ├── pack.js
│ ├── unpublish.js
│ ├── publish.js
│ ├── link_old.js
│ ├── rebuild.js
│ ├── ls.js
│ ├── search.js
│ ├── remove.js
│ ├── clean.js
│ ├── compile.js
│ └── upgrade.js
├── env.js
├── settings.js
├── args.js
├── github.js
├── cache.js
├── logger.js
├── tar.js
├── jamrc.js
├── versions.js
├── packages.js
├── fstream-jam.js
└── project.js
├── LICENSE
├── package.json
├── bin
└── jam.js
├── index.js
└── README.md
/test/unit/fixtures/invalid_json:
--------------------------------------------------------------------------------
1 | asdf 123
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | jam.iml
3 | .idea
4 |
--------------------------------------------------------------------------------
/test/unit/fixtures/valid_json:
--------------------------------------------------------------------------------
1 | {
2 | "one": 1,
3 | "two": 2
4 | }
5 |
--------------------------------------------------------------------------------
/test/unit.bat:
--------------------------------------------------------------------------------
1 | pushd %~dp0
2 | node ..\node_modules\nodeunit\bin\nodeunit unit
3 | popd
4 |
--------------------------------------------------------------------------------
/test/all.bat:
--------------------------------------------------------------------------------
1 | pushd %~dp0
2 | node ..\node_modules\nodeunit\bin\nodeunit unit integration
3 | popd
4 |
--------------------------------------------------------------------------------
/test/integration.bat:
--------------------------------------------------------------------------------
1 | pushd %~dp0
2 | node ..\node_modules\nodeunit\bin\nodeunit integration
3 | popd
4 |
--------------------------------------------------------------------------------
/test/integration/fixtures/package-one/main.js:
--------------------------------------------------------------------------------
1 | deifne(['exports'], function (exports) {
2 | exports.name = 'Package One';
3 | });
4 |
--------------------------------------------------------------------------------
/test/integration/fixtures/package-two-v2/two.js:
--------------------------------------------------------------------------------
1 | deifne(['exports'], function (exports) {
2 | exports.name = 'Package Two';
3 | });
4 |
--------------------------------------------------------------------------------
/test/integration/fixtures/package-two/two.js:
--------------------------------------------------------------------------------
1 | deifne(['exports'], function (exports) {
2 | exports.name = 'Package Two';
3 | });
4 |
--------------------------------------------------------------------------------
/test/integration/fixtures/package-one-v2/main.js:
--------------------------------------------------------------------------------
1 | deifne(['exports'], function (exports) {
2 | exports.name = 'Package One';
3 | });
4 |
--------------------------------------------------------------------------------
/test/integration/fixtures/package-one-v3/main.js:
--------------------------------------------------------------------------------
1 | deifne(['exports'], function (exports) {
2 | exports.name = 'Package One';
3 | });
4 |
--------------------------------------------------------------------------------
/test/integration/fixtures/package-three-invalid-extjs/main.js:
--------------------------------------------------------------------------------
1 | deifne(['exports'], function (exports) {
2 | exports.name = 'Package Three';
3 | });
4 |
--------------------------------------------------------------------------------
/test/integration/fixtures/package-one/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "package-one",
3 | "version": "0.0.1",
4 | "description": "Test package one"
5 | }
6 |
--------------------------------------------------------------------------------
/test/integration/fixtures/package-three-invalid-characters/main.js:
--------------------------------------------------------------------------------
1 | deifne(['exports'], function (exports) {
2 | exports.name = 'Package Three';
3 | });
4 |
--------------------------------------------------------------------------------
/test/integration/fixtures/package-one-v2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "package-one",
3 | "version": "0.0.2",
4 | "description": "Test package one"
5 | }
6 |
--------------------------------------------------------------------------------
/test/integration/fixtures/package-one-v3/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "package-one",
3 | "version": "0.0.3",
4 | "description": "Test package one"
5 | }
6 |
--------------------------------------------------------------------------------
/test/integration/fixtures/project-empty/README:
--------------------------------------------------------------------------------
1 | This is used to test jam commands inside a fresh project without an
2 | existing package.json (either for NPM or for Jam).
3 |
--------------------------------------------------------------------------------
/test/integration/fixtures/project-rangedeps/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "jam": {
3 | "dependencies": {
4 | "package-two": "<=0.0.2"
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/test/integration/fixtures/package-three-invalid-characters/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "package-@",
3 | "version": "0.0.1",
4 | "description": "Test package three"
5 | }
6 |
--------------------------------------------------------------------------------
/test/integration/fixtures/package-three-invalid-extjs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "package-three.js",
3 | "version": "0.0.1",
4 | "description": "Test package three"
5 | }
6 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | ; 'npm config ls -l' to show all defaults.
2 | ; python = "python2.7"
3 |
4 | ; @see http://dragonballs.dev.mail.ru:4874/
5 | registry = "http://dragonballs.dev.mail.ru:5984/registry/_design/app/_rewrite"
6 |
--------------------------------------------------------------------------------
/test/integration/fixtures/project-packagejson/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "jam": {
3 | "dependencies": {
4 | "package-one": null,
5 | "package-two": null
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/test/integration/fixtures/package-two/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "package-two",
3 | "version": "0.0.1",
4 | "description": "Test package two",
5 | "dependencies": {
6 | "package-one": null
7 | },
8 | "main": "two.js"
9 | }
10 |
--------------------------------------------------------------------------------
/test/integration/fixtures/package-two-v2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "package-two",
3 | "version": "0.0.2",
4 | "description": "Test package two",
5 | "dependencies": {
6 | "package-one": "0.0.2"
7 | },
8 | "main": "two.js"
9 | }
10 |
--------------------------------------------------------------------------------
/lib/tmpls/require.config.js:
--------------------------------------------------------------------------------
1 | (function (global) {
2 | var packages = "{data}";
3 |
4 | if (typeof module === 'object' && module.exports) {
5 | module.exports = packages;
6 | } else if (typeof require === 'function' && require.config) {
7 | require.config("{data}");
8 | } else if (typeof global === 'object') {
9 | global.require = packages;
10 | }
11 | })(this);
12 |
13 |
--------------------------------------------------------------------------------
/test/unit.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | SOURCE="${BASH_SOURCE[0]}"
4 | DIR="$( dirname "$SOURCE" )"
5 | while [ -h "$SOURCE" ]
6 | do
7 | SOURCE="$(readlink "$SOURCE")"
8 | [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
9 | DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
10 | done
11 | DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
12 | NODEUNIT="node $DIR/../node_modules/nodeunit/bin/nodeunit"
13 |
14 | cd $DIR
15 | $NODEUNIT unit
16 |
--------------------------------------------------------------------------------
/test/integration/fixtures/project-custompaths/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "project-custompaths",
3 | "version": "1.2.3",
4 | "description": "Example project using custom package path and base URL",
5 | "jam": {
6 | "packageDir": "public/js/vendor",
7 | "baseUrl": "public",
8 | "dependencies": {
9 | "package-one": null,
10 | "package-two": null
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/test/all.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | SOURCE="${BASH_SOURCE[0]}"
4 | DIR="$( dirname "$SOURCE" )"
5 | while [ -h "$SOURCE" ]
6 | do
7 | SOURCE="$(readlink "$SOURCE")"
8 | [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
9 | DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
10 | done
11 | DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
12 | NODEUNIT="node $DIR/../node_modules/nodeunit/bin/nodeunit"
13 |
14 | cd $DIR
15 | $NODEUNIT unit integration
16 |
--------------------------------------------------------------------------------
/test/integration.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | SOURCE="${BASH_SOURCE[0]}"
4 | DIR="$( dirname "$SOURCE" )"
5 | while [ -h "$SOURCE" ]
6 | do
7 | SOURCE="$(readlink "$SOURCE")"
8 | [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
9 | DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
10 | done
11 | DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
12 | NODEUNIT="node $DIR/../node_modules/nodeunit/bin/nodeunit"
13 |
14 | cd $DIR
15 | $NODEUNIT integration
16 |
--------------------------------------------------------------------------------
/test/unit/test-lib-couchdb.js:
--------------------------------------------------------------------------------
1 | var couchdb = require('../../lib/couchdb'),
2 | logger = require('../../lib/logger');
3 |
4 | logger.clean_exit = true;
5 |
6 |
7 | exports['default ports if none specified'] = function (test) {
8 | var db = new couchdb.CouchDB('http://hostname/dbname');
9 | test.equal(db.instance.port, 80);
10 | var db2 = new couchdb.CouchDB('https://hostname/dbname2');
11 | test.equal(db2.instance.port, 443);
12 | test.done();
13 | };
14 |
--------------------------------------------------------------------------------
/lib/schema/package.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "title": "JSON schema for NPM package.json files",
4 | "$schema": "http://json-schema.org/draft-04/schema#",
5 |
6 | "type": "object",
7 | "required": [ "name", "version" ],
8 |
9 | "properties": {
10 | "name": {
11 | "description": "The name of the package.",
12 | "type": "string"
13 | },
14 | "version": {
15 | "description": "Version must be parseable by node-semver, which is bundled with npm as a dependency.",
16 | "type": "string"
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib/commands/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | help: require('./help'),
3 | search: require('./search'),
4 | install: require('./install'),
5 | upgrade: require('./upgrade'),
6 | remove: require('./remove'),
7 | compile: require('./compile'),
8 | clean: require('./clean'),
9 | ls: require('./ls'),
10 | 'clear-cache': require('./clear-cache'),
11 | publish: require('./publish'),
12 | unpublish: require('./unpublish'),
13 | link: require('./link'),
14 | pack: require('./pack'),
15 | rebuild: require('./rebuild')
16 | };
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012 Caolan McMahon
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/lib/commands/help.js:
--------------------------------------------------------------------------------
1 | var utils = require('../utils'),
2 | logger = require('../logger');
3 |
4 |
5 | exports.summary = 'Show help specific to a command';
6 |
7 | exports.usage = '' +
8 | 'jam help [COMMAND]\n' +
9 | '\n' +
10 | 'Parameters:\n' +
11 | ' COMMAND The jam command to show help on\n' +
12 | '\n' +
13 | 'Available commands:\n';
14 |
15 |
16 | exports.run = function (settings, args, commands) {
17 | // add summary of commands to exports.usage
18 | var len = utils.longest(Object.keys(commands));
19 |
20 | for (var k in commands) {
21 | exports.usage += ' ' + utils.padRight(k, len);
22 | exports.usage += ' ' + commands[k].summary + '\n';
23 | }
24 |
25 | if (!args.length) {
26 | console.log('Usage: ' + exports.usage);
27 | logger.clean_exit = true;
28 | }
29 | else {
30 | args.forEach(function (a) {
31 | var cmd = commands[a];
32 | if (cmd) {
33 | console.log(cmd.summary);
34 | console.log('Usage: ' + cmd.usage);
35 | }
36 | });
37 | logger.clean_exit = true;
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/test/unit/test-lib-versions.js:
--------------------------------------------------------------------------------
1 | var versions = require('../../lib/versions'),
2 | logger = require("../../lib/logger"),
3 | Range = require('../../lib/tree').Range;
4 |
5 | //logger.level = "verbose";
6 |
7 | exports['maxSatisfying - should return version without build meta'] = function (test) {
8 | var max, expect;
9 |
10 | max = versions.maxSatisfying(['0.10.0', '0.10.0+build.1', '0.10.0+build.2'], [ new Range((expect = "0.10.0")) ]);
11 |
12 | test.equal(max, expect, "Not equal. Actual: " + max + " Expected: " + expect);
13 | test.done();
14 | };
15 |
16 | exports['maxSatisfying - should return version with highest meta'] = function (test) {
17 | var max, expect;
18 |
19 | max = versions.maxSatisfying(['0.10.0', '0.10.0+build.1', (expect = '0.10.0+build.2')], [ new Range("0.10.0+build.3") ]);
20 |
21 | test.equal(max, expect, "Not equal. Actual: " + max + " Expected: " + expect);
22 | test.done();
23 | };
24 |
25 | exports['equalButNotMeta'] = function (test) {
26 | var warn, expect;
27 |
28 | warn = versions.equalButNotMeta('0.10.0+build.1', [ new Range((expect = "0.10.0")) ]);
29 |
30 | test.equal(warn[0], expect);
31 | test.done();
32 | };
--------------------------------------------------------------------------------
/lib/commands/clear-cache.js:
--------------------------------------------------------------------------------
1 | var logger = require('../logger'),
2 | cache = require('../cache');
3 |
4 |
5 | exports.summary = 'Removes packages from the local cache';
6 |
7 | exports.usage = '' +
8 | 'jam clear-cache [PACKAGE[@VERSION]]\n' +
9 | '\n' +
10 | '* If no package is specified, all packages are cleared from the cache.\n' +
11 | '* If a package is specified without a version, all versions of that\n' +
12 | ' package are cleared.\n' +
13 | '* If a package and a version are specified, only that specific version\n' +
14 | ' is cleared.\n' +
15 | '\n' +
16 | 'Parameters:\n' +
17 | ' PACKAGE Package name to clear\n' +
18 | ' VERSION Package version to clear';
19 |
20 |
21 | exports.run = function (settings, args, done) {
22 | var version;
23 | var name = args[0];
24 |
25 | if (name && name.indexOf('@') !== -1) {
26 | var parts = name.split('@');
27 | name = parts[0];
28 | version = parts.slice(1).join('@');
29 | }
30 |
31 | cache.clear(name, version, function (err) {
32 | if (err) {
33 | return logger.error(err);
34 | }
35 | logger.end();
36 |
37 | if (typeof done === 'function') {
38 | done();
39 | }
40 |
41 | });
42 | };
43 |
--------------------------------------------------------------------------------
/test/unit/test-lib-utils.js:
--------------------------------------------------------------------------------
1 | var utils = require('../../lib/utils'),
2 | path = require('path'),
3 | fs = require('fs'),
4 | child_process = require('child_process'),
5 | logger = require('../../lib/logger');
6 |
7 | logger.clean_exit = true;
8 |
9 | exports['readJSON - valid'] = function (test) {
10 | test.expect(2);
11 | var p = __dirname + '/fixtures/valid_json';
12 | utils.readJSON(p, function (err, settings) {
13 | test.ok(!err);
14 | test.same(settings, {one:1,two:2});
15 | test.done();
16 | });
17 | };
18 |
19 | exports['readJSON - invalid'] = function (test) {
20 | test.expect(1);
21 | var p = __dirname + '/fixtures/invalid_json';
22 | utils.readJSON(p, function (err, settings) {
23 | test.ok(err, 'return JSON parsing errors');
24 | test.done();
25 | });
26 | };
27 |
28 | exports['padRight'] = function (test) {
29 | // pad strings below min length
30 | test.equals(utils.padRight('test', 20), 'test ');
31 | // don't pad strings equals to min length
32 | test.equals(utils.padRight('1234567890', 10), '1234567890');
33 | // don't shorten strings above min length
34 | test.equals(utils.padRight('123456789012345', 10), '123456789012345');
35 | test.done();
36 | };
37 |
--------------------------------------------------------------------------------
/lib/commands/pack.js:
--------------------------------------------------------------------------------
1 | var logger = require('../logger'),
2 | settings = require('../settings'),
3 | tar = require('../tar'),
4 | path = require('path');
5 |
6 |
7 | exports.summary = 'Create a tar.gz package';
8 |
9 | exports.usage = '' +
10 | 'jam pack SOURCE [TARGET]\n' +
11 | '\n' +
12 | 'Parameters:\n' +
13 | ' SOURCE The directory to pack\n' +
14 | ' TARGET The .tar.gz file to create';
15 |
16 |
17 | exports.run = function (_settings, args, commands) {
18 | if (args.length < 1) {
19 | console.log(exports.usage);
20 | logger.clean_exit = true;
21 | return;
22 | }
23 | var source = args[0];
24 |
25 | // TODO: add package.json validation
26 | settings.load(source, function (err, cfg) {
27 | if (err) {
28 | return logger.error(err);
29 | }
30 |
31 | var target = args[1] || cfg.name + '-' + cfg.version + '.tar.gz';
32 |
33 | // TODO: add directory to cache as name/version/package, then pack
34 | // package directory and store as name/version/package.tar.gz and
35 | // cp file to TARGET
36 |
37 | tar.create(cfg, source, target, function (err) {
38 | if (err) {
39 | return logger.error(err);
40 | }
41 | logger.end(target);
42 | });
43 | });
44 | };
45 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jamjs",
3 | "version": "0.10.0",
4 | "description": "",
5 | "maintainers": [
6 | {
7 | "name": "Caolan McMahon",
8 | "web": "https://github.com/caolan"
9 | },
10 | {
11 | "name": "Sergey Kamardin",
12 | "web": "https://github.com/gobwas"
13 | }
14 | ],
15 | "dependencies": {
16 | "almond": "0.2.5",
17 | "async": "~0.1.21",
18 | "chance": "^0.7.3",
19 | "fstream": "~1.0.10",
20 | "fstream-ignore": "~1.0.5",
21 | "inherits": "~1.0.0",
22 | "inherits-js": "^0.1.0",
23 | "is-my-json-valid": "^2.12.0",
24 | "json-honey": "^0.4.1",
25 | "mime": "~1.2.4",
26 | "minimatch": "~0.2.5",
27 | "mkdirp": "~0.3.2",
28 | "ncp": "~0.2.6",
29 | "prompt": "0.2.1",
30 | "request": "~2.9.202",
31 | "requirejs": "2.1.4",
32 | "rimraf": "^2.5.4",
33 | "schinquirer": "^0.1.1",
34 | "semver": "~4.3.3",
35 | "tar": "~2.2.1",
36 | "underscore": "~1.8.3"
37 | },
38 | "devDependencies": {
39 | "cuculus": "^0.3.3",
40 | "nodeunit": "0.9.1",
41 | "sinon": "^1.14.1"
42 | },
43 | "repository": {
44 | "type": "git",
45 | "url": "http://github.com/caolan/jam.git"
46 | },
47 | "engines": {
48 | "node": ">= 0.12.2"
49 | },
50 | "bugs": {
51 | "url": "http://github.com/caolan/jam/issues"
52 | },
53 | "bin": {
54 | "jam": "./bin/jam.js"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/lib/env.js:
--------------------------------------------------------------------------------
1 | var isWindows = exports.isWindows = process.platform === 'win32';
2 | exports.temp = process.env.TMPDIR || process.env.TMP || process.env.TEMP || ( isWindows ? "c:\\windows\\temp" : "/tmp" );
3 | exports.home = ( isWindows ? process.env.USERPROFILE : process.env.HOME );
4 | if (exports.home) {
5 | process.env.HOME = exports.home;
6 | } else {
7 | exports.home = exports.temp;
8 | }
9 |
10 | if ( isWindows ) {
11 | exports.osSep = '\\';
12 | exports.nullDevice = 'NUL';
13 |
14 | // Regex to split a windows path into three parts: [*, device, slash, tail] windows-only
15 | var splitDeviceRe = /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/][^\\\/]+)?([\\\/])?([\s\S]*?)$/;
16 |
17 | /**
18 | * Returns true if the path given is absolute.
19 | * @param {String} p1
20 | * @return {Boolean}
21 | * @api public
22 | */
23 | exports.isAbsolute = function(p) {
24 | var result = splitDeviceRe.exec(p),
25 | device = result[1] || '',
26 | isUnc = device && device.charAt(1) !== ':';
27 | return !!result[2] || isUnc; // UNC paths are always absolute
28 | };
29 |
30 | } else {
31 | exports.osSep = '/';
32 | exports.nullDevice = '/dev/null';
33 |
34 | /**
35 | * Returns true if the path given is absolute.
36 | * @param {String} p1
37 | * @return {Boolean}
38 | * @api public
39 | */
40 | exports.isAbsolute = function(p) {
41 | return p[0] === '/';
42 | };
43 | }
44 |
--------------------------------------------------------------------------------
/lib/settings.js:
--------------------------------------------------------------------------------
1 | var utils = require('./utils'),
2 | path = require('path'),
3 | semver = require('semver'),
4 | logger = require('./logger'),
5 | schema = require('./schema/package.json'),
6 | validator = require('is-my-json-valid'),
7 | async = require('async'),
8 | validate;
9 |
10 | validate = validator(schema);
11 |
12 | exports.load = async.memoize(function (dir, callback) {
13 | var settings_file = path.resolve(dir, 'package.json');
14 | utils.readJSON(settings_file, function (err, settings) {
15 | if (err) {
16 | callback(err);
17 | return;
18 | }
19 | try {
20 | // if there is a jam.name we must override the
21 | // package name early.
22 | if (settings.jam && settings.jam.name) {
23 | settings.name = settings.jam.name;
24 | }
25 | exports.validate(settings, settings_file);
26 | }
27 | catch (e) {
28 | return callback(e);
29 | }
30 | callback(null, settings);
31 | });
32 | });
33 |
34 | exports.validate = function (settings, filename) {
35 | var error;
36 |
37 | if (!validate(settings)) {
38 | error = validate.errors[0];
39 | throw new Error("Validation error: " + error.field + " " + error.message + " at " + filename);
40 | }
41 |
42 | if (!semver.valid(settings.version)) {
43 | throw new Error(
44 | 'Invalid version number in ' + filename + '\n' +
45 | 'Version numbers should follow the format described at ' +
46 | 'http://semver.org (eg, 1.2.3 or 4.5.6-jam.1)'
47 | );
48 | }
49 | };
50 |
--------------------------------------------------------------------------------
/lib/commands/unpublish.js:
--------------------------------------------------------------------------------
1 | var utils = require('../utils'),
2 | logger = require('../logger'),
3 | repository = require('../repository'),
4 | argParse = require('../args').parse,
5 | url = require('url'),
6 | urlParse = url.parse,
7 | urlFormat = url.format;
8 |
9 |
10 | exports.summary = 'Remove a published package from a repository';
11 |
12 | exports.usage = '' +
13 | 'jam unpublish PACKAGE[@VERSION]\n' +
14 | '\n' +
15 | 'Parameters:\n' +
16 | ' PACKAGE Package name to unpublish\n' +
17 | ' VERSION Package version to unpublish, if no version is specified\n' +
18 | ' all versions of the package are removed\n' +
19 | '\n' +
20 | 'Options:\n' +
21 | ' -r, --repository Target repository URL (defaults to first value in jamrc)';
22 |
23 |
24 | exports.run = function (settings, args) {
25 | var a = argParse(args, {
26 | 'repo': {match: ['-r', '--repository'], value: true}
27 | });
28 | var repo = a.options.repo || settings.repositories[0];
29 | if (process.env.JAM_TEST) {
30 | repo = process.env.JAM_TEST_DB;
31 | if (!repo) {
32 | throw 'JAM_TEST environment variable set, but no JAM_TEST_DB set';
33 | }
34 | }
35 |
36 | var name = a.positional[0];
37 | var version;
38 |
39 | if (!name) {
40 | return logger.error('No package name specified');
41 | }
42 | if (name.indexOf('@') !== -1) {
43 | var parts = name.split('@');
44 | name = parts[0];
45 | version = parts.slice(1).join('@');
46 | }
47 |
48 | utils.completeAuth(repo, true, function (err, repo) {
49 | if (err) {
50 | return logger.error(err);
51 | }
52 | utils.catchAuthError(
53 | repository.unpublish, repo, [name, version, a.options],
54 | function (err) {
55 | if (err) {
56 | return logger.error(err);
57 | }
58 | logger.end();
59 | }
60 | );
61 | });
62 | };
63 |
--------------------------------------------------------------------------------
/test/utils.js:
--------------------------------------------------------------------------------
1 | var fork = require('child_process').fork,
2 | rimraf = require('rimraf'),
3 | path = require('path'),
4 | _ = require('underscore');
5 |
6 |
7 | exports.runJam = function (args, /*optional*/opts, callback) {
8 | if (!callback) {
9 | callback = opts;
10 | opts = {};
11 | }
12 | opts.silent = true;
13 |
14 | var bin = path.resolve(__dirname, '..', 'bin', 'jam.js');
15 | var jam = fork(bin, args, opts);
16 | var stdout = '', stderr = '';
17 |
18 | jam.stdout.on('data', function (data) {
19 | stdout += data.toString();
20 | });
21 | jam.stderr.on('data', function (data) {
22 | stderr += data.toString();
23 | });
24 | jam.on('exit', function (code) {
25 | if (code !== 0 && !opts.expect_error) {
26 | console.log(['Jam command failed', args]);
27 | console.log(stdout);
28 | console.log(stderr);
29 | return callback(
30 | new Error('Returned status code: ' + code),
31 | stdout,
32 | stderr
33 | );
34 | }
35 | callback(null, stdout, stderr);
36 | });
37 | jam.disconnect();
38 | };
39 |
40 |
41 | // Invalidates cached module and re-requires it. Used to load require.config.js.
42 | exports.freshRequire = function (p) {
43 | var cached = Object.keys(require.cache);
44 | var resolved = require.resolve(p);
45 | if (_.indexOf(cached, resolved) !== -1) {
46 | delete require.cache[resolved];
47 | }
48 | return require(p);
49 | };
50 |
51 | exports.myrimraf = function (p, callback, tries) {
52 | tries = tries || 50;
53 | rimraf(p, function (err) {
54 | if (err && err.code === 'EMBUSY') {
55 | if (tries) {
56 | setTimeout(function () {
57 | exports.myrimraf(p, callback, tries - 1);
58 | }, 100);
59 | return;
60 | }
61 | }
62 | return callback.apply(this, arguments);
63 | });
64 | };
65 |
--------------------------------------------------------------------------------
/bin/jam.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var path = require('path'),
4 | utils = require('../lib/utils'),
5 | jamrc = require('../lib/jamrc'),
6 | logger = require('../lib/logger'),
7 | commands = require('../lib/commands');
8 |
9 |
10 | var args = process.argv.slice(2);
11 |
12 | for (var i = 0; i < args.length; i += 1) {
13 | if (args[i] === '--debug') {
14 | args.splice(i, 1);
15 | logger.level = 'debug';
16 | }
17 |
18 | if (args[i] === '--verbose') {
19 | args.splice(i, 1);
20 | logger.level = 'verbose';
21 | }
22 | }
23 |
24 | jamrc.load(function (err, settings) {
25 |
26 | function usage() {
27 | console.log('jam COMMAND [ARGS]');
28 | console.log('');
29 | console.log('Available commands:');
30 | var len = utils.longest(Object.keys(commands));
31 | for (var k in commands) {
32 | if (!commands[k].hidden) {
33 | console.log(
34 | ' ' + utils.padRight(k, len) + ' ' + commands[k].summary
35 | );
36 | }
37 | }
38 | logger.clean_exit = true;
39 | }
40 |
41 | if (!args.length) {
42 | usage();
43 | }
44 | else {
45 | var cmd = args.shift();
46 | if (cmd === '-h' || cmd === '--help') {
47 | var concrete = args.shift();
48 |
49 | console.log('Usage:\n');
50 |
51 | if (concrete && commands[concrete]) {
52 | console.log(commands[concrete].usage);
53 | } else {
54 | usage();
55 | }
56 |
57 | console.log('\n');
58 | logger.clean_exit = true;
59 | }
60 | else if (cmd === '-v' || cmd === '--version') {
61 | utils.getJamVersion(function (err, ver) {
62 | if (err) {
63 | return logger.error(err);
64 | }
65 | logger.clean_exit = true;
66 | console.log(ver);
67 | });
68 | }
69 | else if (cmd in commands) {
70 | commands[cmd].run(settings, args, commands);
71 | }
72 | else {
73 | logger.error('No such command: ' + cmd);
74 | usage();
75 | }
76 | }
77 |
78 | });
79 |
--------------------------------------------------------------------------------
/lib/args.js:
--------------------------------------------------------------------------------
1 | var _ = require('underscore');
2 |
3 | /**
4 | * Extracts an option from command line args array, removing the opt from the
5 | * array and returning the value.
6 | */
7 |
8 | exports.getOpt = function (args, opt) {
9 | if (!(opt.match instanceof Array)) {
10 | opt.match = [opt.match];
11 | }
12 | for (var i = 0; i < args.length; i++) {
13 | for (var j = 0; j < opt.match.length; j++) {
14 | if (args[i].split('=')[0] === opt.match[j]) {
15 | if (opt.value) {
16 | var val;
17 | if (args[i].indexOf('=') !== -1) {
18 | val = args[i].split('=').slice(1).join('=');
19 | args.splice(i, 1);
20 | return val;
21 | }
22 | else {
23 | val = args[i + 1];
24 | args.splice(i, 2);
25 | return val;
26 | }
27 | }
28 | else {
29 | args.splice(i, 1);
30 | return true;
31 | }
32 | }
33 | }
34 | }
35 | if (opt.value) {
36 | return undefined;
37 | }
38 | return false;
39 | };
40 |
41 | exports.parse = function (args, opts) {
42 | var result = {positional: args.slice(), options: {}};
43 | for (var k in opts) {
44 | if (opts.hasOwnProperty(k)) {
45 | var val, arr = [];
46 | do {
47 | val = exports.getOpt(result.positional, opts[k]);
48 | arr.push(val);
49 | }
50 | while (val !== undefined && val !== false);
51 |
52 | arr = _.compact(arr);
53 |
54 | if (arr.length > 1) {
55 | if (opts[k].multiple) {
56 | result.options[k] = arr;
57 | }
58 | else {
59 | throw new Error('Multiple values not allowed for ' + k);
60 | }
61 | }
62 | else if (arr.length === 1) {
63 | result.options[k] = opts[k].multiple ? arr: arr[0];
64 | }
65 | else {
66 | result.options[k] = opts[k].multiple ? []: undefined;
67 | }
68 | }
69 | }
70 | return result;
71 | };
72 |
--------------------------------------------------------------------------------
/lib/commands/publish.js:
--------------------------------------------------------------------------------
1 | var utils = require('../utils'),
2 | logger = require('../logger'),
3 | couchdb = require('../couchdb'),
4 | repository = require('../repository'),
5 | argParse = require('../args').parse,
6 | url = require('url'),
7 | urlParse = url.parse,
8 | urlFormat = url.format;
9 |
10 |
11 | exports.summary = 'Publish a package to a repository';
12 |
13 | exports.usage = '' +
14 | 'jam publish [PACKAGE_PATH]\n' +
15 | '\n' +
16 | 'Parameters:\n' +
17 | ' PACKAGE_PATH Path to package directory to publish (defaults to ".")\n' +
18 | '\n' +
19 | 'Options:\n' +
20 | ' -r, --repository Target repository URL (defaults to first value in .jamrc)';
21 |
22 |
23 | exports.run = function (settings, args) {
24 | var a = argParse(args, {
25 | 'repo': {match: ['-r', '--repository'], value: true}
26 | });
27 | var dir = a.positional[0] || '.';
28 | var repo = a.options.repo || settings.repositories[0];
29 | if (process.env.JAM_TEST) {
30 | repo = process.env.JAM_TEST_DB;
31 | if (!repo) {
32 | throw 'JAM_TEST environment variable set, but no JAM_TEST_DB set';
33 | }
34 | }
35 | exports.publish('package', repo, dir, a.options, _.noop);
36 | };
37 |
38 |
39 | // called by both publish and publish-task commands
40 | exports.publish = function (type, repo, dir, options, callback) {
41 | utils.completeAuth(repo, true, function (err, repo) {
42 | if (err) {
43 | return callback(err);
44 | }
45 | utils.catchAuthError(exports.doPublish, repo, [type, dir, options],
46 | function (err) {
47 | if (err) {
48 | logger.error(err);
49 | return callback(err);
50 | }
51 |
52 | logger.end();
53 | callback();
54 | }
55 | );
56 | });
57 | };
58 |
59 |
60 | exports.doPublish = function (repo, type, dir, options, callback) {
61 | var root = couchdb(repo);
62 | root.instance.pathname = '';
63 | root.session(function (err, info, resp) {
64 | if (err) {
65 | return callback(err);
66 | }
67 | options.user = info.userCtx.name;
68 | options.server_time = new Date(resp.headers.date);
69 | repository.publish(dir, repo, options, callback);
70 | });
71 | };
72 |
73 |
--------------------------------------------------------------------------------
/lib/github.js:
--------------------------------------------------------------------------------
1 | var url = require('url'),
2 | request = require('request'),
3 | logger = require('./logger'),
4 | _ = require('underscore');
5 |
6 |
7 | var uc = encodeURIComponent;
8 |
9 |
10 | exports.GITHUB_URL = 'https://api.github.com';
11 | exports.GITHUB_RAW_URL = 'https://raw.github.com';
12 |
13 |
14 | exports.getRaw = function (user, repo, ref, path, callback) {
15 | var req = {
16 | json: true,
17 | url: url.resolve(
18 | exports.GITHUB_RAW_URL,
19 | _([user, repo, ref, path]).compact().map(uc).join('/')
20 | )
21 | };
22 | request.get(req, function (err, res, data) {
23 | if (data.error) {
24 | return callback(data.error);
25 | }
26 | callback(err, data);
27 | });
28 | };
29 |
30 |
31 | exports.repos = {};
32 |
33 | exports.repos.getArchiveLink = function (user, repo, format, ref, callback) {
34 | var req = {
35 | json: true,
36 | followRedirect: false,
37 | headers: {
38 | 'Accept': 'application/vnd.github.beta+json'
39 | },
40 | url: url.resolve(
41 | exports.GITHUB_URL,
42 | _.map(['repos', user, repo, format, ref], uc).join('/')
43 | )
44 | };
45 | logger.debug('getting github archive link', req.url);
46 | request.get(req, function (err, res, data) {
47 | if (err) {
48 | return callback(err);
49 | }
50 | if (data && data.error) {
51 | return callback(res.error);
52 | }
53 | if (res.statusCode === 404) {
54 | return callback('GitHub repository or tag not found');
55 | }
56 | if (!res.headers || !res.headers.location) {
57 | return callback('Failed to get archive link');
58 | }
59 | callback(null, res.headers.location);
60 | });
61 | };
62 |
63 | exports.repos.search = function (q, callback) {
64 | var req = {
65 | json: true,
66 | headers: {
67 | 'Accept': 'application/vnd.github.beta+json'
68 | },
69 | url: url.resolve(
70 | exports.GITHUB_URL,
71 | ['legacy', 'repos', 'search', uc(q)].join('/')
72 | )
73 | };
74 | logger.debug('searching github repositories', req.url);
75 | request.get(req, function (err, res, data) {
76 | if (err) {
77 | return callback(err);
78 | }
79 | if (data && data.error) {
80 | return callback(res.error);
81 | }
82 | callback(null, data);
83 | });
84 | };
85 |
--------------------------------------------------------------------------------
/lib/cache.js:
--------------------------------------------------------------------------------
1 | var tar = require('./tar'),
2 | env = require('./env'),
3 | jamrc = require('./jamrc'),
4 | rimraf = require('rimraf'),
5 | mkdirp = require('mkdirp'),
6 | async = require('async'),
7 | path = require('path'),
8 | fs = require('fs');
9 |
10 | var pathExists = fs.exists || path.exists;
11 |
12 | exports.add = function (cfg, filepath, callback) {
13 | var filename = cfg.name + '-' + cfg.version + '.tar.gz';
14 | var dir = exports.dir(cfg.name, cfg.version);
15 | var tarfile = path.resolve(dir, filename);
16 | var cachedir = path.resolve(dir, 'package');
17 |
18 | mkdirp(dir, function (err) {
19 | if (err) {
20 | return callback(err);
21 | }
22 | async.series([
23 | async.apply(tar.create, cfg, filepath, tarfile),
24 | async.apply(tar.extract, tarfile, cachedir)
25 | ],
26 | function (err) {
27 | callback(err, tarfile, cachedir);
28 | });
29 | });
30 | };
31 |
32 |
33 | exports.get = function (name, version, callback) {
34 | var filename = name + '-' + version + '.tar.gz';
35 | var dir = exports.dir(name, version);
36 | var tarfile = path.resolve(dir, filename);
37 | var cachedir = path.resolve(dir, 'package');
38 |
39 | pathExists(cachedir, function (exists) {
40 | if (exists) {
41 | return callback(null, tarfile, cachedir);
42 | }
43 | else {
44 | // Package not found in cache, return null
45 | return callback(null, null, null);
46 | }
47 | });
48 | };
49 |
50 |
51 | exports.moveTar = function (name, version, filepath, callback) {
52 | var filename = name + '-' + version + '.tar.gz';
53 | var dir = exports.dir(name, version);
54 | var tarfile = path.resolve(dir,filename);
55 | var cachedir = path.resolve(dir,'package');
56 |
57 | async.series([
58 | async.apply(mkdirp, dir),
59 | async.apply(rimraf, cachedir),
60 | async.apply(fs.rename, filepath, tarfile),
61 | async.apply(tar.extract, tarfile, cachedir)
62 | ],
63 | function (err) {
64 | if (err) {
65 | return callback(err);
66 | }
67 | callback(null, tarfile, cachedir);
68 | });
69 | };
70 |
71 |
72 | exports.dir = function (name, version) {
73 | var args = Array.prototype.slice.call(arguments);
74 | return path.resolve.apply(path, [jamrc.getCacheDir()].concat(args));
75 | };
76 |
77 |
78 | exports.clear = function (name, version, callback) {
79 | if (!callback) {
80 | callback = version;
81 | version = null;
82 | }
83 | if (!callback) {
84 | callback = name;
85 | name = null;
86 | }
87 | var dir;
88 | if (!name) {
89 | dir = exports.dir();
90 | }
91 | else if (!version) {
92 | dir = exports.dir(name);
93 | }
94 | else {
95 | dir = exports.dir(name, version);
96 | }
97 | rimraf(dir, callback);
98 | };
99 |
100 |
101 | exports.update = function (cfg, filepath, callback) {
102 | exports.clear(cfg.name, cfg.version, function (err) {
103 | if (err) {
104 | return callback(err);
105 | }
106 | exports.add(cfg, filepath, callback);
107 | });
108 | };
109 |
--------------------------------------------------------------------------------
/lib/commands/link_old.js:
--------------------------------------------------------------------------------
1 | var env = require('../env'),
2 | utils = require('../utils'),
3 | logger = require('../logger'),
4 | settings = require('../settings'),
5 | project = require('../project'),
6 | install = require('./install'),
7 | mkdirp = require('mkdirp'),
8 | rimraf = require('rimraf'),
9 | path = require('path'),
10 | fs = require('fs');
11 |
12 |
13 | var pathExists = fs.exists || path.exists;
14 |
15 |
16 | exports.summary = 'Creates a link to a development package';
17 |
18 | exports.usage = '' +
19 | 'jam link PATH\n' +
20 | '\n' +
21 | 'Parameters:\n' +
22 | ' PATH The path to the package directory\n' +
23 | '\n' +
24 | 'Options:\n' +
25 | ' -r, --repository Source repository URL (otherwise uses values in jamrc)\n' +
26 | ' -d, --package-dir Jam package directory (defaults to "./jam")';
27 |
28 |
29 | exports.run = function (_settings, args, commands) {
30 | var a = argParse(args, {
31 | 'repository': {match: ['-r', '--repository'], value: true},
32 | 'target_dir': {match: ['-d', '--package-dir'], value: true}
33 | });
34 |
35 | var opt = a.options;
36 |
37 | opt.repositories = _settings.repositories;
38 | if (a.options.repository) {
39 | opt.repositories = [a.options.repository];
40 | // don't allow package dir .jamrc file to overwrite repositories
41 | opt.fixed_repositories = true;
42 | }
43 |
44 | if (a.positional.length < 1) {
45 | console.log(exports.usage);
46 | logger.clean_exit = true;
47 | return;
48 | }
49 | var pkg = a.positional[0];
50 | var cwd = process.cwd();
51 |
52 | install.initDir(_settings, cwd, opt, function (err, opt, cfg, proj_dir) {
53 | if (err) {
54 | return logger.error(err);
55 | }
56 |
57 | opt = install.extendOptions(proj_dir, _settings, cfg, opt);
58 |
59 | // load info on package about to be linked
60 | settings.load(pkg, function (err, newpkg) {
61 | if (err) {
62 | return logger.error(err);
63 | }
64 |
65 | project.addJamDependency(cfg, newpkg.name, 'linked');
66 | var newpath = path.resolve(opt.target_dir, newpkg.name || '');
67 |
68 | mkdirp(path.dirname(newpath), function (err) {
69 | if (err) {
70 | return logger.error(err);
71 | }
72 | utils.createLink(path.resolve(pkg), newpath, function (err) {
73 | if (err) {
74 | return logger.error(err);
75 | }
76 | install.reinstallPackages(cfg, opt, function (err) {
77 | if (err) {
78 | return logger.error(err);
79 | }
80 | project.updateRequireConfig(
81 | opt.target_dir,
82 | opt.baseurl,
83 | function (err) {
84 | if (err) {
85 | return logger.error(err);
86 | }
87 | logger.end();
88 | }
89 | );
90 | });
91 | });
92 | });
93 |
94 | });
95 |
96 | });
97 |
98 | };
99 |
--------------------------------------------------------------------------------
/lib/commands/rebuild.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module dependencies
3 | */
4 |
5 | var path = require('path'),
6 | tree = require('../tree'),
7 | utils = require('../utils'),
8 | install = require('./install'),
9 | project = require('../project'),
10 | logger = require('../logger'),
11 | argParse = require('../args').parse;
12 |
13 |
14 | /**
15 | * Usage information and docs
16 | */
17 |
18 | exports.summary = 'Recreates require.js file with the latest config options';
19 |
20 |
21 | exports.usage = '' +
22 | 'jam rebuild\n' +
23 | '\n' +
24 | 'Options:\n' +
25 | ' -d, --package-dir Package directory (defaults to "./jam")',
26 | ' -c, --config Additional require.config.js properties to be included';
27 |
28 |
29 | /**
30 | * Run function called when "jam rebuild" command is used
31 | *
32 | * @param {Object} settings - the values from .jamrc files
33 | * @param {Array} args - command-line arguments
34 | */
35 |
36 | exports.run = function (settings, args) {
37 | var a = argParse(args, {
38 | 'target_dir': {match: ['-d', '--package-dir'], value: true},
39 | 'baseurl': {match: ['-b', '--baseurl'], value: true},
40 | 'config': {match: ['-c', '--config'], value: true},
41 | });
42 |
43 | var opt = a.options;
44 | var cwd = process.cwd();
45 |
46 | install.initDir(settings, cwd, opt, function (err, opt, cfg, proj_dir) {
47 | if (err) {
48 | return logger.error(err);
49 | }
50 |
51 | if (!opt.target_dir) {
52 | if (cfg.jam && cfg.jam.packageDir) {
53 | opt.target_dir = path.resolve(proj_dir, cfg.jam.packageDir);
54 | }
55 | else {
56 | opt.target_dir = path.resolve(proj_dir, settings.package_dir || '');
57 | }
58 | }
59 | if (!opt.baseurl) {
60 | if (cfg.jam && cfg.jam.baseUrl) {
61 | opt.baseurl = path.resolve(proj_dir, cfg.jam.baseUrl);
62 | }
63 | else {
64 | opt.baseurl = path.resolve(proj_dir, settings.baseUrl || '');
65 | }
66 | }
67 | if (!opt.config) {
68 | if (cfg.jam && cfg.jam.config) {
69 | opt.config = cfg.jam.config;
70 | } else {
71 | opt.conifg = {};
72 | }
73 | }
74 | exports.rebuild(settings, cfg, opt, function (err) {
75 | if (err) {
76 | return logger.error(err);
77 | }
78 | logger.end();
79 | });
80 | });
81 | };
82 |
83 |
84 | exports.rebuild = function (settings, cfg, opt, callback) {
85 | exports.readPackages(settings, cfg, opt, function (err, packages) {
86 | if (err) {
87 | return logger.error(err);
88 | }
89 | // TODO: write package.json if --save option provided
90 | project.updateRequireConfig(opt.target_dir, opt.baseurl, opt.config, callback);
91 | });
92 | };
93 |
94 |
95 | exports.readPackages = function (settings, cfg, opt, callback) {
96 | var local_sources = [
97 | install.dirSource(opt.target_dir),
98 | install.repoSource(settings.repositories, cfg)
99 | ];
100 | var newcfg = utils.convertToRootCfg(cfg);
101 | var pkg = {
102 | config: newcfg,
103 | source: 'root'
104 | };
105 | logger.info('Building local version tree...');
106 | tree.build(pkg, local_sources, callback);
107 | };
108 |
--------------------------------------------------------------------------------
/test/integration/test-publish-unpublish.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test description
3 | * ================
4 | *
5 | * - jam publish package-one @ 0.0.1
6 | * - jam publish package-one @ 0.0.2
7 | * - jam publish package-one @ 0.0.3
8 | * - jam unpublish package-one @ 0.0.3, check 0.0.1 and 0.0.2 still there
9 | * - jam unpublish package-one, check all version removed
10 | */
11 |
12 |
13 | var couchdb = require('../../lib/couchdb'),
14 | logger = require('../../lib/logger'),
15 | env = require('../../lib/env'),
16 | utils = require('../utils'),
17 | async = require('async'),
18 | http = require('http'),
19 | path = require('path'),
20 | ncp = require('ncp').ncp,
21 | fs = require('fs'),
22 | _ = require('underscore');
23 |
24 |
25 | var pathExists = fs.exists || path.exists;
26 |
27 |
28 | logger.clean_exit = true;
29 |
30 | // CouchDB database URL to use for testing
31 | var TESTDB = process.env['JAM_TEST_DB'],
32 | BIN = path.resolve(__dirname, '../../bin/jam.js'),
33 | ENV = {JAM_TEST: 'true', JAM_TEST_DB: TESTDB};
34 |
35 | if (!TESTDB) {
36 | throw 'JAM_TEST_DB environment variable not set';
37 | }
38 |
39 | // remove trailing-slash from TESTDB URL
40 | TESTDB = TESTDB.replace(/\/$/, '');
41 |
42 |
43 | exports.setUp = function (callback) {
44 | // change to integration test directory before running test
45 | this._cwd = process.cwd();
46 | process.chdir(__dirname);
47 |
48 | // recreate any existing test db
49 | couchdb(TESTDB).deleteDB(function (err) {
50 | if (err && err.error !== 'not_found') {
51 | return callback(err);
52 | }
53 | // create test db
54 | couchdb(TESTDB).createDB(callback);
55 | });
56 | };
57 |
58 | exports.tearDown = function (callback) {
59 | // change back to original working directory after running test
60 | process.chdir(this._cwd);
61 | // delete test db
62 | couchdb(TESTDB).deleteDB(callback);
63 | };
64 |
65 |
66 | exports['publish, unpublish'] = function (test) {
67 | test.expect(4);
68 | var pkgone = path.resolve(__dirname, 'fixtures', 'package-one'),
69 | pkgonev2 = path.resolve(__dirname, 'fixtures', 'package-one-v2'),
70 | pkgonev3 = path.resolve(__dirname, 'fixtures', 'package-one-v3');
71 |
72 | async.series([
73 | async.apply(utils.runJam, ['publish', pkgone], {env: ENV}),
74 | async.apply(utils.runJam, ['publish', pkgonev2], {env: ENV}),
75 | async.apply(utils.runJam, ['publish', pkgonev3], {env: ENV}),
76 | async.apply(
77 | utils.runJam, ['unpublish', 'package-one@0.0.3'], {env: ENV}
78 | ),
79 | function (cb) {
80 | couchdb(TESTDB).get('package-one', function (err, doc) {
81 | if (err) {
82 | return cb(err);
83 | }
84 | test.same(Object.keys(doc.versions).sort(), [
85 | '0.0.1',
86 | '0.0.2'
87 | ]);
88 | test.equal(doc.tags.latest, '0.0.2');
89 | test.equal(doc.name, 'package-one');
90 | cb();
91 | });
92 | },
93 | async.apply(
94 | utils.runJam, ['unpublish', 'package-one'], {env: ENV}
95 | ),
96 | function (cb) {
97 | couchdb(TESTDB).get('package-one', function (err, doc) {
98 | test.equal(err.error, 'not_found');
99 | cb();
100 | });
101 | }
102 | ],
103 | test.done);
104 | };
105 |
--------------------------------------------------------------------------------
/lib/commands/ls.js:
--------------------------------------------------------------------------------
1 | var logger = require('../logger'),
2 | settings = require('../settings'),
3 | project = require('../project'),
4 | install = require('./install'),
5 | utils = require('../utils'),
6 | async = require('async'),
7 | path = require('path'),
8 | fs = require('fs');
9 |
10 |
11 | var pathExists = fs.exists || path.exists;
12 |
13 |
14 | exports.summary = 'List installed packages';
15 |
16 | exports.usage = '' +
17 | 'jam ls [PACKAGE_DIR]\n' +
18 | '\n' +
19 | 'Packages listed with a leading * are directly installed, others\n' +
20 | 'will be marked as unused once their directly installed dependant\n' +
21 | 'is removed or no longer depends on them.\n' +
22 | '\n' +
23 | 'Parameters:\n' +
24 | ' PACKAGE_DIR The Jam package directory to list packages for\n' +
25 | ' (defaults to "./jam")';
26 |
27 |
28 | exports.run = function (_settings, args, commands) {
29 | var opt = {};
30 | var cwd = process.cwd();
31 | install.initDir(_settings, cwd, opt, function (err, opt, cfg, proj_dir) {
32 | if (err) {
33 | return logger.error(err);
34 | }
35 | var package_dir;
36 | if (!args[0]) {
37 | if (cfg.jam && cfg.jam.packageDir) {
38 | package_dir = path.resolve(proj_dir, cfg.jam.packageDir);
39 | }
40 | else {
41 | package_dir = path.resolve(proj_dir, _settings.package_dir || '');
42 | }
43 | }
44 | else {
45 | package_dir = args[0];
46 | }
47 | exports.ls(_settings, cfg, package_dir, function (err, output, pkgs) {
48 | if (err) {
49 | return logger.error();
50 | }
51 | console.log(output);
52 | logger.clean_exit = true;
53 | });
54 | });
55 | };
56 |
57 |
58 | exports.ls = function (_settings, cfg, package_dir, callback) {
59 | var deps = project.getJamDependencies(cfg);
60 | utils.listDirs(package_dir, function (err, dirs) {
61 | if (err) {
62 | return callback(err);
63 | }
64 |
65 | var packages = [];
66 | var lines = [];
67 |
68 | async.forEachLimit(dirs, 5, function (dir, cb) {
69 | settings.load(dir, function (err, pkg) {
70 | if (err) {
71 | return cb(err);
72 | }
73 | var line = '';
74 | if (deps.hasOwnProperty(pkg.name)) {
75 | // directly installed
76 | line += '* ';
77 | }
78 | else {
79 | line += ' '
80 | }
81 | line += pkg.name + ' ' + logger.yellow(pkg.version);
82 | if (deps[pkg.name] === 'linked') {
83 | var p = path.resolve(package_dir, pkg.name || '');
84 | var realpath = fs.readlinkSync(p);
85 | line += logger.cyan(' => ' + realpath);
86 | }
87 | else if (deps[pkg.name]) {
88 | // locked to a specific version/range
89 | line += logger.red(
90 | ' [locked ' + deps[pkg.name] + ']'
91 | );
92 | }
93 | packages.push(pkg);
94 | lines.push(line);
95 | cb();
96 | });
97 | },
98 | function (err) {
99 | return callback(err, lines.join('\n'), packages);
100 | });
101 | });
102 | };
103 |
--------------------------------------------------------------------------------
/test/integration/test-emptyproject-install-compile.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test description
3 | * ================
4 | *
5 | * Starting with an empty project (no package.json)
6 | * - jam publish package-one @ 0.0.1
7 | * - jam publish package-two @ 0.0.1
8 | * - jam install package-two
9 | * - jam compile output.js, test both modules included in output.js
10 | */
11 |
12 |
13 | var couchdb = require('../../lib/couchdb'),
14 | logger = require('../../lib/logger'),
15 | env = require('../../lib/env'),
16 | utils = require('../utils'),
17 | async = require('async'),
18 | http = require('http'),
19 | path = require('path'),
20 | ncp = require('ncp').ncp,
21 | fs = require('fs'),
22 | _ = require('underscore');
23 |
24 |
25 | var pathExists = fs.exists || path.exists;
26 |
27 |
28 | logger.clean_exit = true;
29 |
30 | // CouchDB database URL to use for testing
31 | var TESTDB = process.env['JAM_TEST_DB'],
32 | BIN = path.resolve(__dirname, '../../bin/jam.js'),
33 | ENV = {JAM_TEST: 'true', JAM_TEST_DB: TESTDB};
34 |
35 | if (!TESTDB) {
36 | throw 'JAM_TEST_DB environment variable not set';
37 | }
38 |
39 | // remove trailing-slash from TESTDB URL
40 | TESTDB = TESTDB.replace(/\/$/, '');
41 |
42 |
43 | exports.setUp = function (callback) {
44 | // change to integration test directory before running test
45 | this._cwd = process.cwd();
46 | process.chdir(__dirname);
47 |
48 | // recreate any existing test db
49 | couchdb(TESTDB).deleteDB(function (err) {
50 | if (err && err.error !== 'not_found') {
51 | return callback(err);
52 | }
53 | // create test db
54 | couchdb(TESTDB).createDB(callback);
55 | });
56 | };
57 |
58 | exports.tearDown = function (callback) {
59 | // change back to original working directory after running test
60 | process.chdir(this._cwd);
61 | // delete test db
62 | couchdb(TESTDB).deleteDB(callback);
63 | };
64 |
65 |
66 | exports['empty project'] = {
67 |
68 | setUp: function (callback) {
69 | this.project_dir = path.resolve(env.temp, 'jamtest-' + Math.random());
70 | // set current project to empty directory
71 | ncp('./fixtures/project-empty', this.project_dir, callback);
72 | },
73 |
74 | /*
75 | tearDown: function (callback) {
76 | var that = this;
77 | // timeout to try and wait until dir is no-longer busy on windows
78 | //utils.myrimraf(that.project_dir, callback);
79 | },
80 | */
81 |
82 | 'publish, install, upgrade': function (test) {
83 | test.expect(5);
84 | var that = this;
85 | process.chdir(that.project_dir);
86 | var pkgone = path.resolve(__dirname, 'fixtures', 'package-one'),
87 | pkgtwo = path.resolve(__dirname, 'fixtures', 'package-two');
88 |
89 | async.series([
90 | async.apply(utils.runJam, ['publish', pkgone], {env: ENV}),
91 | async.apply(utils.runJam, ['publish', pkgtwo], {env: ENV}),
92 | async.apply(
93 | utils.runJam, ['install', 'package-two'], {env: ENV}
94 | ),
95 | async.apply(
96 | utils.runJam, ['compile', 'output.js'], {env: ENV}
97 | ),
98 | function (cb) {
99 | var content = fs.readFileSync('output.js').toString();
100 | test.ok(/Package One/.test(content));
101 | test.ok(/Package Two/.test(content));
102 | test.ok(/requirejs/.test(content));
103 | test.ok(/package-one\/main/.test(content));
104 | test.ok(/package-two\/two/.test(content));
105 | cb();
106 | }
107 | ],
108 | test.done);
109 | }
110 |
111 | };
112 |
--------------------------------------------------------------------------------
/test/integration/test-link.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test description
3 | * ================
4 | *
5 | * This test is skipped on Windows because it doesn't support link command
6 | *
7 | * Starting with an empty project (no package.json)
8 | * - jam link ./fixtures/package-one
9 | * - test symlink installed to ./jam/package-one
10 | * - test require.config.js is updated
11 | */
12 |
13 |
14 | var couchdb = require('../../lib/couchdb'),
15 | logger = require('../../lib/logger'),
16 | env = require('../../lib/env'),
17 | utils = require('../utils'),
18 | async = require('async'),
19 | http = require('http'),
20 | path = require('path'),
21 | ncp = require('ncp').ncp,
22 | fs = require('fs'),
23 | _ = require('underscore');
24 |
25 |
26 | var pathExists = fs.exists || path.exists;
27 |
28 |
29 | if (env.isWindows) {
30 | // skip this test
31 | return;
32 | }
33 |
34 |
35 | logger.clean_exit = true;
36 |
37 | // CouchDB database URL to use for testing
38 | var TESTDB = process.env['JAM_TEST_DB'],
39 | BIN = path.resolve(__dirname, '../../bin/jam.js'),
40 | ENV = {JAM_TEST: 'true', JAM_TEST_DB: TESTDB};
41 |
42 | if (!TESTDB) {
43 | throw 'JAM_TEST_DB environment variable not set';
44 | }
45 |
46 | // remove trailing-slash from TESTDB URL
47 | TESTDB = TESTDB.replace(/\/$/, '');
48 |
49 |
50 | exports.setUp = function (callback) {
51 | // change to integration test directory before running test
52 | this._cwd = process.cwd();
53 | process.chdir(__dirname);
54 |
55 | // recreate any existing test db
56 | couchdb(TESTDB).deleteDB(function (err) {
57 | if (err && err.error !== 'not_found') {
58 | return callback(err);
59 | }
60 | // create test db
61 | couchdb(TESTDB).createDB(callback);
62 | });
63 | };
64 |
65 | exports.tearDown = function (callback) {
66 | // change back to original working directory after running test
67 | process.chdir(this._cwd);
68 | // delete test db
69 | couchdb(TESTDB).deleteDB(callback);
70 | };
71 |
72 |
73 | exports['empty project'] = {
74 |
75 | setUp: function (callback) {
76 | this.project_dir = path.resolve(env.temp, 'jamtest-' + Math.random());
77 | // set current project to empty directory
78 | ncp('./fixtures/project-empty', this.project_dir, callback);
79 | },
80 |
81 | /*
82 | tearDown: function (callback) {
83 | var that = this;
84 | // timeout to try and wait until dir is no-longer busy on windows
85 | //utils.myrimraf(that.project_dir, callback);
86 | },
87 | */
88 |
89 | 'link': function (test) {
90 | test.expect(2);
91 | var that = this;
92 | process.chdir(that.project_dir);
93 | var pkgone = path.resolve(__dirname, 'fixtures', 'package-one');
94 |
95 | async.series([
96 | async.apply(utils.runJam, ['link', pkgone], {env: ENV}),
97 | function (cb) {
98 | var p = path.resolve(that.project_dir, 'jam', 'package-one');
99 | fs.lstat(p, function (err, stats) {
100 | if (err) {
101 | return cb(err);
102 | }
103 | test.ok(stats.isSymbolicLink(), 'package-one is symlink');
104 | var cfg = utils.freshRequire(
105 | path.resolve(that.project_dir, 'jam', 'require.config')
106 | );
107 | test.same(cfg.packages, [
108 | {
109 | name: 'package-one',
110 | location: 'jam/package-one'
111 | }
112 | ]);
113 | cb();
114 | });
115 | }
116 | ],
117 | test.done);
118 | }
119 |
120 | };
121 |
--------------------------------------------------------------------------------
/test/integration/test-publish.js:
--------------------------------------------------------------------------------
1 | var http = require('http'),
2 | couchdb = require('../../lib/couchdb'),
3 | logger = require('../../lib/logger'),
4 | utils = require('../utils'),
5 | path = require('path');
6 |
7 |
8 | logger.clean_exit = true;
9 |
10 | // CouchDB database URL to use for testing
11 | var TESTDB = process.env['JAM_TEST_DB'],
12 | BIN = path.resolve(__dirname, '../../bin/jam.js'),
13 | ENV = {JAM_TEST: 'true', JAM_TEST_DB: TESTDB};
14 |
15 | if (!TESTDB) {
16 | throw 'JAM_TEST_DB environment variable not set';
17 | }
18 |
19 | // remove trailing-slash from TESTDB URL
20 | TESTDB = TESTDB.replace(/\/$/, '');
21 |
22 |
23 | exports.setUp = function (callback) {
24 | // change to integration test directory before running test
25 | this._cwd = process.cwd();
26 | process.chdir(__dirname);
27 |
28 | // recreate any existing test db
29 | couchdb(TESTDB).deleteDB(function (err) {
30 | if (err && err.error !== 'not_found') {
31 | return callback(err);
32 | }
33 | // create test db
34 | couchdb(TESTDB).createDB(callback);
35 | });
36 | };
37 |
38 | exports.tearDown = function (callback) {
39 | // change back to original working directory after running test
40 | process.chdir(this._cwd);
41 | // delete test db
42 | couchdb(TESTDB).deleteDB(callback);
43 | };
44 |
45 |
46 | exports['publish within package directory'] = function (test) {
47 | test.expect(1);
48 | process.chdir('./fixtures/package-one');
49 | utils.runJam(['publish'], {env: ENV}, function (err, stdout, stderr) {
50 | if (err) {
51 | return test.done(err);
52 | }
53 | couchdb(TESTDB).get('package-one', function (err, doc) {
54 | test.equal(doc.name, 'package-one');
55 | test.done(err);
56 | });
57 | });
58 | };
59 |
60 | exports['publish path outside package directory'] = function (test) {
61 | test.expect(1);
62 | var args = ['publish', path.resolve('fixtures', 'package-one')];
63 | utils.runJam(args, {env: ENV}, function (err, stdout, stderr) {
64 | if (err) {
65 | return test.done(err);
66 | }
67 | couchdb(TESTDB).get('package-one', function (err, doc) {
68 | test.equal(doc.name, 'package-one');
69 | test.done(err);
70 | });
71 | });
72 | };
73 |
74 | exports['publish to command-line repo'] = function (test) {
75 | test.expect(1);
76 | var args = [
77 | 'publish',
78 | path.resolve('fixtures', 'package-one'),
79 | '--repository=' + TESTDB
80 | ];
81 | utils.runJam(args, function (err, stdout, stderr) {
82 | if (err) {
83 | return test.done(err);
84 | }
85 | couchdb(TESTDB).get('package-one', function (err, doc) {
86 | test.equal(doc.name, 'package-one');
87 | test.done(err);
88 | });
89 | });
90 | };
91 |
92 | exports['publish with invalid .js package name'] = function (test) {
93 | test.expect(1);
94 | process.chdir('./fixtures/package-three-invalid-extjs');
95 | utils.runJam(['publish'], {env: ENV, expect_error: true},
96 | function (err, stdout, stderr) {
97 | if (err) {
98 | return test.done(err);
99 | }
100 | test.ok(/Invalid name property/.test(stderr));
101 | test.done();
102 | }
103 | );
104 | };
105 |
106 | exports['publish with invalid package name characters'] = function (test) {
107 | test.expect(1);
108 | process.chdir('./fixtures/package-three-invalid-characters');
109 | utils.runJam(['publish'], {env: ENV, expect_error: true},
110 | function (err, stdout, stderr) {
111 | if (err) {
112 | return test.done(err);
113 | }
114 | test.ok(/Invalid name property/.test(stderr));
115 | test.done();
116 | }
117 | );
118 | };
119 |
--------------------------------------------------------------------------------
/lib/commands/search.js:
--------------------------------------------------------------------------------
1 | var utils = require('../utils'),
2 | logger = require('../logger'),
3 | repository = require('../repository'),
4 | github = require('../github'),
5 | async = require('async');
6 |
7 |
8 | exports.summary = 'Search available package sources';
9 |
10 | exports.usage = '' +
11 | 'jam search QUERY\n' +
12 | '\n' +
13 | 'Parameters:\n' +
14 | ' QUERY The package name to search for\n' +
15 | '\n' +
16 | 'Options:\n' +
17 | ' -r, --repository Source repository URL (otherwise uses values in jamrc)\n' +
18 | ' -g, --github Search GitHub for matching repository name\n' +
19 | ' (does not search package repositories when used)\n' +
20 | ' -l, --limit Maximum number of results to return (defaults to 10).\n' +
21 | ' When searching multiple repositories the limit is\n' +
22 | ' applied to each repository, not the total';
23 |
24 |
25 | exports.run = function (settings, args, commands) {
26 | var a = argParse(args, {
27 | 'repository': {match: ['-r', '--repository'], value: true},
28 | 'github': {match: ['-g', '--github']},
29 | 'limit': {match: ['-l', '--limit'], value: true}
30 | });
31 |
32 | var opt = a.options;
33 | var repos = opt.repository ? [opt.repository]: settings.repositories;
34 | var limit = opt.limit || 10;
35 | var q = a.positional[0];
36 |
37 | if (!q) {
38 | logger.error('No query parameter');
39 | return;
40 | }
41 |
42 | if (opt.github) {
43 | exports.searchGitHub(q, limit);
44 | }
45 | else {
46 | exports.searchRepositories(repos, q, limit);
47 | }
48 | };
49 |
50 |
51 | exports.searchRepositories = function (repos, q, limit) {
52 | var total = 0;
53 | async.forEachLimit(repos, 4, function (repo, cb) {
54 | if (typeof repo === 'object' && repo.search === false) {
55 | cb();
56 | return;
57 | }
58 |
59 | repository.search(repo, q, limit, function (err, data) {
60 | if (repos.length > 1) {
61 | console.log(
62 | logger.bold('\n' + logger.magenta(
63 | 'Results for ' + utils.noAuthURL(repo)
64 | ))
65 | );
66 | }
67 | if (err) {
68 | logger.error(err);
69 | return cb(err);
70 | }
71 | total += data.rows.length;
72 | data.rows.forEach(function (r) {
73 | var desc = utils.truncate(r.doc.description.split('\n')[0], 76);
74 | console.log(
75 | logger.bold(r.doc.name) +
76 | logger.yellow(' ' + r.doc.tags.latest + '\n') +
77 | ' ' + desc
78 | );
79 | });
80 | if (repos.length > 1) {
81 | console.log(logger.cyan(
82 | data.rows.length + ' results (limit: ' + limit + ')\n'
83 | ));
84 | }
85 | cb();
86 | });
87 | },
88 | function (err) {
89 | if (!err) {
90 | return logger.end(
91 | total + ' total results' +
92 | (repos.length > 1 ? '': ' (limit: ' + limit + ')')
93 | );
94 | }
95 | });
96 | };
97 |
98 |
99 | exports.searchGitHub = function (q, limit) {
100 | github.repos.search(q, function (err, data) {
101 | if (err) {
102 | return console.error(err);
103 | }
104 | var repos = data.repositories.slice(0, limit);
105 | repos.forEach(function (r) {
106 | var desc = utils.truncate(r.description.split('\n')[0], 76);
107 | console.log(
108 | logger.bold('gh:' + r.owner + '/' + r.name) + '\n' +
109 | //logger.yellow(' ' + r.version + '\n') + //TODO get latest tag?
110 | ' ' + desc
111 | );
112 | });
113 | return logger.end(repos.length + ' total results (limit: ' + limit + ')');
114 | });
115 | };
116 |
--------------------------------------------------------------------------------
/lib/logger.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module dependencies
3 | */
4 |
5 | var util = require('util'),
6 | _ = require('underscore');
7 |
8 | /**
9 | * The level to log at, change this to alter the global logging level.
10 | * Possible options are: error, warning, info, debug. Default level is info.
11 | */
12 | exports.level = 'info';
13 |
14 |
15 | /**
16 | * Wraps some ANSI codes around some text.
17 | */
18 | var wrap = function (code, reset) {
19 | return function (str) {
20 | return "\x1B[" + code + "m" + str + "\x1B[" + reset + "m";
21 | };
22 | };
23 |
24 | /**
25 | * ANSI colors and styles used by the logger module.
26 | */
27 | var bold = exports.bold = wrap(1, 22);
28 | var red = exports.red = wrap(31, 39);
29 | var green = exports.green = wrap(32, 39);
30 | var cyan = exports.cyan = wrap(36, 39);
31 | var yellow = exports.yellow = wrap(33, 39);
32 | var magenta = exports.magenta = wrap(35, 39);
33 |
34 | /**
35 | * Executes a function only if the current log level is in the levels list
36 | *
37 | * @param {Array} levels
38 | * @param {Function} fn
39 | */
40 |
41 | var forLevels = function (levels, fn) {
42 | return function () {
43 | for (var i = 0; i < levels.length; i++) {
44 | if (levels[i] === exports.level) {
45 | return fn.apply(exports, arguments);
46 | }
47 | }
48 | };
49 | };
50 |
51 | /**
52 | * Logs verbose messages, using util.inspect to show the properties of objects
53 | * (logged for 'verbose' level only)
54 | */
55 |
56 | exports.verbose = forLevels(['verbose'], function (label) {
57 | var args, withArgs;
58 |
59 | args = Array.prototype.slice.call(arguments, 0).map(function(arg) {
60 | return typeof arg == "string" ? arg : util.inspect(arg);
61 | });
62 |
63 | withArgs = args.length > 1;
64 |
65 | if (!withArgs) {
66 | label = null;
67 | }
68 |
69 | if (label && withArgs) {
70 | console.log(yellow(label + ' ') + args.slice(1).join("\n"));
71 | } else {
72 | console.log(args.join('\n'));
73 | }
74 | });
75 |
76 | /**
77 | * Logs debug messages, using util.inspect to show the properties of objects
78 | * (logged for 'debug', 'verbose' levels)
79 | */
80 |
81 | exports.debug = forLevels(['debug', 'verbose'], function (label, val) {
82 | if (val === undefined) {
83 | val = label;
84 | label = null;
85 | }
86 | if (typeof val !== 'string') {
87 | val = util.inspect(val);
88 | }
89 | if (label && val) {
90 | console.log(magenta(label + ' ') + val);
91 | }
92 | else {
93 | console.log(label);
94 | }
95 | });
96 |
97 | /**
98 | * Logs info messages (logged for 'info' and 'debug' levels)
99 | */
100 |
101 | exports.info = forLevels(['info', 'debug', 'verbose'], function (label, val) {
102 | if (val === undefined) {
103 | val = label;
104 | label = null;
105 | }
106 | if (typeof val !== 'string') {
107 | val = util.inspect(val);
108 | }
109 | if (label) {
110 | console.log(cyan(label + ' ') + val);
111 | }
112 | else {
113 | console.log(val);
114 | }
115 | });
116 |
117 | /**
118 | * Logs warnings messages (logged for 'warning', 'info' and 'debug' levels)
119 | */
120 |
121 | exports.warning = forLevels(['warning', 'info', 'debug', 'verbose'], function (msg) {
122 | console.log(yellow(bold('Warning: ') + msg));
123 | });
124 |
125 | /**
126 | * Logs error messages (always logged)
127 | */
128 |
129 | exports.error = function (err) {
130 | var msg = err.message || err.error || err;
131 | if (err.stack) {
132 | msg = err.stack.replace(/^Error: /, '');
133 | }
134 | console.error(red(bold('Error: ') + msg));
135 | };
136 |
137 |
138 | /**
139 | * Display a failure message if exit is unexpected.
140 | */
141 |
142 | exports.clean_exit = false;
143 | exports.end = function (msg) {
144 | exports.clean_exit = true;
145 | exports.success(msg);
146 | };
147 | exports.success = function (msg) {
148 | console.log(green(bold('OK') + (msg ? bold(': ') + msg: '')));
149 | };
150 | var _onExit = function () {
151 | if (!exports.clean_exit) {
152 | console.log(red(bold('Failed')));
153 | process.removeListener('exit', _onExit);
154 | process.exit(1);
155 | }
156 | };
157 | process.on('exit', _onExit);
158 |
159 | /**
160 | * Log uncaught exceptions in the same style as normal errors.
161 | */
162 |
163 | process.on('uncaughtException', function (err) {
164 | exports.error(err.stack || err);
165 | });
166 |
--------------------------------------------------------------------------------
/lib/commands/remove.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module dependencies
3 | */
4 |
5 | var path = require('path'),
6 | async = require('async'),
7 | rimraf = require('rimraf'),
8 | install = require('./install'),
9 | tree = require('../tree'),
10 | utils = require('../utils'),
11 | project = require('../project'),
12 | logger = require('../logger'),
13 | clean = require('./clean'),
14 | argParse = require('../args').parse;
15 |
16 |
17 | /**
18 | * Usage information and docs
19 | */
20 |
21 | exports.summary = 'Removes a package from the package directory';
22 |
23 |
24 | exports.usage = '' +
25 | 'jam remove PACKAGE [MORE...]\n' +
26 | '\n' +
27 | 'Parameters:\n' +
28 | ' PACKAGE The package to remove\n' +
29 | '\n' +
30 | 'Options:\n' +
31 | ' -d, --package-dir Package directory (defaults to "./jam")';
32 |
33 | /* TODO
34 | ' --clean Runs clean command after removing the package, to remove\n' +
35 | ' any unused dependencies';
36 | */
37 |
38 |
39 | /**
40 | * Run function called when "jam remove" command is used
41 | *
42 | * @param {Object} settings - the values from .jamrc files
43 | * @param {Array} args - command-line arguments
44 | */
45 |
46 | exports.run = function (settings, args) {
47 | var a = argParse(args, {
48 | 'target_dir': {match: ['-d', '--package-dir'], value: true}
49 | });
50 |
51 | if (a.positional.length < 1) {
52 | console.log(exports.usage);
53 | logger.clean_exit = true;
54 | return;
55 | }
56 |
57 | var opt = a.options;
58 | var cwd = process.cwd();
59 |
60 | install.initDir(settings, cwd, opt, function (err, opt, cfg, proj_dir) {
61 | if (err) {
62 | return logger.error(err);
63 | }
64 |
65 | opt = install.extendOptions(proj_dir, settings, cfg, opt);
66 |
67 | var names = a.positional;
68 | exports.remove(settings, cfg, opt, names, function (err) {
69 | if (err) {
70 | return logger.error(err);
71 | }
72 | logger.end();
73 | });
74 | });
75 | };
76 |
77 |
78 | exports.remove = function (settings, cfg, opt, names, callback) {
79 | exports.checkDependants(settings, cfg, opt, names, function (err) {
80 | if (err) {
81 | return callback(err);
82 | }
83 | async.series([
84 | async.apply(
85 | async.forEach, names,
86 | async.apply(exports.removePackage, cfg, opt)
87 | ),
88 | async.apply(exports.buildLocalTree, settings, cfg, opt),
89 | async.apply(
90 | project.updateRequireConfig, opt.target_dir, opt.baseurl
91 | )
92 | ],
93 | callback);
94 | });
95 | };
96 |
97 |
98 | exports.checkDependants = function (settings, cfg, opt, names, callback) {
99 | exports.buildLocalTree(settings, cfg, opt, function (err, packages) {
100 | if (err) {
101 | return callback(err);
102 | }
103 | var has_dependants = false;
104 | for (var i = 0; i < names.length; i++) {
105 | var name = names[i];
106 | var pkg = packages[name];
107 | if (pkg) {
108 | var ranges = packages[name].ranges;
109 | var dependants = Object.keys(ranges).filter(function (d) {
110 | return d !== '_root' && names.indexOf(d) === -1;
111 | });
112 | if (dependants.length) {
113 | for (var j = 0; j < dependants.length; j++) {
114 | var d = dependants[j];
115 | logger.error(
116 | d + ' depends on ' + name + ' ' + ranges[d]
117 | );
118 | };
119 | has_dependants = true;
120 | }
121 | }
122 | };
123 | if (has_dependants) {
124 | return callback('Cannot remove package with dependants');
125 | }
126 | return callback();
127 | });
128 | };
129 |
130 |
131 | exports.removePackage = function (cfg, opt, name, callback) {
132 | logger.info('removing', name);
133 | cfg = project.removeJamDependency(cfg, name);
134 | rimraf(path.resolve(opt.target_dir, name), callback);
135 | };
136 |
137 |
138 | exports.buildLocalTree = function (settings, cfg, opt, callback) {
139 | var local_sources = [
140 | install.dirSource(opt.target_dir),
141 | install.repoSource(settings.repositories, cfg)
142 | ];
143 | var newcfg = utils.convertToRootCfg(cfg);
144 | var pkg = {
145 | config: newcfg,
146 | source: 'root'
147 | };
148 | logger.info('Building local version tree...');
149 | tree.build(pkg, local_sources, callback);
150 | };
151 |
--------------------------------------------------------------------------------
/test/integration/test-emptyproject-install-rm-rebuild.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test description
3 | * ================
4 | *
5 | * Starting with an empty project (no package.json)
6 | * - jam publish package-one @ 0.0.1
7 | * - jam publish package-two @ 0.0.1
8 | * - jam install package-two, test both modules in require.config.js
9 | * - rm -rf package-two
10 | * - jam rebuild, test only package-one in require.config.js
11 | */
12 |
13 |
14 | var couchdb = require('../../lib/couchdb'),
15 | logger = require('../../lib/logger'),
16 | env = require('../../lib/env'),
17 | utils = require('../utils'),
18 | async = require('async'),
19 | http = require('http'),
20 | path = require('path'),
21 | ncp = require('ncp').ncp,
22 | fs = require('fs'),
23 | _ = require('underscore');
24 |
25 |
26 | var pathExists = fs.exists || path.exists;
27 |
28 |
29 | logger.clean_exit = true;
30 |
31 | // CouchDB database URL to use for testing
32 | var TESTDB = process.env['JAM_TEST_DB'],
33 | BIN = path.resolve(__dirname, '../../bin/jam.js'),
34 | ENV = {JAM_TEST: 'true', JAM_TEST_DB: TESTDB};
35 |
36 | if (!TESTDB) {
37 | throw 'JAM_TEST_DB environment variable not set';
38 | }
39 |
40 | // remove trailing-slash from TESTDB URL
41 | TESTDB = TESTDB.replace(/\/$/, '');
42 |
43 |
44 | exports.setUp = function (callback) {
45 | // change to integration test directory before running test
46 | this._cwd = process.cwd();
47 | process.chdir(__dirname);
48 |
49 | // recreate any existing test db
50 | couchdb(TESTDB).deleteDB(function (err) {
51 | if (err && err.error !== 'not_found') {
52 | return callback(err);
53 | }
54 | // create test db
55 | couchdb(TESTDB).createDB(callback);
56 | });
57 | };
58 |
59 | exports.tearDown = function (callback) {
60 | // change back to original working directory after running test
61 | process.chdir(this._cwd);
62 | // delete test db
63 | couchdb(TESTDB).deleteDB(callback);
64 | };
65 |
66 |
67 | exports['empty project'] = {
68 |
69 | setUp: function (callback) {
70 | this.project_dir = path.resolve(env.temp, 'jamtest-' + Math.random());
71 | // set current project to empty directory
72 | ncp('./fixtures/project-empty', this.project_dir, callback);
73 | },
74 |
75 | /*
76 | tearDown: function (callback) {
77 | var that = this;
78 | // timeout to try and wait until dir is no-longer busy on windows
79 | //utils.myrimraf(that.project_dir, callback);
80 | },
81 | */
82 |
83 | 'publish, install, upgrade': function (test) {
84 | test.expect(2);
85 | var that = this;
86 | process.chdir(that.project_dir);
87 | var pkgone = path.resolve(__dirname, 'fixtures', 'package-one'),
88 | pkgtwo = path.resolve(__dirname, 'fixtures', 'package-two');
89 |
90 | async.series([
91 | async.apply(utils.runJam, ['publish', pkgone], {env: ENV}),
92 | async.apply(utils.runJam, ['publish', pkgtwo], {env: ENV}),
93 | async.apply(
94 | utils.runJam, ['install', 'package-two'], {env: ENV}
95 | ),
96 | function (cb) {
97 | var cfg = utils.freshRequire(
98 | path.resolve(that.project_dir, 'jam', 'require.config')
99 | );
100 | var packages= _.sortBy(cfg.packages, function (p) {
101 | return p.name;
102 | });
103 | test.same(packages, [
104 | {
105 | name: 'package-one',
106 | location: 'jam/package-one'
107 | },
108 | {
109 | name: 'package-two',
110 | location: 'jam/package-two',
111 | main: 'two.js'
112 | }
113 | ]);
114 | cb();
115 | },
116 | async.apply(
117 | utils.myrimraf,
118 | path.resolve(that.project_dir, 'jam', 'package-two')
119 | ),
120 | async.apply(utils.runJam, ['rebuild'], {env: ENV}),
121 | function (cb) {
122 | var cfg = utils.freshRequire(
123 | path.resolve(that.project_dir, 'jam', 'require.config')
124 | );
125 | var packages= _.sortBy(cfg.packages, function (p) {
126 | return p.name;
127 | });
128 | test.same(packages, [
129 | {
130 | name: 'package-one',
131 | location: 'jam/package-one'
132 | }
133 | ]);
134 | cb();
135 | }
136 | ],
137 | test.done);
138 | }
139 |
140 | };
141 |
--------------------------------------------------------------------------------
/test/integration/test-emptyproject-publish-install-upgrade.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test description
3 | * ================
4 | *
5 | * Starting with an empty project (no package.json)
6 | * - jam publish package-one @ 0.0.1
7 | * - jam publish package-one @ 0.0.2
8 | * - jam install package-one @ 0.0.1, test installation succeeded
9 | * - jam upgrade, test package-one is now at 0.0.2
10 | */
11 |
12 |
13 | var couchdb = require('../../lib/couchdb'),
14 | logger = require('../../lib/logger'),
15 | env = require('../../lib/env'),
16 | utils = require('../utils'),
17 | async = require('async'),
18 | http = require('http'),
19 | path = require('path'),
20 | ncp = require('ncp').ncp,
21 | fs = require('fs'),
22 | _ = require('underscore');
23 |
24 |
25 | var pathExists = fs.exists || path.exists;
26 |
27 |
28 | logger.clean_exit = true;
29 |
30 | // CouchDB database URL to use for testing
31 | var TESTDB = process.env['JAM_TEST_DB'],
32 | BIN = path.resolve(__dirname, '../../bin/jam.js'),
33 | ENV = {JAM_TEST: 'true', JAM_TEST_DB: TESTDB};
34 |
35 | if (!TESTDB) {
36 | throw 'JAM_TEST_DB environment variable not set';
37 | }
38 |
39 | // remove trailing-slash from TESTDB URL
40 | TESTDB = TESTDB.replace(/\/$/, '');
41 |
42 |
43 | exports.setUp = function (callback) {
44 | // change to integration test directory before running test
45 | this._cwd = process.cwd();
46 | process.chdir(__dirname);
47 |
48 | // recreate any existing test db
49 | couchdb(TESTDB).deleteDB(function (err) {
50 | if (err && err.error !== 'not_found') {
51 | return callback(err);
52 | }
53 | // create test db
54 | couchdb(TESTDB).createDB(callback);
55 | });
56 | };
57 |
58 | exports.tearDown = function (callback) {
59 | // change back to original working directory after running test
60 | process.chdir(this._cwd);
61 | // delete test db
62 | couchdb(TESTDB).deleteDB(callback);
63 | };
64 |
65 |
66 | exports['empty project'] = {
67 |
68 | setUp: function (callback) {
69 | this.project_dir = path.resolve(env.temp, 'jamtest-' + Math.random());
70 | // set current project to empty directory
71 | ncp('./fixtures/project-empty', this.project_dir, callback);
72 | },
73 |
74 | /*
75 | tearDown: function (callback) {
76 | var that = this;
77 | // timeout to try and wait until dir is no-longer busy on windows
78 | //utils.myrimraf(that.project_dir, callback);
79 | },
80 | */
81 |
82 | 'publish, install, upgrade': function (test) {
83 | test.expect(4);
84 | var that = this;
85 | process.chdir(that.project_dir);
86 | var pkgone = path.resolve(__dirname, 'fixtures', 'package-one'),
87 | pkgonev2 = path.resolve(__dirname, 'fixtures', 'package-one-v2');
88 |
89 | async.series([
90 | async.apply(utils.runJam, ['publish', pkgone], {env: ENV}),
91 | async.apply(utils.runJam, ['publish', pkgonev2], {env: ENV}),
92 | async.apply(
93 | utils.runJam, ['install', 'package-one@0.0.1'], {env: ENV}
94 | ),
95 | function (cb) {
96 | // test that main.js was installed from package
97 | var a = fs.readFileSync(path.resolve(pkgone, 'main.js'));
98 | var b = fs.readFileSync(
99 | path.resolve(that.project_dir, 'jam/package-one/main.js')
100 | );
101 | test.equal(a.toString(), b.toString());
102 |
103 | // make sure the requirejs config includes the new package
104 | var cfg = utils.freshRequire(
105 | path.resolve(that.project_dir, 'jam', 'require.config')
106 | );
107 | test.same(cfg.packages, [
108 | {
109 | name: 'package-one',
110 | location: 'jam/package-one'
111 | }
112 | ]);
113 | cb();
114 | },
115 | function (cb) {
116 | var args = ['upgrade'];
117 | utils.runJam(args, {env: ENV}, function (err, stdout, stderr) {
118 | if (err) {
119 | return cb(err);
120 | }
121 | var cfg = utils.freshRequire(
122 | path.resolve(that.project_dir, 'jam', 'require.config')
123 | );
124 | test.same(cfg.packages, [
125 | {
126 | name: 'package-one',
127 | location: 'jam/package-one'
128 | }
129 | ]);
130 | var p = path.resolve(
131 | that.project_dir,
132 | 'jam/package-one/package.json'
133 | );
134 | var content = fs.readFileSync(p);
135 | var pkg = JSON.parse(content.toString());
136 | test.equal(pkg.version, '0.0.2');
137 | cb();
138 | });
139 | }
140 | ],
141 | test.done);
142 | }
143 |
144 | };
145 |
--------------------------------------------------------------------------------
/lib/tar.js:
--------------------------------------------------------------------------------
1 | var logger = require('./logger'),
2 | env = require('./env'),
3 | Packer = require('./fstream-jam'),
4 | fstream = require('fstream'),
5 | tar = require('tar'),
6 | zlib = require('zlib'),
7 | path = require('path'),
8 | fs = require('fs');
9 |
10 |
11 | var umask = parseInt(022, 8);
12 | var modes = {
13 | exec: 0777 & (~umask),
14 | file: 0666 & (~umask),
15 | umask: umask
16 | };
17 |
18 | var myUid = process.getuid && process.getuid(),
19 | myGid = process.getgid && process.getgid();
20 |
21 | if (process.env.SUDO_UID && myUid === 0) {
22 | if (!isNaN(process.env.SUDO_UID)) {
23 | myUid = +process.env.SUDO_UID;
24 | }
25 | if (!isNaN(process.env.SUDO_GID)) {
26 | myGid = +process.env.SUDO_GID;
27 | }
28 | }
29 |
30 | /**
31 | */
32 |
33 | exports.create = function(cfg, source, target, callback) {
34 |
35 | logger.info('creating', target);
36 |
37 | function returnError(err) {
38 | // don't call the callback multiple times, just return the first error
39 | var _callback = callback;
40 | callback = function () {};
41 | return _callback(err);
42 | }
43 |
44 | var fwriter = fstream.Writer({ type: 'File', path: target });
45 | fwriter.on('error', function (err) {
46 | logger.error('error writing ' + target);
47 | //logger.error(err);
48 | return returnError(err);
49 | });
50 | fwriter.on('close', function () {
51 | callback(null, target);
52 | });
53 |
54 | var istream = Packer({
55 | packageInfo: cfg,
56 | path: source,
57 | type: "Directory",
58 | isDirectory: true
59 | });
60 | istream.on('error', function (err) {
61 | logger.error('error reading ' + source);
62 | //logger.error(err);
63 | return returnError(err);
64 | });
65 | istream.on("child", function (c) {
66 | //var root = path.resolve(c.root.path, '../package');
67 | //logger.info('adding', c.path.substr(root.length + 1));
68 | });
69 |
70 | var packer = tar.Pack({ noProprietary: true });
71 | packer.on('error', function (err) {
72 | logger.error('tar creation error ' + target);
73 | //logger.error(err);
74 | return returnError(err);
75 | });
76 |
77 | var zipper = zlib.Gzip();
78 | zipper.on('error', function (err) {
79 | logger.error('gzip error ' + target);
80 | //logger.error(err);
81 | return returnError(err);
82 | });
83 |
84 | istream.pipe(packer).pipe(zipper).pipe(fwriter);
85 | };
86 |
87 |
88 | /**
89 | */
90 |
91 | exports.extract = function (source, target, callback) {
92 |
93 | logger.info('extracting', source);
94 |
95 | var umask = modes.umask;
96 | var dmode = modes.dmode;
97 | var fmode = modes.fmode;
98 |
99 | function returnError(err) {
100 | // don't call the callback multiple times, just return the first error
101 | var _callback = callback;
102 | callback = function () {};
103 | return _callback(err);
104 | }
105 |
106 | var freader = fs.createReadStream(source);
107 | freader.on('error', function (err) {
108 | logger.error('error reading ' + source);
109 | //logger.error(err);
110 | return returnError(err);
111 | });
112 |
113 | var extract_opts = {
114 | type: 'Directory',
115 | path: target,
116 | strip: 1,
117 | filter: function () {
118 | // symbolic links are not allowed in packages
119 | if (this.type.match(/^.*Link$/)) {
120 | logger.warning(
121 | 'excluding symbolic link',
122 | this.path.substr(target.length + 1) + ' -> ' + this.linkpath
123 | );
124 | return false;
125 | }
126 | return true;
127 | }
128 | };
129 | if (!env.isWindows && typeof myUid === "number" && typeof myGid === "number") {
130 | extract_opts.uid = myUid;
131 | extract_opts.gid = myGid;
132 | }
133 | var extractor = tar.Extract(extract_opts);
134 | extractor.on('error', function (err) {
135 | logger.error('untar error ' + source);
136 | //logger.error(err);
137 | return returnError(err);
138 | });
139 | extractor.on('entry', function (entry) {
140 | //logger.info('extracting', entry.path);
141 | entry.mode = entry.mode || entry.props.mode;
142 | var original_mode = entry.mode;
143 | entry.mode = entry.mode | (entry.type === "Directory" ? dmode: fmode);
144 | entry.mode = entry.mode & (~umask);
145 | if (original_mode !== entry.mode) {
146 | logger.info(
147 | 'modified mode',
148 | original_mode + ' => ' + entry.mode + ' ' + entry.path
149 | );
150 | }
151 |
152 | if (process.platform !== "win32" &&
153 | typeof myUid === "number" && typeof myGid === "number") {
154 | entry.props.uid = entry.uid = myUid;
155 | entry.props.gid = entry.gid = myGid;
156 | }
157 | });
158 | extractor.on('end', function () {
159 | return callback(null, target);
160 | });
161 |
162 | var unzipper = zlib.Unzip();
163 | unzipper.on('error', function (err) {
164 | logger.error('unzip error ' + source);
165 | //logger.error(err);
166 | return returnError(err);
167 | });
168 |
169 | freader.pipe(unzipper).pipe(extractor);
170 | };
171 |
--------------------------------------------------------------------------------
/lib/jamrc.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module handles the loading of the jamrc files used to configure the
3 | * behaviour of the command-line tool.
4 | *
5 | * @module
6 | */
7 |
8 | var utils = require('./utils'),
9 | async = require('async'),
10 | _ = require('underscore'),
11 | path = require('path'),
12 | fs = require('fs'),
13 | env = require('./env');
14 |
15 |
16 | var pathExists = fs.exists || path.exists;
17 |
18 |
19 | /**
20 | * Default paths to lookup when constructing values for jamrc.
21 | * Paths are checked in order, with later paths overriding the values obtained
22 | * from earlier ones.
23 | */
24 |
25 | exports.PATHS = [
26 | //'/etc/jamrc',
27 | //'/usr/local/etc/jamrc',
28 | env.home + '/.jamrc'
29 | ];
30 |
31 |
32 | /**
33 | * The defaults jamrc settings
34 | */
35 |
36 | exports.DEFAULTS = {
37 | repositories: [ "http://jamjs.org/repository" ],
38 | package_dir: 'jam',
39 | link_dir: env.home + '/.jam/link',
40 | system_dir: env.home
41 | };
42 |
43 |
44 | /**
45 | * Loads base jamrc settings from PATHS, and merges them along with the DEFAULT
46 | * values, returning the result.
47 | *
48 | * @param {Function} callback(err, settings)
49 | */
50 |
51 | exports.load = function (callback) {
52 | async.map(exports.PATHS, exports.loadFile, function (err, results) {
53 | if (err) {
54 | return callback(err);
55 | }
56 |
57 | var defaults = _.clone(exports.DEFAULTS);
58 | var settings = results.reduce(function (merged, r) {
59 | return exports.merge(merged, r);
60 | }, defaults);
61 |
62 | exports.jamrc = settings || {};
63 | callback(null, settings);
64 | });
65 | };
66 |
67 |
68 | /**
69 | * Looks for a project-specific .jamrc file to override base settings with.
70 | * Walks up the directory tree until it finds a .jamrc file or hits the root.
71 | * Does not throw when no .jamrc is found, just returns null.
72 | *
73 | * @param {String} p - The starting path to search upwards from
74 | * @param {Function} callback(err, path)
75 | */
76 |
77 | exports.findProjectRC = function (p, callback) {
78 | var filename = path.resolve(p, '.jamrc');
79 | pathExists(filename, function (exists) {
80 | if (exists) {
81 | return callback(null, filename);
82 | }
83 | var newpath = path.dirname(p);
84 | if (newpath === p) { // root directory
85 | return callback(null, null);
86 | }
87 | else {
88 | return exports.findProjectRC(newpath, callback);
89 | }
90 | });
91 | };
92 |
93 |
94 | /**
95 | * Searches for a project-level .jamrc and extends the provided settings
96 | * object if one is found. If no project-level .jamrc is found, returns the
97 | * original settings unmodified.
98 | *
99 | * @param {Object} settings - the base settings to extend
100 | * @param {String} cwd - the path to search upwards from
101 | * @param {Function} callback(err, settings)
102 | */
103 |
104 | exports.loadProjectRC = function (settings, cwd, callback) {
105 | exports.findProjectRC(cwd, function (err, p) {
106 | if (err) {
107 | return callback(err);
108 | }
109 | if (!p) {
110 | // no project-level .jamrc, return original settings
111 | return callback(null, settings);
112 | }
113 | else {
114 | exports.extend(settings, p, callback);
115 | }
116 | });
117 | };
118 |
119 |
120 | /**
121 | * Deep merge for JSON objects, overwrites conflicting properties
122 | *
123 | * @param {Object} a
124 | * @param {Object} b
125 | * @returns {Object}
126 | */
127 |
128 | exports.merge = function (a, b) {
129 | if (!b) {
130 | return a;
131 | }
132 | for (var k in b) {
133 | if (Array.isArray(b[k])) {
134 | a[k] = b[k];
135 | }
136 | else if (typeof b[k] === 'object') {
137 | if (typeof a[k] === 'object') {
138 | exports.merge(a[k], b[k]);
139 | }
140 | else if (b.hasOwnProperty(k)) {
141 | a[k] = b[k];
142 | }
143 | }
144 | else if (b.hasOwnProperty(k)) {
145 | a[k] = b[k]
146 | }
147 | }
148 | return a;
149 | };
150 |
151 | /**
152 | * Checks a jamrc file exists and loads it if available. If the file does not
153 | * exist the function will respond with an empty object.
154 | *
155 | * @param {String} p - the path of the jamrc file to load
156 | * @param {Function} callback(err, settings)
157 | */
158 |
159 | exports.loadFile = function (p, callback) {
160 | pathExists(p, function (exists) {
161 | if (exists) {
162 | try {
163 | var mod = require(path.resolve(p));
164 | }
165 | catch (e) {
166 | return callback(e);
167 | }
168 | callback(null, mod);
169 | }
170 | else {
171 | callback(null, {});
172 | }
173 | });
174 | };
175 |
176 |
177 | /**
178 | * Extend currently loaded settings with another .jamrc file. Used by commands
179 | * specific to a project directory that would like to load project-specific
180 | * settings.
181 | *
182 | * @param {Object} settings - the base settings to extend
183 | * @param {String} path - the path to a .jamrc file to extend settings with
184 | * @param {Function} callback(err, settings)
185 | */
186 |
187 | exports.extend = function (settings, path, callback) {
188 | exports.loadFile(path, function (err, s) {
189 | if (err) {
190 | return callback(err);
191 | }
192 | exports.merge(settings, s);
193 | callback(null, settings);
194 | });
195 | };
196 |
197 | ['Tmp', 'Git', 'Cache'].forEach(function (name) {
198 |
199 | exports['get' + name + 'Dir'] = function () {
200 | var root = env.home;
201 |
202 | if (exports.jamrc && exports.jamrc['system_dir']) {
203 | root = exports.jamrc['system_dir'];
204 | }
205 |
206 | root = root.replace('~', env.home);
207 | return path.resolve(root, '.jam', name.toLowerCase());
208 | };
209 |
210 | });
211 |
--------------------------------------------------------------------------------
/test/integration/test-packagejson-publish-install-ls-remove.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test description
3 | * ================
4 | *
5 | * Starting with a project using package.json with jam deps defined
6 | * - jam publish package-one
7 | * - jam publish package-two (depends on package-one)
8 | * - jam install, test installation succeeded
9 | * - jam ls, test packages are listed
10 | * - jam remove package-two, test it's removed
11 | */
12 |
13 |
14 | var couchdb = require('../../lib/couchdb'),
15 | logger = require('../../lib/logger'),
16 | env = require('../../lib/env'),
17 | utils = require('../utils'),
18 | async = require('async'),
19 | http = require('http'),
20 | path = require('path'),
21 | ncp = require('ncp').ncp,
22 | fs = require('fs'),
23 | _ = require('underscore');
24 |
25 |
26 | var pathExists = fs.exists || path.exists;
27 |
28 |
29 | logger.clean_exit = true;
30 |
31 | // CouchDB database URL to use for testing
32 | var TESTDB = process.env['JAM_TEST_DB'],
33 | BIN = path.resolve(__dirname, '../../bin/jam.js'),
34 | ENV = {JAM_TEST: 'true', JAM_TEST_DB: TESTDB};
35 |
36 | if (!TESTDB) {
37 | throw 'JAM_TEST_DB environment variable not set';
38 | }
39 |
40 | // remove trailing-slash from TESTDB URL
41 | TESTDB = TESTDB.replace(/\/$/, '');
42 |
43 |
44 | exports.setUp = function (callback) {
45 | // change to integration test directory before running test
46 | this._cwd = process.cwd();
47 | process.chdir(__dirname);
48 |
49 | // recreate any existing test db
50 | couchdb(TESTDB).deleteDB(function (err) {
51 | if (err && err.error !== 'not_found') {
52 | return callback(err);
53 | }
54 | // create test db
55 | couchdb(TESTDB).createDB(callback);
56 | });
57 | };
58 |
59 | exports.tearDown = function (callback) {
60 | // change back to original working directory after running test
61 | process.chdir(this._cwd);
62 | // delete test db
63 | couchdb(TESTDB).deleteDB(callback);
64 | };
65 |
66 |
67 | exports['project with package.json'] = {
68 |
69 | setUp: function (callback) {
70 | this.project_dir = path.resolve(env.temp, 'jamtest-' + Math.random());
71 | // set current project to empty directory
72 | ncp('./fixtures/project-packagejson', this.project_dir, callback);
73 | },
74 |
75 | /*
76 | tearDown: function (callback) {
77 | var that = this;
78 | // clear current project
79 | //utils.myrimraf(that.project_dir, callback);
80 | },
81 | */
82 |
83 | 'publish, install, ls, remove': function (test) {
84 | test.expect(6);
85 | var that = this;
86 | process.chdir(that.project_dir);
87 | var pkgone = path.resolve(__dirname, 'fixtures', 'package-one'),
88 | pkgtwo = path.resolve(__dirname, 'fixtures', 'package-two');
89 |
90 | async.series([
91 | async.apply(utils.runJam, ['publish', pkgone], {env: ENV}),
92 | async.apply(utils.runJam, ['publish', pkgtwo], {env: ENV}),
93 | async.apply(utils.runJam, ['install'], {env: ENV}),
94 | function (cb) {
95 | // test that main.js was installed from package
96 | var a = fs.readFileSync(path.resolve(pkgone, 'main.js'));
97 | var b = fs.readFileSync(
98 | path.resolve(that.project_dir, 'jam/package-one/main.js')
99 | );
100 | test.equal(a.toString(), b.toString());
101 | var c = fs.readFileSync(path.resolve(pkgtwo, 'two.js'));
102 | var d = fs.readFileSync(
103 | path.resolve(that.project_dir, 'jam/package-two/two.js')
104 | );
105 | test.equal(c.toString(), d.toString());
106 |
107 | // make sure the requirejs config includes the new package
108 | var cfg = utils.freshRequire(
109 | path.resolve(that.project_dir, 'jam', 'require.config')
110 | );
111 | var packages= _.sortBy(cfg.packages, function (p) {
112 | return p.name;
113 | });
114 | test.same(packages, [
115 | {
116 | name: 'package-one',
117 | location: 'jam/package-one'
118 | },
119 | {
120 | name: 'package-two',
121 | location: 'jam/package-two',
122 | main: 'two.js'
123 | }
124 | ]);
125 | cb();
126 | },
127 | function (cb) {
128 | utils.runJam(['ls'], function (err, stdout, stderr) {
129 | if (err) {
130 | return cb(err);
131 | }
132 | var lines = stdout.replace(/\n$/, '').split('\n');
133 | test.same(lines.sort(), [
134 | '* package-one \u001b[33m0.0.1\u001b[39m',
135 | '* package-two \u001b[33m0.0.1\u001b[39m'
136 | ]);
137 | cb();
138 | });
139 | },
140 | function (cb) {
141 | var args = ['remove', 'package-two'];
142 | utils.runJam(args, function (err, stdout, stderr) {
143 | if (err) {
144 | return cb(err);
145 | }
146 | var cfg = utils.freshRequire(
147 | path.resolve(that.project_dir, 'jam', 'require.config')
148 | );
149 | test.same(cfg.packages, [
150 | {
151 | name: 'package-one',
152 | location: 'jam/package-one'
153 | }
154 | ]);
155 | var p = path.resolve(that.project_dir, 'jam/package-two');
156 | pathExists(p, function (exists) {
157 | test.ok(!exists, 'package-two directory removed');
158 | cb();
159 | });
160 | });
161 | }
162 | ],
163 | test.done);
164 | }
165 |
166 | };
167 |
--------------------------------------------------------------------------------
/test/integration/test-emptyproject-publish-install-ls-remove.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test description
3 | * ================
4 | *
5 | * Starting with an empty project (no package.json)
6 | * - jam publish package-one
7 | * - jam publish package-two (depends on package-one)
8 | * - jam install package-two, test installation succeeded
9 | * - jam ls, test packages are listed
10 | * - jam remove package-two, test it's removed
11 | */
12 |
13 |
14 | var couchdb = require('../../lib/couchdb'),
15 | logger = require('../../lib/logger'),
16 | env = require('../../lib/env'),
17 | utils = require('../utils'),
18 | async = require('async'),
19 | http = require('http'),
20 | path = require('path'),
21 | ncp = require('ncp').ncp,
22 | fs = require('fs'),
23 | _ = require('underscore');
24 |
25 |
26 | var pathExists = fs.exists || path.exists;
27 |
28 |
29 | logger.clean_exit = true;
30 |
31 | // CouchDB database URL to use for testing
32 | var TESTDB = process.env['JAM_TEST_DB'],
33 | BIN = path.resolve(__dirname, '../../bin/jam.js'),
34 | ENV = {JAM_TEST: 'true', JAM_TEST_DB: TESTDB};
35 |
36 | if (!TESTDB) {
37 | throw 'JAM_TEST_DB environment variable not set';
38 | }
39 |
40 | // remove trailing-slash from TESTDB URL
41 | TESTDB = TESTDB.replace(/\/$/, '');
42 |
43 |
44 | exports.setUp = function (callback) {
45 | // change to integration test directory before running test
46 | this._cwd = process.cwd();
47 | process.chdir(__dirname);
48 |
49 | // recreate any existing test db
50 | couchdb(TESTDB).deleteDB(function (err) {
51 | if (err && err.error !== 'not_found') {
52 | return callback(err);
53 | }
54 | // create test db
55 | couchdb(TESTDB).createDB(callback);
56 | });
57 | };
58 |
59 | exports.tearDown = function (callback) {
60 | // change back to original working directory after running test
61 | process.chdir(this._cwd);
62 | // delete test db
63 | couchdb(TESTDB).deleteDB(callback);
64 | };
65 |
66 |
67 | exports['empty project'] = {
68 |
69 | setUp: function (callback) {
70 | this.project_dir = path.resolve(env.temp, 'jamtest-' + Math.random());
71 | // set current project to empty directory
72 | ncp('./fixtures/project-empty', this.project_dir, callback);
73 | },
74 |
75 | /*
76 | tearDown: function (callback) {
77 | var that = this;
78 | // timeout to try and wait until dir is no-longer busy on windows
79 | //utils.myrimraf(that.project_dir, callback);
80 | },
81 | */
82 |
83 | 'publish, install, ls, remove': function (test) {
84 | test.expect(6);
85 | var that = this;
86 | process.chdir(that.project_dir);
87 | var pkgone = path.resolve(__dirname, 'fixtures', 'package-one'),
88 | pkgtwo = path.resolve(__dirname, 'fixtures', 'package-two');
89 |
90 | async.series([
91 | async.apply(utils.runJam, ['publish', pkgone], {env: ENV}),
92 | async.apply(utils.runJam, ['publish', pkgtwo], {env: ENV}),
93 | async.apply(utils.runJam, ['install', 'package-two'], {env: ENV}),
94 | function (cb) {
95 | // test that main.js was installed from package
96 | var a = fs.readFileSync(path.resolve(pkgone, 'main.js'));
97 | var b = fs.readFileSync(
98 | path.resolve(that.project_dir, 'jam/package-one/main.js')
99 | );
100 | test.equal(a.toString(), b.toString());
101 | var c = fs.readFileSync(path.resolve(pkgtwo, 'two.js'));
102 | var d = fs.readFileSync(
103 | path.resolve(that.project_dir, 'jam/package-two/two.js')
104 | );
105 | test.equal(c.toString(), d.toString());
106 |
107 | // make sure the requirejs config includes the new package
108 | var cfg = utils.freshRequire(
109 | path.resolve(that.project_dir, 'jam', 'require.config')
110 | );
111 | var packages= _.sortBy(cfg.packages, function (p) {
112 | return p.name;
113 | });
114 | test.same(packages, [
115 | {
116 | name: 'package-one',
117 | location: 'jam/package-one'
118 | },
119 | {
120 | name: 'package-two',
121 | location: 'jam/package-two',
122 | main: 'two.js'
123 | }
124 | ]);
125 | cb();
126 | },
127 | function (cb) {
128 | utils.runJam(['ls'], function (err, stdout, stderr) {
129 | if (err) {
130 | return cb(err);
131 | }
132 | var lines = stdout.replace(/\n$/, '').split('\n');
133 | test.same(lines.sort(), [
134 | ' package-one \u001b[33m0.0.1\u001b[39m',
135 | ' package-two \u001b[33m0.0.1\u001b[39m'
136 | ]);
137 | cb();
138 | });
139 | },
140 | function (cb) {
141 | var args = ['remove', 'package-two'];
142 | utils.runJam(args, function (err, stdout, stderr) {
143 | if (err) {
144 | return cb(err);
145 | }
146 | var cfg = utils.freshRequire(
147 | path.resolve(that.project_dir, 'jam', 'require.config')
148 | );
149 | test.same(cfg.packages.sort(), [
150 | {
151 | name: 'package-one',
152 | location: 'jam/package-one'
153 | }
154 | ]);
155 | var p = path.resolve(that.project_dir, 'jam/package-two');
156 | pathExists(p, function (exists) {
157 | test.ok(!exists, 'package-two directory removed');
158 | cb();
159 | });
160 | });
161 | }
162 | ],
163 | test.done);
164 | }
165 |
166 | };
167 |
--------------------------------------------------------------------------------
/test/integration/test-custompaths-publish-install-ls-remove.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test description
3 | * ================
4 | *
5 | * Starting with a project using package.json with jam deps defined and
6 | * custom package install path and base URL
7 | * - jam publish package-one
8 | * - jam publish package-two (depends on package-one)
9 | * - jam install, test installation succeeded
10 | * - jam ls, test packages are listed
11 | * - jam remove package-two, test it's removed
12 | */
13 |
14 |
15 | var couchdb = require('../../lib/couchdb'),
16 | logger = require('../../lib/logger'),
17 | env = require('../../lib/env'),
18 | utils = require('../utils'),
19 | async = require('async'),
20 | http = require('http'),
21 | path = require('path'),
22 | ncp = require('ncp').ncp,
23 | fs = require('fs'),
24 | _ = require('underscore');
25 |
26 |
27 | var pathExists = fs.exists || path.exists;
28 |
29 |
30 | logger.clean_exit = true;
31 |
32 | // CouchDB database URL to use for testing
33 | var TESTDB = process.env['JAM_TEST_DB'],
34 | BIN = path.resolve(__dirname, '../../bin/jam.js'),
35 | ENV = {JAM_TEST: 'true', JAM_TEST_DB: TESTDB};
36 |
37 | if (!TESTDB) {
38 | throw 'JAM_TEST_DB environment variable not set';
39 | }
40 |
41 | // remove trailing-slash from TESTDB URL
42 | TESTDB = TESTDB.replace(/\/$/, '');
43 |
44 |
45 | exports.setUp = function (callback) {
46 | // change to integration test directory before running test
47 | this._cwd = process.cwd();
48 | process.chdir(__dirname);
49 |
50 | // recreate any existing test db
51 | couchdb(TESTDB).deleteDB(function (err) {
52 | if (err && err.error !== 'not_found') {
53 | return callback(err);
54 | }
55 | // create test db
56 | couchdb(TESTDB).createDB(callback);
57 | });
58 | };
59 |
60 | exports.tearDown = function (callback) {
61 | // change back to original working directory after running test
62 | process.chdir(this._cwd);
63 | // delete test db
64 | couchdb(TESTDB).deleteDB(callback);
65 | };
66 |
67 |
68 | exports['project with package.json'] = {
69 |
70 | setUp: function (callback) {
71 | this.project_dir = path.resolve(env.temp, 'jamtest-' + Math.random());
72 | // set current project to empty directory
73 | ncp('./fixtures/project-custompaths', this.project_dir, callback);
74 | },
75 |
76 | /*
77 | tearDown: function (callback) {
78 | var that = this;
79 | // timeout to try and wait until dir is no-longer busy on windows
80 | //utils.myrimraf(that.project_dir, callback);
81 | },
82 | */
83 |
84 | 'publish, install, ls, remove': function (test) {
85 | test.expect(6);
86 | var that = this;
87 | process.chdir(that.project_dir);
88 | var pkgone = path.resolve(__dirname, 'fixtures', 'package-one'),
89 | pkgtwo = path.resolve(__dirname, 'fixtures', 'package-two');
90 |
91 | async.series([
92 | async.apply(utils.runJam, ['publish', pkgone], {env: ENV}),
93 | async.apply(utils.runJam, ['publish', pkgtwo], {env: ENV}),
94 | async.apply(utils.runJam, ['install'], {env: ENV}),
95 | function (cb) {
96 | // test that main.js was installed from package
97 | var a = fs.readFileSync(path.resolve(pkgone, 'main.js'));
98 | var b = fs.readFileSync(path.resolve(
99 | that.project_dir,
100 | 'public/js/vendor/package-one/main.js'
101 | ));
102 | test.equal(a.toString(), b.toString());
103 | var c = fs.readFileSync(path.resolve(pkgtwo, 'two.js'));
104 | var d = fs.readFileSync(path.resolve(
105 | that.project_dir,
106 | 'public/js/vendor/package-two/two.js'
107 | ));
108 | test.equal(c.toString(), d.toString());
109 |
110 | // make sure the requirejs config includes the new package
111 | var cfg = utils.freshRequire(path.resolve(
112 | that.project_dir, 'public/js/vendor/require.config'
113 | ));
114 | var packages= _.sortBy(cfg.packages, function (p) {
115 | return p.name;
116 | });
117 | test.same(packages, [
118 | {
119 | name: 'package-one',
120 | location: 'js/vendor/package-one'
121 | },
122 | {
123 | name: 'package-two',
124 | location: 'js/vendor/package-two',
125 | main: 'two.js'
126 | }
127 | ]);
128 | cb();
129 | },
130 | function (cb) {
131 | utils.runJam(['ls'], function (err, stdout, stderr) {
132 | if (err) {
133 | return cb(err);
134 | }
135 | var lines = stdout.replace(/\n$/, '').split('\n');
136 | test.same(lines.sort(), [
137 | '* package-one \u001b[33m0.0.1\u001b[39m',
138 | '* package-two \u001b[33m0.0.1\u001b[39m'
139 | ]);
140 | cb();
141 | });
142 | },
143 | function (cb) {
144 | var args = ['remove', 'package-two'];
145 | utils.runJam(args, function (err, stdout, stderr) {
146 | if (err) {
147 | return cb(err);
148 | }
149 | var cfg = utils.freshRequire(path.resolve(
150 | that.project_dir, 'public/js/vendor/require.config'
151 | ));
152 | test.same(cfg.packages.sort(), [
153 | {
154 | name: 'package-one',
155 | location: 'js/vendor/package-one'
156 | }
157 | ]);
158 | var p = path.resolve(
159 | that.project_dir,
160 | 'public/js/vendor/package-two'
161 | );
162 | pathExists(p, function (exists) {
163 | test.ok(!exists, 'package-two directory removed');
164 | cb();
165 | });
166 | });
167 | }
168 | ],
169 | test.done);
170 | }
171 |
172 | };
173 |
--------------------------------------------------------------------------------
/lib/commands/clean.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module dependencies
3 | */
4 |
5 | var path = require('path'),
6 | fs = require('fs'),
7 | async = require('async'),
8 | install = require('./install'),
9 | utils = require('../utils'),
10 | rimraf = require('rimraf'),
11 | settings = require('../settings'),
12 | project = require('../project'),
13 | tree = require('../tree');
14 | logger = require('../logger'),
15 | argParse = require('../args').parse,
16 | _ = require('underscore');
17 |
18 |
19 | /**
20 | * Usage information and docs
21 | */
22 |
23 | exports.summary = 'Removes unused packages from the package directory';
24 |
25 |
26 | exports.usage = '' +
27 | 'jam clean\n' +
28 | '\n' +
29 | 'Options:\n' +
30 | ' -d, --package-dir Package directory (defaults to "PATH/jam")\n' +
31 | ' -f, --force Do not confirm package removal';
32 |
33 |
34 | /**
35 | * Run function called when "jam clean" command is used
36 | *
37 | * @param {Object} settings - the values from .jamrc files
38 | * @param {Array} args - command-line arguments
39 | */
40 |
41 | exports.run = function (settings, args) {
42 | var a = argParse(args, {
43 | 'target_dir': {match: ['-d', '--package-dir'], value: true},
44 | 'baseurl': {match: ['-b', '--baseurl'], value: true},
45 | 'force': {match: ['-f', '--force']}
46 | });
47 | var opt = a.options;
48 | var cwd = process.cwd();
49 |
50 | install.initDir(settings, cwd, opt, function (err, opt, cfg, proj_dir) {
51 | if (err) {
52 | return logger.error(err);
53 | }
54 |
55 | if (!opt.target_dir) {
56 | if (cfg.jam && cfg.jam.packageDir) {
57 | opt.target_dir = path.resolve(proj_dir, cfg.jam.packageDir);
58 | }
59 | else {
60 | opt.target_dir = path.resolve(proj_dir, settings.package_dir || '');
61 | }
62 | }
63 | if (!opt.baseurl) {
64 | if (cfg.jam && cfg.jam.baseUrl) {
65 | opt.baseurl = path.resolve(proj_dir, cfg.jam.baseUrl);
66 | }
67 | else {
68 | opt.baseurl = path.resolve(proj_dir, settings.baseUrl || '');
69 | }
70 | }
71 |
72 | exports.clean(cwd, opt, function (err) {
73 | if (err) {
74 | return logger.error(err);
75 | }
76 | logger.end();
77 | });
78 | });
79 | };
80 |
81 |
82 | /**
83 | * Clean the project directory's dependencies.
84 | *
85 | * @param {Object} opt - the options object
86 | * @param {Function} callback
87 | */
88 |
89 | exports.clean = function (cwd, opt, callback) {
90 | exports.unusedDirs(cwd, opt, function (err, dirs) {
91 | if (err) {
92 | return callback(err);
93 | }
94 | if (!dirs.length) {
95 | // nothing to remove
96 | return logger.end();
97 | }
98 | var reldirs = dirs.map(function (d) {
99 | return path.relative(opt.taget_dir, d);
100 | });
101 |
102 | if (opt.force) {
103 | exports.deleteDirs(dirs, callback);
104 | }
105 | else {
106 | console.log(
107 | '\n' +
108 | 'The following directories are not required by packages in\n' +
109 | 'package.json and will be REMOVED:\n\n' +
110 | ' ' + reldirs.join('\n ') +
111 | '\n'
112 | );
113 | utils.getConfirmation('Continue', function (err, ok) {
114 | if (err) {
115 | return callback(err);
116 | }
117 | if (ok) {
118 | exports.deleteDirs(dirs, function (err) {
119 | if (err) {
120 | return callback(err);
121 | }
122 | project.updateRequireConfig(
123 | opt.target_dir,
124 | opt.baseurl,
125 | callback
126 | );
127 | });
128 | }
129 | else {
130 | logger.clean_exit = true;
131 | }
132 | });
133 | }
134 | });
135 | };
136 |
137 |
138 | /**
139 | * Delete multiple directory paths.
140 | *
141 | * @param {Array} dirs
142 | * @param {Function} callback
143 | */
144 |
145 | exports.deleteDirs = function (dirs, callback) {
146 | async.forEach(dirs, function (d, cb) {
147 | logger.info('removing', path.basename(d));
148 | rimraf(d, cb);
149 | },
150 | callback);
151 | };
152 |
153 |
154 | /**
155 | * Discover package directories that do not form part of the current
156 | * package version tree.
157 | *
158 | * @param {Object} opt - the options object
159 | * @param {Function} callback
160 | */
161 |
162 | exports.unusedDirs = function (cwd, opt, callback) {
163 | project.loadPackageJSON(cwd, function (err, cfg) {
164 | if (err) {
165 | return callback(err);
166 | }
167 |
168 | if (!cfg) {
169 | cfg = project.DEFAULT;
170 | }
171 |
172 | var sources = [
173 | install.dirSource(opt.target_dir)
174 | ];
175 | var newcfg = utils.convertToRootCfg(cfg);
176 | var pkg = {
177 | config: newcfg,
178 | source: 'root'
179 | };
180 | logger.info('Building version tree...');
181 | tree.build(pkg, sources, function (err, packages) {
182 | if (err) {
183 | return callback(err);
184 | }
185 | return exports.unusedDirsTree(packages, opt, callback);
186 | });
187 | });
188 | };
189 |
190 |
191 | /**
192 | * Lists packages in the package dir and compares against the provided
193 | * version tree, returning the packages not in the tree.
194 | *
195 | * @param {Object} packages - version tree
196 | * @param {Object} opt - options object
197 | * @param {Function} callback
198 | */
199 |
200 | exports.unusedDirsTree = function (packages, opt, callback) {
201 | utils.listDirs(opt.target_dir, function (err, dirs) {
202 | if (err) {
203 | return callback(err);
204 | }
205 | var unused = _.difference(dirs, Object.keys(packages));
206 | var unused = [];
207 | var names = Object.keys(packages);
208 | dirs.forEach(function (d) {
209 | if (!_.contains(names, path.basename(d))) {
210 | unused.push(d);
211 | }
212 | });
213 | return callback(null, unused);
214 | });
215 | };
216 |
--------------------------------------------------------------------------------
/test/integration/test-rangedeps-publish-install-upgrade.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Test description
3 | * ================
4 | *
5 | * Tests that range requirements for dependencies in package.json are
6 | * respected.
7 | *
8 | * Starting with project with *ranged* deps in package.json
9 | * - jam publish package-one @ 0.0.1
10 | * - jam publish package-two @ 0.0.1
11 | * - jam install, test installation succeeded
12 | * - jam publish package-one @ 0.0.2
13 | * - jam publish package-two @ 0.0.2
14 | * - jam publish package-one @ 0.0.3 // this should not get installed
15 | * - jam upgrade, test package versions (both @ 0.0.2)
16 | */
17 |
18 |
19 | var couchdb = require('../../lib/couchdb'),
20 | logger = require('../../lib/logger'),
21 | env = require('../../lib/env'),
22 | utils = require('../utils'),
23 | async = require('async'),
24 | http = require('http'),
25 | path = require('path'),
26 | ncp = require('ncp').ncp,
27 | fs = require('fs'),
28 | _ = require('underscore');
29 |
30 |
31 | var pathExists = fs.exists || path.exists;
32 |
33 |
34 | logger.clean_exit = true;
35 |
36 | // CouchDB database URL to use for testing
37 | var TESTDB = process.env['JAM_TEST_DB'],
38 | BIN = path.resolve(__dirname, '../../bin/jam.js'),
39 | ENV = {JAM_TEST: 'true', JAM_TEST_DB: TESTDB};
40 |
41 | if (!TESTDB) {
42 | throw 'JAM_TEST_DB environment variable not set';
43 | }
44 |
45 | // remove trailing-slash from TESTDB URL
46 | TESTDB = TESTDB.replace(/\/$/, '');
47 |
48 |
49 | exports.setUp = function (callback) {
50 | // change to integration test directory before running test
51 | this._cwd = process.cwd();
52 | process.chdir(__dirname);
53 |
54 | // recreate any existing test db
55 | couchdb(TESTDB).deleteDB(function (err) {
56 | if (err && err.error !== 'not_found') {
57 | return callback(err);
58 | }
59 | // create test db
60 | couchdb(TESTDB).createDB(callback);
61 | });
62 | };
63 |
64 | exports.tearDown = function (callback) {
65 | // change back to original working directory after running test
66 | process.chdir(this._cwd);
67 | // delete test db
68 | couchdb(TESTDB).deleteDB(callback);
69 | };
70 |
71 |
72 | exports['project with ranged dependencies in package.json'] = {
73 |
74 | setUp: function (callback) {
75 | this.project_dir = path.resolve(env.temp, 'jamtest-' + Math.random());
76 | // set current project to empty directory
77 | ncp('./fixtures/project-rangedeps', this.project_dir, callback);
78 | },
79 |
80 | /*
81 | tearDown: function (callback) {
82 | var that = this;
83 | // clear current project
84 | //utils.myrimraf(that.project_dir, callback);
85 | },
86 | */
87 |
88 | 'publish, install, ls, remove': function (test) {
89 | test.expect(6);
90 | var that = this;
91 | process.chdir(that.project_dir);
92 |
93 | async.series([
94 | async.apply(
95 | utils.runJam,
96 | ['publish', path.resolve(__dirname, 'fixtures', 'package-one')],
97 | {env: ENV}
98 | ),
99 | async.apply(
100 | utils.runJam,
101 | ['publish', path.resolve(__dirname, 'fixtures', 'package-two')],
102 | {env: ENV}
103 | ),
104 | async.apply(utils.runJam, ['install'], {env: ENV}),
105 | function (cb) {
106 | var cfg = utils.freshRequire(
107 | path.resolve(that.project_dir, 'jam', 'require.config')
108 | );
109 | var packages = _.sortBy(cfg.packages, function (p) {
110 | return p.name;
111 | });
112 | test.same(packages, [
113 | {
114 | name: 'package-one',
115 | location: 'jam/package-one'
116 | },
117 | {
118 | name: 'package-two',
119 | location: 'jam/package-two',
120 | main: 'two.js'
121 | }
122 | ]);
123 | var p1 = path.resolve(
124 | that.project_dir,
125 | 'jam/package-one/package.json'
126 | );
127 | var p1pkg = JSON.parse(fs.readFileSync(p1).toString());
128 | test.equal(p1pkg.version, '0.0.1');
129 | var p2 = path.resolve(
130 | that.project_dir,
131 | 'jam/package-two/package.json'
132 | );
133 | var p2pkg = JSON.parse(fs.readFileSync(p2).toString());
134 | test.equal(p2pkg.version, '0.0.1');
135 | cb();
136 | },
137 | async.apply(
138 | utils.runJam,
139 | [
140 | 'publish',
141 | path.resolve(__dirname, 'fixtures', 'package-one-v2')
142 | ],
143 | {env: ENV}
144 | ),
145 | async.apply(
146 | utils.runJam,
147 | [
148 | 'publish',
149 | path.resolve(__dirname, 'fixtures', 'package-two-v2')
150 | ],
151 | {env: ENV}
152 | ),
153 | async.apply(
154 | utils.runJam,
155 | [
156 | 'publish',
157 | path.resolve(__dirname, 'fixtures', 'package-one-v3')
158 | ],
159 | {env: ENV}
160 | ),
161 | function (cb) {
162 | var args = ['upgrade'];
163 | utils.runJam(args, {env: ENV}, function (err, stdout, stderr) {
164 | if (err) {
165 | return cb(err);
166 | }
167 | var cfg = utils.freshRequire(
168 | path.resolve(that.project_dir, 'jam', 'require.config')
169 | );
170 | var packages= _.sortBy(cfg.packages, function (p) {
171 | return p.name;
172 | });
173 | test.same(cfg.packages, [
174 | {
175 | name: 'package-one',
176 | location: 'jam/package-one'
177 | },
178 | {
179 | name: 'package-two',
180 | location: 'jam/package-two',
181 | main: 'two.js'
182 | }
183 | ]);
184 | var p1 = path.resolve(
185 | that.project_dir,
186 | 'jam/package-one/package.json'
187 | );
188 | var p1pkg = JSON.parse(fs.readFileSync(p1).toString());
189 | test.equal(p1pkg.version, '0.0.2');
190 | var p2 = path.resolve(
191 | that.project_dir,
192 | 'jam/package-two/package.json'
193 | );
194 | var p2pkg = JSON.parse(fs.readFileSync(p2).toString());
195 | test.equal(p2pkg.version, '0.0.2');
196 | cb();
197 | });
198 | }
199 | ],
200 | test.done);
201 | }
202 |
203 | };
204 |
--------------------------------------------------------------------------------
/lib/versions.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Utilities for dealing with package versions
3 | */
4 |
5 | var semver = require('semver'),
6 | logger = require('./logger'),
7 | _ = require('underscore');
8 |
9 |
10 | /**
11 | * Sorts an array of version numbers in descending semver order (highest
12 | * version number first). This is an alternative to semver.rcompare since it
13 | * doesn't appear to work as expected.
14 | *
15 | * @param {Array} versions - an array of version number strings
16 | * @returns {Array}
17 | */
18 |
19 | exports.sortDescending = function (versions) {
20 | // for some reason semver.rcompare doesn't work
21 | return versions.slice().sort(semver.compare).reverse();
22 | };
23 |
24 |
25 | /**
26 | * Returns the highest version number in an array.
27 | *
28 | * @param {Array} versions - an array of version number strings
29 | * @returns {String}
30 | */
31 |
32 | exports.max = function (versions) {
33 | return exports.sortDescending(versions)[0];
34 | };
35 |
36 | exports.satisfying = function(versions, ranges) {
37 | return versions.filter(function (v) {
38 | return exports.satisfiesAll(v, ranges);
39 | });
40 | };
41 |
42 | /**
43 | * Checks an array of range requirements against an array of available versions,
44 | * returning the highest version number that satisfies all ranges or null if
45 | * all ranges can't be satisfied.
46 | *
47 | * @param {Array} versions - an array of version strings
48 | * @param {Array} ranges - an array of range strings
49 | * @returns {String|null}
50 | */
51 |
52 | exports.maxSatisfying = function (versions, ranges) {
53 | return _.chain(exports.satisfying(versions, ranges))
54 | .map(function(version) {
55 | var equality, inequal;
56 |
57 | equality = 0;
58 | inequal = [];
59 |
60 | _.forEach(ranges, function(r) {
61 | var index, length, equal, versionParsed, versionBuild, versionHunk, rangeHunk, rangeBuild, rangeParsed, range;
62 |
63 | range = r.valueOf();
64 |
65 | versionParsed = semver.parse(version);
66 | rangeParsed = semver.parse(range);
67 |
68 | // try to calc equality of same versions
69 | if ( versionParsed && rangeParsed && semver.eq(version, range) ) {
70 |
71 | versionBuild = versionParsed.build;
72 | rangeBuild = rangeParsed.build;
73 |
74 | if (_.isEqual(versionBuild, rangeBuild)) {
75 | equality+= 1;
76 | } else {
77 | length = rangeBuild.length;
78 | equal = 0;
79 |
80 | if (length > 0) {
81 | for (index = 0; index < length; index++) {
82 | versionHunk = versionBuild[index];
83 | rangeHunk = rangeBuild[index];
84 |
85 | if (versionHunk == rangeHunk) {
86 | equal++;
87 | } else {
88 | inequal.push(versionHunk);
89 | break;
90 | }
91 | }
92 |
93 | equality+= equal / length;
94 | }
95 | }
96 | }
97 | });
98 |
99 | return {
100 | criteria: [ equality].concat(inequal),
101 | data: version,
102 | compare: function(other) {
103 | return semver.compare(this.data, other.data);
104 | }
105 | };
106 | })
107 | .sort(function(a, b) {
108 | var aCriteria, bCriteria, aValue, bValue, compared,
109 | index, length, result;
110 |
111 | compared = a.compare(b);
112 |
113 | if (compared != 0) {
114 | result = compared;
115 | } else {
116 | aCriteria = a.criteria;
117 | bCriteria = b.criteria;
118 |
119 | length = aCriteria.length;
120 | index = 0;
121 |
122 | for (; index < length; index++) {
123 | aValue = aCriteria[index];
124 | bValue = bCriteria[index];
125 |
126 | if (aValue == bValue) {
127 | continue;
128 | }
129 |
130 | if (aValue < bValue) {
131 | result = -1;
132 | } else {
133 | result = 1;
134 | }
135 |
136 | break;
137 | }
138 | }
139 |
140 | if (_.isUndefined(result)) {
141 | result = 0;
142 | }
143 |
144 | return result;
145 | })
146 | .pluck('data')
147 | .last()
148 | .value();
149 | };
150 |
151 |
152 | /**
153 | * Checks if a version number satisfies an array of range requirements.
154 | *
155 | * @param {String} version - a semver version string
156 | * @param {Array} ranges - an array of range strings
157 | * @returns {Boolean}
158 | */
159 |
160 | exports.satisfiesAll = function (version, ranges) {
161 | return ranges.every(function(range) {
162 | var satisfies;
163 |
164 | if (!semver.valid(version)) {
165 | logger.warning('Could not compare version "' + version + '" cause it is not a valid semver');
166 | return false;
167 | }
168 |
169 | // if range is null, linked, installed from URL, or from GitHub, or from Git,
170 | // then any version satisfies that requirement
171 | satisfies = false;
172 | satisfies = satisfies || !range;
173 |
174 | // todo is it good?
175 | satisfies = satisfies || range === 'linked';
176 |
177 | satisfies = satisfies || semver.satisfies(version, range.valueOf());
178 |
179 | return satisfies;
180 | });
181 | };
182 |
183 |
184 | /**
185 | * Experimental method.
186 | *
187 | * @deprecated
188 | * @param version
189 | * @returns {*}
190 | */
191 | exports.validify = function(version) {
192 | var match, preName, preVersion, result;
193 |
194 | if (semver.valid(version)) {
195 | return version;
196 | }
197 |
198 | // match as
199 | // #1 version
200 | // #2 prerelease name delimiter
201 | // #3 prerelease name
202 | // #4 prerelease version delimiter
203 | // #5 prerelease version
204 | if (match = /(\d+(?:\.\d+(?:\.\d+)?)?)(-)?([a-z]{0,})?(\.)?(\d+)?/i.exec(version)) {
205 | result = match[1];
206 |
207 | if (preName = match[3]) {
208 | result += "-" + preName;
209 |
210 | if (preVersion = match[5]) {
211 | result += "." + preVersion;
212 | }
213 | }
214 |
215 | return result;
216 | }
217 |
218 | return null;
219 | };
220 |
221 | exports.equalButNotMeta = function(version, ranges) {
222 | var parsed;
223 |
224 | parsed = semver.parse(version);
225 |
226 | return _.chain(ranges)
227 | .map(function(r) {
228 | var strictVersion, range;
229 |
230 | range = r.valueOf();
231 |
232 | if ( strictVersion = semver.parse(range) ) {
233 | if ( semver.eq(parsed, strictVersion) && !_.isEqual(parsed.build, strictVersion.build) ) {
234 | return range;
235 | }
236 | }
237 | })
238 | .filter()
239 | .value();
240 | };
--------------------------------------------------------------------------------
/test/unit/test-lib-commands-install.js:
--------------------------------------------------------------------------------
1 | var install = require('../../lib/commands/install');
2 | var logger = require("../../lib/logger");
3 | var sinon = require("sinon");
4 | var cuculus = require("cuculus");
5 | var path = require("path");
6 |
7 | //logger.clean_exit = true;
8 |
9 |
10 | exports.setUp = function(cb) {
11 | cuculus.modify(path.resolve(__dirname, '../../lib/jamrc'), function(jamrc, onRestore){
12 | var stub;
13 |
14 | stub = sinon.stub(jamrc, "load", function(cb) {
15 | cb(null, {
16 | repositories: [
17 | "http://jamjs.org/A",
18 | "http://jamjs.org/B",
19 | "http://jamjs.org/C"
20 | ]
21 | });
22 | });
23 |
24 | onRestore(stub.restore.bind(stub));
25 | });
26 |
27 | cb();
28 | };
29 |
30 | exports.tearDown = function(cb) {
31 | // cleanup
32 | cuculus.restore(path.resolve(__dirname, '../../lib/jamrc'));
33 | cb();
34 | };
35 |
36 | exports['extractValidVersion - multiple git'] = function (test) {
37 | var pkg;
38 |
39 | // before
40 | pkg = {
41 | "current_version": "1.0.5",
42 | "requirements": [
43 | {
44 | "enabled": true,
45 | "path": {
46 | "enabled": true,
47 | "name": "toolkit",
48 | "parents": [
49 | "root"
50 | ]
51 | },
52 | "range": {
53 | "range": "1.0.5",
54 | "source": "git://path.to/repo.git#2222"
55 | }
56 | },
57 | {
58 | "enabled": true,
59 | "path": {
60 | "enabled": true,
61 | "name": "toolkit",
62 | "parents": [
63 | "root",
64 | "viewer"
65 | ]
66 | },
67 | "range": {
68 | "range": "1.0.5",
69 | "source": "git://path.to/repo.git#2222"
70 | }
71 | }
72 | ],
73 | "versions": [
74 | {
75 | "priority": 0,
76 | "source": "git",
77 | "version": "1.0.5",
78 | git: {
79 | path: "git://path.to/repo.git",
80 | commitish: "2222",
81 | uri: "git://path.to/repo.git#2222"
82 | },
83 | "config": {
84 | "name": "toolkit",
85 | "version": "1.0.5",
86 | "jam": {}
87 | }
88 | },
89 | {
90 | "priority": 0,
91 | "source": "git",
92 | "version": "1.0.5",
93 | git: {
94 | path: "git://path.to/repo.git",
95 | commitish: "2222",
96 | uri: "git://path.to/repo.git#2222"
97 | },
98 | "config": {
99 | "name": "toolkit",
100 | "version": "1.0.5",
101 | "jam": {}
102 | }
103 | }
104 | ]
105 | };
106 |
107 | install.extractValidVersion(pkg, "test", function(err) {
108 | if (err) {
109 | logger.error(err);
110 | }
111 | test.done(err);
112 | });
113 | };
114 |
115 | exports['extractValidVersion - should get less prioritized, if it is repository source'] = function (test) {
116 | var pkg;
117 |
118 | // before
119 | pkg = {
120 | "current_version": "1.0.5",
121 | "requirements": [
122 | {
123 | "enabled": true,
124 | "path": {
125 | "enabled": true,
126 | "name": "toolkit",
127 | "parents": [
128 | "root"
129 | ]
130 | },
131 | "range": {
132 | "range": "1.0.5",
133 | "source": "1.0.5"
134 | }
135 | }
136 | ],
137 | "versions": [
138 | {
139 | "priority": 0,
140 | "source": "repository",
141 | "version": "1.0.5",
142 | "repository": "http://jamjs.org/B",
143 | "config": {
144 | "name": "toolkit",
145 | "version": "1.0.5",
146 | "jam": {}
147 | }
148 | },
149 | {
150 | "priority": 0,
151 | "source": "repository",
152 | "version": "1.0.5",
153 | "repository": "http://jamjs.org/A",
154 | "config": {
155 | "name": "toolkit",
156 | "version": "1.0.5",
157 | "jam": {}
158 | }
159 | },
160 | {
161 | "priority": 0,
162 | "source": "repository",
163 | "version": "1.0.5",
164 | "repository": "http://jamjs.org/C",
165 | "config": {
166 | "name": "toolkit",
167 | "version": "1.0.5",
168 | "jam": {}
169 | }
170 | }
171 | ]
172 | };
173 |
174 | install.extractValidVersion(pkg, "test", function(err, version) {
175 | if (err) {
176 | logger.error(err);
177 | }
178 | if (version.repository != "http://jamjs.org/A") {
179 | logger.error(err = new Error("Not prioritized repo!"))
180 | }
181 | test.done(err);
182 | });
183 | };
184 |
185 | exports['extractValidVersion - should throw'] = function (test) {
186 | var pkg;
187 |
188 | // before
189 | pkg = {
190 | "current_version": "1.0.5",
191 | "requirements": [
192 | {
193 | "enabled": true,
194 | "path": {
195 | "enabled": true,
196 | "name": "toolkit",
197 | "parents": [
198 | "root"
199 | ]
200 | },
201 | "range": {
202 | "range": "1.0.5",
203 | "source": "1.0.5"
204 | }
205 | }
206 | ],
207 | "versions": [
208 | {
209 | "priority": 0,
210 | "source": "repository",
211 | "version": "1.0.5",
212 | "repository": "http://testrepo.org",
213 | "config": {
214 | "name": "toolkit",
215 | "version": "1.0.5",
216 | "jam": {}
217 | }
218 | },
219 | {
220 | "priority": 0,
221 | "source": "git",
222 | "version": "1.0.5",
223 | git: {
224 | path: "git://path.to/repo.git",
225 | commitish: "2222",
226 | uri: "git://path.to/repo.git#2222"
227 | },
228 | "config": {
229 | "name": "toolkit",
230 | "version": "1.0.5",
231 | "jam": {}
232 | }
233 | }
234 | ]
235 | };
236 |
237 | install.extractValidVersion(pkg, "test", function(err) {
238 | var msg;
239 |
240 | msg = 'Multiple sources are found for the package "test@1.0.5":\n' +
241 | '\thttp://testrepo.org\n' +
242 | '\tgit://path.to/repo.git#2222\n';
243 |
244 | test.ok(err == msg, "Should throw an Error");
245 | test.done();
246 | });
247 | };
248 |
--------------------------------------------------------------------------------
/lib/packages.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Functions related to the finding, loading and manipulation of Jam packages
3 | *
4 | * @module
5 | */
6 |
7 | var settings = require('./settings'),
8 | versions = require('./versions'),
9 | async = require('async'),
10 | logger = require('./logger'),
11 | utils = require('./utils'),
12 | path = require('path'),
13 | fs = require('fs'),
14 | env = require('./env'),
15 | semver = require('semver'),
16 | events = require('events');
17 |
18 |
19 | var pathExists = fs.exists || path.exists;
20 |
21 |
22 | /**
23 | * Resolve the target package and its dependencies, reading the package.json files
24 | * and adding them to cache object.
25 | *
26 | * @param {Object} cache - an object to add packages metadata and path info to
27 | * @param {String} name - name of package to load
28 | * @param {String} range - acceptable version range for target package
29 | * @param {Array} paths - lookup paths for finding packages
30 | * @param {String} source - the original location for resolving relative paths
31 | * @param {String} parent - name of the parent package (if any)
32 | * @param {Function} cb - callback function
33 | */
34 |
35 | exports.readMeta = function (cache, name, range, paths, source, parent, cb) {
36 | var cached = cache[name] = {
37 | ready: false,
38 | ranges: [range],
39 | parent: parent,
40 | ev: new events.EventEmitter()
41 | };
42 | cached.ev.setMaxListeners(10000);
43 | exports.resolve(name, range, paths, source, function (err, v, doc, p) {
44 | if (err) {
45 | return cb(err);
46 | }
47 | cached.path = p;
48 | settings.load(p, function (err, cfg) {
49 | cached.cfg = cfg;
50 | cached.ready = true;
51 | cached.ev.emit('ready');
52 | paths = paths.concat([p + '/jam']);
53 | exports.readMetaDependencies(cache, cache[name], paths, source, cb);
54 | });
55 | });
56 | };
57 |
58 |
59 | /**
60 | * Read dependencies of a cached package loaded by the readMeta function.
61 | *
62 | * @param {Object} cache - an object to add packages metadata and path info to
63 | * @param {Object} pkg - the cached package object
64 | * @param {Array} paths - lookup paths for finding packages
65 | * @param {String} source - the original location for resolving relative paths
66 | * @param {Function} callback - the callback function
67 | */
68 |
69 | exports.readMetaDependencies = function (cache, pkg, paths, source, callback) {
70 | var deps = Object.keys(pkg.cfg.dependencies || {});
71 |
72 | async.forEach(deps, function (dep, cb) {
73 | var range = pkg.cfg.dependencies[dep];
74 |
75 | function testVersion(cached, range) {
76 | if (!semver.satisfies(cached.cfg.version, range)) {
77 | return callback(new Error(
78 | 'Conflicting version requirements for ' +
79 | cached.cfg.name + ':\n' +
80 | 'Version ' + cached.cfg.version + ' loaded by "' +
81 | cached.parent + '" but "' + pkg.cfg.name +
82 | '" requires ' + range
83 | ));
84 | }
85 | }
86 | if (cache[dep]) {
87 | var cached = cache[dep];
88 | cached.ranges.push(range);
89 | if (cached.ready) {
90 | testVersion(cached, range);
91 | // return loaded copy
92 | return cb(null, cached);
93 | }
94 | else {
95 | // wait for existing request to load
96 | cached.ev.on('ready', function () {
97 | testVersion(cached, range);
98 | return cb(null, cached);
99 | });
100 | return;
101 | }
102 | }
103 | else {
104 | exports.readMeta(
105 | cache, dep, range, paths, source, pkg.cfg.name, cb
106 | );
107 | }
108 | }, callback);
109 | };
110 |
111 |
112 | /**
113 | * Generates an array of possible paths from the package name,
114 | * source package path and array of package lookup paths (from .jamrc)
115 | *
116 | * @param {String} name - the name / path of the package to lookup
117 | * @param {String} source - the current package that paths are relative to
118 | * @param {Array} paths - an array of package lookup paths
119 | * @returns {Array}
120 | */
121 |
122 | exports.resolveCandidates = function (name, source, paths) {
123 | var candidates = [];
124 | if ( env.isAbsolute(name) ){
125 | // absolute path to a specific package directory
126 | candidates.push(name);
127 | }
128 | else if (name[0] === '.') {
129 | // relative path to a specific package directory
130 | candidates.push(path.normalize(path.join(source, name)));
131 | }
132 | else {
133 | // just a package name, use lookup paths
134 | candidates = candidates.concat(paths.map(function (dir) {
135 | return path.join(dir, name);
136 | }));
137 | }
138 | return candidates;
139 | };
140 |
141 |
142 | /**
143 | * Returns an object keyed by version number, containing the path and cfg
144 | * for each version, giving priority to paths earlier in the candidates list.
145 | *
146 | * eg, with candidates = [pathA, pathB], if both paths contained v1 of the
147 | * package, pathA and the package.json values from that path will be used for
148 | * that version, because it comes before pathB in the candidates array.
149 | *
150 | * @param {Array} candidates - an array of possible package paths
151 | * @param {Function} callback
152 | */
153 |
154 | exports.availableVersions = function (candidates, callback) {
155 | var versions = [];
156 | async.forEach(candidates, function (c, cb) {
157 | pathExists(path.join(c, 'package.json'), function (exists) {
158 | if (exists) {
159 | settings.load(c, function (err, doc) {
160 | if (err) {
161 | return cb(err);
162 | }
163 | //if (!versions[doc.version]) {
164 | versions.push({
165 | source: 'local',
166 | path: c,
167 | config: doc,
168 | version: doc.version
169 | });
170 | //}
171 | cb();
172 | });
173 | }
174 | else {
175 | cb();
176 | }
177 | });
178 | },
179 | function (err) {
180 | callback(err, versions);
181 | });
182 | };
183 |
184 |
185 | /**
186 | * Looks up the path to a specified package, returning an error if not found.
187 | *
188 | * @param {String} name - the name / path of the package to lookup
189 | * @param {String} range - a version or range of versions to match against
190 | * @param {Array} paths - an array of package lookup paths
191 | * @param {String} source - the current package that paths are relative to
192 | * @param {Function} callback
193 | */
194 |
195 | exports.resolve = async.memoize(function (name, ranges, paths, source, callback) {
196 | if (!Array.isArray(ranges)) {
197 | ranges = [ranges];
198 | }
199 | source = source || process.cwd();
200 |
201 | var candidates = exports.resolveCandidates(name, source, paths);
202 |
203 | exports.availableVersions(candidates, function (err, matches) {
204 | var e;
205 | if (err) {
206 | return callback(err);
207 | }
208 | var vers = Object.keys(matches);
209 | var highest = versions.maxSatisfying(vers, ranges);
210 | if (highest) {
211 | var m = matches[highest];
212 | return callback(null, highest, m.config, m.path);
213 | }
214 | if (vers.length) {
215 | e = new Error(
216 | "Cannot find package '" + name + "' matching " +
217 | ranges.join(' && ') + "\n" +
218 | "Available versions: " + vers.join(', ')
219 | );
220 | e.missing = true;
221 | return callback(e);
222 | }
223 | else {
224 | e = new Error("Cannot find package '" + name + "'");
225 | e.missing = true;
226 | return callback(e);
227 | }
228 | });
229 | });
230 |
--------------------------------------------------------------------------------
/lib/schema/package-full.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "JSON schema for NPM package.json files",
3 | "$schema": "http://json-schema.org/draft-04/schema#",
4 |
5 | "type": "object",
6 | "required": [ "name", "version" ],
7 |
8 | "definitions": {
9 | "person": {
10 | "description": "A person who has been involved in creating or maintaining this package",
11 | "type": [ "object", "string" ],
12 | "required": [ "name" ],
13 | "properties": {
14 | "name": {
15 | "type": "string"
16 | },
17 | "url": {
18 | "type": "string",
19 | "format": "uri"
20 | },
21 | "email": {
22 | "type": "string",
23 | "format": "email"
24 | }
25 | }
26 | },
27 | "dependency": {
28 | "description": "Dependencies are specified with a simple hash of package name to version range. The version range is a string which has one or more space-separated descriptors. Dependencies can also be identified with a tarball or git URL.",
29 | "type": "object",
30 | "additionalProperties": {
31 | "type": "string"
32 | }
33 | }
34 | },
35 |
36 | "patternProperties": {
37 | "^_": {
38 | "description": "Any property starting with _ is valid.",
39 | "additionalProperties": true,
40 | "additionalItems": true
41 | }
42 | },
43 |
44 | "properties": {
45 | "name": {
46 | "description": "The name of the package.",
47 | "type": "string"
48 | },
49 | "version": {
50 | "description": "Version must be parseable by node-semver, which is bundled with npm as a dependency.",
51 | "type": "string"
52 | },
53 | "description": {
54 | "description": "This helps people discover your package, as it's listed in 'npm search'.",
55 | "type": "string"
56 | },
57 | "keywords": {
58 | "description": "This helps people discover your package as it's listed in 'npm search'.",
59 | "type": "array"
60 | },
61 | "homepage": {
62 | "description": "The url to the project homepage.",
63 | "type": "string"
64 | },
65 | "bugs": {
66 | "description": "The url to your project's issue tracker and / or the email address to which issues should be reported. These are helpful for people who encounter issues with your package.",
67 | "type": [ "object", "string" ],
68 | "properties": {
69 | "url": {
70 | "type": "string",
71 | "description": "The url to your project's issue tracker.",
72 | "format": "uri"
73 | },
74 | "email": {
75 | "type": "string",
76 | "description": "The email address to which issues should be reported."
77 | }
78 | }
79 | },
80 | "license": {
81 | "type": "string",
82 | "description": "You should specify a license for your package so that people know how they are permitted to use it, and any restrictions you're placing on it."
83 | },
84 | "licenses": {
85 | "description": "You should specify a license for your package so that people know how they are permitted to use it, and any restrictions you're placing on it.",
86 | "type": "array",
87 | "items": {
88 | "type": "object",
89 | "properties": {
90 | "type": {
91 | "type": "string"
92 | },
93 | "url": {
94 | "type": "string",
95 | "format": "uri"
96 | }
97 | }
98 | }
99 | },
100 | "author": {
101 | "$ref": "#/definitions/person"
102 | },
103 | "contributors": {
104 | "description": "A list of people who contributed to this package.",
105 | "type": "array",
106 | "items": {
107 | "$ref": "#/definitions/person"
108 | }
109 | },
110 | "maintainers": {
111 | "description": "A list of people who maintains this package.",
112 | "type": "array",
113 | "items": {
114 | "$ref": "#/definitions/person"
115 | }
116 | },
117 | "files": {
118 | "description": "The 'files' field is an array of files to include in your project. If you name a folder in the array, then it will also include the files inside that folder.",
119 | "type": "array",
120 | "items": {
121 | "type": "string"
122 | }
123 | },
124 | "main": {
125 | "description": "The main field is a module ID that is the primary entry point to your program.",
126 | "type": "string"
127 | },
128 | "bin": {
129 | "type": [ "string", "object" ],
130 | "additionalProperties": {
131 | "type": "string"
132 | }
133 | },
134 | "man": {
135 | "type": [ "array", "string" ],
136 | "description": "Specify either a single file or an array of filenames to put in place for the man program to find.",
137 | "items": {
138 | "type": "string"
139 | }
140 | },
141 | "directories": {
142 | "type": "object",
143 | "properties": {
144 | "bin": {
145 | "description": "If you specify a 'bin' directory, then all the files in that folder will be used as the 'bin' hash.",
146 | "type": "string"
147 | },
148 | "doc": {
149 | "description": "Put markdown files in here. Eventually, these will be displayed nicely, maybe, someday.",
150 | "type": "string"
151 | },
152 | "example": {
153 | "description": "Put example scripts in here. Someday, it might be exposed in some clever way.",
154 | "type": "string"
155 | },
156 | "lib": {
157 | "description": "Tell people where the bulk of your library is. Nothing special is done with the lib folder in any way, but it's useful meta info.",
158 | "type": "string"
159 | },
160 | "man": {
161 | "description": "A folder that is full of man pages. Sugar to generate a 'man' array by walking the folder.",
162 | "type": "string"
163 | },
164 | "test": {
165 | "type": "string"
166 | }
167 | }
168 | },
169 | "repository": {
170 | "description": "Specify the place where your code lives. This is helpful for people who want to contribute.",
171 | "type": "object",
172 | "properties": {
173 | "type": {
174 | "type": "string"
175 | },
176 | "url": {
177 | "type": "string"
178 | }
179 | }
180 | },
181 | "scripts": {
182 | "description": "The 'scripts' member is an object hash of script commands that are run at various times in the lifecycle of your package. The key is the lifecycle event, and the value is the command to run at that point.",
183 | "type": "object",
184 | "additionalProperties": {
185 | "type": "string"
186 | }
187 | },
188 | "config": {
189 | "description": "A 'config' hash can be used to set configuration parameters used in package scripts that persist across upgrades.",
190 | "type": "object",
191 | "additionalProperties": true
192 | },
193 | "dependencies": {
194 | "$ref": "#/definitions/dependency"
195 | },
196 | "devDependencies": {
197 | "$ref": "#/definitions/dependency"
198 | },
199 | "bundleDependencies": {
200 | "type": "array",
201 | "description": "Array of package names that will be bundled when publishing the package.",
202 | "items": {
203 | "type": "string"
204 | }
205 | },
206 | "bundledDependencies": {
207 | "type": "array",
208 | "description": "Array of package names that will be bundled when publishing the package.",
209 | "items": {
210 | "type": "string"
211 | }
212 | },
213 | "optionalDependencies": {
214 | "$ref": "#/definitions/dependency"
215 | },
216 | "peerDependencies": {
217 | "$ref": "#/definitions/dependency"
218 | },
219 | "engines": {
220 | "type": "object",
221 | "additionalProperties": {
222 | "type": "string"
223 | }
224 | },
225 | "engineStrict": {
226 | "type": "boolean"
227 | },
228 | "os": {
229 | "type": "array",
230 | "items": {
231 | "type": "string"
232 | }
233 | },
234 | "cpu": {
235 | "type": "array",
236 | "items": {
237 | "type": "string"
238 | }
239 | },
240 | "preferGlobal": {
241 | "type": "boolean",
242 | "description": "If your package is primarily a command-line application that should be installed globally, then set this value to true to provide a warning if it is installed locally."
243 | },
244 | "private": {
245 | "type": "boolean",
246 | "description": "If set to true, then npm will refuse to publish it."
247 | },
248 | "publishConfig": {
249 | "type": "object",
250 | "additionalProperties": true
251 | },
252 | "dist": {
253 | "type": "object",
254 | "properties": {
255 | "shasum": {
256 | "type": "string"
257 | },
258 | "tarball": {
259 | "type": "string"
260 | }
261 | }
262 | },
263 | "readme": {
264 | "type": "string"
265 | }
266 | }
267 | }
268 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Public API to Jam features
3 | */
4 |
5 | var ls = require('./lib/commands/ls'),
6 | install = require('./lib/commands/install'),
7 | upgrade = require('./lib/commands/upgrade'),
8 | rebuild = require('./lib/commands/rebuild'),
9 | remove = require('./lib/commands/remove'),
10 | publish = require('./lib/commands/publish'),
11 | repository= require('./lib/repository'),
12 | logger = require('./lib/logger'),
13 | jamrc = require('./lib/jamrc'),
14 | async = require('async'),
15 | path = require('path');
16 |
17 |
18 | // silence logger module by default
19 | logger.level = 'error';
20 | logger.clean_exit = true;
21 |
22 |
23 | /**
24 | * Set log-level, by default only errors not passed back to api callbacks are
25 | * logged. If you'd like more console output, call this function beforehand.
26 | *
27 | * Levels:
28 | *
29 | * error
30 | * warning
31 | * info
32 | * debug
33 | * verbose
34 | *
35 | * @param {String} level
36 | */
37 |
38 | exports.logLevel = function (level) {
39 | logger.level = level;
40 | };
41 |
42 |
43 | /**
44 | * Compiles packages into a single file for production use.
45 | *
46 | * Options:
47 | * cwd String working directory (defaults to process.cwd())
48 | * settings Object values from jamrc (optional)
49 | * includes [String] array of require paths to include in build
50 | * shallowExcludes [String] exclude these modules (not their dependencies)
51 | * deepExcludes [String] exclude these modules AND their dependencies
52 | * output String filename to save output to
53 | * pkgdir String location of jam packages
54 | * baseurl String base url value to pass to requirejs optimizer
55 | * wrap Bool wraps output in anonymous function
56 | * almond Bool uses almond shim
57 | * verbose Bool more verbose output from r.js
58 | * nominify Bool don't minify with uglifyjs
59 | * nolicense Bool strip license comments
60 | *
61 | * @param {Object} options
62 | * @param {Function} callback(err, build_response)
63 | */
64 |
65 | exports.compile = require('./lib/commands/compile').compile;
66 |
67 |
68 | /**
69 | * Install a package using the appropriate settings for the project directory.
70 | * Reads values from .jamrc and package.json to install to the correct
71 | * directory.
72 | *
73 | * @param {String} pdir - the project directory (where package.json is)
74 | * @param {String|Array} names - the package(s) to install
75 | * @param {Function} callback(err)
76 | */
77 |
78 | exports.install = function (pdir, names, callback) {
79 | if (!Array.isArray(names)) {
80 | names = [names];
81 | }
82 | jamrc.load(function (err, settings) {
83 | var opt = {repositories: settings.repositories};
84 |
85 | install.initDir(settings, pdir, opt, function (err, opt, cfg) {
86 | opt = install.extendOptions(pdir, settings, cfg, opt);
87 | install.installPackages(cfg, names, opt, callback);
88 | });
89 | });
90 | };
91 |
92 |
93 | /**
94 | * Upgrades all or specified packages for the provided project. Reads values
95 | * from .jamrc and package.json to find the package directory and repositories.
96 | *
97 | * @param {String} pdir - the project directory (where package.json is)
98 | * @param {String|Array} names - specific package(s) to upgrade (optional)
99 | * @param {Function} callback(err)
100 | */
101 |
102 | exports.upgrade = function (pdir, /*optional*/names, callback) {
103 | if (!callback) {
104 | callback = names;
105 | names = null;
106 | }
107 | if (names && !Array.isArray(names)) {
108 | names = [names];
109 | }
110 | jamrc.load(function (err, settings) {
111 | var opt = {repositories: settings.repositories};
112 |
113 | install.initDir(settings, pdir, opt, function (err, opt, cfg) {
114 | opt = install.extendOptions(pdir, settings, cfg, opt);
115 | upgrade.upgrade(settings, names, opt, cfg, callback);
116 | });
117 | });
118 | };
119 |
120 |
121 | /**
122 | * Removes specified packages from the project's package directory. Reads values
123 | * from .jamrc and package.json to find the package directory.
124 | *
125 | * @param {String} pdir - the project directory (where package.json is)
126 | * @param {String|Array} names - the package(s) to remove
127 | * @param {Function} callback(err)
128 | */
129 |
130 | exports.remove = function (pdir, names, callback) {
131 | if (!Array.isArray(names)) {
132 | names = [names];
133 | }
134 | jamrc.load(function (err, settings) {
135 | var opt = {};
136 | install.initDir(settings, pdir, opt, function (err, opt, cfg) {
137 | opt = install.extendOptions(pdir, settings, cfg, opt);
138 | remove.remove(settings, cfg, opt, names, callback);
139 | });
140 | });
141 | };
142 |
143 |
144 | /**
145 | * Lists installed packages for the given project. The callback gets the
146 | * output that would normally be printed to the terminal and an array of
147 | * package objects (representing the values from each package's package.json
148 | * file).
149 | *
150 | * @param {String} pdir - the project directory (where package.json is)
151 | * @param {Function} callback(err, output, packages)
152 | */
153 |
154 | exports.ls = function (pdir, callback) {
155 | jamrc.load(function (err, settings) {
156 | var opt = {};
157 | install.initDir(settings, pdir, opt, function (err, opt, cfg) {
158 | opt = install.extendOptions(pdir, settings, cfg, opt);
159 | ls.ls(settings, cfg, opt.target_dir, callback);
160 | });
161 | });
162 | };
163 |
164 |
165 | /**
166 | * Searches repositories for a package.
167 | *
168 | * @param {String} pdir - the project directory (where package.json is)
169 | * @param {String|Array} q - the search terms
170 | * @param {Number} limit - limit the number of results per-repository (optional)
171 | * @param {Function} callback(err, results)
172 | */
173 |
174 | exports.search = function (pdir, q, /*optional*/limit, callback) {
175 | if (!callback) {
176 | callback = limit;
177 | limit = 10;
178 | }
179 | jamrc.load(function (err, settings) {
180 | var opt = {repositories: settings.repositories};
181 |
182 | install.initDir(settings, pdir, opt, function (err, opt, cfg) {
183 | opt = install.extendOptions(pdir, settings, cfg, opt);
184 |
185 | async.concat(opt.repositories, function (repo, cb) {
186 | repository.search(repo, q, limit, function (err, data) {
187 | if (err) {
188 | return cb(err);
189 | }
190 | cb(null, data.rows.map(function (r) {
191 | return r.doc;
192 | }));
193 | });
194 | },
195 | callback);
196 | });
197 | });
198 | };
199 |
200 |
201 | /**
202 | * Rebuild require.config.js and require.js according to the packages
203 | * available inside the package directory.
204 | *
205 | * @param {String} pdir - the project directory (where package.json is)
206 | * @param {Function} callback(err)
207 | */
208 |
209 | exports.rebuild = function (pdir, callback) {
210 | jamrc.load(function (err, settings) {
211 | var opt = {};
212 | install.initDir(settings, pdir, opt, function (err, opt, cfg) {
213 | opt = install.extendOptions(pdir, settings, cfg, opt);
214 | rebuild.rebuild(settings, cfg, opt, callback);
215 | });
216 | });
217 | };
218 |
219 | /**
220 | * Publish package
221 | * available inside the package directory.
222 | *
223 | * @param {object} config - the project directory (where package.json is)
224 | * @param {string} [config.dir]
225 | * @param {string} [config.repo]
226 | * @param {string} [config.level]
227 | * @param {object} [config.options]
228 | * @param {Function} callback(err)
229 | */
230 |
231 | exports.publish = function(config, callback) {
232 | var repo, flow, params;
233 |
234 | if (_.isFunction(config)) {
235 | callback = config;
236 | config = {};
237 | }
238 |
239 | if (_.isString(config.level)) {
240 | logger.level = config.level;
241 | }
242 |
243 | flow = {};
244 | params = {};
245 |
246 | params.dir = config.dir || ".";
247 |
248 | repo = config.repo;
249 | if (!repo && process.env.JAM_TEST && !(repo = process.env.JAM_TEST_DB)) {
250 | return callback(new Error('JAM_TEST environment variable set, but no JAM_TEST_DB set'));
251 | }
252 |
253 | if (!repo) {
254 | flow.repo = function(next) {
255 | jamrc.load(function(err, settings) {
256 | if (err) {
257 | return next(err);
258 | }
259 |
260 | next(null, settings.repositories[0]);
261 | });
262 | };
263 | } else {
264 | params.repo = repo;
265 | }
266 |
267 | async.parallel(flow, function(err, results) {
268 | if (err) {
269 | return callback(err);
270 | }
271 |
272 | _.extend(params, results);
273 |
274 | publish.publish('package', params.repo, params.dir, config.options || {}, callback);
275 | });
276 | };
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Jam
2 |
3 | For **front-end** developers who crave maintainable assets,
4 | **Jam** is a **package manager** for JavaScript.
5 | Unlike other repositories, we put the **browser** first.
6 |
7 |
8 | * **Manage dependencies** - Using a stack of script tags isn't the most maintainable way of managing dependencies, with Jam packages and loaders like RequireJS you get automatic dependency resolution.
9 |
10 | * **Fast and modular** - Achieve faster load times with asynchronous loading and the ability to optimize downloads. JavaScript modules and packages provide properly namespaced and more modular code.
11 |
12 | * **Use with existing stack** - Jam manages only your front-end assets, the rest of your app can be written in your favourite language or framework. Node.js tools can use the repository directly with the Jam API.
13 |
14 | * **Custom builds** - No more configuring custom builds of popular libraries. Now, every build can be optimized automatically depending on the parts you use, and additional components can always be loaded later.
15 |
16 | * **Focus on size** - Installing multiple versions works great on the server, but client-side we don't want five versions of jQuery! Jam can use powerful dependency resolution to find a working set of packages using only a single version of each.
17 |
18 | * **100% browser** - Every package you see here will work in the browser and play nicely with module loaders like RequireJS. We're not hijacking an existing repository, we're creating a 100% browser-focused community!
19 |
20 |
21 | [Visit the Jam website](http://jamjs.org)
22 |
23 |
24 | ## Example usage
25 |
26 | $ jam install jquery --save
27 |
28 |
29 | ```html
30 |
31 |
32 |
37 | ```
38 |
39 | [Learn more...](http://jamjs.org)
40 |
41 |
42 | ## Browser packages in package.json
43 |
44 | You can also define your browser dependencies in a project-level package.json
45 | file. If you use Node.js, this format will already familiar to you, and the
46 | Jam dependencies can live alongside your NPM dependencies. It's also possible
47 | to define custom install paths and baseUrls, as well as hand in any requirejs
48 | configuration here:
49 |
50 | ```javascript
51 | {
52 | "name": "my-project",
53 | "version": "0.0.1",
54 | "description": "My example project",
55 | "jam": {
56 | "baseUrl": "public",
57 | "packageDir": "public/vendor",
58 | "dependencies": {
59 | "jquery": "1.7.x",
60 | "underscore": null
61 | },
62 | "config": {
63 | "paths": {
64 | "templates": "public/templates"
65 | }
66 | }
67 | }
68 | }
69 | ```
70 |
71 | ### Git URLs as Dependencies
72 |
73 | Git urls can be of the form:
74 |
75 | + git://github.com/user/project.git#commit-ish
76 | + git+ssh://user@hostname/project.git#commit-ish
77 | + git+http://user@hostname/project/blah.git#commit-ish
78 | + git+https://user@hostname/project/blah.git#commit-ish
79 |
80 | The commit-ish can be any tag, sha, or branch which can be supplied as an argument to git checkout. The default is master.
81 |
82 | ```javascript
83 | {
84 | "name": "my-project",
85 | "version": "0.0.1",
86 | "description": "My example project",
87 | "jam": {
88 | "baseUrl": "public",
89 | "packageDir": "public/vendor",
90 | "dependencies": {
91 | "feature": "git://github.com/user/project.git#0.2.3"
92 | }
93 | }
94 | }
95 | ```
96 |
97 |
98 | ## Installation
99 |
100 | # npm install -g jamjs
101 |
102 | Requires [node.js](http://nodejs.org)
103 |
104 |
105 | ## Settings
106 |
107 | You can customize Jam by creating a `.jamrc` file in your home directory.
108 |
109 | ### .jamrc
110 |
111 | #### repositories
112 |
113 | An array with Jam repositiories. Jam uses `http://jamjs.org/repository` by
114 | default, but it's possible to create a local, e.g. corporate, repository.
115 |
116 | ```javascript
117 | exports.repositories = [
118 | "http://mycorporation.com:5984/repository/",
119 | "http://jamjs.org/repository"
120 | ];
121 | ```
122 |
123 | Repositories are in preference-order, so packages from repositories earlier
124 | in the list will be preferred over packages in repositories later in the
125 | list. However, when no package version is specified, the highest version
126 | number will be installed (even if that's not from the earliest repository).
127 |
128 | You can add custom search URLs to repositories too:
129 |
130 | ```javascript
131 | exports.repositories = [
132 | {
133 | url: "http://mycorporation.com:5984/repository/",
134 | search: "http://db.com:5984/_fti/key/_design/search/something"
135 | },
136 | "http://jamjs.org/repository"
137 | ];
138 | ```
139 |
140 | If your local repository doesn't implement full text search (e.g. you don't want
141 | to install couchdb lucene), you can disable searching functionality for that repository, otherwise
142 | `jam search` would report an error:
143 |
144 | ```javascript
145 | exports.repositories = [
146 | {
147 | url: "http://mycorporation.com:5984/repository/",
148 | search: false
149 | },
150 | "http://jamjs.org/repository"
151 | ];
152 | ```
153 |
154 | See the section below on running your own repository.
155 |
156 |
157 | #### package_dir
158 |
159 | Sets the default package installation directory (normally uses `./jam`). This
160 | is best customized in your project-level package.json file, to ensure other
161 | developers also install to the correct location.
162 |
163 | ```javascript
164 | exports.package_dir = 'libs';
165 | ```
166 |
167 | #### strict
168 |
169 | Puts jam into strict mode. In this mode, during installation, subpackages versions checked to be strict.
170 | If not - the must be hoisted to the root package with strict version declaration.
171 |
172 | #### production
173 |
174 | Puts jam into production mode. In this mode, during installation, dependencies sources are restricted to be repository.
175 |
176 | ## Running the tests
177 |
178 | Jam includes two test suites, unit tests (in `test/unit`) and integration
179 | tests (in `test/integration`). The unit tests are easy to run by running the
180 | `test/unit.sh` script, or `test\unit.bat` on Windows. The integration tests
181 | first require you to set up a CouchDB instance to test against (you can get
182 | a free account at [IrisCouch](http://www.iriscouch.com/) if you don't want to install
183 | CouchDB). You then need to set the JAM\_TEST\_DB environment variable to
184 | point to a CouchDB database URL for testing:
185 |
186 | #### Linux
187 | ```
188 | export JAM_TEST_DB=http://user:password@localhost:5984/jamtest
189 | ```
190 |
191 | #### Windows
192 | ```
193 | set JAM_TEST_DB=http://user:password@localhost:5984/jamtest
194 | ```
195 |
196 | **Warning:** All data in the test database will be deleted!
197 |
198 | You can then run the integration tests using `test/integration.sh` or
199 | `test\integration.bat`. To run BOTH the unit and integration tests use
200 | `test/all.sh` or `test\all.bat`.
201 |
202 |
203 | ## Running your own private repository or mirror
204 | 1. Install couchdb
205 |
206 | #### Mac OS X:
207 |
208 | 1. Install [Homebrew](http://mxcl.github.com/homebrew/).
209 | 2.
210 |
211 | ```
212 | brew install couchdb
213 | ```
214 |
215 | #### Ubuntu:
216 |
217 | ```
218 | apt-get install couchdb
219 | ```
220 | 2. Configure your database
221 |
222 | ```
223 | curl -X POST http://127.0.0.1:5984/_replicate -d '{
224 | "source":"http://jamjs.org/repository",
225 | "target":"http://localhost:5984/repository",
226 | "continuous":true,
227 | "doc_ids":["_design/jam-packages"]
228 | }' -H "Content-Type: application/json"
229 | ```
230 |
231 | #### To create a mirror:
232 |
233 | ```
234 | curl -X POST http://127.0.0.1:5984/_replicate -d '{
235 | "source":"http://jamjs.org/repository",
236 | "target":"repository",
237 | "continuous":true,
238 | "create_target":true
239 | }' -H "Content-Type: application/json"
240 | ```
241 |
242 | #### To create an empty, private repository:
243 |
244 | ```
245 | curl -X PUT http://127.0.0.1:5984/repository
246 | ```
247 |
248 | 3. Edit your ```.jamrc``` file to use your new repository:
249 |
250 | ```
251 | exports.repositories = [
252 | {
253 | url: "http://localhost:5984/repository",
254 | search: false
255 | },
256 | "http://jamjs.org/repository"
257 | ];
258 | ```
259 |
260 | ### Adding search
261 |
262 | 1. [Install couchdb-lucene](https://github.com/rnewson/couchdb-lucene#build-and-run-couchdb-lucene)
263 | 2. Restart couchdb.
264 | 3. Edit your ```.jamrc``` file to allow searching on your repository:
265 |
266 | ```
267 | exports.repositories = [
268 | {
269 | url: "http://localhost:5984/repository",
270 | search: "http://localhost:5984/_fti/local/repository/_design/jam-packages/packages/"
271 | },
272 | "http://jamjs.org/repository"
273 | ];
274 | ```
275 |
276 | ### Publishing packages to your private repository
277 |
278 | ```
279 | jam publish --repository http://localhost:5984/repository
280 | ```
281 |
282 | ## More documentation
283 |
284 | To learn how to create and publish packages etc, and for more info on using
285 | packages, consult the [Jam documentation website](http://jamjs.org/docs).
286 |
287 |
288 | ## Links
289 |
290 | * [Homepage](http://jamjs.org)
291 | * [Packages](http://jamjs.org/packages/)
292 | * [Docs](http://jamjs.org/doc)
293 |
--------------------------------------------------------------------------------
/lib/commands/compile.js:
--------------------------------------------------------------------------------
1 | // TODO
2 | // https://github.com/jrburke/almond
3 | //
4 | //
5 | // jam compile -i backbone -i d3 -o built.js
6 | //
7 | //
8 | // - set up package paths for requirejs optimizer
9 | // - run equivalent of:
10 | // node r.js -o baseUrl=. name=path/to/almond.js include=main
11 | // out=main-built.js wrap=true
12 | //
13 | // (wrap is optional)
14 | //
15 | //
16 | // OR should this just be an extension of the optimize command?
17 |
18 | var logger = require('../logger'),
19 | settings = require('../settings'),
20 | jamrc = require('../jamrc'),
21 | install = require('./install'),
22 | args = require('../args'),
23 | tar = require('../tar'),
24 | path = require('path'),
25 | async = require('async'),
26 | fs = require('fs'),
27 | requirejs = require('requirejs'),
28 | project = require('../project');
29 |
30 |
31 | exports.summary = 'Combines modules and requirejs into a single file';
32 |
33 | exports.usage = '\n' +
34 | 'jam compile TARGET\n' +
35 | 'jam compile -i MODULE ... -o TARGET\n' +
36 | '\n' +
37 | 'When called without -i parameters, all installed packages are included\n' +
38 | 'in the compiled output. With -i parameters, only the specified\n' +
39 | 'packages are compiled, this can also include modules from your own\n' +
40 | 'project directory.\n' +
41 | '\n' +
42 | 'Parameters:\n' +
43 | ' TARGET The filename to save compiled output to\n' +
44 | '\n' +
45 | 'Options:\n' +
46 | ' -i, --include Specific modules to optimize, combining them and\n' +
47 | ' their dependencies into a single file.\n' +
48 | ' -e, --exclude Shallow excludes a module from the build (it\'s\n' +
49 | ' dependencies will still be included).\n' +
50 | ' -E, --deep-exclude Deep excludes a module and it\'s dependencies\n' +
51 | ' from the build.\n' +
52 | ' -o, --out Output file for the compiled code\n' +
53 | ' -d, --package-dir Jam directory to use (defaults to "./jam")\n' +
54 | ' -w, --wrap Wraps the output in an anonymous function to avoid\n' +
55 | ' require and define functions being added to the\n' +
56 | ' global scope, this often makes sense when using the\n' +
57 | ' almond option.\n' +
58 | ' -a, --almond Use the lightweight almond shim instead of RequireJS,\n' +
59 | ' smaller filesize but can only load bundled resources\n' +
60 | ' and cannot request additional modules.\n' +
61 | ' -v, --verbose Increase log level to report all compiled modules\n' +
62 | ' --no-minify Do not minify concatenated file with UglifyJS.\n' +
63 | ' --no-license Do not include license comments.';
64 |
65 |
66 | exports.run = function (settings, _args) {
67 | var logger = require('../logger');
68 |
69 | var a = args.parse(_args, {
70 | includes: {match: ['-i', '--include'], multiple: true, value: true},
71 | shallowExcludes: {
72 | match: ['-e', '--exclude'],
73 | multiple: true,
74 | value: true
75 | },
76 | deepExcludes: {
77 | match: ['-E', '--deep-exclude'],
78 | multiple: true,
79 | value: true
80 | },
81 | output: {match: ['-o', '--out'], value: true},
82 | pkgdir: {match: ['-d','--package-dir'], value: true},
83 | baseurl: {match: ['--baseurl'], value: true},
84 | wrap: {match: ['-w','--wrap']},
85 | almond: {match: ['-a','--almond']},
86 | verbose: {match: ['-v','--verbose']},
87 | nominify: {match: ['--no-minify']},
88 | nolicense: {match: ['--no-license']}
89 | });
90 |
91 | var opt = a.options;
92 | opt.output = opt.output || a.positional[0];
93 |
94 | if (!opt.output) {
95 | logger.error('You must specify an output file');
96 | console.log(exports.usage);
97 | logger.clean_exit = true;
98 | return;
99 | }
100 |
101 | var start_time = new Date().getTime();
102 | opt.cwd = process.cwd();
103 | opt.settings = settings;
104 |
105 | exports.compile(opt, function (err) {
106 | if (err) {
107 | return logger.error(err);
108 | }
109 | var duration = new Date().getTime() - start_time;
110 | logger.end(opt.output + ' (' + duration + 'ms)');
111 | });
112 | };
113 |
114 | exports.filterPackages = function (cfgpackages, pkgdir, names, callback) {
115 | async.filter(names, function (n, cb) {
116 | var pkg = _.detect(cfgpackages, function (pkg) {
117 | return pkg.name === n;
118 | });
119 | if (pkg && pkg.main) {
120 | // main property defined
121 | return cb(true);
122 | }
123 | else {
124 | // check main.js exists, otherwise optimizer fails
125 | fs.exists(path.join(pkgdir, n, 'main.js'), cb);
126 | }
127 | }, callback);
128 | };
129 |
130 |
131 | // DONT forget to update docs in index.js file when changing args!
132 | exports.compile = function (opt, callback) {
133 | if (!opt.settings) {
134 | opt.settings = jamrc.DEFAULTS;
135 | }
136 | if (!opt.cwd) {
137 | opt.cwd = process.cwd();
138 | }
139 | install.initDir(opt.settings, opt.cwd, opt, function (err, opt, cfg, pdir) {
140 | if (err) {
141 | return logger.error(err);
142 | }
143 |
144 | if (!opt.pkgdir) {
145 | if (cfg.jam && cfg.jam.packageDir) {
146 | opt.pkgdir = path.resolve(pdir, cfg.jam.packageDir);
147 | }
148 | else {
149 | opt.pkgdir = path.resolve(pdir, opt.settings.package_dir || '');
150 | }
151 | }
152 | if (!opt.baseurl) {
153 | if (cfg.jam && cfg.jam.baseUrl) {
154 | opt.baseurl = path.resolve(pdir, cfg.jam.baseUrl);
155 | }
156 | else {
157 | opt.baseurl = path.resolve(pdir, opt.settings.baseUrl || '');
158 | }
159 | }
160 |
161 | var configfile = path.resolve(opt.pkgdir, 'require.config');
162 |
163 | var packages = require(configfile).packages;
164 | var pkgnames = packages.map(function (p) {
165 | return p.name;
166 | });
167 | exports.filterPackages(
168 | packages, opt.pkgdir, pkgnames, function (valid_names) {
169 | if (!opt.includes.length) {
170 | // compile all installed modules by default
171 | opt.includes = valid_names;
172 | }
173 | if (!opt.output) {
174 | return callback('You must specify an output file');
175 | }
176 |
177 | logger.info('compiling', opt.output);
178 | if (opt.includes.length) {
179 | logger.info('include', opt.includes.join(', '));
180 | }
181 |
182 | var impl;
183 | if (opt.almond) {
184 | logger.info('using almond.js');
185 | impl = path.relative(
186 | path.resolve(opt.baseurl),
187 | require.resolve('almond')
188 | );
189 | }
190 | else {
191 | var require_path = require.resolve('requirejs');
192 |
193 | impl = path.relative(
194 | path.resolve(opt.baseurl),
195 | path.join(require_path, '../../require.js')
196 | );
197 | }
198 |
199 | var includes;
200 | if (opt.almond) {
201 | includes = opt.includes;
202 | }
203 | else {
204 | includes = [
205 | path.relative(opt.baseurl, configfile)
206 | ].concat(opt.includes);
207 | }
208 |
209 | var config = {
210 | baseUrl: opt.baseurl,
211 | packages: packages,
212 | name: 'requireLib',
213 | wrap: opt.wrap,
214 | optimize: 'uglify',
215 | include: includes,
216 | out: opt.output,
217 | paths: {requireLib: impl}
218 | };
219 | if (opt.verbose) {
220 | config.logLevel = 0;
221 | }
222 | if (opt.nominify) {
223 | config.optimize = 'none';
224 | }
225 | if (opt.nolicense) {
226 | config.preserveLicenseComments = false;
227 | }
228 | if (opt.shallowExcludes && opt.shallowExcludes.length) {
229 | config.excludeShallow = opt.shallowExcludes;
230 | }
231 | if (opt.deepExcludes && opt.deepExcludes.length) {
232 | config.exclude = opt.deepExcludes;
233 | }
234 | try {
235 | requirejs.optimize(config, function (build_response) {
236 | if (/^Error: Error:/.test(build_response)) {
237 | // TODO: try to get better error handling added
238 | // upstream
239 | return callback(build_response);
240 | }
241 | callback(null, build_response);
242 | });
243 | }
244 | catch (e) {
245 | return callback(e);
246 | }
247 | }
248 | );
249 | });
250 | };
251 |
--------------------------------------------------------------------------------
/lib/commands/upgrade.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module dependencies
3 | */
4 |
5 | var path = require('path'),
6 | async = require('async'),
7 | semver = require('semver'),
8 | install = require('./install'),
9 | project = require('../project'),
10 | tree = require('../tree');
11 | utils = require('../utils'),
12 | logger = require('../logger'),
13 | argParse = require('../args').parse,
14 | _ = require('underscore');
15 |
16 |
17 | /**
18 | * Usage information and docs
19 | */
20 |
21 | exports.summary = 'Upgrades packages to the latest compatible version';
22 |
23 |
24 | exports.usage = '' +
25 | 'jam upgrade [PACKAGES ...]\n' +
26 | '\n' +
27 | 'Parameters:\n' +
28 | ' PACKAGES Names of specific packages to upgrade\n' +
29 | '\n' +
30 | 'Options:\n' +
31 | ' -r, --repository Source repository URL (otherwise uses values in jamrc)\n' +
32 | ' -d, --package-dir Package directory (defaults to "./jam")';
33 |
34 |
35 | /**
36 | * Run function called when "jam upgrade" command is used
37 | *
38 | * @param {Object} settings - the values from .jamrc files
39 | * @param {Array} args - command-line arguments
40 | */
41 |
42 | exports.run = function (settings, args) {
43 | var a = argParse(args, {
44 | 'repository': {match: ['-r', '--repository'], value: true},
45 | 'target_dir': {match: ['-d', '--package-dir'], value: true},
46 | 'baseurl': {match: ['-b', '--baseurl'], value: true}
47 | });
48 |
49 | var opt = a.options;
50 | var deps = a.positional;
51 |
52 | opt.repositories = settings.repositories;
53 | if (a.options.repository) {
54 | opt.repositories = [a.options.repository];
55 | // don't allow package dir .jamrc file to overwrite repositories
56 | opt.fixed_repositories = true;
57 | }
58 | if (process.env.JAM_TEST) {
59 | if (!process.env.JAM_TEST_DB) {
60 | throw 'JAM_TEST environment variable set, but no JAM_TEST_DB set';
61 | }
62 | opt.repositories = [process.env.JAM_TEST_DB];
63 | opt.fixed_repositories = true;
64 | }
65 |
66 | var cwd = process.cwd();
67 | install.initDir(settings, cwd, opt, function (err, opt, cfg, proj_dir) {
68 | if (err) {
69 | return logger.error(err);
70 | }
71 | opt = install.extendOptions(proj_dir, settings, cfg, opt);
72 | exports.upgrade(settings, deps, opt, cfg, function (err) {
73 | if (err) {
74 | return logger.error(err);
75 | }
76 | logger.end();
77 | });
78 | });
79 | };
80 |
81 |
82 | /**
83 | * Upgrade the current project directory's dependencies.
84 | *
85 | * @param {Array} deps - an optional sub-set of package names to upgrade
86 | * @param {Object} opt - the options object
87 | * @param {Function} callback
88 | */
89 |
90 | exports.upgrade = function (settings, deps, opt, cfg, callback) {
91 | exports.getOutdated(deps, cfg, opt, function (e, changed, local, updated) {
92 | if (e) {
93 | return callback(e);
94 | }
95 | exports.installChanges(changed, opt, function (err) {
96 | if (err) {
97 | return callback(err);
98 | }
99 | project.updateRequireConfig(
100 | opt.target_dir,
101 | opt.baseurl,
102 | function (err) {
103 | if (err) {
104 | return callback(err);
105 | }
106 | //install.checkUnused(updated, opt, callback);
107 | callback();
108 | }
109 | );
110 | });
111 | });
112 | };
113 |
114 |
115 | /**
116 | * Builds a remote and a local copy of the version tree. This is used to compare
117 | * the installed packages against those that are available in the repositories.
118 | *
119 | * @param {Array|null} deps - an optional subset of packages to upgrade
120 | * @param {Object} cfg - values from package.json for the root package
121 | * @param {Object} opt - the options object
122 | * @param {Function} callback
123 | */
124 |
125 | exports.buildTrees = function (deps, cfg, opt, callback) {
126 | var local_sources = [
127 | install.dirSource(opt.target_dir),
128 | install.repoSource(opt.repositories, cfg)
129 | ];
130 | var newcfg = utils.convertToRootCfg(cfg);
131 |
132 | utils.listDirs(opt.target_dir, function (err, dirs) {
133 | if (err) {
134 | return callback(err);
135 | }
136 | // add packages not referenced by package.json, but still inside
137 | // package dir, to make sure they get upgraded too
138 | dirs.forEach(function (d) {
139 | var name = path.basename(d);
140 | if (!newcfg.dependencies.hasOwnProperty(name)) {
141 | newcfg.dependencies[name] = null;
142 | }
143 | });
144 |
145 | var pkg = {
146 | config: newcfg,
147 | source: 'root'
148 | };
149 | logger.info('Building local version tree...');
150 | tree.build(pkg, local_sources, function (err, local) {
151 | if (err) {
152 | return callback(err);
153 | }
154 | var update_sources = [
155 | // check remote source first to make sure we get highest version
156 | install.repoSource(opt.repositories, cfg),
157 | install.dirSource(opt.target_dir)
158 | ];
159 | var dependency_sources = [
160 | // check local source first to keep local version if possible
161 | install.dirSource(opt.target_dir),
162 | install.repoSource(opt.repositories, cfg)
163 | ];
164 | if (!deps || !deps.length) {
165 | // update all packages if none specified
166 | deps = Object.keys(local);
167 | }
168 |
169 | var packages = {};
170 | // add root package
171 | packages[pkg.config.name] = tree.createPackage([]);
172 | deps.forEach(function (name) {
173 | // prep specified dependencies with the update_sources
174 | packages[name] = tree.createPackage(update_sources);
175 | });
176 |
177 | logger.info('Building remote version tree...');
178 | tree.extend(
179 | pkg, dependency_sources, packages, function (err, updated) {
180 | callback(err, local, updated);
181 | }
182 | );
183 | });
184 | });
185 | };
186 |
187 |
188 | /**
189 | * Gets the remote and local version trees, compares the version numbers for
190 | * each package, and returns a list of packages which have changed.
191 | *
192 | * Each objects in the returned list of changed packages have the following
193 | * properties:
194 | *
195 | * - name - the name of the package
196 | * - version - the new version to be installed
197 | * - old - the old version to be installed (null if it doesn't currently exist)
198 | *
199 | * @param {Object} cfg - the values from package.json for the root package
200 | * @param {Object} opt - the options object
201 | * @param {Function} callback
202 | */
203 |
204 | exports.getOutdated = function (deps, cfg, opt, callback) {
205 | var _ = require('underscore');
206 | exports.buildTrees(deps, cfg, opt, function (err, local, updated) {
207 | if (err) {
208 | return callback(err);
209 | }
210 | var all_names = _.uniq(_.keys(local).concat(_.keys(updated)));
211 |
212 | var changed = all_names.map(function (name) {
213 | var lversion = local[name] ? local[name].current_version: null;
214 | var uversion = updated[name] ? updated[name].current_version: null;
215 |
216 | if (lversion) {
217 | var lpkg = _.findWhere(local[name].versions, { version: lversion });
218 | if (lpkg.source === 'repository') {
219 | // cannot even satisfy requirements with current package,
220 | // this needs re-installing from repositories
221 | return {
222 | name: name,
223 | version: uversion,
224 | old: 'not satisfiable'
225 | };
226 | }
227 | }
228 | if (!local[name] && updated[name] || lversion !== uversion) {
229 | return {name: name, version: uversion, old: lversion};
230 | }
231 | });
232 | callback(null, _.compact(changed), local, updated);
233 | });
234 | };
235 |
236 |
237 | /**
238 | * Accepts an array of changed packages and reports the change to the console
239 | * then installs from the repositories.
240 | *
241 | * @param {Array} packages - array of changed packages
242 | * @param {Object} opt - the options object
243 | * @param {Function} callback
244 | */
245 |
246 | exports.installChanges = function (packages, opt, callback) {
247 | async.forEachLimit(packages, 5, function (dep, cb) {
248 | if (dep.name === '_root') {
249 | return cb();
250 | }
251 | if (!dep.old) {
252 | logger.info('new package', dep.name + '@' + dep.version);
253 | }
254 | else if (semver.lt(dep.old, dep.version)) {
255 | logger.info(
256 | 'upgrade package',
257 | dep.name + '@' + dep.old + ' => ' + dep.name + '@' + dep.version
258 | );
259 | }
260 | else if (semver.gt(dep.old, dep.version)) {
261 | logger.info(
262 | 'downgrade package',
263 | dep.name + '@' + dep.old + ' => ' + dep.name + '@' + dep.version
264 | );
265 | }
266 | install.installRepo(dep.name, dep.version, opt, cb);
267 | }, callback);
268 | };
269 |
--------------------------------------------------------------------------------
/lib/fstream-jam.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Thanks to Isaac Schlueter's work on fstream-npm on which this file is
3 | * based. https://github.com/isaacs/fstream-npm
4 | */
5 |
6 |
7 | var Ignore = require("fstream-ignore"),
8 | minimatch = require('minimatch'),
9 | inherits = require("inherits"),
10 | utils = require('./utils'),
11 | path = require("path"),
12 | fs = require("fs");
13 |
14 |
15 | module.exports = Packer
16 |
17 | inherits(Packer, Ignore)
18 |
19 | function Packer (props) {
20 | if (!(this instanceof Packer)) {
21 | return new Packer(props)
22 | }
23 |
24 | if (typeof props === "string") {
25 | props = { path: props }
26 | }
27 |
28 | props.ignoreFiles = [ ".jamignore",
29 | ".gitignore",
30 | "package.json" ]
31 |
32 | Ignore.call(this, props)
33 |
34 | this.bundled = props.bundled
35 | this.bundleLinks = props.bundleLinks
36 | this.package = props.package
37 | if (props.packageInfo && props.packageInfo.browser) {
38 | this.browserInclude = props.packageInfo.browser.include;
39 | }
40 | if (props.packageInfo && props.packageInfo.jam) {
41 | this.browserInclude = props.packageInfo.jam.include;
42 | }
43 |
44 | // in a node_modules folder, resolve symbolic links to
45 | // bundled dependencies when creating the package.
46 | props.follow = this.follow = this.basename === "node_modules"
47 | // console.error("follow?", this.path, props.follow)
48 |
49 | if (this === this.root ||
50 | this.parent &&
51 | this.parent.basename === "node_modules" &&
52 | this.basename.charAt(0) !== ".") {
53 | this.readBundledLinks()
54 | }
55 |
56 |
57 | this.on("entryStat", function (entry, props) {
58 | // files should *always* get into tarballs
59 | // in a user-writable state, even if they're
60 | // being installed from some wackey vm-mounted
61 | // read-only filesystem.
62 | entry.mode = props.mode = props.mode | 0200
63 | })
64 | }
65 |
66 | Packer.prototype.readBundledLinks = function () {
67 | if (this._paused) {
68 | this.once("resume", this.addIgnoreFiles)
69 | return
70 | }
71 |
72 | this.pause()
73 | fs.readdir(this.path + "/node_modules", function (er, list) {
74 | // no harm if there's no bundle
75 | var l = list && list.length
76 | if (er || l === 0) return this.resume()
77 |
78 | var errState = null
79 | , then = function then (er) {
80 | if (errState) return
81 | if (er) return errState = er, this.resume()
82 | if (-- l === 0) return this.resume()
83 | }.bind(this)
84 |
85 | list.forEach(function (pkg) {
86 | if (pkg.charAt(0) === ".") return then()
87 | var pd = this.path + "/node_modules/" + pkg
88 | fs.realpath(pd, function (er, rp) {
89 | if (er) return then()
90 | this.bundleLinks = this.bundleLinks || {}
91 | this.bundleLinks[pkg] = rp
92 | then()
93 | }.bind(this))
94 | }, this)
95 | }.bind(this))
96 | }
97 |
98 | Packer.prototype.applyIgnores = function (entry, partial, entryObj) {
99 | // package.json files can never be ignored.
100 | if (entry === "package.json") return true
101 |
102 | // if the package.json file has a jam.include property, *only* include
103 | // package.json and the files whitelisted in that property
104 | if (this.browserInclude) {
105 | for (var i = 0; i < this.browserInclude.length; i++) {
106 | if (minimatch(entry, this.browserInclude[i])) {
107 | return true;
108 | }
109 | // test for subpaths, eg jam.include = ['foo'], entry = 'foo/bar.js'
110 | if (utils.isSubPath(this.browserInclude[i], entry)) {
111 | return true;
112 | }
113 | }
114 | return !!(partial);
115 | }
116 |
117 | // special rules. see below.
118 | if (entry === "node_modules") return true
119 |
120 | // some files are *never* allowed under any circumstances
121 | if (entry === ".git" ||
122 | entry === ".lock-wscript" ||
123 | entry.match(/^\.wafpickle-[0-9]+$/) ||
124 | entry === "CVS" ||
125 | entry === ".svn" ||
126 | entry === ".hg" ||
127 | entry.match(/^\..*\.swp$/) ||
128 | entry.match(/^.*~$/) ||
129 | entry === ".DS_Store" ||
130 | entry.match(/^\._/) ||
131 | entry === "npm-debug.log"
132 | ) {
133 | return false
134 | }
135 |
136 | // in a node_modules folder, we only include bundled dependencies
137 | // also, prevent packages in node_modules from being affected
138 | // by rules set in the containing package, so that
139 | // bundles don't get busted.
140 | // Also, once in a bundle, everything is installed as-is
141 | // To prevent infinite cycles in the case of cyclic deps that are
142 | // linked with npm link, even in a bundle, deps are only bundled
143 | // if they're not already present at a higher level.
144 | if (this.basename === "node_modules") {
145 | // bubbling up. stop here and allow anything the bundled pkg allows
146 | if (entry.indexOf("/") !== -1) return true
147 |
148 | // never include the .bin. It's typically full of platform-specific
149 | // stuff like symlinks and .cmd files anyway.
150 | if (entry === ".bin") return false
151 |
152 | var shouldBundle = false
153 | // the package root.
154 | var p = this.parent
155 | // the package before this one.
156 | var pp = p && p.parent
157 |
158 | // if this entry has already been bundled, and is a symlink,
159 | // and it is the *same* symlink as this one, then exclude it.
160 | if (pp && pp.bundleLinks && this.bundleLinks &&
161 | pp.bundleLinks[entry] === this.bundleLinks[entry]) {
162 | return false
163 | }
164 |
165 | // since it's *not* a symbolic link, if we're *already* in a bundle,
166 | // then we should include everything.
167 | if (pp && pp.package) {
168 | return true
169 | }
170 |
171 | // only include it at this point if it's a bundleDependency
172 | var bd = this.package && this.package.bundleDependencies
173 | var shouldBundle = bd && bd.indexOf(entry) !== -1
174 | // if we're not going to bundle it, then it doesn't count as a bundleLink
175 | // if (this.bundleLinks && !shouldBundle) delete this.bundleLinks[entry]
176 | return shouldBundle
177 | }
178 | // if (this.bundled) return true
179 |
180 | return Ignore.prototype.applyIgnores.call(this, entry, partial, entryObj)
181 | }
182 |
183 | Packer.prototype.addIgnoreFiles = function () {
184 | var entries = this.entries
185 | // if there's a .jamignore, then we do *not* want to
186 | // read the .gitignore.
187 | if (-1 !== entries.indexOf(".jamignore")) {
188 | var i = entries.indexOf(".gitignore")
189 | if (i !== -1) {
190 | entries.splice(i, 1)
191 | }
192 | }
193 |
194 | this.entries = entries
195 |
196 | Ignore.prototype.addIgnoreFiles.call(this)
197 | }
198 |
199 |
200 | Packer.prototype.readRules = function (buf, e) {
201 | if (e !== "package.json") {
202 | return Ignore.prototype.readRules.call(this, buf, e)
203 | }
204 |
205 | buf = buf.toString().trim()
206 |
207 | if (buf.length === 0) return []
208 |
209 | try {
210 | var p = this.package = JSON.parse(buf)
211 | } catch (er) {
212 | er.file = path.resolve(this.path, e)
213 | this.error(er)
214 | return
215 | }
216 |
217 | if (this === this.root) {
218 | this.bundleLinks = this.bundleLinks || {}
219 | this.bundleLinks[p.name] = this._path
220 | }
221 |
222 | this.packageRoot = true
223 | this.emit("package", p)
224 |
225 | // make bundle deps predictable
226 | if (p.bundledDependencies && !p.bundleDependencies) {
227 | p.bundleDependencies = p.bundledDependencies
228 | delete p.bundledDependencies
229 | }
230 |
231 | if (!p.files || !Array.isArray(p.files)) return []
232 |
233 | // ignore everything except what's in the files array.
234 | return ["*"].concat(p.files.map(function (f) {
235 | return "!" + f
236 | })).concat(p.files.map(function (f) {
237 | return "!" + f.replace(/\/+$/, "") + "/**"
238 | }))
239 | }
240 |
241 | Packer.prototype.getChildProps = function (stat) {
242 | var props = Ignore.prototype.getChildProps.call(this, stat)
243 |
244 | props.package = this.package
245 |
246 | props.bundled = this.bundled && this.bundled.slice(0)
247 | props.bundleLinks = this.bundleLinks &&
248 | Object.create(this.bundleLinks)
249 |
250 | // Directories have to be read as Packers
251 | // otherwise fstream.Reader will create a DirReader instead.
252 | if (stat.isDirectory()) {
253 | props.type = this.constructor
254 | }
255 |
256 | // only follow symbolic links directly in the node_modules folder.
257 | props.follow = false
258 | return props
259 | }
260 |
261 |
262 | var order =
263 | [ "package.json"
264 | , ".jamignore"
265 | , ".gitignore"
266 | , /^README(\.md)?$/
267 | , "LICENCE"
268 | , "LICENSE"
269 | , /\.js$/ ]
270 |
271 | Packer.prototype.sort = function (a, b) {
272 | for (var i = 0, l = order.length; i < l; i ++) {
273 | var o = order[i]
274 | if (typeof o === "string") {
275 | if (a === o) return -1
276 | if (b === o) return 1
277 | } else {
278 | if (a.match(o)) return -1
279 | if (b.match(o)) return 1
280 | }
281 | }
282 |
283 | // deps go in the back
284 | if (a === "node_modules") return 1
285 | if (b === "node_modules") return -1
286 |
287 | return Ignore.prototype.sort.call(this, a, b)
288 | }
289 |
290 |
291 |
292 | Packer.prototype.emitEntry = function (entry) {
293 | if (this._paused) {
294 | this.once("resume", this.emitEntry.bind(this, entry))
295 | return
296 | }
297 |
298 | // if there is a .gitignore, then we're going to
299 | // rename it to .jammignore in the output.
300 | if (entry.basename === ".gitignore") {
301 | entry.basename = ".jamignore"
302 | entry.path = path.resolve(entry.dirname, entry.basename)
303 | }
304 |
305 | // all *.gyp files are renamed to binding.gyp for node-gyp
306 | // but only when they are in the same folder as a package.json file.
307 | if (entry.basename.match(/\.gyp$/) &&
308 | this.entries.indexOf("package.json") !== -1) {
309 | entry.basename = "binding.gyp"
310 | entry.path = path.resolve(entry.dirname, entry.basename)
311 | }
312 |
313 | // skip over symbolic links
314 | if (entry.type === "SymbolicLink") {
315 | entry.abort()
316 | return
317 | }
318 |
319 | if (entry.type !== "Directory") {
320 | // make it so that the folder in the tarball is named "package"
321 | var h = path.dirname((entry.root || entry).path)
322 | , t = entry.path.substr(h.length + 1).replace(/^[^\/\\]+/, "package")
323 | , p = h + "/" + t
324 |
325 | entry.path = p
326 | entry.dirname = path.dirname(p)
327 | return Ignore.prototype.emitEntry.call(this, entry)
328 | }
329 |
330 | // we don't want empty directories to show up in package
331 | // tarballs.
332 | // don't emit entry events for dirs, but still walk through
333 | // and read them. This means that we need to proxy up their
334 | // entry events so that those entries won't be missed, since
335 | // .pipe() doesn't do anythign special with "child" events, on
336 | // with "entry" events.
337 | var me = this
338 | entry.on("entry", function (e) {
339 | if (e.parent === entry) {
340 | e.parent = me
341 | me.emit("entry", e)
342 | }
343 | })
344 | entry.on("package", this.emit.bind(this, "package"))
345 | }
346 |
--------------------------------------------------------------------------------
/lib/project.js:
--------------------------------------------------------------------------------
1 | var utils = require('./utils'),
2 | path = require('path'),
3 | semver = require('semver'),
4 | async = require('async'),
5 | mkdirp = require('mkdirp'),
6 | ncp = require('ncp').ncp,
7 | fs = require('fs'),
8 | honey = require('json-honey'),
9 | _ = require('underscore');
10 |
11 |
12 | var pathExists = fs.exists || path.exists;
13 |
14 |
15 | exports.readPackageJSON = function (p, callback) {
16 | utils.readJSON(p, function (err, pkg) {
17 | if (err) {
18 | return callback(err, null, p);
19 | }
20 | try {
21 | exports.validate(pkg, p);
22 | }
23 | catch (e) {
24 | return callback(e);
25 | }
26 | return callback(null, pkg, p);
27 | });
28 | };
29 |
30 | exports.writePackageJSON = function (p, pkg, callback) {
31 | var str = honey(pkg);
32 | utils.writeJSON(p, str, callback);
33 | };
34 |
35 |
36 | /**
37 | * Looks for a project's package.json file. Walks up the directory tree until
38 | * it finds a package.json file or hits the root. Does not throw when no
39 | * packages.json is found, just returns null.
40 | *
41 | * @param {String} p - The starting path to search upwards from
42 | * @param {boolean} [r] - Recursive search
43 | * @param {Function} callback - callback(err, path)
44 | */
45 |
46 | exports.findPackageJSON = function (p, r, callback) {
47 | var filename;
48 |
49 | if (_.isFunction(r)) {
50 | callback = r;
51 | r = false;
52 | }
53 |
54 | filename = path.resolve(p, 'package.json');
55 |
56 | pathExists(filename, function (exists) {
57 | var newpath;
58 |
59 | if (exists) {
60 | return callback(null, filename);
61 | }
62 |
63 | if (!r) {
64 | return callback(null, null);
65 | }
66 |
67 | newpath = path.dirname(p);
68 |
69 | if (newpath === p) { // root directory
70 | return callback(null, null);
71 | }
72 |
73 | exports.findPackageJSON(newpath, r, callback);
74 | });
75 | };
76 |
77 | /**
78 | * Searches for package.json and returns an object with it's contents,
79 | * returns a null if no file found. Final
80 | * argument of the callback is the matching file path for package.json,
81 | * or null if none were found.
82 | *
83 | * @param {String} cwd - directory to search upwards from for package.json
84 | * @param {boolean} [r] - Recursive search
85 | * @param {Function} callback(err, package_obj, path)
86 | */
87 |
88 | exports.loadPackageJSON = async.memoize(function (cwd, r, callback) {
89 | if (_.isFunction(r)) {
90 | callback = r;
91 | r = false;
92 | }
93 |
94 | exports.findPackageJSON(cwd, r, function (err, p) {
95 | if (err) {
96 | return callback(err, null, p);
97 | }
98 |
99 | if (!p) {
100 | return callback(null, null, p);
101 | }
102 |
103 | exports.readPackageJSON(p, callback);
104 | });
105 | });
106 |
107 |
108 | exports.updatePackageJSON = function(cwd, r, dependencies, callback) {
109 | if (_.isObject(r)) {
110 | callback = dependencies;
111 | dependencies = r;
112 | r = false;
113 | }
114 |
115 | exports.findPackageJSON(cwd, r, function (err, p) {
116 | if (err) {
117 | return callback(err);
118 | }
119 |
120 | if (!p) {
121 | return callback("could not find package.json in '" + cwd + "'");
122 | }
123 |
124 | logger.info('updating', p);
125 |
126 | exports.readPackageJSON(p, function(err, pkg, path) {
127 | if (err) {
128 | return callback(err);
129 | }
130 |
131 | pkg.jam = pkg.jam || { dependencies: {} };
132 |
133 | _.extend(pkg.jam.dependencies, dependencies);
134 |
135 | exports.writePackageJSON(p, pkg, callback);
136 | });
137 | });
138 | };
139 |
140 | exports.validate = function (settings, filename) {
141 | // nothing to validate yet
142 | };
143 |
144 | exports.DEFAULT = {
145 | jam: {
146 | dependencies: {}
147 | }
148 | };
149 |
150 | /*
151 | exports.createMeta = function (callback) {
152 | utils.getJamVersion(function (err, version) {
153 | if (err) {
154 | return callback(err);
155 | }
156 | callback(null, {
157 | jam_version: version,
158 | dependencies: {}
159 | });
160 | });
161 | };
162 |
163 | exports.writeMeta = function (package_dir, data, callback) {
164 | // TODO: add _rev field to meta file and check if changed since last read
165 | // before writing
166 | var filename = path.resolve(package_dir, 'jam.json');
167 | try {
168 | var str = JSON.stringify(data, null, 4);
169 | }
170 | catch (e) {
171 | return callback(e);
172 | }
173 | mkdirp(package_dir, function (err) {
174 | if (err) {
175 | return callback(err);
176 | }
177 | logger.info('updating', path.relative(process.cwd(), filename));
178 | fs.writeFile(filename, str, function (err) {
179 | // TODO: after adding _rev field, return updated _rev value in data here
180 | return callback(err, data);
181 | });
182 | });
183 | };
184 | */
185 |
186 | // adds RequireJS to project directory
187 | exports.makeRequireJS = function (package_dir, config, callback) {
188 | var require_path = require.resolve('requirejs');
189 | var source = path.join(require_path, '../../require.js')
190 | var dest = path.resolve(package_dir, 'require.js');
191 |
192 | logger.info('updating', path.relative(process.cwd(), dest));
193 |
194 | fs.readFile(source, function (err, content) {
195 | if (err) {
196 | return callback(err);
197 | }
198 | var src = content.toString() + '\n' + config;
199 | fs.writeFile(dest, src, callback);
200 | });
201 | };
202 |
203 | exports.getAllPackages = function (dir, callback) {
204 | utils.listDirs(dir, function (err, dirs) {
205 | if (err) {
206 | return callback(err);
207 | }
208 | async.map(dirs, function (d, cb) {
209 | var filename = path.resolve(d, 'package.json');
210 | exports.readPackageJSON(filename, function (err, cfg, p) {
211 | cb(err, err ? null: {cfg: cfg, dir: path.relative(dir, d)});
212 | });
213 | }, callback);
214 | });
215 | };
216 |
217 | exports.updateRequireConfig = function (package_dir, baseurl, /*opt*/rcfg, callback) {
218 | rcfg = rcfg || {};
219 |
220 | if (!callback) {
221 | callback = rcfg;
222 | rcfg = {};
223 | }
224 |
225 | var packages = [];
226 | var shims = {};
227 |
228 | var basedir = baseurl ? path.relative(baseurl, package_dir): package_dir;
229 | var dir = basedir.split(path.sep).map(encodeURIComponent).join('/');
230 |
231 | exports.getAllPackages(package_dir, function (err, pkgs) {
232 | if (err) {
233 | return callback(err);
234 | }
235 |
236 | pkgs.forEach(function (pkg) {
237 | var cfg = pkg.cfg || {},
238 | val;
239 |
240 | cfg.jam = cfg.jam || {};
241 |
242 | val = {
243 | name: cfg.name,
244 | location: path.join(dir, encodeURIComponent(pkg.dir), cfg.jam.baseUrl || '').replace(/\/+$/, '')
245 | };
246 | var main = cfg.main;
247 | if (cfg.browser && cfg.browser.main) {
248 | main = cfg.browser.main;
249 | }
250 | if (cfg.jam && cfg.jam.main) {
251 | main = cfg.jam.main;
252 | }
253 | if (main) {
254 | val.main = main;
255 | }
256 | if (cfg.jam && cfg.jam.name) {
257 | val.name = cfg.jam.name;
258 | }
259 | packages.push(val);
260 | if (cfg.shim) {
261 | shims[cfg.name] = cfg.shim;
262 | }
263 | if (cfg.browser && cfg.browser.shim) {
264 | shims[cfg.name] = cfg.browser.shim;
265 | }
266 | if (cfg.jam && cfg.jam.shim) {
267 | shims[cfg.name] = cfg.jam.shim;
268 | }
269 | if (cfg.jam && cfg.jam.extra_packages) {
270 | var extra_packages = Object.keys(cfg.jam.extra_packages);
271 | extra_packages.forEach(function(extra_pkg){
272 | var extra_val = {
273 | name: extra_pkg,
274 | location: dir + '/' + encodeURIComponent(pkg.dir),
275 | main: cfg.jam.extra_packages[extra_pkg].main,
276 | local: true
277 | }
278 | packages.push(extra_val);
279 | if (extra_pkg.shim) {
280 | shims[extra_pkg.name] = extra_pkg.name;
281 | }
282 | });
283 | }
284 | });
285 |
286 | utils.getJamVersion(function (err, version) {
287 | if (err) {
288 | return callback(err);
289 | }
290 |
291 | var data = {
292 | // TODO: useful option for cache-busting
293 | //urlArgs: '_jam_build=' + (new Date().getTime()),
294 | packages: packages,
295 | version: version,
296 | shim: shims
297 | };
298 |
299 | // now bring in other require.config.js options to make available
300 | // earlier versions had variable substitution that breaks on r.js compilation
301 | // now there is duplication - however, the original jam has been left untouched.
302 | var cfg = _.clone(rcfg);
303 | cfg.packages = _.union(rcfg.packages || [], packages);
304 | cfg.shim = _.extend({}, rcfg.shim || {}, shims);
305 | var configStr = JSON.stringify(cfg, null, 4);
306 | var dataStr = JSON.stringify(data, null, 4);
307 | var src = fs.readFileSync(path.resolve(__dirname, './tmpls/require.config.js')).toString();
308 |
309 | src = src.replace(/"\{data\}"/g, dataStr);
310 |
311 | var filename = path.resolve(package_dir, 'require.config.js');
312 | mkdirp(package_dir, function (err) {
313 | if (err) {
314 | return callback(err);
315 | }
316 | logger.info('updating', path.relative(process.cwd(), filename));
317 | async.parallel([
318 | async.apply(fs.writeFile, filename, src),
319 | async.apply(exports.makeRequireJS, package_dir, src)
320 | ], callback);
321 | });
322 | });
323 | });
324 | };
325 |
326 | exports.getJamDependencies = function (cfg, name) {
327 | var deps;
328 |
329 | if (cfg.jam && cfg.jam.dependencies) {
330 | deps = cfg.jam.dependencies;
331 | } else {
332 | deps = {};
333 | }
334 |
335 | if (name) {
336 | return deps[name] || null;
337 | }
338 |
339 | return deps;
340 | };
341 |
342 | exports.setJamDependencies = function (cfg, deps) {
343 | if (!cfg.jam) {
344 | cfg.jam = {};
345 | }
346 | cfg.jam.dependencies = deps;
347 | return cfg;
348 | };
349 |
350 | exports.addJamDependency = function (cfg, name, range) {
351 | var deps = exports.getJamDependencies(cfg);
352 | deps[name] = range;
353 | return exports.setJamDependencies(cfg, deps);
354 | };
355 |
356 | exports.removeJamDependency = function (cfg, name) {
357 | var deps = exports.getJamDependencies(cfg);
358 | delete deps[name];
359 | return exports.setJamDependencies(cfg, deps);
360 | };
361 |
--------------------------------------------------------------------------------