package/.eslintignore000644 0000000022 3560116604 011755 0ustar00000000 000000 *.js node_modules package/.eslintrc.js000644 0000001266 3560116604 011524 0ustar00000000 000000 module.exports = { root: true, parserOptions: { ecmaVersion: 2020, sourceType: 'module', }, plugins: ['node', 'prettier', '@typescript-eslint'], parser: '@typescript-eslint/parser', extends: ['eslint:recommended', 'plugin:node/recommended', 'plugin:prettier/recommended'], env: { node: true, }, rules: { 'node/shebang': 'off', }, overrides: [ { env: { mocha: true }, files: '**/*-test.ts', plugins: ['mocha'], extends: ['plugin:mocha/recommended'], rules: { 'node/no-unpublished-require': 'off', 'mocha/no-setup-in-describe': 'off', 'mocha/no-hooks-for-single-case': 'off', }, }, ], }; package/.prettierrc.js000644 0000000203 3560116604 012052 0ustar00000000 000000 module.exports = { singleQuote: true, trailingComma: 'all', arrowParens: 'avoid', printWidth: 100, endOfLine: 'auto', }; package/lib/cache-group.js000644 0000000535 3560116604 012564 0ustar00000000 000000 'use strict'; var Cache = require("./cache"); module.exports = /** @class */ (function () { function CacheGroup() { this.MODULE_ENTRY = new Cache(); this.PATH = new Cache(); this.REAL_FILE_PATH = new Cache(); this.REAL_DIRECTORY_PATH = new Cache(); Object.freeze(this); } return CacheGroup; }()); package/lib/cache.js000644 0000002060 3560116604 011425 0ustar00000000 000000 'use strict'; function makeCache() { // object with no prototype var cache = Object.create(null); // force the jit to immediately realize this object is a dictionary. This // should prevent the JIT from going wastefully one direction (fast mode) // then going another (dict mode) after cache['_cache'] = 1; delete cache['_cache']; return cache; } module.exports = /** @class */ (function () { function Cache() { this._store = makeCache(); } Cache.prototype.set = function (key, value) { return (this._store[key] = value); }; Cache.prototype.get = function (key) { return this._store[key]; }; Cache.prototype.has = function (key) { return key in this._store; }; Cache.prototype.delete = function (key) { delete this._store[key]; }; Object.defineProperty(Cache.prototype, "size", { get: function () { return Object.keys(this._store).length; }, enumerable: false, configurable: true }); return Cache; }()); package/tests/index-test.js000644 0000024636 3560116604 013057 0ustar00000000 000000 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var resolvePackagePath = require("../index"); var Project = require("fixturify-project"); var Cache = require("../lib/cache"); var fixturify = require("fixturify"); var fs = require("fs-extra"); var chai = require("chai"); var path = require("path"); var os = require("os"); var expect = chai.expect; var findUpPackagePath = resolvePackagePath.findUpPackagePath; var FIXTURE_ROOT = __dirname + "/tmp/fixtures/"; describe('resolve-package-path', function () { beforeEach(function () { fs.removeSync(FIXTURE_ROOT); }); afterEach(function () { fs.removeSync(FIXTURE_ROOT); }); it('exposes its cache', function () { expect(resolvePackagePath._CACHE).to.be.ok; expect(resolvePackagePath._resetCache).to.be.a('function'); }); it('exposes helper methods', function () { expect(resolvePackagePath.getRealFilePath).to.be.a('function'); expect(resolvePackagePath.getRealDirectoryPath).to.be.a('function'); // smoke tests, real tests are unit tests of the underlying utilities expect(resolvePackagePath.getRealFilePath(__filename)).to.eql(__filename); expect(resolvePackagePath.getRealDirectoryPath(__dirname)).to.eql(__dirname); }); it('appears to reset cache', function () { resolvePackagePath._CACHE.PATH.set('hi', 1); expect(resolvePackagePath._CACHE.PATH.has('hi')).eql(true); resolvePackagePath._resetCache(); expect(resolvePackagePath._CACHE.PATH.has('hi')).eql(false); }); describe('npm usage', function () { var app, rsvp, a, orange, apple; beforeEach(function () { app = new Project('app', '3.1.1', function (app) { rsvp = app.addDependency('rsvp', '3.2.2', function (rsvp) { a = rsvp.addDependency('a', '1.1.1'); }); orange = app.addDependency('orange', '1.0.0'); apple = app.addDependency('apple', '1.0.0'); }); }); it('smoke test', function () { app.writeSync(); expect(resolvePackagePath('app', app.root)).to.eql(null); expect(resolvePackagePath('rsvp', app.baseDir)).to.eql(path.normalize(app.root + "/app/node_modules/rsvp/package.json")); expect(resolvePackagePath('orange', app.baseDir)).to.eql(path.normalize(app.root + "/app/node_modules/orange/package.json")); expect(resolvePackagePath('apple', app.baseDir)).to.eql(path.normalize(app.root + "/app/node_modules/apple/package.json")); expect(resolvePackagePath('a', app.baseDir)).to.eql(null); expect(resolvePackagePath('a', rsvp.baseDir)).to.eql(path.normalize(rsvp.baseDir + "/node_modules/a/package.json")); expect(resolvePackagePath('rsvp', a.baseDir)).to.eql(path.normalize(rsvp.baseDir + "/package.json")); expect(resolvePackagePath('orange', a.baseDir)).to.eql(path.normalize(orange.baseDir + "/package.json")); expect(resolvePackagePath('apple', a.baseDir)).to.eql(path.normalize(apple.baseDir + "/package.json")); expect(resolvePackagePath('app', a.baseDir)).to.eql(null); }); }); if (require('os').platform() !== 'win32') { describe('yarn pnp usage', function () { this.timeout(30000); // in-case the network IO is slow var app; var execa = require('execa'); beforeEach(function () { app = new Project('dummy', '1.0.0', function (app) { app.pkg.private = true; app.pkg.name; app.pkg.scripts = { test: 'node ./test.js', }; app.pkg.installConfig = { pnp: true, }; app.addDependency('ember-source-channel-url', '1.1.0'); app.addDependency('resolve-package-path', 'link:' + path.join(__dirname, '..')); app.files = { 'test.js': 'require("resolve-package-path")(process.argv[2], __dirname); console.log("success!");', }; }); app.writeSync(); execa.sync('yarn', { cwd: app.baseDir, }); }); afterEach(function () { app.dispose(); }); it('handles yarn pnp usage - package exists', function () { var result = execa.sync('yarn', ['test', 'ember-source-channel-url'], { cwd: app.baseDir, }); expect(result.stdout.toString()).includes('success!'); }); it('handles yarn pnp usage - package missing', function () { var result = execa.sync('yarn', ['test', 'some-non-existent-package'], { cwd: app.baseDir, }); expect(result.stdout.toString()).includes('success!'); }); }); } describe('findUpPackagePath', function () { var tmpDir; var cache; beforeEach(function () { resolvePackagePath._resetCache(); tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'findUpPackagePath')); cache = resolvePackagePath._FIND_UP_CACHE; }); it('returns null if no package.json exists on the path to root', function () { fixturify.writeSync(tmpDir, { foo: { bar: { baz: 'hello', }, }, }); // test against dir var testPath = path.join(tmpDir, 'foo/bar'); expect(fs.existsSync(testPath)).to.equal(true); expect(findUpPackagePath(testPath)).to.equal(null); // tests against file testPath = path.join(tmpDir, 'foo/bar/baz'); expect(fs.existsSync(testPath)).to.equal(true); expect(findUpPackagePath(testPath)).to.equal(null); }); it('returns the nearest package.json, caching results', function () { fixturify.writeSync(tmpDir, { foo: { bar: { 'package.json': JSON.stringify({ name: 'foo' }), baz: { qux: 'hello', 'package.json': JSON.stringify({ name: 'baz' }), }, a: { b: { c: { d: 'hello again', }, }, }, }, }, }); expect(findUpPackagePath(path.join(tmpDir, 'foo/bar/baz'))).to.equal(path.join(tmpDir, 'foo/bar/baz/package.json')); expect(cache.size).to.equal(1); expect(cache.get(path.join(tmpDir, 'foo/bar/baz'))).to.equal(path.join(tmpDir, 'foo/bar/baz/package.json')); expect(findUpPackagePath(path.join(tmpDir, 'foo/bar'))).to.equal(path.join(tmpDir, 'foo/bar/package.json')); expect(cache.size).to.equal(2); expect(cache.get(path.join(tmpDir, 'foo/bar'))).to.equal(path.join(tmpDir, 'foo/bar/package.json')); expect(findUpPackagePath(path.join(tmpDir, 'foo/bar/a/b/c'))).to.equal(path.join(tmpDir, 'foo/bar/package.json')); expect(cache.size).to.equal(3); expect(cache.get(path.join(tmpDir, 'foo/bar/a/b/c'))).to.equal(path.join(tmpDir, 'foo/bar/package.json')); expect(findUpPackagePath(path.join(tmpDir, 'foo/bar/a/b/c/d'))).to.equal(path.join(tmpDir, 'foo/bar/package.json')); expect(cache.size).to.equal(4); expect(cache.get(path.join(tmpDir, 'foo/bar/a/b/c/d'))).to.equal(path.join(tmpDir, 'foo/bar/package.json')); expect(findUpPackagePath(path.join(tmpDir, 'foo'))).to.equal(null); expect(cache.size).to.equal(5); expect(cache.get(path.join(tmpDir, 'foo'))).to.equal(null); expect(findUpPackagePath(path.join(tmpDir, 'foo/'))).to.equal(null); // foo and foo/ should have same entry in the cache as cache is normalized expect(cache.size).to.equal(5); }); it('accepts a custom cache', function () { var customCache = new Cache(); fixturify.writeSync(tmpDir, { foo: { 'package.json': JSON.stringify({ name: 'foo' }), }, }); expect(findUpPackagePath(path.join(tmpDir, 'foo'), customCache)).to.equal(path.join(tmpDir, 'foo/package.json')); expect(cache.size).to.equal(0); expect(customCache.size).to.equal(1); expect(customCache.get(path.join(tmpDir, 'foo'))).to.equal(path.join(tmpDir, 'foo/package.json')); }); it('accepts cache=true', function () { fixturify.writeSync(tmpDir, { foo: { 'package.json': JSON.stringify({ name: 'foo' }), }, }); expect(findUpPackagePath(path.join(tmpDir, 'foo'), true)).to.equal(path.join(tmpDir, 'foo/package.json')); expect(cache.size).to.equal(1); expect(cache.get(path.join(tmpDir, 'foo'))).to.equal(path.join(tmpDir, 'foo/package.json')); }); it('accepts cache=false', function () { fixturify.writeSync(tmpDir, { foo: { 'package.json': JSON.stringify({ name: 'foo' }), }, }); expect(findUpPackagePath(path.join(tmpDir, 'foo'), false)).to.equal(path.join(tmpDir, 'foo/package.json')); expect(cache.size).to.equal(0); }); it('resetCache resets the findUpPackagePath cache', function () { fixturify.writeSync(tmpDir, { foo: { 'package.json': JSON.stringify({ name: 'foo' }), }, }); expect(findUpPackagePath(path.join(tmpDir, 'foo'), true)).to.equal(path.join(tmpDir, 'foo/package.json')); expect(resolvePackagePath._FIND_UP_CACHE.size).to.equal(1); expect(resolvePackagePath._FIND_UP_CACHE.get(path.join(tmpDir, 'foo'))).to.equal(path.join(tmpDir, 'foo/package.json')); resolvePackagePath._resetCache(); expect(resolvePackagePath._FIND_UP_CACHE.size).to.equal(0); }); }); }); package/index.js000644 0000011050 3560116604 010722 0ustar00000000 000000 'use strict'; var path = require("path"); var customResolvePackagePath = require('./lib/resolve-package-path'); var ALLOWED_ERROR_CODES = { // resolve package error codes MODULE_NOT_FOUND: true, // Yarn PnP Error Codes UNDECLARED_DEPENDENCY: true, MISSING_PEER_DEPENDENCY: true, MISSING_DEPENDENCY: true, }; var CacheGroup = require("./lib/cache-group"); var Cache = require("./lib/cache"); var getRealFilePath = customResolvePackagePath._getRealFilePath; var getRealDirectoryPath = customResolvePackagePath._getRealDirectoryPath; var __findUpPackagePath = customResolvePackagePath._findUpPackagePath; var CACHE = new CacheGroup(); var FIND_UP_CACHE = new Cache(); var pnp; try { pnp = require('pnpapi'); // eslint-ignore node/no-missing-require } catch (error) { // not in Yarn PnP; not a problem } /** * Search each directory in the absolute path `basedir`, from leaf to root, for * a `package.json`, and return the first match, or `null` if no `package.json` * was found. * * @public * @param {string} basedir - an absolute path in which to search for a `package.json` * @param {CacheGroup|boolean} [_cache] (optional) * * if true: will choose the default global cache * * if false: will not cache * * if undefined or omitted, will choose the default global cache * * otherwise we assume the argument is an external cache of the form provided by resolve-package-path/lib/cache-group.js * * @return {string|null} a full path to the resolved package.json if found or null if not */ function _findUpPackagePath(basedir, _cache) { var cache; if (_cache === undefined || _cache === null || _cache === true) { // if no cache specified, or if cache is true then use the global cache cache = FIND_UP_CACHE; } else if (_cache === false) { // if cache is explicity false, create a throw-away cache; cache = new Cache(); } else { // otherwise, assume the user has provided an alternative cache for the following form: // provided by resolve-package-path/lib/cache-group.js cache = _cache; } var absoluteStart = path.resolve(basedir); return __findUpPackagePath(cache, absoluteStart); } function resolvePackagePath(target, basedir, _cache) { var cache; if (_cache === undefined || _cache === null || _cache === true) { // if no cache specified, or if cache is true then use the global cache cache = CACHE; } else if (_cache === false) { // if cache is explicity false, create a throw-away cache; cache = new CacheGroup(); } else { // otherwise, assume the user has provided an alternative cache for the following form: // provided by resolve-package-path/lib/cache-group.js cache = _cache; } var key = target + '\x00' + basedir; var pkgPath; if (cache.PATH.has(key)) { pkgPath = cache.PATH.get(key); } else { try { // the custom `pnp` code here can be removed when yarn 1.13 is the // current release. This is due to Yarn 1.13 and resolve interoperating // together seamlessly. pkgPath = pnp ? pnp.resolveToUnqualified(target + '/package.json', basedir) : customResolvePackagePath(cache, target, basedir); } catch (e) { if (e !== null && typeof e === 'object') { var code = e.code; if (ALLOWED_ERROR_CODES[code] === true) { pkgPath = null; } else { throw e; } } else { throw e; } } cache.PATH.set(key, pkgPath); } return pkgPath; } resolvePackagePath._resetCache = function () { CACHE = new CacheGroup(); FIND_UP_CACHE = new Cache(); }; (function (resolvePackagePath) { resolvePackagePath._FIND_UP_CACHE = FIND_UP_CACHE; resolvePackagePath.findUpPackagePath = _findUpPackagePath; })(resolvePackagePath || (resolvePackagePath = {})); Object.defineProperty(resolvePackagePath, '_CACHE', { get: function () { return CACHE; }, }); Object.defineProperty(resolvePackagePath, '_FIND_UP_CACHE', { get: function () { return FIND_UP_CACHE; }, }); resolvePackagePath.getRealFilePath = function (filePath) { return getRealFilePath(CACHE.REAL_FILE_PATH, filePath); }; resolvePackagePath.getRealDirectoryPath = function (directoryPath) { return getRealDirectoryPath(CACHE.REAL_DIRECTORY_PATH, directoryPath); }; module.exports = resolvePackagePath; package/tests/resolve-package-path-test.js000644 0000023735 3560116604 015751 0ustar00000000 000000 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var path = require("path"); var chai = require("chai"); var resolvePackagePath = require("../lib/resolve-package-path"); var Cache = require("../lib/cache"); var CacheGroup = require("../lib/cache-group"); var fs = require("fs"); var Project = require("fixturify-project"); var fixturesPath = path.join(__dirname, 'fixtures'); var assert = chai.assert; var _cache = new Cache(); describe('resolvePackagePath', function () { var project, foo, fooBar, dedupped, linked, linkedBar, unlinked; beforeEach(function () { project = new Project('example', '0.0.0', function (project) { dedupped = project.addDependency('dedupped', '1.0.0', function (dedupped) { dedupped.addDependency('dedupped-child', '1.0.0'); }); foo = project.addDependency('foo', '1.0.0', function (foo) { fooBar = foo.addDependency('bar', '1.0.0'); }); unlinked = project.addDependency('linked', '1.0.0', function (linked) { linkedBar = linked.addDependency('bar', '1.0.0'); }); }); project.writeSync(); fs.symlinkSync('../../linked', foo.baseDir + "/node_modules/linked"); linked = { baseDir: foo.baseDir + "/node_modules/linked", }; }); afterEach(function () { project.dispose(); }); describe('._findPackagePath', function () { // NOTE: _findPackagePath requires that 'dir' must be non-empty and valid. it('finds linked package.json', function () { var result = resolvePackagePath._findPackagePath(_cache, 'linked/node_modules/bar/package.json', fooBar.baseDir); assert.equal(result, path.join(linkedBar.baseDir, 'package.json'), 'should resolve link to real path package.json'); }); it('does not find invalid package.json file name', function () { var result = resolvePackagePath._findPackagePath(_cache, 'dedupped/package2.json', fooBar.baseDir); assert.isNull(result, 'invalid package.json should return null'); }); it('does not find invalid package file name', function () { var result = resolvePackagePath._findPackagePath(_cache, 'dedupped2/package.json', fooBar.baseDir); assert.isNull(result, 'invalid package filename should return null'); }); it('finds child package.json', function () { var result = resolvePackagePath._findPackagePath(_cache, 'bar/package.json', foo.baseDir); assert.equal(result, path.join(fooBar.baseDir, 'package.json'), '"child" package.json should resolve correctly'); }); it('finds parent package.json', function () { var result = resolvePackagePath._findPackagePath(_cache, 'foo/package.json', fooBar.baseDir); assert.equal(result, path.join(foo.baseDir, 'package.json'), '"parent" package.json should resolve correctly'); }); // Note: we do not need to provide a 'find self package.json' because this private function is only called // during resolvePackagePath when the path does not start with './'. it('finds uncle package.json', function () { var result = resolvePackagePath._findPackagePath(_cache, 'dedupped/package.json', fooBar.baseDir); assert.equal(result, path.join(dedupped.baseDir, 'package.json'), '"uncle" package.json should resolve correctly'); }); }); describe('._getRealDirectoryPath', function () { var cache; var unlinkedDirPath = path.join(fixturesPath, 'node_modules', 'linked'); beforeEach(function () { cache = new Cache(); }); it('resolves linked dir through link to unlinked dir', function () { var result = resolvePackagePath._getRealDirectoryPath(cache, linked.baseDir); assert.equal(result, unlinked.baseDir, 'link should resolve to real path package.json'); assert.equal(cache.size, 1, 'cache should contain 1 entry'); assert.equal(cache.get(linked.baseDir), unlinked.baseDir, 'cached entry from linked path should be to unlinked path'); }); it('resolves unlinked dir to itself', function () { var result1 = resolvePackagePath._getRealDirectoryPath(cache, linked.baseDir); // repeat just to load an entry var result2 = resolvePackagePath._getRealDirectoryPath(cache, unlinked.baseDir); assert.equal(cache.size, 2, 'cache should now contain 2 entries'); assert.equal(result1, result2, '2 cache entries should both have the same value (different keys)'); assert.equal(result2, unlinked.baseDir, 'resolving the unlinked path should not change the value'); assert.equal(cache.get(unlinked.baseDir), unlinked.baseDir, 'the cached value for the unlinked path should be to itself'); }); it('resolves an existing file as null (it is not a directory)', function () { var filePath = path.join(unlinkedDirPath, 'index.js'); var result = resolvePackagePath._getRealDirectoryPath(cache, filePath); assert.isNull(result, 'reference to a file should return null'); assert.isNull(cache.get(filePath), 'cache reference to file should return null'); }); it('resolves a non-existent path as null', function () { var result = resolvePackagePath._getRealDirectoryPath(cache, unlinkedDirPath + '2'); assert.isNull(result, 'reference to a non-existent path correctly returns null'); }); }); describe('._getRealFilePath', function () { // create a temporary cache to make sure that we're actually caching things. var cache; beforeEach(function () { cache = new Cache(); }); it('resolves linked package.json through link to unlinked', function () { var result = resolvePackagePath._getRealFilePath(cache, path.join(linked.baseDir, 'package.json')); assert.equal(result, path.join(unlinked.baseDir, 'package.json'), 'link should resolve to real path package.json'); assert.equal(cache.size, 1, 'cache should contain 1 entry'); assert.equal(cache.get(path.join(linked.baseDir, 'package.json')), path.join(unlinked.baseDir, 'package.json'), 'cached entry from linked path should be to unlinked path'); }); it('resolves unlinked package.json to itself', function () { var result1 = resolvePackagePath._getRealFilePath(cache, path.join(linked.baseDir, 'package.json')); // repeat just to load an entry var result2 = resolvePackagePath._getRealFilePath(cache, path.join(unlinked.baseDir, 'package.json')); assert.equal(cache.size, 2, 'cache should now contain 2 entries'); assert.equal(result1, result2, '2 cache entries should both have the same value (different keys)'); assert.equal(result2, path.join(unlinked.baseDir, 'package.json'), 'resolving the unlinked path should not change the value'); assert.equal(cache.get(path.join(unlinked.baseDir, 'package.json')), path.join(unlinked.baseDir, 'package.json'), 'the cached value for the unlinked path should be to itself'); }); it('resolves an existing directory as null (it is not a file)', function () { var result = resolvePackagePath._getRealFilePath(cache, project.root); assert.isNull(result, 'reference to a directory should return null'); assert.isNull(cache.get(project.root), 'cache reference to directory should return null'); }); it('resolves a non-existent path as null', function () { var result = resolvePackagePath._getRealFilePath(cache, project.root + '2'); assert.isNull(result, 'reference to a non-existent path correctly returns null'); }); }); describe('resolvePackagePath', function () { var caches; beforeEach(function () { caches = new CacheGroup(); }); it('no module name', function () { assert.throws(function () { return resolvePackagePath(caches, undefined, '/'); }, TypeError); }); it('baseDir were the end of the path does not exist, but the start does and if resolution proceeds would be a hit', function () { assert.equal(resolvePackagePath(caches, 'bar', path.join(foo.baseDir, 'node_modules', 'does-not-exist')), path.join(foo.baseDir, 'node_modules', 'bar', 'package.json')); }); it('invalid basedir', function () { assert.equal(resolvePackagePath(caches, 'omg', 'asf'), null); }); it('linked directory as name', function () { var result = resolvePackagePath(caches, linked.baseDir, undefined); assert.equal(path.join(unlinked.baseDir, 'package.json'), result, 'should resolve to unlinked "linked/package.json"'); }); it('through linked directory as dir to node_modules package', function () { var result = resolvePackagePath(caches, 'bar', linked.baseDir); assert.equal(path.join(unlinked.baseDir, 'node_modules', 'bar', 'package.json'), result, 'should resolve to unlinked "linked/node_ odules/bar/package.json"'); }); it('.. relative path through "linked" directory', function () { var result = resolvePackagePath(caches, '../linked', fooBar.baseDir); assert.equal(path.join(unlinked.baseDir, 'package.json'), result, 'should resolve to unlinked "linked/package.json"'); }); it('. relative path ', function () { var result = resolvePackagePath(caches, '..', path.join(foo.baseDir, 'node_modules')); assert.equal(path.join(foo.baseDir, 'package.json'), result, 'should resolve to "foo/package.json"'); }); it('. relative empty path ', function () { var result = resolvePackagePath(caches, '.', foo.baseDir); assert.equal(path.join(foo.baseDir, 'package.json'), result, 'should resolve to "foo/package.json"'); }); }); }); package/lib/resolve-package-path.js000644 0000024504 3560116604 014373 0ustar00000000 000000 'use strict'; // credit goes to https://github.com/davecombs // extracted in part from: https://github.com/stefanpenner/hash-for-dep/blob/15b2ebcf22024ceb2eb7907f8c412ae40f87b15e/lib/resolve-package-path.js#L1 // var fs = require("fs"); var path = require("path"); var pathRoot = require("path-root"); /* * Define a regex that will match against the 'name' value passed into * resolvePackagePath. The regex corresponds to the following test: * Match any of the following 3 alternatives: * * 1) dot, then optional second dot, then / or nothing i.e. . ./ .. ../ OR * 2) / i.e. / OR * 3) (A-Za-z colon - [optional]), then / or \ i.e. optional drive letter + colon, then / or \ * * Basically, the three choices mean "explicitly relative or absolute path, on either * Unix/Linux or Windows" */ var ABSOLUTE_OR_RELATIVE_PATH_REGEX = /^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[\/\\])/; var shouldPreserveSymlinks = require("./should-preserve-symlinks"); var PRESERVE_SYMLINKS = shouldPreserveSymlinks(process); /* * Resolve the real path for a file. Return null if does not * exist or is not a file or FIFO, return the real path otherwise. * * Cache the result in the passed-in cache for performance, * keyed on the filePath passed in. * * NOTE: Because this is a private method, it does not attempt to normalize * the path passed in - it assumes the caller has done that. * * @private * @method _getRealFilePath * @param {Cache} realFilePathCache the Cache object to cache the real (resolved) * path in, keyed by filePath. See lib/cache.js and lib/cache-group.js * @param {String} filePath the path to the file of interest (which must have * been normalized, but not necessarily resolved to a real path). * @return {String} real path or null */ function _getRealFilePath(realFilePathCache, filePath) { if (realFilePathCache.has(filePath)) { return realFilePathCache.get(filePath); // could be null } var realPath = null; // null = 'FILE NOT FOUND' try { var stat = fs.statSync(filePath); // I don't know if Node would handle having the filePath actually // be a FIFO, but as the following is also part of the node-resolution // algorithm in resolve.sync(), we'll do the same check here. if (stat.isFile() || stat.isFIFO()) { if (PRESERVE_SYMLINKS) { realPath = filePath; } else { realPath = fs.realpathSync(filePath); } } } catch (e) { if (e === null || typeof e !== 'object' || e.code !== 'ENOENT') { throw e; } } realFilePathCache.set(filePath, realPath); return realPath; } /* * Resolve the real path for a directory, return null if does not * exist or is not a directory, return the real path otherwise. * * @param {Cache} realDirectoryPathCache the Cache object to cache the real (resolved) * path in, keyed by directoryPath. See lib/cache.js and lib/cache-group.js * @param {String} directoryPath the path to the directory of interest (which must have * been normalized, but not necessarily resolved to a real path). * @return {String} real path or null */ function _getRealDirectoryPath(realDirectoryPathCache, directoryPath) { if (realDirectoryPathCache.has(directoryPath)) { return realDirectoryPathCache.get(directoryPath); // could be null } var realPath = null; try { var stat = fs.statSync(directoryPath); if (stat.isDirectory()) { if (PRESERVE_SYMLINKS) { realPath = directoryPath; } else { realPath = fs.realpathSync(directoryPath); } } } catch (e) { if (e === null || typeof e !== 'object' || (e.code !== 'ENOENT' && e.code !== 'ENOTDIR')) { throw e; } } realDirectoryPathCache.set(directoryPath, realPath); return realPath; } /* * Given a package 'name' and starting directory, resolve to a real (existing) file path. * * Do it similar to how it is done in resolve.sync() - travel up the directory hierarchy, * attaching 'node-modules' to each directory and seeing if the directory exists and * has the relevant 'package.json' file we're searching for. It is *much* faster than * resolve.sync(), because we don't test that the requested name is a directory. * This routine assumes that it is only called when we don't already have * the cached entry. * * NOTE: it is valid for 'name' to be an absolute or relative path. * Because this is an internal routine, we'll require that 'dir' be non-empty * if this is called, to make things simpler (see resolvePackagePath). * * @param realFilePathCache the cache containing the real paths corresponding to * various file and directory paths (which may or may not be already resolved). * * @param name the 'name' of the module, i.e. x in require(x), but with * '/package.json' on the end. It is NOT referring to a directory (so we don't * have to do the directory checks that resolve.sync does). * NOTE: because this is an internal routine, for speed it does not check * that '/package.json' is actually the end of the name. * * @param dir the directory (MUST BE non-empty, and valid) to start from, appending the name to the * directory and checking that the file exists. Go up the directory hierarchy from there. * if name is itself an absolute path, * * @result the path to the actual package.json file that's found, or null if not. */ function _findPackagePath(realFilePathCache, name, dir) { var fsRoot = pathRoot(dir); var currPath = dir; while (currPath !== fsRoot) { // when testing for 'node_modules', need to allow names like NODE_MODULES, // which can occur with case-insensitive OSes. var endsWithNodeModules = path.basename(currPath).toLowerCase() === 'node_modules'; var filePath = path.join(currPath, endsWithNodeModules ? '' : 'node_modules', name); var realPath = _getRealFilePath(realFilePathCache, filePath); if (realPath) { return realPath; } if (endsWithNodeModules) { // go up past the ending node_modules directory so the next dirname // goes up past that (if ending in node_modules, going up just one // directory below will then add 'node_modules' on the next loop and // re-process this same node_modules directory. currPath = path.dirname(currPath); } currPath = path.dirname(currPath); } return null; } /* * Resolve the path to the nearest `package.json` from the given initial search * directory. * * @param {Cache} findUpCache - a cache of memoized results that is * prioritized to avoid I/O. * * @param {string} initialSearchDir - the normalized path to start searching * from. * * @return {string | null} - the deepest directory on the path to root from * `initialSearchDir` that contains a {{package.json}}, or `null` if no such * directory exists. */ function _findUpPackagePath(findUpCache, initialSearchDir) { var previous; var dir = initialSearchDir; var maybePackageJsonPath; var result = null; do { if (findUpCache.has(dir)) { result = findUpCache.get(dir); break; } maybePackageJsonPath = path.join(dir, 'package.json'); if (fs.existsSync(maybePackageJsonPath)) { result = maybePackageJsonPath; break; } previous = dir; dir = path.dirname(dir); } while (dir !== previous); findUpCache.set(initialSearchDir, result); return result; } /* * Resolve the path to a module's package.json file, if it exists. The * name and dir are as in hashForDep and ModuleEntry.locate. * * @param caches an instance of CacheGroup where information will be cached * during processing. * * @param name the 'name' of the module. The name may also be a path, * either relative or absolute. The path must be to a module DIRECTORY, NOT to the * package.json file in the directory, as we attach 'package.json' here. * * @param dir (optional) the root directory to run the path resolution from. * if dir is not provided, __dirname for this module is used instead. * * @return the realPath corresponding to the module's package.json file, or null * if that file is not found or is not a file. * * Note: 'name' is expected in the format expected for require(x), i.e., it is * resolved using the Node path-normalization rules. */ function resolvePackagePath(caches, name, dir) { if (typeof name !== 'string' || name.length === 0) { throw new TypeError("resolvePackagePath: 'name' must be a non-zero-length string."); } // Perform tests similar to those in resolve.sync(). var basedir = dir || __dirname; // Ensure that basedir is an absolute path at this point. If it does not refer to // a real directory, go up the path until a real directory is found, or return an error. // BUG!: this will never throw an exception, at least on Unix/Linux. If the path is // relative, path.resolve() will make it absolute by putting the current directory // before it, so it won't fail. If the path is already absolute, / will always be // valid, so again it won't fail. var absoluteStart = path.resolve(basedir); while (_getRealDirectoryPath(caches.REAL_DIRECTORY_PATH, absoluteStart) === null) { absoluteStart = path.dirname(absoluteStart); } if (!absoluteStart) { var error = new TypeError("resolvePackagePath: 'dir' or one of the parent directories in its path must refer to a valid directory."); error.code = 'MODULE_NOT_FOUND'; throw error; } if (ABSOLUTE_OR_RELATIVE_PATH_REGEX.test(name)) { var res = path.resolve(absoluteStart, name); return _getRealFilePath(caches.REAL_FILE_PATH, path.join(res, 'package.json')); // XXX Do we need to handle the core(x) case too? Not sure. } else { return _findPackagePath(caches.REAL_FILE_PATH, path.join(name, 'package.json'), absoluteStart); } } resolvePackagePath._findPackagePath = _findPackagePath; resolvePackagePath._findUpPackagePath = _findUpPackagePath; resolvePackagePath._getRealFilePath = _getRealFilePath; resolvePackagePath._getRealDirectoryPath = _getRealDirectoryPath; module.exports = resolvePackagePath; package/tests/should-preserve-symlinks-test.js000644 0000002174 3560116604 016737 0ustar00000000 000000 "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var shouldPreserveSymlinks = require("../lib/should-preserve-symlinks"); var chai = require("chai"); var expect = chai.expect; describe('shouldPreserveSymlinks', function () { it('works', function () { expect(shouldPreserveSymlinks({ execArgv: [], env: { NODE_PRESERVE_SYMLINKS: '1' }, })).to.eql(true); expect(shouldPreserveSymlinks({ execArgv: [], env: { NODE_PRESERVE_SYMLINKS: 'false' }, })).to.eql(true); expect(shouldPreserveSymlinks({ execArgv: [], env: { NODE_PRESERVE_SYMLINKS: '' }, })).to.eql(false); expect(shouldPreserveSymlinks({ execArgv: [], env: { foo: 'bar' } })).to.eql(false); expect(shouldPreserveSymlinks({ execArgv: ['--preserve-symlinks'], env: {} })).to.eql(true); expect(shouldPreserveSymlinks({ execArgv: ['--a', '--preserve-symlinks', '--b'], env: {}, })).to.eql(true); expect(shouldPreserveSymlinks({ execArgv: [], env: {} })).to.eql(false); }); }); package/lib/should-preserve-symlinks.js000644 0000000513 3560116604 015361 0ustar00000000 000000 'use strict'; function includes(array, entry) { for (var i = 0; i < array.length; i++) { if (array[i] === entry) { return true; } } return false; } module.exports = function (process) { return !!process.env.NODE_PRESERVE_SYMLINKS || includes(process.execArgv, '--preserve-symlinks'); }; package/package.json000644 0000002553 3560116604 011553 0ustar00000000 000000 { "name": "resolve-package-path", "description": "a special purpose fast memoizing way to resolve a node modules package.json", "version": "3.1.0", "repository": "git@github.com:stefanpenner/resolve-package-path.git", "main": "index.js", "types": "index.d.ts", "license": "MIT", "dependencies": { "path-root": "^0.1.1", "resolve": "^1.17.0" }, "devDependencies": { "@types/chai": "^4.2.13", "@types/fs-extra": "^9.0.2", "@types/mocha": "^8.0.3", "@types/node": "^14.11.8", "@typescript-eslint/eslint-plugin": "^4.4.1", "@typescript-eslint/parser": "^4.4.1", "chai": "^4.2.0", "eslint": "^7.11.0", "eslint-config-prettier": "^6.12.0", "eslint-plugin-mocha": "^8.0.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^3.1.4", "execa": "^4.0.3", "fixturify-project": "^2.1.0", "fs-extra": "^9.0.1", "mocha": "^8.1.3", "npm-run-all": "^4.1.5", "prettier": "^2.1.2", "typescript": "^4.0.3" }, "scripts": { "build": "tsc -b .", "watch": "tsc --watch .", "clean": "tsc -b --clean .", "test": "npm-run-all build lint test:js", "test:js": "mocha tests/*-test.js", "test:js:debug": "mocha debug tests/*-test.js", "lint": "eslint ." }, "engines": { "node": "10.* || >= 12" }, "volta": { "node": "12.19.0", "yarn": "1.22.10" } } package/tsconfig.json000644 0000000370 3560116604 011767 0ustar00000000 000000 { "compilerOptions": { "declaration": true, "strict": true, "moduleResolution": "node", "module": "commonjs", "esModuleInterop": true, "target": "es5", "baseUrl": ".", "paths": { "*": ["types/*"] } } } package/README.md000644 0000005261 3560116604 010543 0ustar00000000 000000 # resolve-package-path ![CI](https://github.com/stefanpenner/resolve-package-path/workflows/CI/badge.svg) This project is special-purpose, made to resolve `package.json` files for: - a given module name and basedir or - a given basedir It cannot and does not resolve anything else. To achieve its file-resolution performance, it does two specific things: - It memoizes results identically to node's `require`. Specifically, for a given moduleName and baseDir it will, for the duration of the process, always return the exact same response. - It re-implements the parts of `require.resolve` needed to resolve package.json files ONLY. This removes unneeded I/O. (based on @davecombs approach) ## Usage ```sh yarn add resolve-package-path ``` ```js const resolvePackagePath = require('resolve-package-path'); resolvePackagePath('rsvp', 'base-dir/to/start/the/node_resolution-algorithm-from') // => /path/to/rsvp.json or null const { findUpPackagePath } = resolvePackagePath; findUpPackagePath('base-dir/to/start') // => path/to/package.json or null ``` ## Advanced usage ### Preserve Symlinks Node supports `--preserve-symlinks` and `NODE_PRESERVE_SYMLINKS=1` for compatibility this library respects these. ### Disable default caching Although by default `resolve-package-path` caches or memoizes results, this feature can be disabled: ```js const resolvePackagePath = require('resolve-package-path'); resolvePackagePath('rsvp', 'base-dir/to/start/the/node_resolution-algorithm-from', false) // => uncached result /path/to/rsvp.json or null const { findUpPackagePath } = resolvePackagePath; findUpPackagePath('base-dir/to/start', false) // => path/to/package.json or null ``` ### Purge the cache ```js const resolvePackagePath = require('resolve-package-path'); resolvePackagePath._resetCache(); ``` ### Provide an alternative cache In some advanced circumtances, you may want to gain access to the cache to share between more systems. In that case, a cache instance of the following form can be provided as a third argument: ```js cache = { RESOLVED_PACKAGE_PATH: new Map(), REAL_FILE_PATH: new Map(), REAL_DIRECTORY_PATH: new Map(), }; findUpCache = new Map(); const resolvePackagePath = require('resolve-package-path'); resolvePackagePath('rsvp', 'path/to/start/from', cache); const { findUpPackagePath } = resolvePackagePath; findUpPackagePath('base-dir/to/start', findUpCache) // => path/to/package.json or null ``` ### Use internal helper functions For consumers who also do `getRealFilePath` or `getRealDirectoryPath` calls on relevant paths, we expose them as utilities. These utilties ensure identical functionality to resolve-package-path, and a shared cache, which may help reduce IO. package/lib/cache-group.d.ts000644 0000000335 3560116604 013016 0ustar00000000 000000 import Cache = require('./cache'); declare const _default: { new (): { MODULE_ENTRY: Cache; PATH: Cache; REAL_FILE_PATH: Cache; REAL_DIRECTORY_PATH: Cache; }; }; export = _default; package/lib/cache-group.ts000644 0000004722 3560116604 012600 0ustar00000000 000000 'use strict'; import Cache = require('./cache'); /* * CacheGroup is used to both speed up and ensure consistency of hashForDep. * * The CacheGroup contains three separate caches: * * - MODULE_ENTRY: the cache of realPathKey => ModuleEntry objects. * Each ModuleEntry contains information about a particular module * found during hash-for-dep processing. realPathKey is a hash * of the resolved real path to the module's package.json file. * * Having the real path means that when resolving dependencies, we can * take the resolved paths, hash them and check quickly for cache entries, * speeding creation of the cache. Because many modules may refer to * the same module as a dependency, this eliminates duplication and having * to reread package.json files. * * However, that also means we need a second cache to map from the original * name and dir passed to hashForDep to the final resolved path for the * module's package.json file. * * - PATH: the cache of nameDirKey => realPathKey. nameDirKey is a hash of the * combination of the name and starting directory passed to hashForDep. * realPathKey is a hash of the resulting real path to the relevant package.json file. * * - REAL_FILE_PATH: the cache of filePath => resolved realPath for relevant files. * When determining the location of a file, the file path may involve links. * This cache is keyed on the original file path, with a value of the real path. * This cache helps to speed up path resolution in both cases by minimizing * the use of costly 'fs.statSync' calls. * * - REAL_DIRECTORY_PATH: the cache of filePath => resolved realPath for relevant directories. * When determining the location of a file by going up the node_modules chain, * paths to intervening directories may contain links. Similar to REAL_FILE_PATH, * this cache is keyed on the original directory path, with a value of the real path. * This cache helps to speed up path resolution in both cases by minimizing * the use of costly 'fs.statSync' calls. * * Note: when discussing 'real paths' above, the paths are normalized by routines * like path.join() or path.resolve(). We do nothing beyond that (e.g., we do not attempt * to force the paths into POSIX style.) */ export = class CacheGroup { MODULE_ENTRY = new Cache(); PATH = new Cache(); REAL_FILE_PATH = new Cache(); REAL_DIRECTORY_PATH = new Cache(); constructor() { Object.freeze(this); } }; package/lib/cache.d.ts000644 0000000467 3560116604 011672 0ustar00000000 000000 declare const _default: { new (): { _store: { [key: string]: string; }; set(key: string, value: any): any; get(key: string): string; has(key: string): boolean; delete(key: string): void; readonly size: number; }; }; export = _default; package/lib/cache.ts000644 0000001444 3560116604 011444 0ustar00000000 000000 'use strict'; function makeCache() { // object with no prototype const cache = Object.create(null); // force the jit to immediately realize this object is a dictionary. This // should prevent the JIT from going wastefully one direction (fast mode) // then going another (dict mode) after cache['_cache'] = 1; delete cache['_cache']; return cache; } export = class Cache { private _store: { [key: string]: string }; constructor() { this._store = makeCache(); } set(key: string, value: any) { return (this._store[key] = value); } get(key: string) { return this._store[key]; } has(key: string) { return key in this._store; } delete(key: string) { delete this._store[key]; } get size() { return Object.keys(this._store).length; } }; package/tests/index-test.d.ts000644 0000000013 3560116604 013272 0ustar00000000 000000 export {}; package/tests/index-test.ts000644 0000023073 3560116604 013063 0ustar00000000 000000 'use strict'; import resolvePackagePath = require('../index'); import Project = require('fixturify-project'); import Cache = require('../lib/cache'); import fixturify = require('fixturify'); import fs = require('fs-extra'); import chai = require('chai'); import path = require('path'); import os = require('os'); const expect = chai.expect; const { findUpPackagePath } = resolvePackagePath; const FIXTURE_ROOT = `${__dirname}/tmp/fixtures/`; describe('resolve-package-path', function () { beforeEach(function () { fs.removeSync(FIXTURE_ROOT); }); afterEach(function () { fs.removeSync(FIXTURE_ROOT); }); it('exposes its cache', function () { expect(resolvePackagePath._CACHE).to.be.ok; expect(resolvePackagePath._resetCache).to.be.a('function'); }); it('exposes helper methods', function () { expect(resolvePackagePath.getRealFilePath).to.be.a('function'); expect(resolvePackagePath.getRealDirectoryPath).to.be.a('function'); // smoke tests, real tests are unit tests of the underlying utilities expect(resolvePackagePath.getRealFilePath(__filename)).to.eql(__filename); expect(resolvePackagePath.getRealDirectoryPath(__dirname)).to.eql(__dirname); }); it('appears to reset cache', function () { resolvePackagePath._CACHE.PATH.set('hi', 1); expect(resolvePackagePath._CACHE.PATH.has('hi')).eql(true); resolvePackagePath._resetCache(); expect(resolvePackagePath._CACHE.PATH.has('hi')).eql(false); }); describe('npm usage', function () { let app: Project, rsvp: Project, a: Project, orange: Project, apple: Project; beforeEach(function () { app = new Project('app', '3.1.1', app => { rsvp = app.addDependency('rsvp', '3.2.2', rsvp => { a = rsvp.addDependency('a', '1.1.1'); }); orange = app.addDependency('orange', '1.0.0'); apple = app.addDependency('apple', '1.0.0'); }); }); it('smoke test', function () { app.writeSync(); expect(resolvePackagePath('app', app.root)).to.eql(null); expect(resolvePackagePath('rsvp', app.baseDir)).to.eql( path.normalize(`${app.root}/app/node_modules/rsvp/package.json`), ); expect(resolvePackagePath('orange', app.baseDir)).to.eql( path.normalize(`${app.root}/app/node_modules/orange/package.json`), ); expect(resolvePackagePath('apple', app.baseDir)).to.eql( path.normalize(`${app.root}/app/node_modules/apple/package.json`), ); expect(resolvePackagePath('a', app.baseDir)).to.eql(null); expect(resolvePackagePath('a', rsvp.baseDir)).to.eql( path.normalize(`${rsvp.baseDir}/node_modules/a/package.json`), ); expect(resolvePackagePath('rsvp', a.baseDir)).to.eql( path.normalize(`${rsvp.baseDir}/package.json`), ); expect(resolvePackagePath('orange', a.baseDir)).to.eql( path.normalize(`${orange.baseDir}/package.json`), ); expect(resolvePackagePath('apple', a.baseDir)).to.eql( path.normalize(`${apple.baseDir}/package.json`), ); expect(resolvePackagePath('app', a.baseDir)).to.eql(null); }); }); if (require('os').platform() !== 'win32') { describe('yarn pnp usage', function () { this.timeout(30000); // in-case the network IO is slow let app: Project; const execa = require('execa'); beforeEach(function () { app = new Project('dummy', '1.0.0', app => { app.pkg.private = true; app.pkg.name; app.pkg.scripts = { test: 'node ./test.js', }; app.pkg.installConfig = { pnp: true, }; app.addDependency('ember-source-channel-url', '1.1.0'); app.addDependency('resolve-package-path', 'link:' + path.join(__dirname, '..')); app.files = { 'test.js': 'require("resolve-package-path")(process.argv[2], __dirname); console.log("success!");', }; }); app.writeSync(); execa.sync('yarn', { cwd: app.baseDir, }); }); afterEach(function () { app.dispose(); }); it('handles yarn pnp usage - package exists', function () { let result = execa.sync('yarn', ['test', 'ember-source-channel-url'], { cwd: app.baseDir, }); expect(result.stdout.toString()).includes('success!'); }); it('handles yarn pnp usage - package missing', function () { let result = execa.sync('yarn', ['test', 'some-non-existent-package'], { cwd: app.baseDir, }); expect(result.stdout.toString()).includes('success!'); }); }); } describe('findUpPackagePath', function () { let tmpDir: string; let cache: typeof resolvePackagePath._FIND_UP_CACHE; beforeEach(function () { resolvePackagePath._resetCache(); tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'findUpPackagePath')); cache = resolvePackagePath._FIND_UP_CACHE; }); it('returns null if no package.json exists on the path to root', function () { fixturify.writeSync(tmpDir, { foo: { bar: { baz: 'hello', }, }, }); // test against dir let testPath = path.join(tmpDir, 'foo/bar'); expect(fs.existsSync(testPath)).to.equal(true); expect(findUpPackagePath(testPath)).to.equal(null); // tests against file testPath = path.join(tmpDir, 'foo/bar/baz'); expect(fs.existsSync(testPath)).to.equal(true); expect(findUpPackagePath(testPath)).to.equal(null); }); it('returns the nearest package.json, caching results', function () { fixturify.writeSync(tmpDir, { foo: { bar: { 'package.json': JSON.stringify({ name: 'foo' }), baz: { qux: 'hello', 'package.json': JSON.stringify({ name: 'baz' }), }, a: { b: { c: { d: 'hello again', }, }, }, }, }, }); expect(findUpPackagePath(path.join(tmpDir, 'foo/bar/baz'))).to.equal( path.join(tmpDir, 'foo/bar/baz/package.json'), ); expect(cache.size).to.equal(1); expect(cache.get(path.join(tmpDir, 'foo/bar/baz'))).to.equal( path.join(tmpDir, 'foo/bar/baz/package.json'), ); expect(findUpPackagePath(path.join(tmpDir, 'foo/bar'))).to.equal( path.join(tmpDir, 'foo/bar/package.json'), ); expect(cache.size).to.equal(2); expect(cache.get(path.join(tmpDir, 'foo/bar'))).to.equal( path.join(tmpDir, 'foo/bar/package.json'), ); expect(findUpPackagePath(path.join(tmpDir, 'foo/bar/a/b/c'))).to.equal( path.join(tmpDir, 'foo/bar/package.json'), ); expect(cache.size).to.equal(3); expect(cache.get(path.join(tmpDir, 'foo/bar/a/b/c'))).to.equal( path.join(tmpDir, 'foo/bar/package.json'), ); expect(findUpPackagePath(path.join(tmpDir, 'foo/bar/a/b/c/d'))).to.equal( path.join(tmpDir, 'foo/bar/package.json'), ); expect(cache.size).to.equal(4); expect(cache.get(path.join(tmpDir, 'foo/bar/a/b/c/d'))).to.equal( path.join(tmpDir, 'foo/bar/package.json'), ); expect(findUpPackagePath(path.join(tmpDir, 'foo'))).to.equal(null); expect(cache.size).to.equal(5); expect(cache.get(path.join(tmpDir, 'foo'))).to.equal(null); expect(findUpPackagePath(path.join(tmpDir, 'foo/'))).to.equal(null); // foo and foo/ should have same entry in the cache as cache is normalized expect(cache.size).to.equal(5); }); it('accepts a custom cache', function () { let customCache = new Cache(); fixturify.writeSync(tmpDir, { foo: { 'package.json': JSON.stringify({ name: 'foo' }), }, }); expect(findUpPackagePath(path.join(tmpDir, 'foo'), customCache)).to.equal( path.join(tmpDir, 'foo/package.json'), ); expect(cache.size).to.equal(0); expect(customCache.size).to.equal(1); expect(customCache.get(path.join(tmpDir, 'foo'))).to.equal( path.join(tmpDir, 'foo/package.json'), ); }); it('accepts cache=true', function () { fixturify.writeSync(tmpDir, { foo: { 'package.json': JSON.stringify({ name: 'foo' }), }, }); expect(findUpPackagePath(path.join(tmpDir, 'foo'), true)).to.equal( path.join(tmpDir, 'foo/package.json'), ); expect(cache.size).to.equal(1); expect(cache.get(path.join(tmpDir, 'foo'))).to.equal(path.join(tmpDir, 'foo/package.json')); }); it('accepts cache=false', function () { fixturify.writeSync(tmpDir, { foo: { 'package.json': JSON.stringify({ name: 'foo' }), }, }); expect(findUpPackagePath(path.join(tmpDir, 'foo'), false)).to.equal( path.join(tmpDir, 'foo/package.json'), ); expect(cache.size).to.equal(0); }); it('resetCache resets the findUpPackagePath cache', function () { fixturify.writeSync(tmpDir, { foo: { 'package.json': JSON.stringify({ name: 'foo' }), }, }); expect(findUpPackagePath(path.join(tmpDir, 'foo'), true)).to.equal( path.join(tmpDir, 'foo/package.json'), ); expect(resolvePackagePath._FIND_UP_CACHE.size).to.equal(1); expect(resolvePackagePath._FIND_UP_CACHE.get(path.join(tmpDir, 'foo'))).to.equal( path.join(tmpDir, 'foo/package.json'), ); resolvePackagePath._resetCache(); expect(resolvePackagePath._FIND_UP_CACHE.size).to.equal(0); }); }); }); package/index.d.ts000644 0000002517 3560116604 011166 0ustar00000000 000000 import CacheGroup = require('./lib/cache-group'); import Cache = require('./lib/cache'); /** * Search each directory in the absolute path `basedir`, from leaf to root, for * a `package.json`, and return the first match, or `null` if no `package.json` * was found. * * @public * @param {string} basedir - an absolute path in which to search for a `package.json` * @param {CacheGroup|boolean} [_cache] (optional) * * if true: will choose the default global cache * * if false: will not cache * * if undefined or omitted, will choose the default global cache * * otherwise we assume the argument is an external cache of the form provided by resolve-package-path/lib/cache-group.js * * @return {string|null} a full path to the resolved package.json if found or null if not */ declare function _findUpPackagePath(basedir: string, _cache?: Cache | boolean): any; export = resolvePackagePath; declare function resolvePackagePath(target: string, basedir: string, _cache?: CacheGroup | boolean): string | null; declare namespace resolvePackagePath { var _resetCache: () => void; var getRealFilePath: (filePath: string) => any; var getRealDirectoryPath: (directoryPath: string) => any; } declare module resolvePackagePath { let _CACHE: CacheGroup; let _FIND_UP_CACHE: Cache; let findUpPackagePath: typeof _findUpPackagePath; } package/index.ts000644 0000011710 3560116604 010737 0ustar00000000 000000 'use strict'; import path = require('path'); const customResolvePackagePath = require('./lib/resolve-package-path'); const ALLOWED_ERROR_CODES: { [key: string]: boolean } = { // resolve package error codes MODULE_NOT_FOUND: true, // Yarn PnP Error Codes UNDECLARED_DEPENDENCY: true, MISSING_PEER_DEPENDENCY: true, MISSING_DEPENDENCY: true, }; import CacheGroup = require('./lib/cache-group'); import Cache = require('./lib/cache'); const getRealFilePath = customResolvePackagePath._getRealFilePath; const getRealDirectoryPath = customResolvePackagePath._getRealDirectoryPath; const __findUpPackagePath = customResolvePackagePath._findUpPackagePath; let CACHE = new CacheGroup(); let FIND_UP_CACHE = new Cache(); let pnp: any; try { pnp = require('pnpapi'); // eslint-ignore node/no-missing-require } catch (error) { // not in Yarn PnP; not a problem } /** * Search each directory in the absolute path `basedir`, from leaf to root, for * a `package.json`, and return the first match, or `null` if no `package.json` * was found. * * @public * @param {string} basedir - an absolute path in which to search for a `package.json` * @param {CacheGroup|boolean} [_cache] (optional) * * if true: will choose the default global cache * * if false: will not cache * * if undefined or omitted, will choose the default global cache * * otherwise we assume the argument is an external cache of the form provided by resolve-package-path/lib/cache-group.js * * @return {string|null} a full path to the resolved package.json if found or null if not */ function _findUpPackagePath(basedir: string, _cache?: Cache | boolean) { let cache; if (_cache === undefined || _cache === null || _cache === true) { // if no cache specified, or if cache is true then use the global cache cache = FIND_UP_CACHE; } else if (_cache === false) { // if cache is explicity false, create a throw-away cache; cache = new Cache(); } else { // otherwise, assume the user has provided an alternative cache for the following form: // provided by resolve-package-path/lib/cache-group.js cache = _cache; } let absoluteStart = path.resolve(basedir); return __findUpPackagePath(cache, absoluteStart); } /* * @public * * @method resolvePackagePathSync * @param {string} name name of the dependency module. * @param {string} basedir root dir to run the resolve from * @param {Boolean|CustomCache} (optional) * * if true: will choose the default global cache * * if false: will not cache * * if undefined or omitted, will choose the default global cache * * otherwise we assume the argument is an external cache of the form provided by resolve-package-path/lib/cache-group.js * * @return {string|null} a full path to the resolved package.json if found or null if not */ export = resolvePackagePath; function resolvePackagePath( target: string, basedir: string, _cache?: CacheGroup | boolean, ): string | null { let cache; if (_cache === undefined || _cache === null || _cache === true) { // if no cache specified, or if cache is true then use the global cache cache = CACHE; } else if (_cache === false) { // if cache is explicity false, create a throw-away cache; cache = new CacheGroup(); } else { // otherwise, assume the user has provided an alternative cache for the following form: // provided by resolve-package-path/lib/cache-group.js cache = _cache; } const key = target + '\x00' + basedir; let pkgPath; if (cache.PATH.has(key)) { pkgPath = cache.PATH.get(key); } else { try { // the custom `pnp` code here can be removed when yarn 1.13 is the // current release. This is due to Yarn 1.13 and resolve interoperating // together seamlessly. pkgPath = pnp ? pnp.resolveToUnqualified(target + '/package.json', basedir) : customResolvePackagePath(cache, target, basedir); } catch (e) { if (e !== null && typeof e === 'object') { const code: keyof typeof ALLOWED_ERROR_CODES = e.code; if (ALLOWED_ERROR_CODES[code] === true) { pkgPath = null; } else { throw e; } } else { throw e; } } cache.PATH.set(key, pkgPath); } return pkgPath; } resolvePackagePath._resetCache = function () { CACHE = new CacheGroup(); FIND_UP_CACHE = new Cache(); }; module resolvePackagePath { export let _CACHE: CacheGroup; export let _FIND_UP_CACHE = FIND_UP_CACHE; export let findUpPackagePath = _findUpPackagePath; } Object.defineProperty(resolvePackagePath, '_CACHE', { get: function () { return CACHE; }, }); Object.defineProperty(resolvePackagePath, '_FIND_UP_CACHE', { get: function () { return FIND_UP_CACHE; }, }); resolvePackagePath.getRealFilePath = function (filePath: string) { return getRealFilePath(CACHE.REAL_FILE_PATH, filePath); }; resolvePackagePath.getRealDirectoryPath = function (directoryPath: string) { return getRealDirectoryPath(CACHE.REAL_DIRECTORY_PATH, directoryPath); }; package/types/path-root.d.ts000644 0000000103 3560116604 013125 0ustar00000000 000000 declare function pathRoot(foo: string): string; export = pathRoot; package/tests/resolve-package-path-test.d.ts000644 0000000013 3560116604 016165 0ustar00000000 000000 export {}; package/tests/resolve-package-path-test.ts000644 0000024241 3560116604 015754 0ustar00000000 000000 'use strict'; import path = require('path'); import chai = require('chai'); import resolvePackagePath = require('../lib/resolve-package-path'); import Cache = require('../lib/cache'); import CacheGroup = require('../lib/cache-group'); import fs = require('fs'); import Project = require('fixturify-project'); const fixturesPath = path.join(__dirname, 'fixtures'); const assert = chai.assert; const _cache = new Cache(); describe('resolvePackagePath', function () { let project: Project, foo: Project, fooBar: Project, dedupped: Project, linked: Project, linkedBar: Project, unlinked: Project; beforeEach(function () { project = new Project('example', '0.0.0', project => { dedupped = project.addDependency('dedupped', '1.0.0', dedupped => { dedupped.addDependency('dedupped-child', '1.0.0'); }); foo = project.addDependency('foo', '1.0.0', foo => { fooBar = foo.addDependency('bar', '1.0.0'); }); unlinked = project.addDependency('linked', '1.0.0', linked => { linkedBar = linked.addDependency('bar', '1.0.0'); }); }); project.writeSync(); fs.symlinkSync('../../linked', `${foo.baseDir}/node_modules/linked`); linked = { baseDir: `${foo.baseDir}/node_modules/linked`, } as Project; }); afterEach(function () { project.dispose(); }); describe('._findPackagePath', function () { // NOTE: _findPackagePath requires that 'dir' must be non-empty and valid. it('finds linked package.json', function () { let result = resolvePackagePath._findPackagePath( _cache, 'linked/node_modules/bar/package.json', fooBar.baseDir, ); assert.equal( result, path.join(linkedBar.baseDir, 'package.json'), 'should resolve link to real path package.json', ); }); it('does not find invalid package.json file name', function () { let result = resolvePackagePath._findPackagePath( _cache, 'dedupped/package2.json', fooBar.baseDir, ); assert.isNull(result, 'invalid package.json should return null'); }); it('does not find invalid package file name', function () { let result = resolvePackagePath._findPackagePath( _cache, 'dedupped2/package.json', fooBar.baseDir, ); assert.isNull(result, 'invalid package filename should return null'); }); it('finds child package.json', function () { let result = resolvePackagePath._findPackagePath(_cache, 'bar/package.json', foo.baseDir); assert.equal( result, path.join(fooBar.baseDir, 'package.json'), '"child" package.json should resolve correctly', ); }); it('finds parent package.json', function () { let result = resolvePackagePath._findPackagePath(_cache, 'foo/package.json', fooBar.baseDir); assert.equal( result, path.join(foo.baseDir, 'package.json'), '"parent" package.json should resolve correctly', ); }); // Note: we do not need to provide a 'find self package.json' because this private function is only called // during resolvePackagePath when the path does not start with './'. it('finds uncle package.json', function () { let result = resolvePackagePath._findPackagePath( _cache, 'dedupped/package.json', fooBar.baseDir, ); assert.equal( result, path.join(dedupped.baseDir, 'package.json'), '"uncle" package.json should resolve correctly', ); }); }); describe('._getRealDirectoryPath', function () { let cache: Cache; let unlinkedDirPath = path.join(fixturesPath, 'node_modules', 'linked'); beforeEach(function () { cache = new Cache(); }); it('resolves linked dir through link to unlinked dir', function () { let result = resolvePackagePath._getRealDirectoryPath(cache, linked.baseDir); assert.equal(result, unlinked.baseDir, 'link should resolve to real path package.json'); assert.equal(cache.size, 1, 'cache should contain 1 entry'); assert.equal( cache.get(linked.baseDir), unlinked.baseDir, 'cached entry from linked path should be to unlinked path', ); }); it('resolves unlinked dir to itself', function () { let result1 = resolvePackagePath._getRealDirectoryPath(cache, linked.baseDir); // repeat just to load an entry let result2 = resolvePackagePath._getRealDirectoryPath(cache, unlinked.baseDir); assert.equal(cache.size, 2, 'cache should now contain 2 entries'); assert.equal( result1, result2, '2 cache entries should both have the same value (different keys)', ); assert.equal( result2, unlinked.baseDir, 'resolving the unlinked path should not change the value', ); assert.equal( cache.get(unlinked.baseDir), unlinked.baseDir, 'the cached value for the unlinked path should be to itself', ); }); it('resolves an existing file as null (it is not a directory)', function () { let filePath = path.join(unlinkedDirPath, 'index.js'); let result = resolvePackagePath._getRealDirectoryPath(cache, filePath); assert.isNull(result, 'reference to a file should return null'); assert.isNull(cache.get(filePath), 'cache reference to file should return null'); }); it('resolves a non-existent path as null', function () { let result = resolvePackagePath._getRealDirectoryPath(cache, unlinkedDirPath + '2'); assert.isNull(result, 'reference to a non-existent path correctly returns null'); }); }); describe('._getRealFilePath', function () { // create a temporary cache to make sure that we're actually caching things. let cache: Cache; beforeEach(function () { cache = new Cache(); }); it('resolves linked package.json through link to unlinked', function () { let result = resolvePackagePath._getRealFilePath( cache, path.join(linked.baseDir, 'package.json'), ); assert.equal( result, path.join(unlinked.baseDir, 'package.json'), 'link should resolve to real path package.json', ); assert.equal(cache.size, 1, 'cache should contain 1 entry'); assert.equal( cache.get(path.join(linked.baseDir, 'package.json')), path.join(unlinked.baseDir, 'package.json'), 'cached entry from linked path should be to unlinked path', ); }); it('resolves unlinked package.json to itself', function () { let result1 = resolvePackagePath._getRealFilePath( cache, path.join(linked.baseDir, 'package.json'), ); // repeat just to load an entry let result2 = resolvePackagePath._getRealFilePath( cache, path.join(unlinked.baseDir, 'package.json'), ); assert.equal(cache.size, 2, 'cache should now contain 2 entries'); assert.equal( result1, result2, '2 cache entries should both have the same value (different keys)', ); assert.equal( result2, path.join(unlinked.baseDir, 'package.json'), 'resolving the unlinked path should not change the value', ); assert.equal( cache.get(path.join(unlinked.baseDir, 'package.json')), path.join(unlinked.baseDir, 'package.json'), 'the cached value for the unlinked path should be to itself', ); }); it('resolves an existing directory as null (it is not a file)', function () { let result = resolvePackagePath._getRealFilePath(cache, project.root); assert.isNull(result, 'reference to a directory should return null'); assert.isNull(cache.get(project.root), 'cache reference to directory should return null'); }); it('resolves a non-existent path as null', function () { let result = resolvePackagePath._getRealFilePath(cache, project.root + '2'); assert.isNull(result, 'reference to a non-existent path correctly returns null'); }); }); describe('resolvePackagePath', function () { let caches: CacheGroup; beforeEach(function () { caches = new CacheGroup(); }); it('no module name', function () { assert.throws(() => resolvePackagePath(caches, undefined, '/'), TypeError); }); it('baseDir were the end of the path does not exist, but the start does and if resolution proceeds would be a hit', function () { assert.equal( resolvePackagePath(caches, 'bar', path.join(foo.baseDir, 'node_modules', 'does-not-exist')), path.join(foo.baseDir, 'node_modules', 'bar', 'package.json'), ); }); it('invalid basedir', function () { assert.equal(resolvePackagePath(caches, 'omg', 'asf'), null); }); it('linked directory as name', function () { let result = resolvePackagePath(caches, linked.baseDir, undefined); assert.equal( path.join(unlinked.baseDir, 'package.json'), result, 'should resolve to unlinked "linked/package.json"', ); }); it('through linked directory as dir to node_modules package', function () { let result = resolvePackagePath(caches, 'bar', linked.baseDir); assert.equal( path.join(unlinked.baseDir, 'node_modules', 'bar', 'package.json'), result, 'should resolve to unlinked "linked/node_ odules/bar/package.json"', ); }); it('.. relative path through "linked" directory', function () { let result = resolvePackagePath(caches, '../linked', fooBar.baseDir); assert.equal( path.join(unlinked.baseDir, 'package.json'), result, 'should resolve to unlinked "linked/package.json"', ); }); it('. relative path ', function () { let result = resolvePackagePath(caches, '..', path.join(foo.baseDir, 'node_modules')); assert.equal( path.join(foo.baseDir, 'package.json'), result, 'should resolve to "foo/package.json"', ); }); it('. relative empty path ', function () { let result = resolvePackagePath(caches, '.', foo.baseDir); assert.equal( path.join(foo.baseDir, 'package.json'), result, 'should resolve to "foo/package.json"', ); }); }); }); package/lib/resolve-package-path.d.ts000644 0000001174 3560116604 014625 0ustar00000000 000000 import Cache = require('./cache'); import CacheGroup = require('./cache-group'); declare function resolvePackagePath(caches: CacheGroup, name?: string, dir?: string): string | null; declare namespace resolvePackagePath { var _findPackagePath: (realFilePathCache: Cache, name: string, dir: string) => string | null; var _findUpPackagePath: (findUpCache: Cache, initialSearchDir: string) => string | null; var _getRealFilePath: (realFilePathCache: Cache, filePath: string) => string | null; var _getRealDirectoryPath: (realDirectoryPathCache: Cache, directoryPath: string) => string | null; } export = resolvePackagePath; package/lib/resolve-package-path.ts000644 0000024217 3560116604 014406 0ustar00000000 000000 'use strict'; // credit goes to https://github.com/davecombs // extracted in part from: https://github.com/stefanpenner/hash-for-dep/blob/15b2ebcf22024ceb2eb7907f8c412ae40f87b15e/lib/resolve-package-path.js#L1 // import fs = require('fs'); import path = require('path'); import pathRoot = require('path-root'); import Cache = require('./cache'); import CacheGroup = require('./cache-group'); /* * Define a regex that will match against the 'name' value passed into * resolvePackagePath. The regex corresponds to the following test: * Match any of the following 3 alternatives: * * 1) dot, then optional second dot, then / or nothing i.e. . ./ .. ../ OR * 2) / i.e. / OR * 3) (A-Za-z colon - [optional]), then / or \ i.e. optional drive letter + colon, then / or \ * * Basically, the three choices mean "explicitly relative or absolute path, on either * Unix/Linux or Windows" */ const ABSOLUTE_OR_RELATIVE_PATH_REGEX = /^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[\/\\])/; import shouldPreserveSymlinks = require('./should-preserve-symlinks'); const PRESERVE_SYMLINKS = shouldPreserveSymlinks(process); /* * Resolve the real path for a file. Return null if does not * exist or is not a file or FIFO, return the real path otherwise. * * Cache the result in the passed-in cache for performance, * keyed on the filePath passed in. * * NOTE: Because this is a private method, it does not attempt to normalize * the path passed in - it assumes the caller has done that. * * @private * @method _getRealFilePath * @param {Cache} realFilePathCache the Cache object to cache the real (resolved) * path in, keyed by filePath. See lib/cache.js and lib/cache-group.js * @param {String} filePath the path to the file of interest (which must have * been normalized, but not necessarily resolved to a real path). * @return {String} real path or null */ function _getRealFilePath(realFilePathCache: Cache, filePath: string) { if (realFilePathCache.has(filePath)) { return realFilePathCache.get(filePath); // could be null } let realPath = null; // null = 'FILE NOT FOUND' try { const stat = fs.statSync(filePath); // I don't know if Node would handle having the filePath actually // be a FIFO, but as the following is also part of the node-resolution // algorithm in resolve.sync(), we'll do the same check here. if (stat.isFile() || stat.isFIFO()) { if (PRESERVE_SYMLINKS) { realPath = filePath; } else { realPath = fs.realpathSync(filePath); } } } catch (e) { if (e === null || typeof e !== 'object' || e.code !== 'ENOENT') { throw e; } } realFilePathCache.set(filePath, realPath); return realPath; } /* * Resolve the real path for a directory, return null if does not * exist or is not a directory, return the real path otherwise. * * @param {Cache} realDirectoryPathCache the Cache object to cache the real (resolved) * path in, keyed by directoryPath. See lib/cache.js and lib/cache-group.js * @param {String} directoryPath the path to the directory of interest (which must have * been normalized, but not necessarily resolved to a real path). * @return {String} real path or null */ function _getRealDirectoryPath(realDirectoryPathCache: Cache, directoryPath: string) { if (realDirectoryPathCache.has(directoryPath)) { return realDirectoryPathCache.get(directoryPath); // could be null } let realPath = null; try { const stat = fs.statSync(directoryPath); if (stat.isDirectory()) { if (PRESERVE_SYMLINKS) { realPath = directoryPath; } else { realPath = fs.realpathSync(directoryPath); } } } catch (e) { if (e === null || typeof e !== 'object' || (e.code !== 'ENOENT' && e.code !== 'ENOTDIR')) { throw e; } } realDirectoryPathCache.set(directoryPath, realPath); return realPath; } /* * Given a package 'name' and starting directory, resolve to a real (existing) file path. * * Do it similar to how it is done in resolve.sync() - travel up the directory hierarchy, * attaching 'node-modules' to each directory and seeing if the directory exists and * has the relevant 'package.json' file we're searching for. It is *much* faster than * resolve.sync(), because we don't test that the requested name is a directory. * This routine assumes that it is only called when we don't already have * the cached entry. * * NOTE: it is valid for 'name' to be an absolute or relative path. * Because this is an internal routine, we'll require that 'dir' be non-empty * if this is called, to make things simpler (see resolvePackagePath). * * @param realFilePathCache the cache containing the real paths corresponding to * various file and directory paths (which may or may not be already resolved). * * @param name the 'name' of the module, i.e. x in require(x), but with * '/package.json' on the end. It is NOT referring to a directory (so we don't * have to do the directory checks that resolve.sync does). * NOTE: because this is an internal routine, for speed it does not check * that '/package.json' is actually the end of the name. * * @param dir the directory (MUST BE non-empty, and valid) to start from, appending the name to the * directory and checking that the file exists. Go up the directory hierarchy from there. * if name is itself an absolute path, * * @result the path to the actual package.json file that's found, or null if not. */ function _findPackagePath(realFilePathCache: Cache, name: string, dir: string) { const fsRoot = pathRoot(dir); let currPath = dir; while (currPath !== fsRoot) { // when testing for 'node_modules', need to allow names like NODE_MODULES, // which can occur with case-insensitive OSes. let endsWithNodeModules = path.basename(currPath).toLowerCase() === 'node_modules'; let filePath = path.join(currPath, endsWithNodeModules ? '' : 'node_modules', name); let realPath = _getRealFilePath(realFilePathCache, filePath); if (realPath) { return realPath; } if (endsWithNodeModules) { // go up past the ending node_modules directory so the next dirname // goes up past that (if ending in node_modules, going up just one // directory below will then add 'node_modules' on the next loop and // re-process this same node_modules directory. currPath = path.dirname(currPath); } currPath = path.dirname(currPath); } return null; } /* * Resolve the path to the nearest `package.json` from the given initial search * directory. * * @param {Cache} findUpCache - a cache of memoized results that is * prioritized to avoid I/O. * * @param {string} initialSearchDir - the normalized path to start searching * from. * * @return {string | null} - the deepest directory on the path to root from * `initialSearchDir` that contains a {{package.json}}, or `null` if no such * directory exists. */ function _findUpPackagePath(findUpCache: Cache, initialSearchDir: string) { let previous; let dir = initialSearchDir; let maybePackageJsonPath; let result = null; do { if (findUpCache.has(dir)) { result = findUpCache.get(dir); break; } maybePackageJsonPath = path.join(dir, 'package.json'); if (fs.existsSync(maybePackageJsonPath)) { result = maybePackageJsonPath; break; } previous = dir; dir = path.dirname(dir); } while (dir !== previous); findUpCache.set(initialSearchDir, result); return result; } /* * Resolve the path to a module's package.json file, if it exists. The * name and dir are as in hashForDep and ModuleEntry.locate. * * @param caches an instance of CacheGroup where information will be cached * during processing. * * @param name the 'name' of the module. The name may also be a path, * either relative or absolute. The path must be to a module DIRECTORY, NOT to the * package.json file in the directory, as we attach 'package.json' here. * * @param dir (optional) the root directory to run the path resolution from. * if dir is not provided, __dirname for this module is used instead. * * @return the realPath corresponding to the module's package.json file, or null * if that file is not found or is not a file. * * Note: 'name' is expected in the format expected for require(x), i.e., it is * resolved using the Node path-normalization rules. */ function resolvePackagePath(caches: CacheGroup, name?: string, dir?: string) { if (typeof name !== 'string' || name.length === 0) { throw new TypeError("resolvePackagePath: 'name' must be a non-zero-length string."); } // Perform tests similar to those in resolve.sync(). let basedir = dir || __dirname; // Ensure that basedir is an absolute path at this point. If it does not refer to // a real directory, go up the path until a real directory is found, or return an error. // BUG!: this will never throw an exception, at least on Unix/Linux. If the path is // relative, path.resolve() will make it absolute by putting the current directory // before it, so it won't fail. If the path is already absolute, / will always be // valid, so again it won't fail. let absoluteStart = path.resolve(basedir); while (_getRealDirectoryPath(caches.REAL_DIRECTORY_PATH, absoluteStart) === null) { absoluteStart = path.dirname(absoluteStart); } if (!absoluteStart) { let error = new TypeError( "resolvePackagePath: 'dir' or one of the parent directories in its path must refer to a valid directory.", ); (error as any).code = 'MODULE_NOT_FOUND'; throw error; } if (ABSOLUTE_OR_RELATIVE_PATH_REGEX.test(name)) { let res = path.resolve(absoluteStart, name); return _getRealFilePath(caches.REAL_FILE_PATH, path.join(res, 'package.json')); // XXX Do we need to handle the core(x) case too? Not sure. } else { return _findPackagePath(caches.REAL_FILE_PATH, path.join(name, 'package.json'), absoluteStart); } } export = resolvePackagePath; resolvePackagePath._findPackagePath = _findPackagePath; resolvePackagePath._findUpPackagePath = _findUpPackagePath; resolvePackagePath._getRealFilePath = _getRealFilePath; resolvePackagePath._getRealDirectoryPath = _getRealDirectoryPath; package/tests/should-preserve-symlinks-test.d.ts000644 0000000013 3560116604 017161 0ustar00000000 000000 export {}; package/tests/should-preserve-symlinks-test.ts000644 0000002045 3560116604 016746 0ustar00000000 000000 import shouldPreserveSymlinks = require('../lib/should-preserve-symlinks'); import chai = require('chai'); const expect = chai.expect; describe('shouldPreserveSymlinks', function () { it('works', function () { expect( shouldPreserveSymlinks({ execArgv: [], env: { NODE_PRESERVE_SYMLINKS: '1' }, }), ).to.eql(true); expect( shouldPreserveSymlinks({ execArgv: [], env: { NODE_PRESERVE_SYMLINKS: 'false' }, }), ).to.eql(true); expect( shouldPreserveSymlinks({ execArgv: [], env: { NODE_PRESERVE_SYMLINKS: '' }, }), ).to.eql(false); expect(shouldPreserveSymlinks({ execArgv: [], env: { foo: 'bar' } })).to.eql(false); expect(shouldPreserveSymlinks({ execArgv: ['--preserve-symlinks'], env: {} })).to.eql(true); expect( shouldPreserveSymlinks({ execArgv: ['--a', '--preserve-symlinks', '--b'], env: {}, }), ).to.eql(true); expect(shouldPreserveSymlinks({ execArgv: [], env: {} })).to.eql(false); }); }); package/lib/should-preserve-symlinks.d.ts000644 0000000106 3560116604 015613 0ustar00000000 000000 declare const _default: (process: any) => boolean; export = _default; package/lib/should-preserve-symlinks.ts000644 0000000607 3560116604 015377 0ustar00000000 000000 'use strict'; function includes(array: [string], entry: string) { for (let i = 0; i < array.length; i++) { if (array[i] === entry) { return true; } } return false; } /* * utility to detect if node is respective symlinks or not */ export = function (process: any) { return !!process.env.NODE_PRESERVE_SYMLINKS || includes(process.execArgv, '--preserve-symlinks'); }; package/.github/workflows/ci.yml000644 0000001101 3560116604 013764 0ustar00000000 000000 name: CI on: push: branches: - master - 'v*' # older version branches tags: - '*' pull_request: {} schedule: - cron: '0 6 * * 0' # weekly, on sundays jobs: test: name: Tests runs-on: ${{ matrix.os }} strategy: matrix: node: ['12', '14'] os: [ubuntu-latest, macOS-latest, windows-latest] steps: - uses: actions/checkout@v1 - uses: volta-cli/action@v1 with: node-version: ${{ matrix.node }} - name: install dependencies run: yarn - name: test run: yarn test