Skip to content

Commit 07d2769

Browse files
Improve the resolution of relative paths
- resolve module relative path for component - prioritize coffeescript over json
1 parent d7c67c8 commit 07d2769

2 files changed

Lines changed: 345 additions & 67 deletions

File tree

lib/compiler.js

Lines changed: 85 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ var debug = require('debug')('loopback:boot:compiler');
88
var Module = require('module');
99
var _ = require('lodash');
1010

11+
var FILE_EXTENSION_JSON = '.json';
12+
1113
/**
1214
* Gather all bootstrap-related configuration data and compile it into
1315
* a single object containing instruction for `boot.execute`.
@@ -160,12 +162,11 @@ function findScripts(dir) {
160162
}
161163

162164
var filepath = path.resolve(path.join(dir, filename));
163-
var ext = path.extname(filename);
164165
var stats = fs.statSync(filepath);
165166

166167
// only require files supported by require.extensions (.txt .md etc.)
167168
if (stats.isFile()) {
168-
if (ext in require.extensions)
169+
if (isPreferredExtension(filename))
169170
results.push(filepath);
170171
else
171172
debug('Skipping file %s - unknown extension', filepath);
@@ -299,8 +300,8 @@ function findModelDefinitions(rootDir, sources) {
299300
return registry;
300301
}
301302

302-
function resolveAppPath(rootDir, relativePath) {
303-
var resolvedPath = tryResolveAppPath(rootDir, relativePath);
303+
function resolveAppPath(rootDir, relativePath, resolveOptions) {
304+
var resolvedPath = tryResolveAppPath(rootDir, relativePath, resolveOptions);
304305
if (resolvedPath === undefined) {
305306
var err = new Error('Cannot resolve path "' + relativePath + '"');
306307
err.code = 'PATH_NOT_FOUND';
@@ -309,12 +310,27 @@ function resolveAppPath(rootDir, relativePath) {
309310
return resolvedPath;
310311
}
311312

312-
function tryResolveAppPath(rootDir, relativePath) {
313-
var fullPath = path.resolve(rootDir, relativePath);
314-
if (fs.existsSync(fullPath))
313+
function tryResolveAppPath(rootDir, relativePath, resolveOptions) {
314+
var fullPath;
315+
var start = relativePath.substring(0, 2);
316+
317+
/* In order to retain backward compatibility, while resolving
318+
* component path, `resolveOptions` parameter is added where
319+
* `resolveOptions.strict` = true,
320+
* means retain backward compatibility when resolving the path and
321+
* `resolveOptions.strict` = false,
322+
* does not enforce any such restriction when resolving the path */
323+
resolveOptions = resolveOptions || { strict: false };
324+
325+
if (relativePath[0] === '/') {
326+
fullPath = relativePath;
327+
} else if (start === './' || start === '..' || !resolveOptions.strict) {
328+
fullPath = path.resolve(rootDir, relativePath);
329+
}
330+
331+
if (fullPath && fs.existsSync(fullPath))
315332
return fullPath;
316333

317-
var start = relativePath.substring(0, 2);
318334
if (start !== './' && start !== '..') {
319335
// Handle module-relative path, e.g. `loopback/common/models`
320336

@@ -339,6 +355,7 @@ function tryResolveAppPath(rootDir, relativePath) {
339355
return fs.existsSync(candidate);
340356
})
341357
[0];
358+
342359
if (fullPath)
343360
return fullPath;
344361
} else {
@@ -356,27 +373,12 @@ function tryResolveAppPath(rootDir, relativePath) {
356373

357374
function loadModelDefinition(rootDir, jsonFile, allFiles) {
358375
var definition = require(jsonFile);
359-
var basename = path.basename(jsonFile, path.extname(jsonFile));
360376

361377
// find a matching file with a supported extension like `.js` or `.coffee`
362-
var base;
363-
var ext;
364-
var validFileType;
365-
var sourceFile = allFiles
366-
.filter(function(f) {
367-
ext = path.extname(f);
368-
base = path.basename(f, ext);
369-
validFileType = (ext !== '.node') && (ext !== '.json') &&
370-
((typeof require.extensions[ext]) === 'function');
371-
return validFileType && (base === basename);
372-
})[0];
378+
var sourceFile = fixFileExtension(jsonFile, allFiles, true);
373379

374-
try {
375-
sourceFile = path.join(path.dirname(jsonFile), sourceFile);
376-
sourceFile = require.resolve(sourceFile);
377-
} catch (err) {
378-
debug('Model source code not found: %s - %s', sourceFile, err.code || err);
379-
sourceFile = undefined;
380+
if (sourceFile === undefined) {
381+
debug('Model source code not found: %s', sourceFile);
380382
}
381383

382384
debug('Found model "%s" - %s %s', definition.name,
@@ -456,7 +458,7 @@ function resolveMiddlewarePath(rootDir, middleware) {
456458
}
457459

458460
if (!fragment) {
459-
resolved.sourceFile = resolveAppPath(rootDir, middlewarePath);
461+
resolved.sourceFile = resolveAppScriptPath(rootDir, middlewarePath);
460462
return resolved;
461463
}
462464

@@ -466,7 +468,7 @@ function resolveMiddlewarePath(rootDir, middleware) {
466468
// function
467469
var m = require(pathName);
468470
if (typeof m[fragment] === 'function') {
469-
resolved.sourceFile = resolveAppPath(rootDir, middlewarePath);
471+
resolved.sourceFile = resolveAppScriptPath(rootDir, middlewarePath);
470472
return resolved;
471473
}
472474

@@ -484,7 +486,7 @@ function resolveMiddlewarePath(rootDir, middleware) {
484486

485487
for (var ix in candidates) {
486488
try {
487-
resolved.sourceFile = resolveAppPath(rootDir, candidates[ix]);
489+
resolved.sourceFile = resolveAppScriptPath(rootDir, candidates[ix]);
488490
delete resolved.fragment;
489491
return resolved;
490492
}
@@ -513,16 +515,8 @@ function buildComponentInstructions(rootDir, componentConfig) {
513515
return Object.keys(componentConfig)
514516
.filter(function(name) { return !!componentConfig[name]; })
515517
.map(function(name) {
516-
var sourceFile;
517-
if (name.indexOf('./') === 0 || name.indexOf('../') === 0) {
518-
// Relative path
519-
sourceFile = path.resolve(rootDir, name);
520-
} else {
521-
sourceFile = require.resolve(name);
522-
}
523-
524518
return {
525-
sourceFile: sourceFile,
519+
sourceFile: resolveAppScriptPath(rootDir, name, {'strict': true}),
526520
config: componentConfig[name]
527521
};
528522
});
@@ -538,3 +532,56 @@ function resolveRelativePaths(relativePaths, appRootDir) {
538532
}
539533
});
540534
}
535+
536+
function getExcludedExtensions() {
537+
return {
538+
'.json': '.json',
539+
'.node': 'node'
540+
};
541+
}
542+
543+
function isPreferredExtension (filename) {
544+
var includeExtensions = require.extensions;
545+
546+
var ext = path.extname(filename);
547+
return (ext in includeExtensions) && !(ext in getExcludedExtensions());
548+
}
549+
550+
function fixFileExtension (filepath, files, onlyScriptsExportingFunction) {
551+
var results = [];
552+
var otherFile;
553+
554+
/* Prefer coffee scripts over json */
555+
if (isPreferredExtension(filepath)) return filepath;
556+
557+
var basename = path.basename(filepath, FILE_EXTENSION_JSON);
558+
var sourceDir = path.dirname(filepath);
559+
560+
files.forEach(function(f) {
561+
otherFile = path.resolve(sourceDir, f);
562+
563+
var stats = fs.statSync(otherFile);
564+
if (stats.isFile()) {
565+
var otherFileExtension = path.extname(f);
566+
567+
if (!(otherFileExtension in getExcludedExtensions()) &&
568+
path.basename(f, otherFileExtension) == basename) {
569+
if (!onlyScriptsExportingFunction)
570+
results.push(otherFile);
571+
else if (onlyScriptsExportingFunction &&
572+
(typeof require.extensions[otherFileExtension]) === 'function') {
573+
results.push(otherFile);
574+
}
575+
}
576+
}
577+
});
578+
return (results.length > 0 ? results[0] : undefined);
579+
}
580+
581+
function resolveAppScriptPath (rootDir, relativePath, resolveOptions) {
582+
var resolvedPath = resolveAppPath (rootDir, relativePath, resolveOptions);
583+
var sourceDir = path.dirname(resolvedPath);
584+
var files = tryReadDir(sourceDir);
585+
var fixedFile = fixFileExtension (resolvedPath, files, false);
586+
return (fixedFile === undefined ? resolvedPath : fixedFile);
587+
}

0 commit comments

Comments
 (0)