Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

find location of packages outside of node_modules, support packages without package.json #576

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/build/bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ exports.Bundle = class {
}

getDependencyLocations() {
return this.includes.filter(inclusion => inclusion.description)
return this.includes.filter(inclusion => inclusion.description && inclusion.description.location)
.map(inclusion => {
let normalizedLocation = path.posix.normalize(inclusion.description.location).replace(/\\/g, '\/');
return {
Expand Down
51 changes: 40 additions & 11 deletions lib/build/package-analyzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,23 @@ exports.PackageAnalyzer = class {
description.source = 'custom';
description.loaderConfig = loaderConfig;

return Promise.resolve(description);
return loadPackageMetadata(this.project, description).then(() => description);
}
};

function loadPackageMetadata(project, description) {
return setLocation(project, description)
.then(() => fs.readFile(description.metadataLocation))
.then(data => {
description.metadata = JSON.parse(data.toString());
.then(() => {
if (description.metadataLocation) {
return fs.readFile(description.metadataLocation)
.then(data => {
description.metadata = JSON.parse(data.toString());
});
}
})
.catch(e => {
console.log(`Unable to load package metadata (package.json) of ${description.name}:`);
console.log(e);
});
}

Expand All @@ -45,16 +53,20 @@ function determineLoaderConfig(project, description) {
let location = path.resolve(description.location);
let sourcePath;

if (metadata.jspm) {
let jspm = metadata.jspm;
if (metadata) {
if (metadata.jspm) {
let jspm = metadata.jspm;

if (jspm.directories && jspm.directories.dist) {
sourcePath = path.join(location, jspm.directories.dist, jspm.main);
if (jspm.directories && jspm.directories.dist) {
sourcePath = path.join(location, jspm.directories.dist, jspm.main);
} else {
sourcePath = path.join(location, metadata.main);
}
} else {
sourcePath = path.join(location, metadata.main);
}
} else {
sourcePath = path.join(location, metadata.main);
sourcePath = path.join(location, 'index');
}

sourcePath = path.relative(path.resolve(project.paths.root), sourcePath);
Expand All @@ -71,13 +83,30 @@ function setLocation(project, description) {
return getPackageFolder(project, description)
.then(packageFolder => {
description.location = packageFolder;
description.metadataLocation = path.join(description.location, 'package.json');

return tryFindMetadata(project, description);
});
case 'custom':
if (!description.loaderConfig || !description.loaderConfig.packageRoot) {
return Promise.reject(`Error: packageRoot property not defined for the "${description.name}" package. It is recommended to ` +
'define the packageRoot for packages outside the node_modules folder, so that the files end up in' +
'the correct bundle');
}

description.location = path.resolve(project.paths.root, description.loaderConfig.packageRoot);

return tryFindMetadata(project, description);
default:
throw new Error(`The package source "${description.source}" is not supported.`);
return Promise.reject(`The package source "${description.source}" is not supported.`);
}
}

function tryFindMetadata(project, description) {
return fs.stat(path.join(description.location, 'package.json'))
.then(() => description.metadataLocation = path.join(description.location, 'package.json'))
.catch(() => {});
}

function getPackageFolder(project, description) {
if (!description.loaderConfig || !description.loaderConfig.path) {
return lookupPackageFolderDefaultStrategy(project.paths.root)
Expand Down
174 changes: 174 additions & 0 deletions spec/lib/build/package-analyzer.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
'use strict';

const path = require('path');
const PackageAnalyzer = require('../../../lib/build/package-analyzer').PackageAnalyzer;

describe('The PackageAnalyzer', () => {
let mockfs;
let project;
let sut;

beforeEach(() => {
mockfs = require('mock-fs');

project = {
paths: {
root: './src/'
}
};

sut = new PackageAnalyzer(project);

const fsConfig = {};
mockfs(fsConfig);
});

afterEach(() => {
mockfs.restore();
});

it('sets source to npm when node_modules is found in the path', done => {
// setup mock package.json
const fsConfig = {};
fsConfig[path.join('node_modules/my-package', 'package.json')] = '{ }';
mockfs(fsConfig);

let loaderConfig = {
name: 'my-package',
path: '../node_modules/my-package'
};

sut.reverseEngineer(loaderConfig)
.then(description => {
expect(description.source).toBe('npm');
expect(description.loaderConfig).toBe(loaderConfig);
done();
})
.catch(e => done.fail(e));
});

it('sets source to custom when node_modules is not found in the path', done => {
// setup mock package.json
const fsConfig = {};
fsConfig[path.join('some-folder/my-package', 'package.json')] = '{ }';
mockfs(fsConfig);

let loaderConfig = {
name: 'my-package',
path: '../some-folder/my-package',
packageRoot: '../some-folder/my-package'
};

sut.reverseEngineer(loaderConfig)
.then(description => {
expect(description.source).toBe('custom');
expect(description.loaderConfig).toBe(loaderConfig);
done();
})
.catch(e => done.fail(e));
});

it('creates description when there is no package.json', done => {
const fsConfig = {};
mockfs(fsConfig);

let loaderConfig = {
name: 'my-package',
path: '../some-folder/my-package',
packageRoot: '../some-folder/my-package'
};

sut.reverseEngineer(loaderConfig)
.then(description => {
expect(description.source).toBe('custom');
expect(description.loaderConfig).toBe(loaderConfig);
done();
})
.catch(e => done.fail(e));
});

it('reads package.json as package metadata', done => {
// setup mock package.json
const fsConfig = {};
fsConfig[path.join('some-folder/my-package', 'package.json')] = '{ "name": "my-package" }';
mockfs(fsConfig);

let loaderConfig = {
name: 'my-package',
path: '../some-folder/my-package',
packageRoot: '../some-folder/my-package'
};

sut.reverseEngineer(loaderConfig)
.then(description => {
expect(description.metadata.name).toBe('my-package');
done();
})
.catch(e => done.fail(e));
});

it('analyze() reads package.json as package metadata', done => {
// setup mock package.json
// setup mock package.json
const fsConfig = {};
fsConfig[path.join('node_modules/my-package', 'package.json')] = '{ "name": "my-package", "main": "index.js" }';
fsConfig[project.paths.root] = {};
mockfs(fsConfig);

sut.analyze('my-package')
.then(description => {
expect(description.metadata.name).toBe('my-package');
done();
})
.catch(e => done.fail(e));
});

it('analyze() determines loaderConfig', done => {
// setup mock package.json
const fsConfig = {};
fsConfig[path.join('node_modules/my-package', 'package.json')] = '{ "name": "my-package", "main": "index.js" }';
fsConfig[project.paths.root] = {};
mockfs(fsConfig);

sut.analyze('my-package')
.then(description => {
expect(description.loaderConfig.name).toBe('my-package');
expect(description.loaderConfig.path).toBe('..\\node_modules\\my-package\\index');
done();
})
.catch(e => done.fail(e));
});

it('analyze() uses jspm.directories.dist and jspm.main path if available', done => {
// setup mock package.json
const fsConfig = {};
let json = '{ "name": "my-package", "main": "index.js", "jspm": { "directories": { "dist": "foobar" }, "main": "my-main.js" } }';
fsConfig[path.join('node_modules/my-package', 'package.json')] = json;
fsConfig[project.paths.root] = {};
mockfs(fsConfig);

sut.analyze('my-package')
.then(description => {
expect(description.loaderConfig.name).toBe('my-package');
expect(description.loaderConfig.path).toBe('..\\node_modules\\my-package\\foobar\\my-main');
done();
})
.catch(e => done.fail(e));
});

it('analyze() works when there is no package.json. Uses index.js as the main file', done => {
// setup mock package.json
const fsConfig = {};
fsConfig[path.join('node_modules/my-package')] = {};
fsConfig[project.paths.root] = {};
mockfs(fsConfig);

sut.analyze('my-package')
.then(description => {
expect(description.loaderConfig.name).toBe('my-package');
expect(description.loaderConfig.path).toBe('..\\node_modules\\my-package\\index');
done();
})
.catch(e => done.fail(e));
});
});