mirror of
https://github.com/bower/bower.git
synced 2026-01-14 16:57:57 -05:00
325 lines
8.1 KiB
JavaScript
325 lines
8.1 KiB
JavaScript
var fs = require('graceful-fs');
|
|
var path = require('path');
|
|
var deepExtend = require('deep-extend');
|
|
var isAsset = require('./util/isAsset');
|
|
var isComponent = require('./util/isComponent');
|
|
var createError = require('./util/createError');
|
|
|
|
var possibleJsons = ['bower.json', 'component.json', '.bower.json'];
|
|
|
|
function read(file, options, callback) {
|
|
if (typeof options === 'function') {
|
|
callback = options;
|
|
options = {};
|
|
}
|
|
|
|
// Check if file is a directory
|
|
fs.stat(file, function(err, stat) {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
|
|
// It's a directory, so we find the json inside it
|
|
if (stat.isDirectory()) {
|
|
return find(file, function(err, file) {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
|
|
read(file, options, callback);
|
|
});
|
|
}
|
|
|
|
// Otherwise read it
|
|
fs.readFile(file, function(err, contents) {
|
|
var json;
|
|
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
|
|
try {
|
|
json = JSON.parse(contents.toString());
|
|
} catch (err) {
|
|
err.file = path.resolve(file);
|
|
err.code = 'EMALFORMED';
|
|
return callback(err);
|
|
}
|
|
|
|
// Parse it
|
|
try {
|
|
json = parse(json, options);
|
|
} catch (err) {
|
|
err.file = path.resolve(file);
|
|
return callback(err);
|
|
}
|
|
|
|
callback(null, json, file);
|
|
});
|
|
});
|
|
}
|
|
|
|
function readSync(file, options) {
|
|
var stat;
|
|
var filename;
|
|
var contents;
|
|
var json;
|
|
|
|
if (!options) {
|
|
options = {};
|
|
}
|
|
try {
|
|
stat = fs.statSync(file);
|
|
} catch (err) {
|
|
return err;
|
|
}
|
|
if (stat.isDirectory()) {
|
|
filename = findSync(file);
|
|
return readSync(filename);
|
|
}
|
|
|
|
contents = fs.readFileSync(file);
|
|
|
|
try {
|
|
json = JSON.parse(contents.toString());
|
|
} catch (err) {
|
|
err.file = path.resolve(file);
|
|
err.code = 'EMALFORMED';
|
|
return err;
|
|
}
|
|
|
|
try {
|
|
json = parse(json, options);
|
|
} catch (err) {
|
|
err.file = path.resolve(file);
|
|
return err;
|
|
}
|
|
|
|
return json;
|
|
}
|
|
|
|
function parse(json, options) {
|
|
options = deepExtend(
|
|
{
|
|
normalize: false,
|
|
validate: true,
|
|
clone: false
|
|
},
|
|
options || {}
|
|
);
|
|
|
|
// Clone
|
|
if (options.clone) {
|
|
json = deepExtend({}, json);
|
|
}
|
|
|
|
// Validate
|
|
if (options.validate) {
|
|
validate(json);
|
|
}
|
|
|
|
// Normalize
|
|
if (options.normalize) {
|
|
normalize(json);
|
|
}
|
|
|
|
return json;
|
|
}
|
|
|
|
// This function implements:
|
|
//
|
|
// https://github.com/bower/bower.json-spec
|
|
function getIssues(json) {
|
|
// For things that shouldn't happen
|
|
var errors = [];
|
|
|
|
// For things that happen but they shoudn't
|
|
var warnings = [];
|
|
|
|
if (!json.name) {
|
|
errors.push('No "name" property set');
|
|
} else {
|
|
if (!/^[a-zA-Z0-9_@][a-zA-Z0-9_@\.\- \/]*$/.test(json.name)) {
|
|
errors.push(
|
|
'Name must be lowercase, can contain digits, dots, dashes, "@" or spaces'
|
|
);
|
|
}
|
|
|
|
if (json.name.length > 50) {
|
|
warnings.push('The "name" is too long, the limit is 50 characters');
|
|
}
|
|
|
|
if (!/^[a-z0-9_][a-z0-9_\.\-]*$/.test(json.name)) {
|
|
warnings.push(
|
|
'The "name" is recommended to be lowercase, can contain digits, dots, dashes'
|
|
);
|
|
}
|
|
|
|
if (/^[\.-]/.test(json.name)) {
|
|
warnings.push('The "name" cannot start with dot or dash');
|
|
}
|
|
|
|
if (/[\.-]$/.test(json.name)) {
|
|
warnings.push('The "name" cannot end with dot or dash');
|
|
}
|
|
}
|
|
|
|
if (json.description && json.description.length > 140) {
|
|
warnings.push(
|
|
'The "description" is too long, the limit is 140 characters'
|
|
);
|
|
}
|
|
|
|
if (json.main !== undefined) {
|
|
var main = json.main;
|
|
if (typeof main === 'string') {
|
|
main = [main];
|
|
}
|
|
if (!(main instanceof Array)) {
|
|
errors.push(
|
|
'The "main" field has to be either an Array or a String'
|
|
);
|
|
} else {
|
|
var ext2files = {};
|
|
main.forEach(function(filename) {
|
|
if (typeof filename !== 'string') {
|
|
errors.push('The "main" Array has to contain only Strings');
|
|
}
|
|
if (/[*]/.test(filename)) {
|
|
warnings.push(
|
|
'The "main" field cannot contain globs (example: "*.js")'
|
|
);
|
|
}
|
|
if (/[.]min[.][^/]+$/.test(filename)) {
|
|
warnings.push(
|
|
'The "main" field cannot contain minified files'
|
|
);
|
|
}
|
|
if (isAsset(filename)) {
|
|
warnings.push(
|
|
'The "main" field cannot contain font, image, audio, or video files'
|
|
);
|
|
}
|
|
var ext = path.extname(filename);
|
|
if (ext.length >= 2) {
|
|
var files = ext2files[ext];
|
|
if (!files) {
|
|
files = ext2files[ext] = [];
|
|
}
|
|
files.push(filename);
|
|
}
|
|
});
|
|
Object.keys(ext2files).forEach(function(ext) {
|
|
var files = ext2files[ext];
|
|
if (files.length > 1) {
|
|
warnings.push(
|
|
'The "main" field has to contain only 1 file per filetype; found multiple ' +
|
|
ext +
|
|
' files: ' +
|
|
JSON.stringify(files)
|
|
);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
return {
|
|
errors: errors,
|
|
warnings: warnings
|
|
};
|
|
}
|
|
|
|
// For backward compatibility, it throws first error
|
|
function validate(json) {
|
|
var issues = getIssues(json);
|
|
|
|
if (issues.errors && issues.errors.length > 0) {
|
|
throw createError(issues.errors[0], 'EINVALID');
|
|
}
|
|
}
|
|
|
|
function normalize(json) {
|
|
if (typeof json.main === 'string') {
|
|
json.main = [json.main];
|
|
}
|
|
|
|
return json;
|
|
}
|
|
|
|
function find(folder, files, callback) {
|
|
var err;
|
|
var file;
|
|
|
|
if (typeof files === 'function') {
|
|
callback = files;
|
|
files = possibleJsons;
|
|
}
|
|
|
|
if (!files.length) {
|
|
err = createError(
|
|
'None of ' + possibleJsons.join(', ') + ' were found in ' + folder,
|
|
'ENOENT'
|
|
);
|
|
return callback(err);
|
|
}
|
|
|
|
file = path.resolve(path.join(folder, files[0]));
|
|
fs.exists(file, function(exists) {
|
|
if (!exists) {
|
|
return find(folder, files.slice(1), callback);
|
|
}
|
|
|
|
if (files[0] !== 'component.json') {
|
|
return callback(null, file);
|
|
}
|
|
|
|
// If the file is component.json, check it it's a component(1) file
|
|
// If it is, we ignore it and keep searching
|
|
isComponent(file, function(is) {
|
|
if (is) {
|
|
return find(folder, files.slice(1), callback);
|
|
}
|
|
|
|
callback(null, file);
|
|
});
|
|
});
|
|
}
|
|
|
|
function findSync(folder, files) {
|
|
var file;
|
|
var exists;
|
|
|
|
if (files === undefined) {
|
|
files = possibleJsons;
|
|
}
|
|
|
|
if (!files.length) {
|
|
return createError(
|
|
'None of ' + possibleJsons.join(', ') + ' were found in ' + folder,
|
|
'ENOENT'
|
|
);
|
|
}
|
|
|
|
file = path.resolve(path.join(folder, files[0]));
|
|
try {
|
|
exists = fs.statSync(file);
|
|
} catch (err) {
|
|
exists = false;
|
|
}
|
|
if (exists && exists.isFile()) {
|
|
return file;
|
|
} else {
|
|
return findSync(folder, files.slice(1));
|
|
}
|
|
}
|
|
|
|
module.exports = read;
|
|
module.exports.read = read;
|
|
module.exports.readSync = readSync;
|
|
module.exports.parse = parse;
|
|
module.exports.getIssues = getIssues;
|
|
module.exports.validate = validate;
|
|
module.exports.normalize = normalize;
|
|
module.exports.find = find;
|
|
module.exports.findSync = findSync;
|