Ajout de promotion et de commande
This commit is contained in:
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"WebFetch(domain:github.com)",
|
||||
"WebSearch",
|
||||
"WebFetch(domain:raw.githubusercontent.com)",
|
||||
"WebFetch(domain:api.github.com)",
|
||||
"WebFetch(domain:www.npmjs.com)",
|
||||
"Bash(tree:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash(npm view:*)",
|
||||
"Bash(node test/resolver_sync.js:*)",
|
||||
"Bash(xxd:*)",
|
||||
"Bash(npm info:*)",
|
||||
"Bash(grep:*)",
|
||||
"Bash(npm run submodule:update:*)",
|
||||
"Bash(git rev-parse:*)",
|
||||
"Bash(git -C test/list-exports diff packages/tests/fixtures/resolve-2/project/test/resolver/nested_symlinks/mylib/async.js)",
|
||||
"Bash(gh run list:*)",
|
||||
"Bash(ls:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
max_line_length = 200
|
||||
|
||||
[*.{,m}js]
|
||||
block_comment_start = /*
|
||||
block_comment = *
|
||||
block_comment_end = */
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
indent_size = 1
|
||||
|
||||
[{package.json,*.mjs}]
|
||||
indent_style = tab
|
||||
|
||||
[CHANGELOG.md]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[{*.json,Makefile,CONTRIBUTING.md,readme.markdown}]
|
||||
max_line_length = unset
|
||||
|
||||
[test/{dotdot,resolver,module_dir,multirepo,node_path,pathfilter,precedence}/**/*]
|
||||
indent_style = unset
|
||||
indent_size = unset
|
||||
max_line_length = unset
|
||||
insert_final_newline = unset
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [ljharb]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: npm/resolve
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
# Incident Response Process for **resolve**
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We take the security of **resolve** very seriously. If you believe you’ve found a security vulnerability, please inform us responsibly through coordinated disclosure.
|
||||
|
||||
### How to Report
|
||||
|
||||
> **Do not** report security vulnerabilities through public GitHub issues, discussions, or social media.
|
||||
|
||||
Instead, please use one of these secure channels:
|
||||
|
||||
1. **GitHub Security Advisories**
|
||||
Use the **Report a vulnerability** button in the Security tab of the [browserify/resolve repository](https://github.com/browserify/resolve).
|
||||
|
||||
2. **Email**
|
||||
Follow the posted [Security Policy](https://github.com/browserify/resolve/security/policy).
|
||||
|
||||
### What to Include
|
||||
|
||||
**Required Information:**
|
||||
- Brief description of the vulnerability type
|
||||
- Affected version(s) and components
|
||||
- Steps to reproduce the issue
|
||||
- Impact assessment (what an attacker could achieve)
|
||||
- Confirm the issue is not present in test files (in other words, only via the official entry points in `exports`)
|
||||
|
||||
**Helpful Additional Details:**
|
||||
- Full paths of affected source files
|
||||
- Specific commit or branch where the issue exists
|
||||
- Required configuration to reproduce
|
||||
- Proof-of-concept code (if available)
|
||||
- Suggested mitigation or fix
|
||||
|
||||
## Our Response Process
|
||||
|
||||
**Timeline Commitments:**
|
||||
- **Initial acknowledgment**: Within 24 hours
|
||||
- **Detailed response**: Within 3 business days
|
||||
- **Status updates**: Every 7 days until resolved
|
||||
- **Resolution target**: 90 days for most issues
|
||||
|
||||
**What We’ll Do:**
|
||||
1. Acknowledge your report and assign a tracking ID
|
||||
2. Assess the vulnerability and determine severity
|
||||
3. Develop and test a fix
|
||||
4. Coordinate disclosure timeline with you
|
||||
5. Release a security update and publish an advisory and CVE
|
||||
6. Credit you in our security advisory (if desired)
|
||||
|
||||
## Disclosure Policy
|
||||
|
||||
- **Coordinated disclosure**: We’ll work with you on timing
|
||||
- **Typical timeline**: 90 days from report to public disclosure
|
||||
- **Early disclosure**: If actively exploited
|
||||
- **Delayed disclosure**: For complex issues
|
||||
|
||||
## Scope
|
||||
|
||||
**In Scope:**
|
||||
- **resolve** package (all supported versions)
|
||||
- Official examples and documentation
|
||||
- Core resolution APIs
|
||||
- Dependencies with direct security implications
|
||||
|
||||
**Out of Scope:**
|
||||
- Third-party wrappers or extensions
|
||||
- Bundler-specific integrations
|
||||
- Social engineering or physical attacks
|
||||
- Theoretical vulnerabilities without practical exploitation
|
||||
- Issues in non-production files
|
||||
|
||||
## Security Measures
|
||||
|
||||
**Our Commitments:**
|
||||
- Regular vulnerability scanning via `npm audit`
|
||||
- Automated security checks in CI/CD (GitHub Actions)
|
||||
- Secure coding practices and mandatory code review
|
||||
- Prompt patch releases for critical issues
|
||||
|
||||
**User Responsibilities:**
|
||||
- Keep **resolve** updated
|
||||
- Monitor dependency vulnerabilities
|
||||
- Follow secure configuration guidelines for module resolution
|
||||
|
||||
## Legal Safe Harbor
|
||||
|
||||
**We will NOT:**
|
||||
- Initiate legal action
|
||||
- Contact law enforcement
|
||||
- Suspend or terminate your access
|
||||
|
||||
**You must:**
|
||||
- Only test against your own installations
|
||||
- Not access, modify, or delete user data
|
||||
- Not degrade service availability
|
||||
- Not publicly disclose before coordinated disclosure
|
||||
- Act in good faith
|
||||
|
||||
## Recognition
|
||||
|
||||
- **Advisory Credits**: Credit in GitHub Security Advisories (unless anonymous)
|
||||
|
||||
## Security Updates
|
||||
|
||||
**Stay Informed:**
|
||||
- Subscribe to npm updates for **resolve**
|
||||
- Enable GitHub Security Advisory notifications
|
||||
|
||||
**Update Process:**
|
||||
- Patch releases (e.g., 1.22.10 → 1.22.11)
|
||||
- Out-of-band releases for critical issues
|
||||
- Advisories via GitHub Security Advisories
|
||||
|
||||
## Contact Information
|
||||
|
||||
- **Security reports**: Security tab of [browserify/resolve](https://github.com/browserify/resolve/security)
|
||||
- **General inquiries**: GitHub Discussions or Issues
|
||||
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
## Threat Model for resolve (module path resolution library)
|
||||
|
||||
### 1. Library Overview
|
||||
|
||||
- **Library Name:** resolve
|
||||
- **Brief Description:** Implements Node.js `require.resolve()` algorithm for synchronous and asynchronous file path resolution. Used to locate modules and files in Node.js projects.
|
||||
- **Key Public APIs/Functions:** `resolve.sync()` / `resolve/sync`, `resolve()` / `resolve/async`
|
||||
|
||||
### 2. Define Scope
|
||||
|
||||
This threat model focuses on the core path resolution algorithm, including filesystem interaction, option handling, and cache management.
|
||||
|
||||
### 3. Conceptual System Diagram
|
||||
|
||||
```
|
||||
Caller Application → resolve(id, options) → Resolution Algorithm → File System
|
||||
│
|
||||
└→ Options Handling
|
||||
└→ Cache System
|
||||
```
|
||||
|
||||
**Trust Boundaries:**
|
||||
- **Input module IDs:** May come from untrusted sources (user input, configuration)
|
||||
- **Filesystem access:** The library interacts with the filesystem to resolve paths
|
||||
- **Options:** Provided by the caller
|
||||
- **Cache:** Used to improve performance, but could be a vector for tampering or information disclosure if not handled securely
|
||||
|
||||
### 4. Identify Assets
|
||||
|
||||
- **Integrity of resolution output:** Ensure correct and safe file path matching.
|
||||
- **Confidentiality of configuration:** Prevent sensitive path information from being leaked.
|
||||
- **Availability/performance for host application:** Prevent crashes or resource exhaustion.
|
||||
- **Security of host application:** Prevent path traversal or unintended filesystem access.
|
||||
- **Reputation of library:** Maintain trust by avoiding supply chain attacks and vulnerabilities[1][3][4].
|
||||
|
||||
### 5. Identify Threats
|
||||
|
||||
| Component / API / Interaction | S | T | R | I | D | E |
|
||||
|-----------------------------------------------------|----|----|----|----|----|----|
|
||||
| Public API Call (`resolve/async`, `resolve/sync`) | ✓ | ✓ | – | ✓ | – | – |
|
||||
| Filesystem Access | – | ✓ | – | ✓ | ✓ | – |
|
||||
| Options Handling | ✓ | ✓ | – | ✓ | – | – |
|
||||
| Cache System | – | ✓ | – | ✓ | – | – |
|
||||
|
||||
**Key Threats:**
|
||||
- **Spoofing:** Malicious module IDs mimicking legitimate packages, or spoofing configuration options[1].
|
||||
- **Tampering:** Caller-provided paths altering resolution order, or cache tampering leading to incorrect results[1][4].
|
||||
- **Information Disclosure:** Error messages revealing filesystem structure or sensitive paths[1].
|
||||
- **Denial of Service:** Recursive or excessive resolution exhausting filesystem handles or causing application crashes[1].
|
||||
- **Path Traversal:** Malicious input allowing access to files outside the intended directory[4].
|
||||
|
||||
### 6. Mitigation/Countermeasures
|
||||
|
||||
| Threat Identified | Proposed Mitigation |
|
||||
|--------------------------------------------|---------------------|
|
||||
| Spoofing (malicious module IDs/config) | Sanitize input IDs; validate against known patterns; restrict `basedir` to app-controlled paths[1][4]. |
|
||||
| Tampering (path traversal, cache) | Validate input IDs for directory escapes; secure cache reads/writes; restrict cache to trusted sources[1][4]. |
|
||||
| Information Disclosure (error messages) | Generic "not found" errors without internal paths; avoid exposing sensitive configuration in errors[1]. |
|
||||
| Denial of Service (resource exhaustion) | Limit recursive resolution depth; implement timeout; monitor for excessive filesystem operations[1]. |
|
||||
|
||||
### 7. Risk Ranking
|
||||
|
||||
- **High:** Path traversal via malicious IDs (if not properly mitigated)
|
||||
- **Medium:** Cache tampering or spoofing (if cache is not secured)
|
||||
- **Low:** Information disclosure in errors (if error handling is generic)
|
||||
|
||||
### 8. Next Steps & Review
|
||||
|
||||
1. **Implement input sanitization for module IDs and configuration.**
|
||||
2. **Add resolution depth limiting and timeout.**
|
||||
3. **Audit cache handling for race conditions and tampering.**
|
||||
4. **Regularly review dependencies for vulnerabilities.**
|
||||
5. **Keep documentation and threat model up to date.**
|
||||
6. **Monitor for new threats as the ecosystem and library evolve[1][3].**
|
||||
Generated
+4
@@ -0,0 +1,4 @@
|
||||
[submodule "test/list-exports"]
|
||||
path = test/list-exports
|
||||
url = https://github.com/ljharb/list-exports.git
|
||||
shallow = true
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2012 James Halliday
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
# Security
|
||||
|
||||
Please file a private vulnerability via GitHub, email [@ljharb](https://github.com/ljharb), or see https://tidelift.com/security if you have a potential security vulnerability to report.
|
||||
|
||||
## Incident Response
|
||||
|
||||
See our [Incident Response Process](.github/INCIDENT_RESPONSE_PROCESS.md).
|
||||
|
||||
## Threat Model
|
||||
|
||||
See [THREAT_MODEL.md](./THREAT_MODEL.md).
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = require('./lib/async');
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
|
||||
if (
|
||||
String(process.env.npm_lifecycle_script).slice(0, 8) !== 'resolve '
|
||||
&& (
|
||||
!process.argv
|
||||
|| process.argv.length < 2
|
||||
|| (process.argv[1] !== __filename && fs.statSync(process.argv[1]).ino !== fs.statSync(__filename).ino)
|
||||
|| (process.env.npm_lifecycle_event !== 'npx' && process.env._ && fs.realpathSync(path.resolve(process.env._)) !== __filename)
|
||||
)
|
||||
) {
|
||||
console.error('Error: `resolve` must be run directly as an executable');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var supportsPreserveSymlinkFlag = require('supports-preserve-symlinks-flag');
|
||||
|
||||
var preserveSymlinks = false;
|
||||
for (var i = 2; i < process.argv.length; i += 1) {
|
||||
if (process.argv[i].slice(0, 2) === '--') {
|
||||
if (supportsPreserveSymlinkFlag && process.argv[i] === '--preserve-symlinks') {
|
||||
preserveSymlinks = true;
|
||||
} else if (process.argv[i].length > 2) {
|
||||
console.error('Unknown argument ' + process.argv[i].replace(/[=].*$/, ''));
|
||||
process.exit(2);
|
||||
}
|
||||
process.argv.splice(i, 1);
|
||||
i -= 1;
|
||||
if (process.argv[i] === '--') { break; } // eslint-disable-line no-restricted-syntax
|
||||
}
|
||||
}
|
||||
|
||||
if (process.argv.length < 3) {
|
||||
console.error('Error: `resolve` expects a specifier');
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
var resolve = require('../');
|
||||
|
||||
var result = resolve.sync(process.argv[2], {
|
||||
basedir: process.cwd(),
|
||||
preserveSymlinks: preserveSymlinks
|
||||
});
|
||||
|
||||
console.log(result);
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
import ljharb from '@ljharb/eslint-config/flat';
|
||||
|
||||
export default [
|
||||
...ljharb,
|
||||
{
|
||||
ignores: [
|
||||
'test/resolver/malformed_package_json/package.json',
|
||||
'test/list-exports/**',
|
||||
],
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'array-bracket-newline': 'off',
|
||||
complexity: 'off',
|
||||
'consistent-return': 'off',
|
||||
curly: 'off',
|
||||
'dot-notation': ['error', { allowKeywords: true }],
|
||||
eqeqeq: ['error', 'allow-null'],
|
||||
'func-name-matching': 'off',
|
||||
'func-style': 'off',
|
||||
'global-require': 'warn',
|
||||
'id-length': ['error', { min: 1, max: 40 }],
|
||||
'max-depth': 'off',
|
||||
'max-lines-per-function': 'off',
|
||||
'max-lines': 'off',
|
||||
'max-nested-callbacks': 'off',
|
||||
'max-params': 'off',
|
||||
'max-statements-per-line': ['error', { max: 2 }],
|
||||
'max-statements': 'off',
|
||||
'multiline-comment-style': 'off',
|
||||
'no-extra-parens': 'off',
|
||||
'no-magic-numbers': 'off',
|
||||
'no-shadow': 'off',
|
||||
'no-use-before-define': 'off',
|
||||
'sort-keys': 'off',
|
||||
strict: 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.js'],
|
||||
rules: {
|
||||
indent: ['error', 4],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['bin/**'],
|
||||
rules: {
|
||||
'no-process-exit': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['example/**'],
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['test/resolver/nested_symlinks/mylib/*.js'],
|
||||
rules: {
|
||||
'no-throw-literal': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['test/**'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 5,
|
||||
parserOptions: {
|
||||
allowReserved: false,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'dot-notation': ['error', { allowPattern: 'throws' }],
|
||||
'max-lines': 'off',
|
||||
'max-lines-per-function': 'off',
|
||||
'no-unused-vars': ['error', {
|
||||
vars: 'all',
|
||||
args: 'none',
|
||||
caughtErrors: 'none',
|
||||
}],
|
||||
},
|
||||
},
|
||||
];
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
var resolve = require('../');
|
||||
resolve('tap', { basedir: __dirname }, function (err, res) {
|
||||
if (err) console.error(err);
|
||||
else console.log(res);
|
||||
});
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
var resolve = require('../');
|
||||
var res = resolve.sync('tap', { basedir: __dirname });
|
||||
console.log(res);
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
var async = require('./lib/async');
|
||||
async.sync = require('./lib/sync');
|
||||
|
||||
module.exports = async;
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import async from 'resolve/async';
|
||||
import sync from 'resolve/sync';
|
||||
|
||||
export { async, sync };
|
||||
+641
File diff suppressed because it is too large
Load Diff
+12
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
var $Error = require('es-errors');
|
||||
|
||||
module.exports = function () {
|
||||
// see https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
|
||||
var origPrepareStackTrace = $Error.prepareStackTrace;
|
||||
$Error.prepareStackTrace = function (_, stack) { return stack; };
|
||||
var stack = (new $Error()).stack;
|
||||
$Error.prepareStackTrace = origPrepareStackTrace;
|
||||
return stack[2].getFileName();
|
||||
};
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
'use strict';
|
||||
|
||||
var objectKeys = require('object-keys');
|
||||
var $Error = require('es-errors');
|
||||
|
||||
// Check if an exports map key looks like a subpath (starts with '.')
|
||||
function isSubpathKey(key) {
|
||||
return key.length > 0 && key.charAt(0) === '.';
|
||||
}
|
||||
|
||||
// Normalize the exports field into a map of subpath -> target
|
||||
function normalizeExports(exportsField) {
|
||||
if (typeof exportsField === 'string') {
|
||||
return { __proto__: null, '.': exportsField };
|
||||
}
|
||||
if (Array.isArray(exportsField)) {
|
||||
return { __proto__: null, '.': exportsField };
|
||||
}
|
||||
if (typeof exportsField === 'object' && exportsField !== null) {
|
||||
var keys = objectKeys(exportsField);
|
||||
if (keys.length === 0) {
|
||||
return { __proto__: null };
|
||||
}
|
||||
// If any key starts with '.', it's a subpath map
|
||||
// If no key starts with '.', it's a conditions object for '.'
|
||||
var hasSubpath = false;
|
||||
for (var i = 0; !hasSubpath && i < keys.length; i++) {
|
||||
if (isSubpathKey(keys[i])) {
|
||||
hasSubpath = true;
|
||||
}
|
||||
}
|
||||
// Copy to new object with null prototype
|
||||
var result = { __proto__: null };
|
||||
for (var j = 0; j < keys.length; j++) {
|
||||
result[keys[j]] = exportsField[keys[j]];
|
||||
}
|
||||
if (hasSubpath) {
|
||||
return result;
|
||||
}
|
||||
return { __proto__: null, '.': result };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Resolve a target value through conditions
|
||||
// conditions: array of condition strings, or null (broken: string/array only)
|
||||
function resolveTarget(target, conditions) {
|
||||
if (typeof target === 'string') {
|
||||
return target;
|
||||
}
|
||||
|
||||
if (target === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Array.isArray(target)) {
|
||||
for (var i = 0; i < target.length; i++) {
|
||||
var resolved = resolveTarget(target[i], conditions);
|
||||
if (resolved !== null && typeof resolved !== 'undefined') {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof target === 'object') {
|
||||
// If no conditions supported (broken category), can't resolve objects
|
||||
if (conditions === null) {
|
||||
return null;
|
||||
}
|
||||
var keys = objectKeys(target);
|
||||
for (var j = 0; j < keys.length; j++) {
|
||||
var key = keys[j];
|
||||
for (var k = 0; k < conditions.length; k++) {
|
||||
if (key === conditions[k]) {
|
||||
var result = resolveTarget(target[key], conditions);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Validate a resolved path
|
||||
function validateTarget(target) {
|
||||
if (typeof target !== 'string') {
|
||||
return false;
|
||||
}
|
||||
if (target.slice(0, 2) !== './') {
|
||||
return false;
|
||||
}
|
||||
if (target.indexOf('/node_modules/') !== -1) {
|
||||
return false;
|
||||
}
|
||||
// Check for '..' path traversal
|
||||
var parts = target.split('/');
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
if (parts[i] === '..') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Find the best pattern match for a subpath among keys with '*'
|
||||
function findPatternMatch(subpath, exportsMap, allowPatternTrailers) {
|
||||
var keys = objectKeys(exportsMap);
|
||||
var bestKey = null;
|
||||
var bestPrefixLen = -1;
|
||||
var bestMatch = '';
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var key = keys[i];
|
||||
var starIndex = key.indexOf('*');
|
||||
// Key must have exactly one '*'
|
||||
if (starIndex !== -1 && key.indexOf('*', starIndex + 1) === -1) {
|
||||
var prefix = key.slice(0, starIndex);
|
||||
var suffix = key.slice(starIndex + 1);
|
||||
|
||||
// Pattern trailers: if suffix is non-empty after *, need allowPatternTrailers
|
||||
if (suffix.length === 0 || allowPatternTrailers) {
|
||||
if (
|
||||
subpath.length >= prefix.length + suffix.length
|
||||
&& subpath.slice(0, prefix.length) === prefix
|
||||
&& (suffix.length === 0 || subpath.slice(subpath.length - suffix.length) === suffix)
|
||||
) {
|
||||
// Longest prefix wins
|
||||
if (prefix.length > bestPrefixLen) {
|
||||
bestPrefixLen = prefix.length;
|
||||
bestKey = key;
|
||||
bestMatch = subpath.slice(prefix.length, subpath.length - suffix.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestKey !== null) {
|
||||
return {
|
||||
__proto__: null, key: bestKey, match: bestMatch
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Find directory slash match (for categories that support it)
|
||||
function findDirSlashMatch(subpath, exportsMap) {
|
||||
var keys = objectKeys(exportsMap);
|
||||
var bestKey = null;
|
||||
var bestPrefixLen = -1;
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var key = keys[i];
|
||||
if (key.charAt(key.length - 1) === '/') {
|
||||
if (subpath.slice(0, key.length) === key && key.length > bestPrefixLen) {
|
||||
bestPrefixLen = key.length;
|
||||
bestKey = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestKey !== null) {
|
||||
return {
|
||||
__proto__: null, key: bestKey, remainder: subpath.slice(bestKey.length)
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Replace '*' in target string with match value
|
||||
function substitutePattern(target, match) {
|
||||
if (typeof target === 'string') {
|
||||
return target.split('*').join(match);
|
||||
}
|
||||
if (Array.isArray(target)) {
|
||||
var result = [];
|
||||
for (var i = 0; i < target.length; i++) {
|
||||
result.push(substitutePattern(target[i], match));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (typeof target === 'object' && target !== null) {
|
||||
var obj = { __proto__: null };
|
||||
var keys = objectKeys(target);
|
||||
for (var j = 0; j < keys.length; j++) {
|
||||
obj[keys[j]] = substitutePattern(target[keys[j]], match);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
// Main exports resolution function
|
||||
// exportsField: the value of package.json "exports"
|
||||
// subpath: the subpath to resolve (e.g., "." or "./foo/bar")
|
||||
// conditions: array of condition strings, or null for broken category
|
||||
// options: { patterns: boolean, patternTrailers: boolean, dirSlash: boolean }
|
||||
// Returns: resolved relative path string, or null if no exports field
|
||||
// Throws: when exports field exists but subpath is not exported
|
||||
module.exports = function resolveExports(exportsField, subpath, conditions, options) {
|
||||
if (typeof exportsField === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
var exportsMap = normalizeExports(exportsField);
|
||||
if (!exportsMap) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var allowPatterns = options && options.patterns;
|
||||
var allowPatternTrailers = options && options.patternTrailers;
|
||||
var allowDirSlash = options && options.dirSlash;
|
||||
|
||||
// 1. Exact key match
|
||||
if (typeof exportsMap[subpath] !== 'undefined') {
|
||||
var resolved = resolveTarget(exportsMap[subpath], conditions);
|
||||
if (resolved !== null && typeof resolved !== 'undefined') {
|
||||
if (!validateTarget(resolved)) {
|
||||
var invalidError = new $Error('Invalid "exports" target "' + resolved + '" for subpath "' + subpath + '"');
|
||||
invalidError.code = 'ERR_INVALID_PACKAGE_CONFIG';
|
||||
throw invalidError;
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
// Target exists but resolved to null (explicitly not exported)
|
||||
var notExportedError = new $Error('Package subpath "' + subpath + '" is not defined by "exports"');
|
||||
notExportedError.code = 'ERR_PACKAGE_PATH_NOT_EXPORTED';
|
||||
throw notExportedError;
|
||||
}
|
||||
|
||||
// 2. Pattern match (keys with '*')
|
||||
if (allowPatterns) {
|
||||
var patternResult = findPatternMatch(subpath, exportsMap, allowPatternTrailers);
|
||||
if (patternResult) {
|
||||
var substituted = substitutePattern(exportsMap[patternResult.key], patternResult.match);
|
||||
var patternResolved = resolveTarget(substituted, conditions);
|
||||
if (patternResolved !== null && typeof patternResolved !== 'undefined') {
|
||||
if (!validateTarget(patternResolved)) {
|
||||
var patternInvalidError = new $Error('Invalid "exports" target "' + patternResolved + '" for subpath "' + subpath + '"');
|
||||
patternInvalidError.code = 'ERR_INVALID_PACKAGE_CONFIG';
|
||||
throw patternInvalidError;
|
||||
}
|
||||
return patternResolved;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Directory slash match (for older categories)
|
||||
if (allowDirSlash) {
|
||||
var dirResult = findDirSlashMatch(subpath, exportsMap);
|
||||
if (dirResult) {
|
||||
var dirTarget = resolveTarget(exportsMap[dirResult.key], conditions);
|
||||
if (dirTarget !== null && typeof dirTarget !== 'undefined' && typeof dirTarget === 'string') {
|
||||
var dirResolved = dirTarget + dirResult.remainder;
|
||||
if (!validateTarget(dirResolved)) {
|
||||
var dirInvalidError = new $Error('Invalid "exports" target "' + dirResolved + '" for subpath "' + subpath + '"');
|
||||
dirInvalidError.code = 'ERR_INVALID_PACKAGE_CONFIG';
|
||||
throw dirInvalidError;
|
||||
}
|
||||
return dirResolved;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var err = new $Error('Package subpath "' + subpath + '" is not defined by "exports"');
|
||||
err.code = 'ERR_PACKAGE_PATH_NOT_EXPORTED';
|
||||
throw err;
|
||||
};
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
'use strict';
|
||||
|
||||
var isCategory = require('node-exports-info/isCategory');
|
||||
var getCategoriesForRange = require('node-exports-info/getCategoriesForRange');
|
||||
var $TypeError = require('es-errors/type');
|
||||
|
||||
var selectMostRestrictive = require('./select-most-restrictive');
|
||||
|
||||
// Determine the active exports category from resolve options
|
||||
// Returns null if no exports resolution should be applied
|
||||
// Returns 'engines' if engines: true (needs consumer package.json lookup)
|
||||
// Throws TypeError if invalid options are provided
|
||||
/** @type {(opts?: { exportsCategory?: import('node-exports-info/getCategory').Category, engines?: boolean | string }) => null | import('node-exports-info/getCategory').Category} */
|
||||
module.exports = function getExportsCategory(opts) {
|
||||
if (!opts) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var hasCategory = typeof opts.exportsCategory !== 'undefined';
|
||||
var engines = opts.engines;
|
||||
var hasEngines = typeof engines !== 'undefined' && engines !== false;
|
||||
|
||||
if (hasCategory && hasEngines) {
|
||||
throw new $TypeError('`exportsCategory` and `engines` are mutually exclusive.');
|
||||
}
|
||||
|
||||
if (hasCategory) {
|
||||
if (!isCategory(opts.exportsCategory)) {
|
||||
var catError = new $TypeError('Invalid exports category: "' + opts.exportsCategory + '"');
|
||||
catError.code = 'INVALID_EXPORTS_CATEGORY';
|
||||
throw catError;
|
||||
}
|
||||
return opts.exportsCategory;
|
||||
}
|
||||
|
||||
if (hasEngines) {
|
||||
// engines: true means read from consumer's package.json
|
||||
if (engines === true) {
|
||||
return 'engines';
|
||||
}
|
||||
|
||||
// engines must be a non-empty string (semver range)
|
||||
if (typeof engines !== 'string' || engines === '') {
|
||||
throw new $TypeError('`engines` must be `true`, `false`, or a non-empty string semver range.');
|
||||
}
|
||||
|
||||
var categories = getCategoriesForRange(engines);
|
||||
return selectMostRestrictive(categories);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
var os = require('os');
|
||||
|
||||
// adapted from https://github.com/sindresorhus/os-homedir/blob/11e089f4754db38bb535e5a8416320c4446e8cfd/index.js
|
||||
|
||||
module.exports = os.homedir || function homedir() {
|
||||
var home = process.env.HOME;
|
||||
var user = process.env.LOGNAME || process.env.USER || process.env.LNAME || process.env.USERNAME;
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
return process.env.USERPROFILE || process.env.HOMEDRIVE + process.env.HOMEPATH || home || null;
|
||||
}
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
return home || (user ? '/Users/' + user : null);
|
||||
}
|
||||
|
||||
if (process.platform === 'linux') {
|
||||
return home || (process.getuid() === 0 ? '/root' : (user ? '/home/' + user : null));
|
||||
}
|
||||
|
||||
return home || null;
|
||||
};
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
var path = require('path');
|
||||
var parse = path.parse || require('path-parse'); // eslint-disable-line global-require
|
||||
|
||||
var driveLetterRegex = /^([A-Za-z]:)/;
|
||||
var uncPathRegex = /^\\\\/;
|
||||
|
||||
function getNodeModulesDirs(absoluteStart, modules) {
|
||||
var prefix = '/';
|
||||
if (driveLetterRegex.test(absoluteStart)) {
|
||||
prefix = '';
|
||||
} else if (uncPathRegex.test(absoluteStart)) {
|
||||
prefix = '\\\\';
|
||||
}
|
||||
|
||||
var paths = [absoluteStart];
|
||||
var parsed = parse(absoluteStart);
|
||||
while (parsed.dir !== paths[paths.length - 1]) {
|
||||
paths.push(parsed.dir);
|
||||
parsed = parse(parsed.dir);
|
||||
}
|
||||
|
||||
return paths.reduce(function (dirs, aPath) {
|
||||
return dirs.concat(modules.map(function (moduleDir) {
|
||||
return path.resolve(prefix, aPath, moduleDir);
|
||||
}));
|
||||
}, []);
|
||||
}
|
||||
|
||||
module.exports = function nodeModulesPaths(start, opts, request) {
|
||||
var modules = opts && opts.moduleDirectory
|
||||
? [].concat(opts.moduleDirectory)
|
||||
: ['node_modules'];
|
||||
|
||||
if (opts && typeof opts.paths === 'function') {
|
||||
return opts.paths(
|
||||
request,
|
||||
start,
|
||||
function () { return getNodeModulesDirs(start, modules); },
|
||||
opts
|
||||
);
|
||||
}
|
||||
|
||||
var dirs = getNodeModulesDirs(start, modules);
|
||||
return opts && opts.paths ? dirs.concat(opts.paths) : dirs;
|
||||
};
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
module.exports = function (x, opts) {
|
||||
/**
|
||||
* This file is purposefully a passthrough. It's expected that third-party
|
||||
* environments will override it at runtime in order to inject special logic
|
||||
* into `resolve` (by manipulating the options). One such example is the PnP
|
||||
* code path in Yarn.
|
||||
*/
|
||||
|
||||
return opts || {};
|
||||
};
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
'use strict';
|
||||
|
||||
/** @type {(x: string) => { __proto__: null, name: string, subpath: string }} */
|
||||
module.exports = function parsePackageSpecifier(x) {
|
||||
if (x.charAt(0) === '@') {
|
||||
var slashIndex = x.indexOf('/');
|
||||
if (slashIndex === -1) {
|
||||
return {
|
||||
__proto__: null, name: x, subpath: '.'
|
||||
};
|
||||
}
|
||||
var secondSlash = x.indexOf('/', slashIndex + 1);
|
||||
if (secondSlash === -1) {
|
||||
return {
|
||||
__proto__: null, name: x, subpath: '.'
|
||||
};
|
||||
}
|
||||
return {
|
||||
__proto__: null, name: x.slice(0, secondSlash), subpath: '.' + x.slice(secondSlash)
|
||||
};
|
||||
}
|
||||
var firstSlash = x.indexOf('/');
|
||||
if (firstSlash === -1) {
|
||||
return {
|
||||
__proto__: null, name: x, subpath: '.'
|
||||
};
|
||||
}
|
||||
return {
|
||||
__proto__: null, name: x.slice(0, firstSlash), subpath: '.' + x.slice(firstSlash)
|
||||
};
|
||||
};
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
'use strict';
|
||||
|
||||
// Category ranking from most restrictive (lowest rank) to least restrictive (highest rank)
|
||||
// Lower rank = more restrictive = fewer features supported
|
||||
var categoryRank = /** @type {const} */ {
|
||||
__proto__: null,
|
||||
'pre-exports': /** @type {const} */ (0),
|
||||
broken: /** @type {const} */ (1),
|
||||
experimental: /** @type {const} */ (2),
|
||||
conditions: /** @type {const} */ (3),
|
||||
'broken-dir-slash-conditions': /** @type {const} */ (4),
|
||||
patterns: /** @type {const} */ (5),
|
||||
'pattern-trailers': /** @type {const} */ (6),
|
||||
'pattern-trailers+json-imports': /** @type {const} */ (7),
|
||||
'pattern-trailers-no-dir-slash': /** @type {const} */ (8),
|
||||
'pattern-trailers-no-dir-slash+json-imports': /** @type {const} */ (9),
|
||||
'require-esm': /** @type {const} */ (10),
|
||||
'strips-types': /** @type {const} */ (11),
|
||||
'subpath-imports-slash': /** @type {const} */ (12)
|
||||
};
|
||||
|
||||
// Select the most restrictive category from an array of categories
|
||||
/** @type {(categories?: ReturnType<import('node-exports-info/getCategory')>[]) => import('node-exports-info/getCategory').Category | null} */
|
||||
module.exports = function selectMostRestrictive(categories) {
|
||||
if (!categories || categories.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var mostRestrictive = null;
|
||||
var lowestRank = Infinity;
|
||||
|
||||
for (var i = 0; i < categories.length; i++) {
|
||||
var cat = categories[i];
|
||||
var rank = categoryRank[cat];
|
||||
if (typeof rank === 'number' && rank < lowestRank) {
|
||||
lowestRank = rank;
|
||||
mostRestrictive = cat;
|
||||
}
|
||||
}
|
||||
|
||||
return mostRestrictive;
|
||||
};
|
||||
+418
@@ -0,0 +1,418 @@
|
||||
var isCore = require('is-core-module');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var $Error = require('es-errors');
|
||||
var $TypeError = require('es-errors/type');
|
||||
|
||||
var getHomedir = require('./homedir');
|
||||
var caller = require('./caller');
|
||||
var nodeModulesPaths = require('./node-modules-paths');
|
||||
var normalizeOptions = require('./normalize-options');
|
||||
var resolveExports = require('./exports-resolve');
|
||||
var parsePackageSpecifier = require('./parse-package-specifier');
|
||||
var getExportsCategory = require('./get-exports-category');
|
||||
var getCategoryInfo = require('node-exports-info/getCategoryInfo');
|
||||
var getCategoriesForRange = require('node-exports-info/getCategoriesForRange');
|
||||
var selectMostRestrictive = require('./select-most-restrictive');
|
||||
|
||||
var realpathFS = process.platform !== 'win32' && fs.realpathSync && typeof fs.realpathSync.native === 'function' ? fs.realpathSync.native : fs.realpathSync;
|
||||
|
||||
var relativePathRegex = /^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[/\\])/;
|
||||
var windowsDriveRegex = /^\w:[/\\]*$/;
|
||||
var nodeModulesRegex = /[/\\]node_modules[/\\]*$/;
|
||||
|
||||
var homedir = getHomedir();
|
||||
function defaultPaths() {
|
||||
return [
|
||||
path.join(homedir, '.node_modules'),
|
||||
path.join(homedir, '.node_libraries')
|
||||
];
|
||||
}
|
||||
|
||||
var defaultIsFile = function isFile(file) {
|
||||
try {
|
||||
var stat = fs.statSync(file, { throwIfNoEntry: false });
|
||||
} catch (e) {
|
||||
if (e && (e.code === 'ENOENT' || e.code === 'ENOTDIR')) return false;
|
||||
throw e;
|
||||
}
|
||||
return !!stat && (stat.isFile() || stat.isFIFO());
|
||||
};
|
||||
|
||||
var defaultIsDir = function isDirectory(dir) {
|
||||
try {
|
||||
var stat = fs.statSync(dir, { throwIfNoEntry: false });
|
||||
} catch (e) {
|
||||
if (e && (e.code === 'ENOENT' || e.code === 'ENOTDIR')) return false;
|
||||
throw e;
|
||||
}
|
||||
return !!stat && stat.isDirectory();
|
||||
};
|
||||
|
||||
var defaultRealpathSync = function realpathSync(x) {
|
||||
try {
|
||||
return realpathFS(x);
|
||||
} catch (realpathErr) {
|
||||
if (realpathErr.code !== 'ENOENT') {
|
||||
throw realpathErr;
|
||||
}
|
||||
}
|
||||
return x;
|
||||
};
|
||||
|
||||
function maybeRealpathSync(realpathSync, x, opts) {
|
||||
if (!opts || !opts.preserveSymlinks) {
|
||||
return realpathSync(x);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
function defaultReadPackageSync(readFileSync, pkgfile) {
|
||||
return JSON.parse(readFileSync(pkgfile));
|
||||
}
|
||||
|
||||
function getPackageCandidates(x, start, opts) {
|
||||
var dirs = nodeModulesPaths(start, opts, x);
|
||||
for (var i = 0; i < dirs.length; i++) {
|
||||
dirs[i] = path.join(dirs[i], x);
|
||||
}
|
||||
return dirs;
|
||||
}
|
||||
|
||||
function findConsumerPackageSync(startDir, isFile, readPackageSync, readFileSync) {
|
||||
var dir = path.resolve(startDir);
|
||||
while (true) {
|
||||
var pkgfile = path.join(dir, 'package.json');
|
||||
if (isFile(pkgfile)) {
|
||||
try {
|
||||
return readPackageSync(readFileSync, pkgfile);
|
||||
} catch (e) {
|
||||
if (!(e instanceof SyntaxError)) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
var parentDir = path.dirname(dir);
|
||||
if (parentDir === dir) {
|
||||
return null;
|
||||
}
|
||||
dir = parentDir;
|
||||
}
|
||||
}
|
||||
|
||||
function findPackageWithDirSync(startDir, isFile, readPackageSync, readFileSync) {
|
||||
var dir = path.resolve(startDir);
|
||||
while (true) {
|
||||
// Stop at node_modules boundaries - can't self-reference across node_modules
|
||||
if (nodeModulesRegex.test(dir)) {
|
||||
return null;
|
||||
}
|
||||
var pkgfile = path.join(dir, 'package.json');
|
||||
if (isFile(pkgfile)) {
|
||||
try {
|
||||
var pkg = readPackageSync(readFileSync, pkgfile);
|
||||
return {
|
||||
__proto__: null, pkg: pkg, dir: dir
|
||||
};
|
||||
} catch (e) {
|
||||
if (!(e instanceof SyntaxError)) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
var parentDir = path.dirname(dir);
|
||||
if (parentDir === dir) {
|
||||
return null;
|
||||
}
|
||||
dir = parentDir;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function resolveSync(x, options) {
|
||||
if (typeof x !== 'string') {
|
||||
throw new $TypeError('Path must be a string.');
|
||||
}
|
||||
var opts = normalizeOptions(x, options);
|
||||
|
||||
var isFile = opts.isFile || defaultIsFile;
|
||||
var isDirectory = opts.isDirectory || defaultIsDir;
|
||||
var readFileSync = opts.readFileSync || fs.readFileSync;
|
||||
var realpathSync = opts.realpathSync || defaultRealpathSync;
|
||||
var readPackageSync = opts.readPackageSync || defaultReadPackageSync;
|
||||
if (opts.readFileSync && opts.readPackageSync) {
|
||||
throw new $TypeError('`readFileSync` and `readPackageSync` are mutually exclusive.');
|
||||
}
|
||||
var packageIterator = opts.packageIterator;
|
||||
|
||||
var extensions = opts.extensions || ['.js'];
|
||||
var includeCoreModules = opts.includeCoreModules !== false;
|
||||
var basedir = opts.basedir || path.dirname(caller());
|
||||
var parent = opts.filename || basedir;
|
||||
|
||||
opts.paths = opts.paths || defaultPaths();
|
||||
|
||||
// Determine exports category
|
||||
var exportsCategory = getExportsCategory(opts);
|
||||
if (exportsCategory === 'engines') {
|
||||
// Read consumer's package.json to get engines.node
|
||||
var consumerPkg = findConsumerPackageSync(basedir, isFile, readPackageSync, readFileSync);
|
||||
if (consumerPkg && consumerPkg.engines && consumerPkg.engines.node) {
|
||||
var categories = getCategoriesForRange(consumerPkg.engines.node);
|
||||
exportsCategory = selectMostRestrictive(categories);
|
||||
} else {
|
||||
exportsCategory = null;
|
||||
}
|
||||
}
|
||||
|
||||
var useExports = exportsCategory !== null && exportsCategory !== 'pre-exports';
|
||||
|
||||
// ensure that `basedir` is an absolute path at this point, resolving against the process' current working directory
|
||||
var absoluteStart = maybeRealpathSync(realpathSync, path.resolve(basedir), opts);
|
||||
|
||||
if (opts.basedir && !isDirectory(absoluteStart)) {
|
||||
var dirError = new $TypeError('Provided basedir "' + opts.basedir + '" is not a directory' + (opts.preserveSymlinks ? '' : ', or a symlink to a directory'));
|
||||
dirError.code = 'INVALID_BASEDIR';
|
||||
throw dirError;
|
||||
}
|
||||
|
||||
if (relativePathRegex.test(x)) {
|
||||
var res = path.resolve(absoluteStart, x);
|
||||
if (x === '.' || x === '..' || x.slice(-1) === '/') res += '/';
|
||||
var m = loadAsFileSync(res) || loadAsDirectorySync(res);
|
||||
if (m) return maybeRealpathSync(realpathSync, m, opts);
|
||||
} else if (includeCoreModules && isCore(x)) {
|
||||
return x;
|
||||
} else if (useExports) {
|
||||
// Try self-reference resolution first
|
||||
var selfRef = resolveSelfReferenceSync(x, absoluteStart);
|
||||
if (selfRef) return maybeRealpathSync(realpathSync, selfRef, opts);
|
||||
var nE = loadNodeModulesWithExportsSync(x, absoluteStart);
|
||||
if (nE) return maybeRealpathSync(realpathSync, nE, opts);
|
||||
} else {
|
||||
var n = loadNodeModulesSync(x, absoluteStart);
|
||||
if (n) return maybeRealpathSync(realpathSync, n, opts);
|
||||
}
|
||||
|
||||
var err = new $Error("Cannot find module '" + x + "' from '" + parent + "'");
|
||||
err.code = 'MODULE_NOT_FOUND';
|
||||
throw err;
|
||||
|
||||
function resolveSelfReferenceSync(x, startDir) {
|
||||
var parsed = parsePackageSpecifier(x);
|
||||
var pkgInfo = findPackageWithDirSync(startDir, isFile, readPackageSync, readFileSync);
|
||||
|
||||
if (!pkgInfo || !pkgInfo.pkg || pkgInfo.pkg.name !== parsed.name) {
|
||||
return null; // Not a self-reference
|
||||
}
|
||||
|
||||
var pkg = pkgInfo.pkg;
|
||||
var pkgDir = pkgInfo.dir;
|
||||
|
||||
if (opts.packageFilter) {
|
||||
pkg = opts.packageFilter(pkg, path.join(pkgDir, 'package.json'), pkgDir);
|
||||
}
|
||||
|
||||
// If package has exports field, resolve via exports
|
||||
if (typeof pkg.exports !== 'undefined') {
|
||||
var categoryInfo = getCategoryInfo(exportsCategory, 'require');
|
||||
var conditions = opts.conditions || categoryInfo.conditions;
|
||||
var resolved = resolveExports(pkg.exports, parsed.subpath, conditions, categoryInfo.flags);
|
||||
if (resolved) {
|
||||
var resolvedPath = path.resolve(pkgDir, resolved);
|
||||
if (isFile(resolvedPath)) {
|
||||
return resolvedPath;
|
||||
}
|
||||
}
|
||||
// exports field exists but didn't resolve - this is an error per Node semantics
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// No exports field - fall back to traditional resolution for self-reference
|
||||
// (Note: this matches Node's behavior where self-ref without exports uses main)
|
||||
if (parsed.subpath === '.') {
|
||||
return loadAsDirectorySync(pkgDir);
|
||||
}
|
||||
var subPath = path.join(pkgDir, parsed.subpath.slice(1));
|
||||
var sm = loadAsFileSync(subPath);
|
||||
if (sm) return sm;
|
||||
return loadAsDirectorySync(subPath);
|
||||
}
|
||||
|
||||
function loadAsFileSync(x) {
|
||||
var pkg = loadpkg(path.dirname(x));
|
||||
|
||||
if (pkg && pkg.dir && pkg.pkg && opts.pathFilter) {
|
||||
var rfile = path.relative(pkg.dir, x);
|
||||
var r = opts.pathFilter(pkg.pkg, x, rfile);
|
||||
if (r) {
|
||||
x = path.resolve(pkg.dir, r); // eslint-disable-line no-param-reassign
|
||||
}
|
||||
}
|
||||
|
||||
if (isFile(x)) {
|
||||
return x;
|
||||
}
|
||||
|
||||
for (var i = 0; i < extensions.length; i++) {
|
||||
var file = x + extensions[i];
|
||||
if (isFile(file)) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function loadpkg(dir) {
|
||||
if (dir === '' || dir === '/') return;
|
||||
if (process.platform === 'win32' && windowsDriveRegex.test(dir)) {
|
||||
return;
|
||||
}
|
||||
if (nodeModulesRegex.test(dir)) return;
|
||||
|
||||
var pkgfile = path.join(isDirectory(dir) ? maybeRealpathSync(realpathSync, dir, opts) : dir, 'package.json');
|
||||
|
||||
if (!isFile(pkgfile)) {
|
||||
return loadpkg(path.dirname(dir));
|
||||
}
|
||||
|
||||
var pkg;
|
||||
try {
|
||||
pkg = readPackageSync(readFileSync, pkgfile);
|
||||
} catch (e) {
|
||||
if (!(e instanceof SyntaxError)) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (pkg && opts.packageFilter) {
|
||||
pkg = opts.packageFilter(pkg, pkgfile, dir);
|
||||
}
|
||||
|
||||
return { pkg: pkg, dir: dir };
|
||||
}
|
||||
|
||||
function loadAsDirectorySync(x) {
|
||||
var pkgfile = path.join(isDirectory(x) ? maybeRealpathSync(realpathSync, x, opts) : x, '/package.json');
|
||||
if (isFile(pkgfile)) {
|
||||
try {
|
||||
var pkg = readPackageSync(readFileSync, pkgfile);
|
||||
} catch (e) {}
|
||||
|
||||
if (pkg && opts.packageFilter) {
|
||||
pkg = opts.packageFilter(pkg, pkgfile, x);
|
||||
}
|
||||
|
||||
if (pkg && pkg.main) {
|
||||
if (typeof pkg.main !== 'string') {
|
||||
var mainError = new $TypeError('package “' + pkg.name + '” `main` must be a string');
|
||||
mainError.code = 'INVALID_PACKAGE_MAIN';
|
||||
throw mainError;
|
||||
}
|
||||
if (pkg.main === '.' || pkg.main === './') {
|
||||
pkg.main = 'index';
|
||||
}
|
||||
try {
|
||||
var mainPath = path.resolve(x, pkg.main);
|
||||
var m = loadAsFileSync(mainPath);
|
||||
if (m) return m;
|
||||
var n = loadAsDirectorySync(mainPath);
|
||||
if (n) return n;
|
||||
var checkIndex = loadAsFileSync(path.resolve(x, 'index'));
|
||||
if (checkIndex) return checkIndex;
|
||||
} catch (e) { }
|
||||
var incorrectMainError = new $Error("Cannot find module '" + path.resolve(x, pkg.main) + "'. Please verify that the package.json has a valid \"main\" entry");
|
||||
incorrectMainError.code = 'INCORRECT_PACKAGE_MAIN';
|
||||
throw incorrectMainError;
|
||||
}
|
||||
}
|
||||
|
||||
return loadAsFileSync(path.join(x, '/index'));
|
||||
}
|
||||
|
||||
function loadNodeModulesSync(x, start) {
|
||||
var thunk = function () { return getPackageCandidates(x, start, opts); };
|
||||
var dirs = packageIterator ? packageIterator(x, start, thunk, opts) : thunk();
|
||||
|
||||
for (var i = 0; i < dirs.length; i++) {
|
||||
var dir = dirs[i];
|
||||
if (isDirectory(path.dirname(dir))) {
|
||||
var m = loadAsFileSync(dir);
|
||||
if (m) return m;
|
||||
var n = loadAsDirectorySync(dir);
|
||||
if (n) return n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function loadNodeModulesWithExportsSync(x, start) {
|
||||
var parsed = parsePackageSpecifier(x);
|
||||
var categoryInfo = getCategoryInfo(exportsCategory, 'require');
|
||||
var conditions = opts.conditions || categoryInfo.conditions;
|
||||
|
||||
// Get candidate directories for the package name
|
||||
var thunk = function () { return getPackageCandidates(parsed.name, start, opts); };
|
||||
var dirs = packageIterator ? packageIterator(parsed.name, start, thunk, opts) : thunk();
|
||||
|
||||
for (var i = 0; i < dirs.length; i++) {
|
||||
var pkgDir = dirs[i];
|
||||
if (isDirectory(pkgDir)) {
|
||||
var pkgfile = path.join(pkgDir, 'package.json');
|
||||
if (isFile(pkgfile)) {
|
||||
var pkg;
|
||||
try {
|
||||
pkg = readPackageSync(readFileSync, pkgfile);
|
||||
} catch (e) {
|
||||
if (!(e instanceof SyntaxError)) {
|
||||
throw e;
|
||||
}
|
||||
pkg = null;
|
||||
}
|
||||
|
||||
if (pkg) {
|
||||
if (opts.packageFilter) {
|
||||
pkg = opts.packageFilter(pkg, pkgfile, pkgDir);
|
||||
}
|
||||
|
||||
// If package has exports field, use exports resolution
|
||||
if (typeof pkg.exports !== 'undefined') {
|
||||
var resolved = resolveExports(pkg.exports, parsed.subpath, conditions, categoryInfo.flags);
|
||||
if (resolved) {
|
||||
var resolvedPath = path.resolve(pkgDir, resolved);
|
||||
if (isFile(resolvedPath)) {
|
||||
return resolvedPath;
|
||||
}
|
||||
}
|
||||
// exports field exists but didn't resolve - this is an error per Node semantics
|
||||
// (don't fall through to main/index)
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// No exports field, fall back to traditional resolution
|
||||
if (parsed.subpath === '.') {
|
||||
var result = loadAsDirectorySync(pkgDir);
|
||||
if (result) { return result; }
|
||||
} else {
|
||||
var subPath = path.join(pkgDir, parsed.subpath.slice(1));
|
||||
var sm = loadAsFileSync(subPath);
|
||||
if (sm) { return sm; }
|
||||
var sn = loadAsDirectorySync(subPath);
|
||||
if (sn) { return sn; }
|
||||
}
|
||||
}
|
||||
} else if (parsed.subpath === '.') {
|
||||
// No package.json, fall back to file/directory resolution
|
||||
var m = loadAsFileSync(pkgDir);
|
||||
if (m) { return m; }
|
||||
var n = loadAsDirectorySync(pkgDir);
|
||||
if (n) { return n; }
|
||||
} else {
|
||||
// No package.json, fall back to file/directory resolution for subpath
|
||||
var fullPath = path.join(pkgDir, parsed.subpath.slice(1));
|
||||
var m2 = loadAsFileSync(fullPath);
|
||||
if (m2) { return m2; }
|
||||
var n2 = loadAsDirectorySync(fullPath);
|
||||
if (n2) { return n2; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"name": "resolve",
|
||||
"description": "resolve like require.resolve() on behalf of files asynchronously and synchronously",
|
||||
"version": "2.0.0-next.6",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "ssh://github.com/browserify/resolve.git"
|
||||
},
|
||||
"bin": {
|
||||
"resolve": "./bin/resolve"
|
||||
},
|
||||
"main": "index.js",
|
||||
"exports": {
|
||||
".": [
|
||||
{
|
||||
"import": "./index.mjs",
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./index.js"
|
||||
],
|
||||
"./sync": "./lib/sync.js",
|
||||
"./async": "./lib/async.js",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"keywords": [
|
||||
"resolve",
|
||||
"require",
|
||||
"node",
|
||||
"module"
|
||||
],
|
||||
"scripts": {
|
||||
"prepack": "npmignore --auto --commentLines=autogenerated",
|
||||
"prepublishOnly": "safe-publish-latest",
|
||||
"prepublish": "not-in-publish || npm run prepublishOnly",
|
||||
"prelint": "eclint check $(git ls-files | grep -Ev test\\/list-exports$ | xargs find 2> /dev/null | grep -vE 'node_modules|\\.git')",
|
||||
"lint": "eslint .",
|
||||
"pretests-only": "npm run submodule:update && cd ./test/resolver/nested_symlinks && node mylib/sync && node mylib/async",
|
||||
"tests-only": "tape test/*.js",
|
||||
"pretest": "npm run lint",
|
||||
"test": "npm run --silent tests-only",
|
||||
"posttest": "npm run test:multirepo && npx npm@\">= 10.2\" audit --production",
|
||||
"test:multirepo": "cd ./test/resolver/multirepo && npm install && npm test",
|
||||
"submodule:update": "git submodule update --init --depth 1 && cd test/list-exports && git sparse-checkout init --cone && git sparse-checkout set packages/tests"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
"name": "James Halliday",
|
||||
"email": "mail@substack.net",
|
||||
"url": "http://substack.net"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
},
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"is-core-module": "^2.16.1",
|
||||
"node-exports-info": "^1.6.0",
|
||||
"object-keys": "^1.1.1",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ljharb/eslint-config": "^22.1.3",
|
||||
"array.prototype.map": "^1.0.8",
|
||||
"copy-dir": "^1.3.0",
|
||||
"eclint": "^2.8.1",
|
||||
"eslint": "^9.39.1",
|
||||
"in-publish": "^2.0.1",
|
||||
"jiti": "^0.0.0",
|
||||
"mkdirp": "^0.5.6",
|
||||
"mv": "^2.1.1",
|
||||
"npmignore": "^0.3.5",
|
||||
"rimraf": "^2.7.1",
|
||||
"safe-publish-latest": "^2.0.0",
|
||||
"tap": "^0.4.13",
|
||||
"tape": "^5.9.0",
|
||||
"tmp": "^0.0.31"
|
||||
},
|
||||
"publishConfig": {
|
||||
"ignore": [
|
||||
".github/workflows",
|
||||
".github/.well-known",
|
||||
"appveyor.yml",
|
||||
"CONTRIBUTING.md",
|
||||
"test/resolver/malformed_package_json",
|
||||
"test/list-exports"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
}
|
||||
+322
@@ -0,0 +1,322 @@
|
||||
# resolve <sup>[![Version Badge][2]][1]</sup>
|
||||
|
||||
implements the [node `require.resolve()` algorithm](https://nodejs.org/api/modules.html#modules_all_together) such that you can `require.resolve()` on behalf of a file asynchronously and synchronously
|
||||
|
||||
[![github actions][actions-image]][actions-url]
|
||||
[![coverage][codecov-image]][codecov-url]
|
||||
[![License][license-image]][license-url]
|
||||
[![Downloads][downloads-image]][downloads-url]
|
||||
[](https://bestpractices.coreinfrastructure.org/projects/10759)
|
||||
|
||||
[![npm badge][11]][1]
|
||||
|
||||
# example
|
||||
|
||||
asynchronously resolve:
|
||||
|
||||
```js
|
||||
var resolve = require('resolve/async'); // or, require('resolve')
|
||||
resolve('tap', { basedir: __dirname }, function (err, res) {
|
||||
if (err) console.error(err);
|
||||
else console.log(res);
|
||||
});
|
||||
```
|
||||
|
||||
```
|
||||
$ node example/async.js
|
||||
/home/substack/projects/node-resolve/node_modules/tap/lib/main.js
|
||||
```
|
||||
|
||||
synchronously resolve:
|
||||
|
||||
```js
|
||||
var resolve = require('resolve/sync'); // or, `require('resolve').sync
|
||||
var res = resolve('tap', { basedir: __dirname });
|
||||
console.log(res);
|
||||
```
|
||||
|
||||
```
|
||||
$ node example/sync.js
|
||||
/home/substack/projects/node-resolve/node_modules/tap/lib/main.js
|
||||
```
|
||||
|
||||
# methods
|
||||
|
||||
```js
|
||||
var resolve = require('resolve');
|
||||
var async = require('resolve/async');
|
||||
var sync = require('resolve/sync');
|
||||
```
|
||||
|
||||
For both the synchronous and asynchronous methods, errors may have any of the following `err.code` values:
|
||||
|
||||
- `MODULE_NOT_FOUND`: the given path string (`id`) could not be resolved to a module
|
||||
- `INVALID_BASEDIR`: the specified `opts.basedir` doesn't exist, or is not a directory
|
||||
- `INVALID_PACKAGE_MAIN`: a `package.json` was encountered with an invalid `main` property (eg. not a string)
|
||||
- `ERR_PACKAGE_PATH_NOT_EXPORTED`: the requested subpath is not defined in the package's `exports` field
|
||||
- `ERR_INVALID_PACKAGE_CONFIG`: the package's `exports` field contains an invalid target
|
||||
- `INVALID_EXPORTS_CATEGORY`: an invalid `exportsCategory` was specified
|
||||
|
||||
## resolve(id, opts={}, cb)
|
||||
|
||||
Asynchronously resolve the module path string `id` into `cb(err, res [, pkg])`, where `pkg` (if defined) is the data from `package.json`.
|
||||
|
||||
options are:
|
||||
|
||||
* opts.basedir - directory to begin resolving from
|
||||
|
||||
* opts.package - `package.json` data applicable to the module being loaded
|
||||
|
||||
* opts.extensions - array of file extensions to search in order
|
||||
|
||||
* opts.includeCoreModules - set to `false` to exclude node core modules (e.g. `fs`) from the search
|
||||
|
||||
* opts.readFile - how to read files asynchronously
|
||||
|
||||
* opts.isFile - function to asynchronously test whether a file exists
|
||||
|
||||
* opts.isDirectory - function to asynchronously test whether a file exists and is a directory
|
||||
|
||||
* opts.realpath - function to asynchronously resolve a potential symlink to its real path
|
||||
|
||||
* `opts.readPackage(readFile, pkgfile, cb)` - function to asynchronously read and parse a package.json file
|
||||
* readFile - the passed `opts.readFile` or `fs.readFile` if not specified
|
||||
* pkgfile - path to package.json
|
||||
* cb - callback. a SyntaxError error argument will be ignored, all other error arguments will be treated as an error.
|
||||
|
||||
* `opts.packageFilter(pkg, pkgfile, dir)` - transform the parsed package.json contents before looking at the "main" field
|
||||
* pkg - package data
|
||||
* pkgfile - path to package.json
|
||||
* dir - directory that contains package.json
|
||||
|
||||
* `opts.pathFilter(pkg, path, relativePath)` - transform a path within a package
|
||||
* pkg - package data
|
||||
* path - the path being resolved
|
||||
* relativePath - the path relative from the package.json location
|
||||
* returns - a relative path that will be joined from the package.json location
|
||||
|
||||
* opts.paths - require.paths array to use if nothing is found on the normal `node_modules` recursive walk (probably don't use this)
|
||||
|
||||
For advanced users, `paths` can also be a `opts.paths(request, start, opts)` function
|
||||
* request - the import specifier being resolved
|
||||
* start - lookup path
|
||||
* getNodeModulesDirs - a thunk (no-argument function) that returns the paths using standard `node_modules` resolution
|
||||
* opts - the resolution options
|
||||
|
||||
* `opts.packageIterator(request, start, opts)` - return the list of candidate paths where the packages sources may be found (probably don't use this)
|
||||
* request - the import specifier being resolved
|
||||
* start - lookup path
|
||||
* getPackageCandidates - a thunk (no-argument function) that returns the paths using standard `node_modules` resolution
|
||||
* opts - the resolution options
|
||||
|
||||
* opts.moduleDirectory - directory (or directories) in which to recursively look for modules. default: `"node_modules"`
|
||||
|
||||
* opts.preserveSymlinks - if true, doesn't resolve `basedir` to real path before resolving.
|
||||
This is the way Node resolves dependencies when executed with the [--preserve-symlinks](https://nodejs.org/api/all.html#cli_preserve_symlinks) flag.
|
||||
|
||||
* opts.exportsCategory - a [node-exports-info](https://npmjs.com/package/node-exports-info) category string (e.g. `'conditions'`, `'patterns'`, `'pre-exports'`) that determines which `exports` field semantics to use.
|
||||
When set, resolution will use the package's `exports` field according to that category's supported conditions and features.
|
||||
This also enables **self-reference** support, allowing a package to import itself by name (e.g., `require('my-package')` from within `my-package`).
|
||||
|
||||
* opts.engines - determines `exports` field resolution based on Node.js version semantics:
|
||||
* When a **string** (e.g. `'>= 14'`): treated as a semver range, mapped to the most restrictive [node-exports-info](https://npmjs.com/package/node-exports-info) category covering that range.
|
||||
* When `true`: reads `engines.node` from the nearest `package.json` to `basedir` and uses the most restrictive category for that range.
|
||||
* When `false` or omitted: no engine-based exports resolution.
|
||||
* Throws if set to an empty string or non-boolean/non-string value.
|
||||
|
||||
**Note:** `exportsCategory` and `engines` are mutually exclusive - only one can be specified.
|
||||
|
||||
* opts.conditions - an array of condition strings (e.g. `['require', 'node']`) to use when resolving the `exports` field.
|
||||
If specified, this overrides the conditions that would otherwise be derived from the category.
|
||||
This option only has effect when `exportsCategory` or `engines` is also set.
|
||||
|
||||
default `opts` values:
|
||||
|
||||
```js
|
||||
{
|
||||
paths: [],
|
||||
basedir: __dirname,
|
||||
extensions: ['.js'],
|
||||
includeCoreModules: true,
|
||||
readFile: fs.readFile,
|
||||
isFile: function isFile(file, cb) {
|
||||
fs.stat(file, function (err, stat) {
|
||||
if (!err) {
|
||||
return cb(null, stat.isFile() || stat.isFIFO());
|
||||
}
|
||||
if (err.code === 'ENOENT' || err.code === 'ENOTDIR') return cb(null, false);
|
||||
return cb(err);
|
||||
});
|
||||
},
|
||||
isDirectory: function isDirectory(dir, cb) {
|
||||
fs.stat(dir, function (err, stat) {
|
||||
if (!err) {
|
||||
return cb(null, stat.isDirectory());
|
||||
}
|
||||
if (err.code === 'ENOENT' || err.code === 'ENOTDIR') return cb(null, false);
|
||||
return cb(err);
|
||||
});
|
||||
},
|
||||
realpath: function realpath(file, cb) {
|
||||
var realpath = typeof fs.realpath.native === 'function' ? fs.realpath.native : fs.realpath;
|
||||
realpath(file, function (realPathErr, realPath) {
|
||||
if (realPathErr && realPathErr.code !== 'ENOENT') cb(realPathErr);
|
||||
else cb(null, realPathErr ? file : realPath);
|
||||
});
|
||||
},
|
||||
readPackage: function defaultReadPackage(readFile, pkgfile, cb) {
|
||||
readFile(pkgfile, function (readFileErr, body) {
|
||||
if (readFileErr) cb(readFileErr);
|
||||
else {
|
||||
try {
|
||||
var pkg = JSON.parse(body);
|
||||
cb(null, pkg);
|
||||
} catch (jsonErr) {
|
||||
cb(jsonErr);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
moduleDirectory: 'node_modules',
|
||||
preserveSymlinks: false
|
||||
}
|
||||
```
|
||||
|
||||
## resolve.sync(id, opts)
|
||||
|
||||
Synchronously resolve the module path string `id`, returning the result and
|
||||
throwing an error when `id` can't be resolved.
|
||||
|
||||
options are:
|
||||
|
||||
* opts.basedir - directory to begin resolving from
|
||||
|
||||
* opts.extensions - array of file extensions to search in order
|
||||
|
||||
* opts.includeCoreModules - set to `false` to exclude node core modules (e.g. `fs`) from the search
|
||||
|
||||
* opts.readFileSync - how to read files synchronously
|
||||
|
||||
* opts.isFile - function to synchronously test whether a file exists
|
||||
|
||||
* opts.isDirectory - function to synchronously test whether a file exists and is a directory
|
||||
|
||||
* opts.realpathSync - function to synchronously resolve a potential symlink to its real path
|
||||
|
||||
* `opts.readPackageSync(readFileSync, pkgfile)` - function to synchronously read and parse a package.json file. a thrown SyntaxError will be ignored, all other exceptions will propagate.
|
||||
* readFileSync - the passed `opts.readFileSync` or `fs.readFileSync` if not specified
|
||||
* pkgfile - path to package.json
|
||||
|
||||
* `opts.packageFilter(pkg, pkgfile, dir)` - transform the parsed package.json contents before looking at the "main" field
|
||||
* pkg - package data
|
||||
* pkgfile - path to package.json
|
||||
* dir - directory that contains package.json
|
||||
|
||||
* `opts.pathFilter(pkg, path, relativePath)` - transform a path within a package
|
||||
* pkg - package data
|
||||
* path - the path being resolved
|
||||
* relativePath - the path relative from the package.json location
|
||||
* returns - a relative path that will be joined from the package.json location
|
||||
|
||||
* opts.paths - require.paths array to use if nothing is found on the normal `node_modules` recursive walk (probably don't use this)
|
||||
|
||||
For advanced users, `paths` can also be a `opts.paths(request, start, opts)` function
|
||||
* request - the import specifier being resolved
|
||||
* start - lookup path
|
||||
* getNodeModulesDirs - a thunk (no-argument function) that returns the paths using standard `node_modules` resolution
|
||||
* opts - the resolution options
|
||||
|
||||
* `opts.packageIterator(request, start, opts)` - return the list of candidate paths where the packages sources may be found (probably don't use this)
|
||||
* request - the import specifier being resolved
|
||||
* start - lookup path
|
||||
* getPackageCandidates - a thunk (no-argument function) that returns the paths using standard `node_modules` resolution
|
||||
* opts - the resolution options
|
||||
|
||||
* opts.moduleDirectory - directory (or directories) in which to recursively look for modules. default: `"node_modules"`
|
||||
|
||||
* opts.preserveSymlinks - if true, doesn't resolve `basedir` to real path before resolving.
|
||||
This is the way Node resolves dependencies when executed with the [--preserve-symlinks](https://nodejs.org/api/all.html#cli_preserve_symlinks) flag.
|
||||
|
||||
* opts.exportsCategory - a [node-exports-info](https://npmjs.com/package/node-exports-info) category string (e.g. `'conditions'`, `'patterns'`, `'pre-exports'`) that determines which `exports` field semantics to use. When set, resolution will use the package's `exports` field according to that category's supported conditions and features. This also enables **self-reference** support, allowing a package to import itself by name (e.g., `require('my-package')` from within `my-package`).
|
||||
|
||||
* opts.enginesRange - a semver range string (e.g. `'>= 14'`) that will be mapped to the most restrictive [node-exports-info](https://npmjs.com/package/node-exports-info) category covering that range.
|
||||
|
||||
* opts.engines - if `true`, reads `engines.node` from the nearest `package.json` to `basedir` and uses the most restrictive [node-exports-info](https://npmjs.com/package/node-exports-info) category for that range.
|
||||
|
||||
**Note:** `exportsCategory`, `enginesRange`, and `engines` are mutually exclusive - only one can be specified.
|
||||
|
||||
* opts.conditions - an array of condition strings (e.g. `['require', 'node']`) to use when resolving the `exports` field. If specified, this overrides the conditions that would otherwise be derived from the category. This option only has effect when one of `exportsCategory`, `enginesRange`, or `engines` is also set.
|
||||
|
||||
default `opts` values:
|
||||
|
||||
```js
|
||||
{
|
||||
paths: [],
|
||||
basedir: __dirname,
|
||||
extensions: ['.js'],
|
||||
includeCoreModules: true,
|
||||
readFileSync: fs.readFileSync,
|
||||
isFile: function isFile(file) {
|
||||
try {
|
||||
var stat = fs.statSync(file);
|
||||
} catch (e) {
|
||||
if (e && (e.code === 'ENOENT' || e.code === 'ENOTDIR')) return false;
|
||||
throw e;
|
||||
}
|
||||
return stat.isFile() || stat.isFIFO();
|
||||
},
|
||||
isDirectory: function isDirectory(dir) {
|
||||
try {
|
||||
var stat = fs.statSync(dir);
|
||||
} catch (e) {
|
||||
if (e && (e.code === 'ENOENT' || e.code === 'ENOTDIR')) return false;
|
||||
throw e;
|
||||
}
|
||||
return stat.isDirectory();
|
||||
},
|
||||
realpathSync: function realpathSync(file) {
|
||||
try {
|
||||
var realpath = typeof fs.realpathSync.native === 'function' ? fs.realpathSync.native : fs.realpathSync;
|
||||
return realpath(file);
|
||||
} catch (realPathErr) {
|
||||
if (realPathErr.code !== 'ENOENT') {
|
||||
throw realPathErr;
|
||||
}
|
||||
}
|
||||
return file;
|
||||
},
|
||||
readPackageSync: function defaultReadPackageSync(readFileSync, pkgfile) {
|
||||
return JSON.parse(readFileSync(pkgfile));
|
||||
},
|
||||
moduleDirectory: 'node_modules',
|
||||
preserveSymlinks: false
|
||||
}
|
||||
```
|
||||
|
||||
# install
|
||||
|
||||
With [npm](https://npmjs.org) do:
|
||||
|
||||
```sh
|
||||
npm install resolve
|
||||
```
|
||||
|
||||
# license
|
||||
|
||||
MIT
|
||||
|
||||
[1]: https://npmjs.org/package/resolve
|
||||
[2]: https://versionbadg.es/browserify/resolve.svg
|
||||
[5]: https://david-dm.org/browserify/resolve.svg
|
||||
[6]: https://david-dm.org/browserify/resolve
|
||||
[7]: https://david-dm.org/browserify/resolve/dev-status.svg
|
||||
[8]: https://david-dm.org/browserify/resolve#info=devDependencies
|
||||
[11]: https://nodei.co/npm/resolve.png?downloads=true&stars=true
|
||||
[license-image]: https://img.shields.io/npm/l/resolve.svg
|
||||
[license-url]: LICENSE
|
||||
[downloads-image]: https://img.shields.io/npm/dm/resolve.svg
|
||||
[downloads-url]: https://npm-stat.com/charts.html?package=resolve
|
||||
[codecov-image]: https://codecov.io/gh/browserify/resolve/branch/main/graphs/badge.svg
|
||||
[codecov-url]: https://app.codecov.io/gh/browserify/resolve/
|
||||
[actions-image]: https://img.shields.io/github/check-runs/browserify/resolve/main
|
||||
[actions-url]: https://github.com/browserify/resolve/actions
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = require('./lib/sync');
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
var path = require('path');
|
||||
var test = require('tape');
|
||||
var resolve = require('../');
|
||||
|
||||
test('dotdot', function (t) {
|
||||
t.plan(4);
|
||||
var dir = path.join(__dirname, '/dotdot/abc');
|
||||
|
||||
resolve('..', { basedir: dir }, function (err, res, pkg) {
|
||||
t.ifError(err);
|
||||
t.equal(res, path.join(__dirname, 'dotdot/index.js'));
|
||||
});
|
||||
|
||||
resolve('.', { basedir: dir }, function (err, res, pkg) {
|
||||
t.ifError(err);
|
||||
t.equal(res, path.join(dir, 'index.js'));
|
||||
});
|
||||
});
|
||||
|
||||
test('dotdot sync', function (t) {
|
||||
t.plan(2);
|
||||
var dir = path.join(__dirname, '/dotdot/abc');
|
||||
|
||||
var a = resolve.sync('..', { basedir: dir });
|
||||
t.equal(a, path.join(__dirname, 'dotdot/index.js'));
|
||||
|
||||
var b = resolve.sync('.', { basedir: dir });
|
||||
t.equal(b, path.join(dir, 'index.js'));
|
||||
});
|
||||
+461
@@ -0,0 +1,461 @@
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var test = require('tape');
|
||||
var resolve = require('../');
|
||||
|
||||
var fixturesDir = path.join(__dirname, 'list-exports', 'packages', 'tests', 'fixtures');
|
||||
|
||||
var categories = [
|
||||
'broken',
|
||||
'broken-dir-slash-conditions',
|
||||
'conditions',
|
||||
'experimental',
|
||||
'pattern-trailers',
|
||||
'pattern-trailers+json-imports',
|
||||
'pattern-trailers-no-dir-slash',
|
||||
'pattern-trailers-no-dir-slash+json-imports',
|
||||
'patterns',
|
||||
'require-esm',
|
||||
'strips-types',
|
||||
'subpath-imports-slash'
|
||||
// 'pre-exports' is tested separately since it uses main/index resolution
|
||||
];
|
||||
|
||||
// Fixtures that are symlinks pointing outside the fixture dir cause path confusion
|
||||
// ex-private is a private package whose expected files don't include exports data
|
||||
var skipFixtures = ['list-exports', 'ls-exports', 'ex-private'];
|
||||
|
||||
function getFixtures() {
|
||||
return fs.readdirSync(fixturesDir).filter(function (name) {
|
||||
if (skipFixtures.indexOf(name) !== -1) {
|
||||
return false;
|
||||
}
|
||||
var stat = fs.statSync(path.join(fixturesDir, name));
|
||||
return stat.isDirectory();
|
||||
});
|
||||
}
|
||||
|
||||
function loadExpected(fixtureName, category) {
|
||||
var expectedPath = path.join(fixturesDir, fixtureName, 'expected', category + '.json');
|
||||
if (!fs.existsSync(expectedPath)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(expectedPath, 'utf8'));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function loadProjectPkg(fixtureName) {
|
||||
var pkgPath = path.join(fixturesDir, fixtureName, 'project', 'package.json');
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
test('async exports resolution - exportsCategory option', function (t) {
|
||||
var fixtures = getFixtures();
|
||||
|
||||
fixtures.forEach(function (fixtureName) {
|
||||
var projectPkg = loadProjectPkg(fixtureName);
|
||||
if (!projectPkg) {
|
||||
return;
|
||||
}
|
||||
var projectDir = path.join(fixturesDir, fixtureName, 'project');
|
||||
var pkgName = projectPkg.name;
|
||||
|
||||
categories.forEach(function (category) {
|
||||
var expected = loadExpected(fixtureName, category);
|
||||
if (!expected || !expected.exports || !expected.exports[category]) {
|
||||
return;
|
||||
}
|
||||
|
||||
var requireMap = expected.exports[category].require;
|
||||
if (!requireMap || typeof requireMap !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(requireMap).forEach(function (subpath) {
|
||||
var expectedFile = requireMap[subpath];
|
||||
var specifier = subpath === '.' ? pkgName : pkgName + subpath.substring(1);
|
||||
|
||||
t.test(fixtureName + ' / ' + category + ' / ' + subpath, function (st) {
|
||||
st.plan(1);
|
||||
resolve(specifier, {
|
||||
basedir: __dirname,
|
||||
exportsCategory: category,
|
||||
extensions: ['.js', '.json'],
|
||||
packageIterator: function () {
|
||||
return [projectDir];
|
||||
}
|
||||
}, function (err, result) {
|
||||
if (err) {
|
||||
st.fail('Unexpected error for ' + specifier + ': ' + err.message);
|
||||
return;
|
||||
}
|
||||
var relativeResult = './' + path.relative(projectDir, result).split(path.sep).join('/');
|
||||
st.equal(relativeResult, expectedFile, specifier + ' resolves to ' + expectedFile);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('async exports resolution - pre-exports category uses main/index', function (t) {
|
||||
var fixtures = getFixtures();
|
||||
|
||||
fixtures.forEach(function (fixtureName) {
|
||||
var projectPkg = loadProjectPkg(fixtureName);
|
||||
if (!projectPkg) {
|
||||
return;
|
||||
}
|
||||
var projectDir = path.join(fixturesDir, fixtureName, 'project');
|
||||
var pkgName = projectPkg.name;
|
||||
|
||||
var expected = loadExpected(fixtureName, 'pre-exports');
|
||||
if (!expected || !expected.exports || !expected.exports['pre-exports']) {
|
||||
return;
|
||||
}
|
||||
|
||||
var requireMap = expected.exports['pre-exports'].require;
|
||||
if (!requireMap || typeof requireMap !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
// For pre-exports, only test the main entry point (.)
|
||||
var mainEntry = requireMap['.'];
|
||||
if (!mainEntry) {
|
||||
return;
|
||||
}
|
||||
|
||||
t.test(fixtureName + ' / pre-exports / .', function (st) {
|
||||
st.plan(1);
|
||||
resolve(pkgName, {
|
||||
basedir: __dirname,
|
||||
exportsCategory: 'pre-exports',
|
||||
extensions: ['.js', '.json'],
|
||||
packageIterator: function () {
|
||||
return [projectDir];
|
||||
}
|
||||
}, function (err, result) {
|
||||
if (err) {
|
||||
st.fail('Unexpected error for ' + pkgName + ': ' + err.message);
|
||||
return;
|
||||
}
|
||||
var relativeResult = './' + path.relative(projectDir, result).split(path.sep).join('/');
|
||||
st.equal(relativeResult, mainEntry, pkgName + ' resolves to ' + mainEntry);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('async exports resolution - mutual exclusivity of options', function (t) {
|
||||
t.test('exportsCategory and engines (string) are mutually exclusive', function (st) {
|
||||
st.plan(1);
|
||||
resolve('tape', {
|
||||
basedir: __dirname,
|
||||
exportsCategory: 'conditions',
|
||||
engines: '>= 14'
|
||||
}, function (err) {
|
||||
st.ok(err && (/mutually exclusive/).test(err.message), 'throws with mutually exclusive message');
|
||||
});
|
||||
});
|
||||
|
||||
t.test('exportsCategory and engines (true) are mutually exclusive', function (st) {
|
||||
st.plan(1);
|
||||
resolve('tape', {
|
||||
basedir: __dirname,
|
||||
exportsCategory: 'conditions',
|
||||
engines: true
|
||||
}, function (err) {
|
||||
st.ok(err && (/mutually exclusive/).test(err.message), 'throws with mutually exclusive message');
|
||||
});
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('async exports resolution - invalid category', function (t) {
|
||||
t.plan(2);
|
||||
resolve('tape', {
|
||||
basedir: __dirname,
|
||||
exportsCategory: 'not-a-real-category'
|
||||
}, function (err) {
|
||||
t.equal(err && err.code, 'INVALID_EXPORTS_CATEGORY', 'has correct error code');
|
||||
t.ok(err && (/Invalid exports category/).test(err.message), 'has correct error message');
|
||||
});
|
||||
});
|
||||
|
||||
test('async exports resolution - engines option', function (t) {
|
||||
var projectDir = path.join(fixturesDir, 'ex-exports-string', 'project');
|
||||
|
||||
t.test('engines string maps to category', function (st) {
|
||||
st.plan(1);
|
||||
resolve('ex-exports-string', {
|
||||
basedir: __dirname,
|
||||
engines: '>= 14',
|
||||
packageIterator: function () {
|
||||
return [projectDir];
|
||||
}
|
||||
}, function (err, result) {
|
||||
if (err) {
|
||||
st.fail(err.message);
|
||||
return;
|
||||
}
|
||||
st.ok(result.indexOf('index.js') > -1, 'resolves to index.js');
|
||||
});
|
||||
});
|
||||
|
||||
t.test('engines: false is same as omitting', function (st) {
|
||||
st.plan(1);
|
||||
resolve('tape', {
|
||||
basedir: __dirname,
|
||||
engines: false
|
||||
}, function (err, result) {
|
||||
if (err) {
|
||||
st.fail(err.message);
|
||||
return;
|
||||
}
|
||||
st.ok(result.indexOf('tape') > -1, 'resolves without exports resolution');
|
||||
});
|
||||
});
|
||||
|
||||
t.test('engines: empty string throws', function (st) {
|
||||
st.plan(1);
|
||||
resolve('tape', {
|
||||
basedir: __dirname,
|
||||
engines: ''
|
||||
}, function (err) {
|
||||
st.ok(err && (/must be.*true.*false.*non-empty string/i).test(err.message), 'throws with correct message');
|
||||
});
|
||||
});
|
||||
|
||||
t.test('engines: number throws', function (st) {
|
||||
st.plan(1);
|
||||
resolve('tape', {
|
||||
basedir: __dirname,
|
||||
engines: 14
|
||||
}, function (err) {
|
||||
st.ok(err && (/must be.*true.*false.*non-empty string/i).test(err.message), 'throws with correct message');
|
||||
});
|
||||
});
|
||||
|
||||
t.test('engines: object throws', function (st) {
|
||||
st.plan(1);
|
||||
resolve('tape', {
|
||||
basedir: __dirname,
|
||||
engines: { node: '>= 14' }
|
||||
}, function (err) {
|
||||
st.ok(err && (/must be.*true.*false.*non-empty string/i).test(err.message), 'throws with correct message');
|
||||
});
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('async exports resolution - conditions override', function (t) {
|
||||
var projectDir = path.join(fixturesDir, 'ex-conditions', 'project');
|
||||
|
||||
t.test('default category conditions resolve to require.js', function (st) {
|
||||
st.plan(1);
|
||||
resolve('ex-conditions/rdni', {
|
||||
basedir: __dirname,
|
||||
exportsCategory: 'conditions',
|
||||
packageIterator: function () {
|
||||
return [projectDir];
|
||||
}
|
||||
}, function (err, result) {
|
||||
if (err) {
|
||||
st.fail(err.message);
|
||||
return;
|
||||
}
|
||||
st.ok(result.indexOf('require.js') > -1, 'resolves to require.js with default conditions');
|
||||
});
|
||||
});
|
||||
|
||||
t.test('conditions override to [default] resolves to default.js', function (st) {
|
||||
st.plan(1);
|
||||
resolve('ex-conditions/rdni', {
|
||||
basedir: __dirname,
|
||||
exportsCategory: 'conditions',
|
||||
conditions: ['default'],
|
||||
packageIterator: function () {
|
||||
return [projectDir];
|
||||
}
|
||||
}, function (err, result) {
|
||||
if (err) {
|
||||
st.fail(err.message);
|
||||
return;
|
||||
}
|
||||
st.ok(result.indexOf('default.js') > -1, 'resolves to default.js with conditions override');
|
||||
});
|
||||
});
|
||||
|
||||
t.test('conditions override to [node] resolves to node.js', function (st) {
|
||||
st.plan(1);
|
||||
resolve('ex-conditions/rdni', {
|
||||
basedir: __dirname,
|
||||
exportsCategory: 'conditions',
|
||||
conditions: ['node'],
|
||||
packageIterator: function () {
|
||||
return [projectDir];
|
||||
}
|
||||
}, function (err, result) {
|
||||
if (err) {
|
||||
st.fail(err.message);
|
||||
return;
|
||||
}
|
||||
st.ok(result.indexOf('node.js') > -1, 'resolves to node.js with conditions override');
|
||||
});
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('async exports resolution - subpath not exported throws', function (t) {
|
||||
var projectDir = path.join(fixturesDir, 'ex-exports-string', 'project');
|
||||
|
||||
t.plan(2);
|
||||
resolve('ex-exports-string/not-exported', {
|
||||
basedir: __dirname,
|
||||
exportsCategory: 'conditions',
|
||||
packageIterator: function () {
|
||||
return [projectDir];
|
||||
}
|
||||
}, function (err) {
|
||||
t.equal(err && err.code, 'ERR_PACKAGE_PATH_NOT_EXPORTED', 'has correct error code');
|
||||
t.ok(err && (/not defined by "exports"/).test(err.message), 'has correct error message');
|
||||
});
|
||||
});
|
||||
|
||||
test('async existing resolution without exports options still works', function (t) {
|
||||
t.plan(1);
|
||||
resolve('tape', { basedir: __dirname }, function (err, result) {
|
||||
if (err) {
|
||||
t.fail(err.message);
|
||||
return;
|
||||
}
|
||||
t.ok(result.indexOf('tape') > -1, 'resolves tape without exports options');
|
||||
});
|
||||
});
|
||||
|
||||
test('all fixtures are tested', function (t) {
|
||||
var fixtures = getFixtures();
|
||||
var testedFixtures = [];
|
||||
|
||||
fixtures.forEach(function (fixtureName) {
|
||||
var projectPkg = loadProjectPkg(fixtureName);
|
||||
if (!projectPkg) {
|
||||
t.fail('Fixture ' + fixtureName + ' has no loadable package.json');
|
||||
return;
|
||||
}
|
||||
|
||||
var hasAnyTests = false;
|
||||
|
||||
// Check if at least one category has expected results
|
||||
categories.forEach(function (category) {
|
||||
var expected = loadExpected(fixtureName, category);
|
||||
if (expected && expected.exports && expected.exports[category]) {
|
||||
var requireMap = expected.exports[category].require;
|
||||
if (requireMap && typeof requireMap === 'object' && Object.keys(requireMap).length > 0) {
|
||||
hasAnyTests = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Also check pre-exports
|
||||
var preExpected = loadExpected(fixtureName, 'pre-exports');
|
||||
if (preExpected && preExpected.exports && preExpected.exports['pre-exports']) {
|
||||
var preRequireMap = preExpected.exports['pre-exports'].require;
|
||||
if (preRequireMap && preRequireMap['.']) {
|
||||
hasAnyTests = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasAnyTests) {
|
||||
testedFixtures.push(fixtureName);
|
||||
} else {
|
||||
t.fail('Fixture ' + fixtureName + ' has no testable entrypoints');
|
||||
}
|
||||
});
|
||||
|
||||
t.ok(testedFixtures.length > 0, 'At least one fixture is tested');
|
||||
t.equal(testedFixtures.length, fixtures.length, 'All ' + fixtures.length + ' fixtures have testable entrypoints');
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('async exports resolution - self-reference', function (t) {
|
||||
var projectDir = path.join(fixturesDir, 'ex-exports-string', 'project');
|
||||
|
||||
t.test('self-reference resolves via exports when inside package', function (st) {
|
||||
st.plan(1);
|
||||
// basedir is inside the package, specifier is the package name
|
||||
resolve('ex-exports-string', {
|
||||
basedir: projectDir,
|
||||
exportsCategory: 'conditions'
|
||||
}, function (err, result) {
|
||||
if (err) {
|
||||
st.fail(err.message);
|
||||
return;
|
||||
}
|
||||
st.ok(result.indexOf('index.js') > -1, 'self-reference resolves to index.js via exports');
|
||||
});
|
||||
});
|
||||
|
||||
t.test('self-reference with subpath resolves via exports', function (st) {
|
||||
var conditionsDir = path.join(fixturesDir, 'ex-conditions', 'project');
|
||||
st.plan(1);
|
||||
resolve('ex-conditions/rdni', {
|
||||
basedir: conditionsDir,
|
||||
exportsCategory: 'conditions'
|
||||
}, function (err, result) {
|
||||
if (err) {
|
||||
st.fail(err.message);
|
||||
return;
|
||||
}
|
||||
st.ok(result.indexOf('require.js') > -1, 'self-reference subpath resolves correctly');
|
||||
});
|
||||
});
|
||||
|
||||
t.test('self-reference without exports falls back to main', function (st) {
|
||||
var mainDotlessDir = path.join(fixturesDir, 'ex-main-dotless', 'project');
|
||||
st.plan(1);
|
||||
resolve('ex-main-dotless', {
|
||||
basedir: mainDotlessDir,
|
||||
exportsCategory: 'conditions'
|
||||
}, function (err, result) {
|
||||
// If it throws, that's also acceptable behavior (no exports means not exported)
|
||||
st.ok(!err || result, 'self-reference without exports throws or resolves');
|
||||
});
|
||||
});
|
||||
|
||||
t.test('self-reference does not cross node_modules boundary', function (st) {
|
||||
// basedir is inside node_modules, should NOT self-reference parent package
|
||||
var nodeModulesDir = path.join(__dirname, '..', 'node_modules', 'tape');
|
||||
st.plan(1);
|
||||
resolve('resolve', {
|
||||
basedir: nodeModulesDir,
|
||||
exportsCategory: 'conditions'
|
||||
}, function (err, result) {
|
||||
if (err) {
|
||||
st.fail(err.message);
|
||||
return;
|
||||
}
|
||||
// The result should be from node_modules, not from a self-reference
|
||||
st.ok(result.indexOf('node_modules') > -1 || result === 'resolve', 'does not self-reference across node_modules');
|
||||
});
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
+442
@@ -0,0 +1,442 @@
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var test = require('tape');
|
||||
var resolve = require('../sync');
|
||||
|
||||
var fixturesDir = path.join(__dirname, 'list-exports', 'packages', 'tests', 'fixtures');
|
||||
|
||||
var categories = [
|
||||
'broken',
|
||||
'broken-dir-slash-conditions',
|
||||
'conditions',
|
||||
'experimental',
|
||||
'pattern-trailers',
|
||||
'pattern-trailers+json-imports',
|
||||
'pattern-trailers-no-dir-slash',
|
||||
'pattern-trailers-no-dir-slash+json-imports',
|
||||
'patterns',
|
||||
'require-esm',
|
||||
'strips-types',
|
||||
'subpath-imports-slash'
|
||||
// 'pre-exports' is tested separately since it uses main/index resolution
|
||||
];
|
||||
|
||||
// Fixtures that are symlinks pointing outside the fixture dir cause path confusion
|
||||
// ex-private is a private package whose expected files don't include exports data
|
||||
var skipFixtures = ['list-exports', 'ls-exports', 'ex-private'];
|
||||
|
||||
function getFixtures() {
|
||||
return fs.readdirSync(fixturesDir).filter(function (name) {
|
||||
if (skipFixtures.indexOf(name) !== -1) {
|
||||
return false;
|
||||
}
|
||||
var stat = fs.statSync(path.join(fixturesDir, name));
|
||||
return stat.isDirectory();
|
||||
});
|
||||
}
|
||||
|
||||
function loadExpected(fixtureName, category) {
|
||||
var expectedPath = path.join(fixturesDir, fixtureName, 'expected', category + '.json');
|
||||
if (!fs.existsSync(expectedPath)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(expectedPath, 'utf8'));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function loadProjectPkg(fixtureName) {
|
||||
var pkgPath = path.join(fixturesDir, fixtureName, 'project', 'package.json');
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
test('exports resolution - exportsCategory option', function (t) {
|
||||
var fixtures = getFixtures();
|
||||
|
||||
fixtures.forEach(function (fixtureName) {
|
||||
var projectPkg = loadProjectPkg(fixtureName);
|
||||
if (!projectPkg) {
|
||||
return;
|
||||
}
|
||||
var projectDir = path.join(fixturesDir, fixtureName, 'project');
|
||||
var pkgName = projectPkg.name;
|
||||
|
||||
categories.forEach(function (category) {
|
||||
var expected = loadExpected(fixtureName, category);
|
||||
if (!expected || !expected.exports || !expected.exports[category]) {
|
||||
return;
|
||||
}
|
||||
|
||||
var requireMap = expected.exports[category].require;
|
||||
if (!requireMap || typeof requireMap !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(requireMap).forEach(function (subpath) {
|
||||
var expectedFile = requireMap[subpath];
|
||||
var specifier = subpath === '.' ? pkgName : pkgName + subpath.substring(1);
|
||||
|
||||
t.test(fixtureName + ' / ' + category + ' / ' + subpath, function (st) {
|
||||
st.plan(1);
|
||||
try {
|
||||
var result = resolve(specifier, {
|
||||
basedir: __dirname,
|
||||
exportsCategory: category,
|
||||
extensions: ['.js', '.json'],
|
||||
packageIterator: function () {
|
||||
return [projectDir];
|
||||
}
|
||||
});
|
||||
var relativeResult = './' + path.relative(projectDir, result).split(path.sep).join('/');
|
||||
st.equal(relativeResult, expectedFile, specifier + ' resolves to ' + expectedFile);
|
||||
} catch (e) {
|
||||
st.fail('Unexpected error for ' + specifier + ': ' + e.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('exports resolution - pre-exports category uses main/index', function (t) {
|
||||
var fixtures = getFixtures();
|
||||
|
||||
fixtures.forEach(function (fixtureName) {
|
||||
var projectPkg = loadProjectPkg(fixtureName);
|
||||
if (!projectPkg) {
|
||||
return;
|
||||
}
|
||||
var projectDir = path.join(fixturesDir, fixtureName, 'project');
|
||||
var pkgName = projectPkg.name;
|
||||
|
||||
var expected = loadExpected(fixtureName, 'pre-exports');
|
||||
if (!expected || !expected.exports || !expected.exports['pre-exports']) {
|
||||
return;
|
||||
}
|
||||
|
||||
var requireMap = expected.exports['pre-exports'].require;
|
||||
if (!requireMap || typeof requireMap !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
// For pre-exports, only test the main entry point (.)
|
||||
var mainEntry = requireMap['.'];
|
||||
if (!mainEntry) {
|
||||
return;
|
||||
}
|
||||
|
||||
t.test(fixtureName + ' / pre-exports / .', function (st) {
|
||||
st.plan(1);
|
||||
try {
|
||||
var result = resolve(pkgName, {
|
||||
basedir: __dirname,
|
||||
exportsCategory: 'pre-exports',
|
||||
extensions: ['.js', '.json'],
|
||||
packageIterator: function () {
|
||||
return [projectDir];
|
||||
}
|
||||
});
|
||||
var relativeResult = './' + path.relative(projectDir, result).split(path.sep).join('/');
|
||||
st.equal(relativeResult, mainEntry, pkgName + ' resolves to ' + mainEntry);
|
||||
} catch (e) {
|
||||
st.fail('Unexpected error for ' + pkgName + ': ' + e.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('exports resolution - mutual exclusivity of options', function (t) {
|
||||
t.test('exportsCategory and engines (string) are mutually exclusive', function (st) {
|
||||
st.plan(1);
|
||||
try {
|
||||
resolve('tape', {
|
||||
basedir: __dirname,
|
||||
exportsCategory: 'conditions',
|
||||
engines: '>= 14'
|
||||
});
|
||||
st.fail('should have thrown');
|
||||
} catch (e) {
|
||||
st.ok((/mutually exclusive/).test(e.message), 'throws with mutually exclusive message');
|
||||
}
|
||||
});
|
||||
|
||||
t.test('exportsCategory and engines (true) are mutually exclusive', function (st) {
|
||||
st.plan(1);
|
||||
try {
|
||||
resolve('tape', {
|
||||
basedir: __dirname,
|
||||
exportsCategory: 'conditions',
|
||||
engines: true
|
||||
});
|
||||
st.fail('should have thrown');
|
||||
} catch (e) {
|
||||
st.ok((/mutually exclusive/).test(e.message), 'throws with mutually exclusive message');
|
||||
}
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('exports resolution - invalid category', function (t) {
|
||||
t.plan(2);
|
||||
try {
|
||||
resolve('tape', {
|
||||
basedir: __dirname,
|
||||
exportsCategory: 'not-a-real-category'
|
||||
});
|
||||
t.fail('should have thrown');
|
||||
} catch (e) {
|
||||
t.equal(e.code, 'INVALID_EXPORTS_CATEGORY', 'has correct error code');
|
||||
t.ok((/Invalid exports category/).test(e.message), 'has correct error message');
|
||||
}
|
||||
});
|
||||
|
||||
test('exports resolution - engines option', function (t) {
|
||||
var projectDir = path.join(fixturesDir, 'ex-exports-string', 'project');
|
||||
|
||||
t.test('engines string maps to category', function (st) {
|
||||
st.plan(1);
|
||||
var result = resolve('ex-exports-string', {
|
||||
basedir: __dirname,
|
||||
engines: '>= 14',
|
||||
packageIterator: function () {
|
||||
return [projectDir];
|
||||
}
|
||||
});
|
||||
st.ok(result.indexOf('index.js') > -1, 'resolves to index.js');
|
||||
});
|
||||
|
||||
t.test('engines: false is same as omitting', function (st) {
|
||||
st.plan(1);
|
||||
var result = resolve('tape', {
|
||||
basedir: __dirname,
|
||||
engines: false
|
||||
});
|
||||
st.ok(result.indexOf('tape') > -1, 'resolves without exports resolution');
|
||||
});
|
||||
|
||||
t.test('engines: empty string throws', function (st) {
|
||||
st.plan(1);
|
||||
try {
|
||||
resolve('tape', {
|
||||
basedir: __dirname,
|
||||
engines: ''
|
||||
});
|
||||
st.fail('should have thrown');
|
||||
} catch (e) {
|
||||
st.ok((/must be.*true.*false.*non-empty string/i).test(e.message), 'throws with correct message');
|
||||
}
|
||||
});
|
||||
|
||||
t.test('engines: number throws', function (st) {
|
||||
st.plan(1);
|
||||
try {
|
||||
resolve('tape', {
|
||||
basedir: __dirname,
|
||||
engines: 14
|
||||
});
|
||||
st.fail('should have thrown');
|
||||
} catch (e) {
|
||||
st.ok((/must be.*true.*false.*non-empty string/i).test(e.message), 'throws with correct message');
|
||||
}
|
||||
});
|
||||
|
||||
t.test('engines: object throws', function (st) {
|
||||
st.plan(1);
|
||||
try {
|
||||
resolve('tape', {
|
||||
basedir: __dirname,
|
||||
engines: { node: '>= 14' }
|
||||
});
|
||||
st.fail('should have thrown');
|
||||
} catch (e) {
|
||||
st.ok((/must be.*true.*false.*non-empty string/i).test(e.message), 'throws with correct message');
|
||||
}
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('exports resolution - conditions override', function (t) {
|
||||
var projectDir = path.join(fixturesDir, 'ex-conditions', 'project');
|
||||
|
||||
t.test('default category conditions resolve to require.js', function (st) {
|
||||
st.plan(1);
|
||||
var result = resolve('ex-conditions/rdni', {
|
||||
basedir: __dirname,
|
||||
exportsCategory: 'conditions',
|
||||
packageIterator: function () {
|
||||
return [projectDir];
|
||||
}
|
||||
});
|
||||
st.ok(result.indexOf('require.js') > -1, 'resolves to require.js with default conditions');
|
||||
});
|
||||
|
||||
t.test('conditions override to [default] resolves to default.js', function (st) {
|
||||
st.plan(1);
|
||||
var result = resolve('ex-conditions/rdni', {
|
||||
basedir: __dirname,
|
||||
exportsCategory: 'conditions',
|
||||
conditions: ['default'],
|
||||
packageIterator: function () {
|
||||
return [projectDir];
|
||||
}
|
||||
});
|
||||
st.ok(result.indexOf('default.js') > -1, 'resolves to default.js with conditions override');
|
||||
});
|
||||
|
||||
t.test('conditions override to [node] resolves to node.js', function (st) {
|
||||
st.plan(1);
|
||||
var result = resolve('ex-conditions/rdni', {
|
||||
basedir: __dirname,
|
||||
exportsCategory: 'conditions',
|
||||
conditions: ['node'],
|
||||
packageIterator: function () {
|
||||
return [projectDir];
|
||||
}
|
||||
});
|
||||
st.ok(result.indexOf('node.js') > -1, 'resolves to node.js with conditions override');
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('exports resolution - subpath not exported throws', function (t) {
|
||||
var projectDir = path.join(fixturesDir, 'ex-exports-string', 'project');
|
||||
|
||||
t.plan(2);
|
||||
try {
|
||||
resolve('ex-exports-string/not-exported', {
|
||||
basedir: __dirname,
|
||||
exportsCategory: 'conditions',
|
||||
packageIterator: function () {
|
||||
return [projectDir];
|
||||
}
|
||||
});
|
||||
t.fail('should have thrown');
|
||||
} catch (e) {
|
||||
t.equal(e.code, 'ERR_PACKAGE_PATH_NOT_EXPORTED', 'has correct error code');
|
||||
t.ok((/not defined by "exports"/).test(e.message), 'has correct error message');
|
||||
}
|
||||
});
|
||||
|
||||
test('existing resolution without exports options still works', function (t) {
|
||||
t.plan(1);
|
||||
var result = resolve('tape', { basedir: __dirname });
|
||||
t.ok(result.indexOf('tape') > -1, 'resolves tape without exports options');
|
||||
});
|
||||
|
||||
test('all fixtures are tested', function (t) {
|
||||
var fixtures = getFixtures();
|
||||
var testedFixtures = [];
|
||||
|
||||
fixtures.forEach(function (fixtureName) {
|
||||
var projectPkg = loadProjectPkg(fixtureName);
|
||||
if (!projectPkg) {
|
||||
t.fail('Fixture ' + fixtureName + ' has no loadable package.json');
|
||||
return;
|
||||
}
|
||||
|
||||
var hasAnyTests = false;
|
||||
|
||||
// Check if at least one category has expected results
|
||||
categories.forEach(function (category) {
|
||||
var expected = loadExpected(fixtureName, category);
|
||||
if (expected && expected.exports && expected.exports[category]) {
|
||||
var requireMap = expected.exports[category].require;
|
||||
if (requireMap && typeof requireMap === 'object' && Object.keys(requireMap).length > 0) {
|
||||
hasAnyTests = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Also check pre-exports
|
||||
var preExpected = loadExpected(fixtureName, 'pre-exports');
|
||||
if (preExpected && preExpected.exports && preExpected.exports['pre-exports']) {
|
||||
var preRequireMap = preExpected.exports['pre-exports'].require;
|
||||
if (preRequireMap && preRequireMap['.']) {
|
||||
hasAnyTests = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasAnyTests) {
|
||||
testedFixtures.push(fixtureName);
|
||||
} else {
|
||||
t.fail('Fixture ' + fixtureName + ' has no testable entrypoints');
|
||||
}
|
||||
});
|
||||
|
||||
t.ok(testedFixtures.length > 0, 'At least one fixture is tested');
|
||||
t.equal(testedFixtures.length, fixtures.length, 'All ' + fixtures.length + ' fixtures have testable entrypoints');
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('exports resolution - self-reference', function (t) {
|
||||
var projectDir = path.join(fixturesDir, 'ex-exports-string', 'project');
|
||||
|
||||
t.test('self-reference resolves via exports when inside package', function (st) {
|
||||
st.plan(1);
|
||||
// basedir is inside the package, specifier is the package name
|
||||
var result = resolve('ex-exports-string', {
|
||||
basedir: projectDir,
|
||||
exportsCategory: 'conditions'
|
||||
});
|
||||
st.ok(result.indexOf('index.js') > -1, 'self-reference resolves to index.js via exports');
|
||||
});
|
||||
|
||||
t.test('self-reference with subpath resolves via exports', function (st) {
|
||||
var conditionsDir = path.join(fixturesDir, 'ex-conditions', 'project');
|
||||
st.plan(1);
|
||||
var result = resolve('ex-conditions/rdni', {
|
||||
basedir: conditionsDir,
|
||||
exportsCategory: 'conditions'
|
||||
});
|
||||
st.ok(result.indexOf('require.js') > -1, 'self-reference subpath resolves correctly');
|
||||
});
|
||||
|
||||
t.test('self-reference without exports falls back to main', function (st) {
|
||||
// Create a scenario where there's no exports field
|
||||
var mainDotlessDir = path.join(fixturesDir, 'ex-main-dotless', 'project');
|
||||
st.plan(1);
|
||||
try {
|
||||
var result = resolve('ex-main-dotless', {
|
||||
basedir: mainDotlessDir,
|
||||
exportsCategory: 'conditions'
|
||||
});
|
||||
st.ok(result.indexOf('main.js') > -1 || result.indexOf('index.js') > -1, 'self-reference without exports uses main/index');
|
||||
} catch (e) {
|
||||
// If it throws, that's also acceptable behavior (no exports means not exported)
|
||||
st.ok(true, 'self-reference without exports throws or resolves');
|
||||
}
|
||||
});
|
||||
|
||||
t.test('self-reference does not cross node_modules boundary', function (st) {
|
||||
// basedir is inside node_modules, should NOT self-reference parent package
|
||||
var nodeModulesDir = path.join(__dirname, '..', 'node_modules', 'tape');
|
||||
st.plan(1);
|
||||
// Trying to resolve 'resolve' from inside node_modules/tape should NOT
|
||||
// self-reference the root resolve package - it should use normal resolution
|
||||
var result = resolve('resolve', {
|
||||
basedir: nodeModulesDir,
|
||||
exportsCategory: 'conditions'
|
||||
});
|
||||
// The result should be from node_modules, not from a self-reference
|
||||
// (self-reference would give us the current working directory's resolve)
|
||||
st.ok(result.indexOf('node_modules') > -1 || result === 'resolve', 'does not self-reference across node_modules');
|
||||
});
|
||||
|
||||
t.end();
|
||||
});
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
var test = require('tape');
|
||||
var path = require('path');
|
||||
var resolve = require('../');
|
||||
|
||||
test('faulty basedir must produce error in windows', { skip: process.platform !== 'win32' }, function (t) {
|
||||
t.plan(1);
|
||||
|
||||
var resolverDir = 'C:\\a\\b\\c\\d';
|
||||
|
||||
resolve('tape/lib/test.js', { basedir: resolverDir }, function (err, res, pkg) {
|
||||
t.equal(!!err, true);
|
||||
});
|
||||
});
|
||||
|
||||
test('non-existent basedir should not throw when preserveSymlinks is false', function (t) {
|
||||
t.plan(2);
|
||||
|
||||
var opts = {
|
||||
basedir: path.join(path.sep, 'unreal', 'path', 'that', 'does', 'not', 'exist'),
|
||||
preserveSymlinks: false
|
||||
};
|
||||
|
||||
var module = './dotdot/abc';
|
||||
|
||||
resolve(module, opts, function (err, res) {
|
||||
t.equal(err.code, 'INVALID_BASEDIR');
|
||||
t.equal(res, undefined);
|
||||
});
|
||||
});
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
var path = require('path');
|
||||
var test = require('tape');
|
||||
var resolve = require('../');
|
||||
|
||||
test('filter', function (t) {
|
||||
t.plan(5);
|
||||
var dir = path.join(__dirname, 'resolver');
|
||||
var packageFilterArgs;
|
||||
resolve('./baz', {
|
||||
basedir: dir,
|
||||
packageFilter: function (pkg, pkgfile, dir) {
|
||||
pkg.main = 'doom'; // eslint-disable-line no-param-reassign
|
||||
packageFilterArgs = [pkg, pkgfile, dir];
|
||||
return pkg;
|
||||
}
|
||||
}, function (err, res, pkg) {
|
||||
if (err) t.fail(err);
|
||||
|
||||
t.equal(res, path.join(dir, 'baz/doom.js'), 'changing the package "main" works');
|
||||
|
||||
var packageData = packageFilterArgs[0];
|
||||
t.equal(pkg, packageData, 'first packageFilter argument is "pkg"');
|
||||
t.equal(packageData.main, 'doom', 'package "main" was altered');
|
||||
|
||||
var packageFile = packageFilterArgs[1];
|
||||
t.equal(
|
||||
packageFile,
|
||||
path.join(dir, 'baz/package.json'),
|
||||
'second packageFilter argument is "pkgfile"'
|
||||
);
|
||||
|
||||
var packageFileDir = packageFilterArgs[2];
|
||||
t.equal(packageFileDir, path.join(dir, 'baz'), 'third packageFilter argument is "dir"');
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
var path = require('path');
|
||||
var test = require('tape');
|
||||
var resolve = require('../');
|
||||
|
||||
test('filter', function (t) {
|
||||
var dir = path.join(__dirname, 'resolver');
|
||||
var packageFilterArgs;
|
||||
var res = resolve.sync('./baz', {
|
||||
basedir: dir,
|
||||
packageFilter: function (pkg, pkgfile, dir) {
|
||||
pkg.main = 'doom'; // eslint-disable-line no-param-reassign
|
||||
packageFilterArgs = [pkg, pkgfile, dir];
|
||||
return pkg;
|
||||
}
|
||||
});
|
||||
|
||||
t.equal(res, path.join(dir, 'baz/doom.js'), 'changing the package "main" works');
|
||||
|
||||
var packageData = packageFilterArgs[0];
|
||||
t.equal(packageData.main, 'doom', 'package "main" was altered');
|
||||
|
||||
var packageFile = packageFilterArgs[1];
|
||||
t.equal(
|
||||
packageFile,
|
||||
path.join(dir, 'baz/package.json'),
|
||||
'second packageFilter argument is "pkgfile"'
|
||||
);
|
||||
|
||||
var packageDir = packageFilterArgs[2];
|
||||
t.equal(packageDir, path.join(dir, 'baz'), 'third packageFilter argument is "dir"');
|
||||
|
||||
t.end();
|
||||
});
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var homedir = require('../lib/homedir');
|
||||
var path = require('path');
|
||||
|
||||
var test = require('tape');
|
||||
var mkdirp = require('mkdirp');
|
||||
var rimraf = require('rimraf');
|
||||
var mv = require('mv');
|
||||
var copyDir = require('copy-dir');
|
||||
var tmp = require('tmp');
|
||||
|
||||
var HOME = homedir();
|
||||
|
||||
var hnm = path.join(HOME, '.node_modules');
|
||||
var hnl = path.join(HOME, '.node_libraries');
|
||||
|
||||
var resolve = require('../async');
|
||||
|
||||
function makeDir(t, dir, cb) {
|
||||
mkdirp(dir, function (err) {
|
||||
if (err) {
|
||||
cb(err);
|
||||
} else {
|
||||
t.teardown(function cleanup() {
|
||||
rimraf.sync(dir);
|
||||
});
|
||||
cb();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function makeTempDir(t, dir, cb) {
|
||||
if (fs.existsSync(dir)) {
|
||||
var tmpResult = tmp.dirSync();
|
||||
t.teardown(tmpResult.removeCallback);
|
||||
var backup = path.join(tmpResult.name, path.basename(dir));
|
||||
mv(dir, backup, function (err) {
|
||||
if (err) {
|
||||
cb(err);
|
||||
} else {
|
||||
t.teardown(function () {
|
||||
mv(backup, dir, cb);
|
||||
});
|
||||
makeDir(t, dir, cb);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
makeDir(t, dir, cb);
|
||||
}
|
||||
}
|
||||
|
||||
test('homedir module paths', function (t) {
|
||||
t.plan(7);
|
||||
|
||||
makeTempDir(t, hnm, function (err) {
|
||||
t.error(err, 'no error with HNM temp dir');
|
||||
if (err) {
|
||||
return t.end();
|
||||
}
|
||||
|
||||
var bazHNMDir = path.join(hnm, 'baz');
|
||||
var dotMainDir = path.join(hnm, 'dot_main');
|
||||
copyDir.sync(path.join(__dirname, 'resolver/baz'), bazHNMDir);
|
||||
copyDir.sync(path.join(__dirname, 'resolver/dot_main'), dotMainDir);
|
||||
|
||||
var bazPkg = { name: 'baz', main: 'quux.js' };
|
||||
var dotMainPkg = { main: 'index' };
|
||||
|
||||
var bazHNMmain = path.join(bazHNMDir, 'quux.js');
|
||||
t.equal(require.resolve('baz'), bazHNMmain, 'sanity check: require.resolve finds HNM `baz`');
|
||||
var dotMainMain = path.join(dotMainDir, 'index.js');
|
||||
t.equal(require.resolve('dot_main'), dotMainMain, 'sanity check: require.resolve finds `dot_main`');
|
||||
|
||||
makeTempDir(t, hnl, function (err) {
|
||||
t.error(err, 'no error with HNL temp dir');
|
||||
if (err) {
|
||||
return t.end();
|
||||
}
|
||||
var bazHNLDir = path.join(hnl, 'baz');
|
||||
copyDir.sync(path.join(__dirname, 'resolver/baz'), bazHNLDir);
|
||||
|
||||
var dotSlashMainDir = path.join(hnl, 'dot_slash_main');
|
||||
var dotSlashMainMain = path.join(dotSlashMainDir, 'index.js');
|
||||
var dotSlashMainPkg = { main: 'index' };
|
||||
copyDir.sync(path.join(__dirname, 'resolver/dot_slash_main'), dotSlashMainDir);
|
||||
|
||||
t.equal(require.resolve('baz'), bazHNMmain, 'sanity check: require.resolve finds HNM `baz`');
|
||||
t.equal(require.resolve('dot_slash_main'), dotSlashMainMain, 'sanity check: require.resolve finds HNL `dot_slash_main`');
|
||||
|
||||
t.test('with temp dirs', function (st) {
|
||||
st.plan(3);
|
||||
|
||||
st.test('just in `$HOME/.node_modules`', function (s2t) {
|
||||
s2t.plan(3);
|
||||
|
||||
resolve('dot_main', function (err, res, pkg) {
|
||||
s2t.error(err, 'no error resolving `dot_main`');
|
||||
s2t.equal(res, dotMainMain, '`dot_main` resolves in `$HOME/.node_modules`');
|
||||
s2t.deepEqual(pkg, dotMainPkg);
|
||||
});
|
||||
});
|
||||
|
||||
st.test('just in `$HOME/.node_libraries`', function (s2t) {
|
||||
s2t.plan(3);
|
||||
|
||||
resolve('dot_slash_main', function (err, res, pkg) {
|
||||
s2t.error(err, 'no error resolving `dot_slash_main`');
|
||||
s2t.equal(res, dotSlashMainMain, '`dot_slash_main` resolves in `$HOME/.node_libraries`');
|
||||
s2t.deepEqual(pkg, dotSlashMainPkg);
|
||||
});
|
||||
});
|
||||
|
||||
st.test('in `$HOME/.node_libraries` and `$HOME/.node_modules`', function (s2t) {
|
||||
s2t.plan(3);
|
||||
|
||||
resolve('baz', function (err, res, pkg) {
|
||||
s2t.error(err, 'no error resolving `baz`');
|
||||
s2t.equal(res, bazHNMmain, '`baz` resolves in `$HOME/.node_modules` when in both');
|
||||
s2t.deepEqual(pkg, bazPkg);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var homedir = require('../lib/homedir');
|
||||
var path = require('path');
|
||||
|
||||
var test = require('tape');
|
||||
var mkdirp = require('mkdirp');
|
||||
var rimraf = require('rimraf');
|
||||
var mv = require('mv');
|
||||
var copyDir = require('copy-dir');
|
||||
var tmp = require('tmp');
|
||||
|
||||
var HOME = homedir();
|
||||
|
||||
var hnm = path.join(HOME, '.node_modules');
|
||||
var hnl = path.join(HOME, '.node_libraries');
|
||||
|
||||
var resolve = require('../sync');
|
||||
|
||||
function makeDir(t, dir, cb) {
|
||||
mkdirp(dir, function (err) {
|
||||
if (err) {
|
||||
cb(err);
|
||||
} else {
|
||||
t.teardown(function cleanup() {
|
||||
rimraf.sync(dir);
|
||||
});
|
||||
cb();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function makeTempDir(t, dir, cb) {
|
||||
if (fs.existsSync(dir)) {
|
||||
var tmpResult = tmp.dirSync();
|
||||
t.teardown(tmpResult.removeCallback);
|
||||
var backup = path.join(tmpResult.name, path.basename(dir));
|
||||
mv(dir, backup, function (err) {
|
||||
if (err) {
|
||||
cb(err);
|
||||
} else {
|
||||
t.teardown(function () {
|
||||
mv(backup, dir, cb);
|
||||
});
|
||||
makeDir(t, dir, cb);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
makeDir(t, dir, cb);
|
||||
}
|
||||
}
|
||||
|
||||
test('homedir module paths', function (t) {
|
||||
t.plan(7);
|
||||
|
||||
makeTempDir(t, hnm, function (err) {
|
||||
t.error(err, 'no error with HNM temp dir');
|
||||
if (err) {
|
||||
return t.end();
|
||||
}
|
||||
|
||||
var bazHNMDir = path.join(hnm, 'baz');
|
||||
var dotMainDir = path.join(hnm, 'dot_main');
|
||||
copyDir.sync(path.join(__dirname, 'resolver/baz'), bazHNMDir);
|
||||
copyDir.sync(path.join(__dirname, 'resolver/dot_main'), dotMainDir);
|
||||
|
||||
var bazHNMmain = path.join(bazHNMDir, 'quux.js');
|
||||
t.equal(require.resolve('baz'), bazHNMmain, 'sanity check: require.resolve finds HNM `baz`');
|
||||
var dotMainMain = path.join(dotMainDir, 'index.js');
|
||||
t.equal(require.resolve('dot_main'), dotMainMain, 'sanity check: require.resolve finds `dot_main`');
|
||||
|
||||
makeTempDir(t, hnl, function (err) {
|
||||
t.error(err, 'no error with HNL temp dir');
|
||||
if (err) {
|
||||
return t.end();
|
||||
}
|
||||
var bazHNLDir = path.join(hnl, 'baz');
|
||||
copyDir.sync(path.join(__dirname, 'resolver/baz'), bazHNLDir);
|
||||
|
||||
var dotSlashMainDir = path.join(hnl, 'dot_slash_main');
|
||||
var dotSlashMainMain = path.join(dotSlashMainDir, 'index.js');
|
||||
copyDir.sync(path.join(__dirname, 'resolver/dot_slash_main'), dotSlashMainDir);
|
||||
|
||||
t.equal(require.resolve('baz'), bazHNMmain, 'sanity check: require.resolve finds HNM `baz`');
|
||||
t.equal(require.resolve('dot_slash_main'), dotSlashMainMain, 'sanity check: require.resolve finds HNL `dot_slash_main`');
|
||||
|
||||
t.test('with temp dirs', function (st) {
|
||||
st.plan(3);
|
||||
|
||||
st.test('just in `$HOME/.node_modules`', function (s2t) {
|
||||
s2t.plan(1);
|
||||
|
||||
var res = resolve('dot_main');
|
||||
s2t.equal(res, dotMainMain, '`dot_main` resolves in `$HOME/.node_modules`');
|
||||
});
|
||||
|
||||
st.test('just in `$HOME/.node_libraries`', function (s2t) {
|
||||
s2t.plan(1);
|
||||
|
||||
var res = resolve('dot_slash_main');
|
||||
s2t.equal(res, dotSlashMainMain, '`dot_slash_main` resolves in `$HOME/.node_libraries`');
|
||||
});
|
||||
|
||||
st.test('in `$HOME/.node_libraries` and `$HOME/.node_modules`', function (s2t) {
|
||||
s2t.plan(1);
|
||||
|
||||
var res = resolve('baz');
|
||||
s2t.equal(res, bazHNMmain, '`baz` resolves in `$HOME/.node_modules` when in both');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+315
@@ -0,0 +1,315 @@
|
||||
var path = require('path');
|
||||
var test = require('tape');
|
||||
var resolve = require('../');
|
||||
|
||||
test('mock', function (t) {
|
||||
t.plan(8);
|
||||
|
||||
var files = {};
|
||||
files[path.resolve('/foo/bar/baz.js')] = 'beep';
|
||||
|
||||
var dirs = {};
|
||||
dirs[path.resolve('/foo/bar')] = true;
|
||||
|
||||
function opts(basedir) {
|
||||
return {
|
||||
basedir: path.resolve(basedir),
|
||||
isFile: function (file, cb) {
|
||||
cb(null, Object.prototype.hasOwnProperty.call(files, path.resolve(file)));
|
||||
},
|
||||
isDirectory: function (dir, cb) {
|
||||
cb(null, !!dirs[path.resolve(dir)]);
|
||||
},
|
||||
readFile: function (file, cb) {
|
||||
cb(null, files[path.resolve(file)]);
|
||||
},
|
||||
realpath: function (file, cb) {
|
||||
cb(null, file);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
resolve('./baz', opts('/foo/bar'), function (err, res, pkg) {
|
||||
if (err) return t.fail(err);
|
||||
t.equal(res, path.resolve('/foo/bar/baz.js'));
|
||||
t.equal(pkg, undefined);
|
||||
});
|
||||
|
||||
resolve('./baz.js', opts('/foo/bar'), function (err, res, pkg) {
|
||||
if (err) return t.fail(err);
|
||||
t.equal(res, path.resolve('/foo/bar/baz.js'));
|
||||
t.equal(pkg, undefined);
|
||||
});
|
||||
|
||||
resolve('baz', opts('/foo/bar'), function (err, res) {
|
||||
t.equal(err.message, "Cannot find module 'baz' from '" + path.resolve('/foo/bar') + "'");
|
||||
t.equal(err.code, 'MODULE_NOT_FOUND');
|
||||
});
|
||||
|
||||
resolve('../baz', opts('/foo/bar'), function (err, res) {
|
||||
t.equal(err.message, "Cannot find module '../baz' from '" + path.resolve('/foo/bar') + "'");
|
||||
t.equal(err.code, 'MODULE_NOT_FOUND');
|
||||
});
|
||||
});
|
||||
|
||||
test('mock from package', function (t) {
|
||||
t.plan(8);
|
||||
|
||||
var files = {};
|
||||
files[path.resolve('/foo/bar/baz.js')] = 'beep';
|
||||
|
||||
var dirs = {};
|
||||
dirs[path.resolve('/foo/bar')] = true;
|
||||
|
||||
function opts(basedir) {
|
||||
return {
|
||||
basedir: path.resolve(basedir),
|
||||
isFile: function (file, cb) {
|
||||
cb(null, Object.prototype.hasOwnProperty.call(files, file));
|
||||
},
|
||||
isDirectory: function (dir, cb) {
|
||||
cb(null, !!dirs[path.resolve(dir)]);
|
||||
},
|
||||
'package': { main: 'bar' },
|
||||
readFile: function (file, cb) {
|
||||
cb(null, files[file]);
|
||||
},
|
||||
realpath: function (file, cb) {
|
||||
cb(null, file);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
resolve('./baz', opts('/foo/bar'), function (err, res, pkg) {
|
||||
if (err) return t.fail(err);
|
||||
t.equal(res, path.resolve('/foo/bar/baz.js'));
|
||||
t.equal(pkg && pkg.main, 'bar');
|
||||
});
|
||||
|
||||
resolve('./baz.js', opts('/foo/bar'), function (err, res, pkg) {
|
||||
if (err) return t.fail(err);
|
||||
t.equal(res, path.resolve('/foo/bar/baz.js'));
|
||||
t.equal(pkg && pkg.main, 'bar');
|
||||
});
|
||||
|
||||
resolve('baz', opts('/foo/bar'), function (err, res) {
|
||||
t.equal(err.message, "Cannot find module 'baz' from '" + path.resolve('/foo/bar') + "'");
|
||||
t.equal(err.code, 'MODULE_NOT_FOUND');
|
||||
});
|
||||
|
||||
resolve('../baz', opts('/foo/bar'), function (err, res) {
|
||||
t.equal(err.message, "Cannot find module '../baz' from '" + path.resolve('/foo/bar') + "'");
|
||||
t.equal(err.code, 'MODULE_NOT_FOUND');
|
||||
});
|
||||
});
|
||||
|
||||
test('mock package', function (t) {
|
||||
t.plan(2);
|
||||
|
||||
var files = {};
|
||||
files[path.resolve('/foo/node_modules/bar/baz.js')] = 'beep';
|
||||
files[path.resolve('/foo/node_modules/bar/package.json')] = JSON.stringify({
|
||||
main: './baz.js'
|
||||
});
|
||||
|
||||
var dirs = {};
|
||||
dirs[path.resolve('/foo')] = true;
|
||||
dirs[path.resolve('/foo/node_modules')] = true;
|
||||
|
||||
function opts(basedir) {
|
||||
return {
|
||||
basedir: path.resolve(basedir),
|
||||
isFile: function (file, cb) {
|
||||
cb(null, Object.prototype.hasOwnProperty.call(files, path.resolve(file)));
|
||||
},
|
||||
isDirectory: function (dir, cb) {
|
||||
cb(null, !!dirs[path.resolve(dir)]);
|
||||
},
|
||||
readFile: function (file, cb) {
|
||||
cb(null, files[path.resolve(file)]);
|
||||
},
|
||||
realpath: function (file, cb) {
|
||||
cb(null, file);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
resolve('bar', opts('/foo'), function (err, res, pkg) {
|
||||
if (err) return t.fail(err);
|
||||
t.equal(res, path.resolve('/foo/node_modules/bar/baz.js'));
|
||||
t.equal(pkg && pkg.main, './baz.js');
|
||||
});
|
||||
});
|
||||
|
||||
test('mock package from package', function (t) {
|
||||
t.plan(2);
|
||||
|
||||
var files = {};
|
||||
files[path.resolve('/foo/node_modules/bar/baz.js')] = 'beep';
|
||||
files[path.resolve('/foo/node_modules/bar/package.json')] = JSON.stringify({
|
||||
main: './baz.js'
|
||||
});
|
||||
|
||||
var dirs = {};
|
||||
dirs[path.resolve('/foo')] = true;
|
||||
dirs[path.resolve('/foo/node_modules')] = true;
|
||||
|
||||
function opts(basedir) {
|
||||
return {
|
||||
basedir: path.resolve(basedir),
|
||||
isFile: function (file, cb) {
|
||||
cb(null, Object.prototype.hasOwnProperty.call(files, path.resolve(file)));
|
||||
},
|
||||
isDirectory: function (dir, cb) {
|
||||
cb(null, !!dirs[path.resolve(dir)]);
|
||||
},
|
||||
'package': { main: 'bar' },
|
||||
readFile: function (file, cb) {
|
||||
cb(null, files[path.resolve(file)]);
|
||||
},
|
||||
realpath: function (file, cb) {
|
||||
cb(null, file);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
resolve('bar', opts('/foo'), function (err, res, pkg) {
|
||||
if (err) return t.fail(err);
|
||||
t.equal(res, path.resolve('/foo/node_modules/bar/baz.js'));
|
||||
t.equal(pkg && pkg.main, './baz.js');
|
||||
});
|
||||
});
|
||||
|
||||
test('symlinked', function (t) {
|
||||
t.plan(4);
|
||||
|
||||
var files = {};
|
||||
files[path.resolve('/foo/bar/baz.js')] = 'beep';
|
||||
files[path.resolve('/foo/bar/symlinked/baz.js')] = 'beep';
|
||||
|
||||
var dirs = {};
|
||||
dirs[path.resolve('/foo/bar')] = true;
|
||||
dirs[path.resolve('/foo/bar/symlinked')] = true;
|
||||
|
||||
function opts(basedir) {
|
||||
return {
|
||||
preserveSymlinks: false,
|
||||
basedir: path.resolve(basedir),
|
||||
isFile: function (file, cb) {
|
||||
cb(null, Object.prototype.hasOwnProperty.call(files, path.resolve(file)));
|
||||
},
|
||||
isDirectory: function (dir, cb) {
|
||||
cb(null, !!dirs[path.resolve(dir)]);
|
||||
},
|
||||
readFile: function (file, cb) {
|
||||
cb(null, files[path.resolve(file)]);
|
||||
},
|
||||
realpath: function (file, cb) {
|
||||
var resolved = path.resolve(file);
|
||||
|
||||
if (resolved.indexOf('symlinked') >= 0) {
|
||||
cb(null, resolved);
|
||||
return;
|
||||
}
|
||||
|
||||
var ext = path.extname(resolved);
|
||||
|
||||
if (ext) {
|
||||
var dir = path.dirname(resolved);
|
||||
var base = path.basename(resolved);
|
||||
cb(null, path.join(dir, 'symlinked', base));
|
||||
} else {
|
||||
cb(null, path.join(resolved, 'symlinked'));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
resolve('./baz', opts('/foo/bar'), function (err, res, pkg) {
|
||||
if (err) return t.fail(err);
|
||||
t.equal(res, path.resolve('/foo/bar/symlinked/baz.js'));
|
||||
t.equal(pkg, undefined);
|
||||
});
|
||||
|
||||
resolve('./baz.js', opts('/foo/bar'), function (err, res, pkg) {
|
||||
if (err) return t.fail(err);
|
||||
t.equal(res, path.resolve('/foo/bar/symlinked/baz.js'));
|
||||
t.equal(pkg, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
test('readPackage', function (t) {
|
||||
t.plan(3);
|
||||
|
||||
var files = {};
|
||||
files[path.resolve('/foo/node_modules/bar/something-else.js')] = 'beep';
|
||||
files[path.resolve('/foo/node_modules/bar/package.json')] = JSON.stringify({
|
||||
main: './baz.js'
|
||||
});
|
||||
files[path.resolve('/foo/node_modules/bar/baz.js')] = 'boop';
|
||||
|
||||
var dirs = {};
|
||||
dirs[path.resolve('/foo')] = true;
|
||||
dirs[path.resolve('/foo/node_modules')] = true;
|
||||
|
||||
function opts(basedir) {
|
||||
return {
|
||||
basedir: path.resolve(basedir),
|
||||
isFile: function (file, cb) {
|
||||
cb(null, Object.prototype.hasOwnProperty.call(files, path.resolve(file)));
|
||||
},
|
||||
isDirectory: function (dir, cb) {
|
||||
cb(null, !!dirs[path.resolve(dir)]);
|
||||
},
|
||||
'package': { main: 'bar' },
|
||||
readFile: function (file, cb) {
|
||||
cb(null, files[path.resolve(file)]);
|
||||
},
|
||||
realpath: function (file, cb) {
|
||||
cb(null, file);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
t.test('with readFile', function (st) {
|
||||
st.plan(3);
|
||||
|
||||
resolve('bar', opts('/foo'), function (err, res, pkg) {
|
||||
st.error(err);
|
||||
st.equal(res, path.resolve('/foo/node_modules/bar/baz.js'));
|
||||
st.equal(pkg && pkg.main, './baz.js');
|
||||
});
|
||||
});
|
||||
|
||||
function readPackage(readFile, file, cb) {
|
||||
var barPackage = path.join('bar', 'package.json');
|
||||
if (file.slice(-barPackage.length) === barPackage) {
|
||||
cb(null, { main: './something-else.js' });
|
||||
} else {
|
||||
cb(null, JSON.parse(files[path.resolve(file)]));
|
||||
}
|
||||
}
|
||||
|
||||
t.test('with readPackage', function (st) {
|
||||
st.plan(3);
|
||||
|
||||
var options = opts('/foo');
|
||||
delete options.readFile;
|
||||
options.readPackage = readPackage;
|
||||
resolve('bar', options, function (err, res, pkg) {
|
||||
st.error(err);
|
||||
st.equal(res, path.resolve('/foo/node_modules/bar/something-else.js'));
|
||||
st.equal(pkg && pkg.main, './something-else.js');
|
||||
});
|
||||
});
|
||||
|
||||
t.test('with readFile and readPackage', function (st) {
|
||||
st.plan(1);
|
||||
|
||||
var options = opts('/foo');
|
||||
options.readPackage = readPackage;
|
||||
resolve('bar', options, function (err) {
|
||||
st.throws(function () { throw err; }, TypeError, 'errors when both readFile and readPackage are provided');
|
||||
});
|
||||
});
|
||||
});
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
var path = require('path');
|
||||
var test = require('tape');
|
||||
var resolve = require('../');
|
||||
|
||||
test('mock', function (t) {
|
||||
t.plan(4);
|
||||
|
||||
var files = {};
|
||||
files[path.resolve('/foo/bar/baz.js')] = 'beep';
|
||||
|
||||
var dirs = {};
|
||||
dirs[path.resolve('/foo/bar')] = true;
|
||||
dirs[path.resolve('/foo/node_modules')] = true;
|
||||
|
||||
function opts(basedir) {
|
||||
return {
|
||||
basedir: path.resolve(basedir),
|
||||
isFile: function (file) {
|
||||
return Object.prototype.hasOwnProperty.call(files, path.resolve(file));
|
||||
},
|
||||
isDirectory: function (dir) {
|
||||
return !!dirs[path.resolve(dir)];
|
||||
},
|
||||
readFileSync: function (file) {
|
||||
return files[path.resolve(file)];
|
||||
},
|
||||
realpathSync: function (file) {
|
||||
return file;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
t.equal(
|
||||
resolve.sync('./baz', opts('/foo/bar')),
|
||||
path.resolve('/foo/bar/baz.js')
|
||||
);
|
||||
|
||||
t.equal(
|
||||
resolve.sync('./baz.js', opts('/foo/bar')),
|
||||
path.resolve('/foo/bar/baz.js')
|
||||
);
|
||||
|
||||
t.throws(function () {
|
||||
resolve.sync('baz', opts('/foo/bar'));
|
||||
});
|
||||
|
||||
t.throws(function () {
|
||||
resolve.sync('../baz', opts('/foo/bar'));
|
||||
});
|
||||
});
|
||||
|
||||
test('mock package', function (t) {
|
||||
t.plan(1);
|
||||
|
||||
var files = {};
|
||||
files[path.resolve('/foo/node_modules/bar/baz.js')] = 'beep';
|
||||
files[path.resolve('/foo/node_modules/bar/package.json')] = JSON.stringify({
|
||||
main: './baz.js'
|
||||
});
|
||||
|
||||
var dirs = {};
|
||||
dirs[path.resolve('/foo')] = true;
|
||||
dirs[path.resolve('/foo/node_modules')] = true;
|
||||
|
||||
function opts(basedir) {
|
||||
return {
|
||||
basedir: path.resolve(basedir),
|
||||
isFile: function (file) {
|
||||
return Object.prototype.hasOwnProperty.call(files, path.resolve(file));
|
||||
},
|
||||
isDirectory: function (dir) {
|
||||
return !!dirs[path.resolve(dir)];
|
||||
},
|
||||
readFileSync: function (file) {
|
||||
return files[path.resolve(file)];
|
||||
},
|
||||
realpathSync: function (file) {
|
||||
return file;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
t.equal(
|
||||
resolve.sync('bar', opts('/foo')),
|
||||
path.resolve('/foo/node_modules/bar/baz.js')
|
||||
);
|
||||
});
|
||||
|
||||
test('symlinked', function (t) {
|
||||
t.plan(2);
|
||||
|
||||
var files = {};
|
||||
files[path.resolve('/foo/bar/baz.js')] = 'beep';
|
||||
files[path.resolve('/foo/bar/symlinked/baz.js')] = 'beep';
|
||||
|
||||
var dirs = {};
|
||||
dirs[path.resolve('/foo/bar')] = true;
|
||||
dirs[path.resolve('/foo/bar/symlinked')] = true;
|
||||
|
||||
function opts(basedir) {
|
||||
return {
|
||||
preserveSymlinks: false,
|
||||
basedir: path.resolve(basedir),
|
||||
isFile: function (file) {
|
||||
return Object.prototype.hasOwnProperty.call(files, path.resolve(file));
|
||||
},
|
||||
isDirectory: function (dir) {
|
||||
return !!dirs[path.resolve(dir)];
|
||||
},
|
||||
readFileSync: function (file) {
|
||||
return files[path.resolve(file)];
|
||||
},
|
||||
realpathSync: function (file) {
|
||||
var resolved = path.resolve(file);
|
||||
|
||||
if (resolved.indexOf('symlinked') >= 0) {
|
||||
return resolved;
|
||||
}
|
||||
|
||||
var ext = path.extname(resolved);
|
||||
|
||||
if (ext) {
|
||||
var dir = path.dirname(resolved);
|
||||
var base = path.basename(resolved);
|
||||
return path.join(dir, 'symlinked', base);
|
||||
}
|
||||
return path.join(resolved, 'symlinked');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
t.equal(
|
||||
resolve.sync('./baz', opts('/foo/bar')),
|
||||
path.resolve('/foo/bar/symlinked/baz.js')
|
||||
);
|
||||
|
||||
t.equal(
|
||||
resolve.sync('./baz.js', opts('/foo/bar')),
|
||||
path.resolve('/foo/bar/symlinked/baz.js')
|
||||
);
|
||||
});
|
||||
|
||||
test('readPackageSync', function (t) {
|
||||
t.plan(3);
|
||||
|
||||
var files = {};
|
||||
files[path.resolve('/foo/node_modules/bar/something-else.js')] = 'beep';
|
||||
files[path.resolve('/foo/node_modules/bar/package.json')] = JSON.stringify({
|
||||
main: './baz.js'
|
||||
});
|
||||
files[path.resolve('/foo/node_modules/bar/baz.js')] = 'boop';
|
||||
|
||||
var dirs = {};
|
||||
dirs[path.resolve('/foo')] = true;
|
||||
dirs[path.resolve('/foo/node_modules')] = true;
|
||||
|
||||
function opts(basedir, useReadPackage) {
|
||||
return {
|
||||
basedir: path.resolve(basedir),
|
||||
isFile: function (file) {
|
||||
return Object.prototype.hasOwnProperty.call(files, path.resolve(file));
|
||||
},
|
||||
isDirectory: function (dir) {
|
||||
return !!dirs[path.resolve(dir)];
|
||||
},
|
||||
readFileSync: useReadPackage ? null : function (file) {
|
||||
return files[path.resolve(file)];
|
||||
},
|
||||
realpathSync: function (file) {
|
||||
return file;
|
||||
}
|
||||
};
|
||||
}
|
||||
t.test('with readFile', function (st) {
|
||||
st.plan(1);
|
||||
|
||||
st.equal(
|
||||
resolve.sync('bar', opts('/foo')),
|
||||
path.resolve('/foo/node_modules/bar/baz.js')
|
||||
);
|
||||
});
|
||||
|
||||
function readPackageSync(readFileSync, file) {
|
||||
if (file.indexOf(path.join('bar', 'package.json')) >= 0) {
|
||||
return { main: './something-else.js' };
|
||||
}
|
||||
return JSON.parse(files[path.resolve(file)]);
|
||||
}
|
||||
|
||||
t.test('with readPackage', function (st) {
|
||||
st.plan(1);
|
||||
|
||||
var options = opts('/foo');
|
||||
delete options.readFileSync;
|
||||
options.readPackageSync = readPackageSync;
|
||||
|
||||
st.equal(
|
||||
resolve.sync('bar', options),
|
||||
path.resolve('/foo/node_modules/bar/something-else.js')
|
||||
);
|
||||
});
|
||||
|
||||
t.test('with readFile and readPackage', function (st) {
|
||||
st.plan(1);
|
||||
|
||||
var options = opts('/foo');
|
||||
options.readPackageSync = readPackageSync;
|
||||
st.throws(
|
||||
function () { resolve.sync('bar', options); },
|
||||
TypeError,
|
||||
'errors when both readFile and readPackage are provided'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
var path = require('path');
|
||||
var test = require('tape');
|
||||
var resolve = require('../');
|
||||
|
||||
test('moduleDirectory strings', function (t) {
|
||||
t.plan(4);
|
||||
var dir = path.join(__dirname, 'module_dir');
|
||||
var xopts = {
|
||||
basedir: dir,
|
||||
moduleDirectory: 'xmodules'
|
||||
};
|
||||
resolve('aaa', xopts, function (err, res, pkg) {
|
||||
t.ifError(err);
|
||||
t.equal(res, path.join(dir, '/xmodules/aaa/index.js'));
|
||||
});
|
||||
|
||||
var yopts = {
|
||||
basedir: dir,
|
||||
moduleDirectory: 'ymodules'
|
||||
};
|
||||
resolve('aaa', yopts, function (err, res, pkg) {
|
||||
t.ifError(err);
|
||||
t.equal(res, path.join(dir, '/ymodules/aaa/index.js'));
|
||||
});
|
||||
});
|
||||
|
||||
test('moduleDirectory array', function (t) {
|
||||
t.plan(6);
|
||||
var dir = path.join(__dirname, 'module_dir');
|
||||
var aopts = {
|
||||
basedir: dir,
|
||||
moduleDirectory: ['xmodules', 'ymodules', 'zmodules']
|
||||
};
|
||||
resolve('aaa', aopts, function (err, res, pkg) {
|
||||
t.ifError(err);
|
||||
t.equal(res, path.join(dir, '/xmodules/aaa/index.js'));
|
||||
});
|
||||
|
||||
var bopts = {
|
||||
basedir: dir,
|
||||
moduleDirectory: ['zmodules', 'ymodules', 'xmodules']
|
||||
};
|
||||
resolve('aaa', bopts, function (err, res, pkg) {
|
||||
t.ifError(err);
|
||||
t.equal(res, path.join(dir, '/ymodules/aaa/index.js'));
|
||||
});
|
||||
|
||||
var copts = {
|
||||
basedir: dir,
|
||||
moduleDirectory: ['xmodules', 'ymodules', 'zmodules']
|
||||
};
|
||||
resolve('bbb', copts, function (err, res, pkg) {
|
||||
t.ifError(err);
|
||||
t.equal(res, path.join(dir, '/zmodules/bbb/main.js'));
|
||||
});
|
||||
});
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"main": "main.js"
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
var test = require('tape');
|
||||
var path = require('path');
|
||||
var parse = path.parse || require('path-parse');
|
||||
var keys = require('object-keys');
|
||||
|
||||
var nodeModulesPaths = require('../lib/node-modules-paths');
|
||||
|
||||
function verifyDirs(t, start, dirs, moduleDirectories, paths) {
|
||||
var moduleDirs = [].concat(moduleDirectories || 'node_modules');
|
||||
if (paths) {
|
||||
for (var k = 0; k < paths.length; ++k) {
|
||||
moduleDirs.push(path.basename(paths[k]));
|
||||
}
|
||||
}
|
||||
|
||||
var foundModuleDirs = {};
|
||||
var uniqueDirs = {};
|
||||
var parsedDirs = {};
|
||||
for (var i = 0; i < dirs.length; ++i) {
|
||||
var parsed = parse(dirs[i]);
|
||||
if (!foundModuleDirs[parsed.base]) { foundModuleDirs[parsed.base] = 0; }
|
||||
foundModuleDirs[parsed.base] += 1;
|
||||
parsedDirs[parsed.dir] = true;
|
||||
uniqueDirs[dirs[i]] = true;
|
||||
}
|
||||
t.equal(keys(parsedDirs).length >= start.split(path.sep).length, true, 'there are >= dirs than "start" has');
|
||||
var foundModuleDirNames = keys(foundModuleDirs);
|
||||
t.deepEqual(foundModuleDirNames, moduleDirs, 'all desired module dirs were found');
|
||||
t.equal(keys(uniqueDirs).length, dirs.length, 'all dirs provided were unique');
|
||||
|
||||
var counts = {};
|
||||
for (var j = 0; j < foundModuleDirNames.length; ++j) {
|
||||
counts[foundModuleDirs[j]] = true;
|
||||
}
|
||||
t.equal(keys(counts).length, 1, 'all found module directories had the same count');
|
||||
}
|
||||
|
||||
test('node-modules-paths', function (t) {
|
||||
t.test('no options', function (t) {
|
||||
var start = path.join(__dirname, 'resolver');
|
||||
var dirs = nodeModulesPaths(start);
|
||||
|
||||
verifyDirs(t, start, dirs);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test('empty options', function (t) {
|
||||
var start = path.join(__dirname, 'resolver');
|
||||
var dirs = nodeModulesPaths(start, {});
|
||||
|
||||
verifyDirs(t, start, dirs);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test('with paths=array option', function (t) {
|
||||
var start = path.join(__dirname, 'resolver');
|
||||
var paths = ['a', 'b'];
|
||||
var dirs = nodeModulesPaths(start, { paths: paths });
|
||||
|
||||
verifyDirs(t, start, dirs, null, paths);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test('with paths=function option', function (t) {
|
||||
function paths(request, absoluteStart, getNodeModulesDirs, opts) {
|
||||
return getNodeModulesDirs().concat(path.join(absoluteStart, 'not node modules', request));
|
||||
}
|
||||
|
||||
var start = path.join(__dirname, 'resolver');
|
||||
var dirs = nodeModulesPaths(start, { paths: paths }, 'pkg');
|
||||
|
||||
verifyDirs(t, start, dirs, null, [path.join(start, 'not node modules', 'pkg')]);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test('with paths=function skipping node modules resolution', function (t) {
|
||||
function paths(request, absoluteStart, getNodeModulesDirs, opts) {
|
||||
return [];
|
||||
}
|
||||
var start = path.join(__dirname, 'resolver');
|
||||
var dirs = nodeModulesPaths(start, { paths: paths });
|
||||
t.deepEqual(dirs, [], 'no node_modules was computed');
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test('with moduleDirectory option', function (t) {
|
||||
var start = path.join(__dirname, 'resolver');
|
||||
var moduleDirectory = 'not node modules';
|
||||
var dirs = nodeModulesPaths(start, { moduleDirectory: moduleDirectory });
|
||||
|
||||
verifyDirs(t, start, dirs, moduleDirectory);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test('with 1 moduleDirectory and paths options', function (t) {
|
||||
var start = path.join(__dirname, 'resolver');
|
||||
var paths = ['a', 'b'];
|
||||
var moduleDirectory = 'not node modules';
|
||||
var dirs = nodeModulesPaths(start, { paths: paths, moduleDirectory: moduleDirectory });
|
||||
|
||||
verifyDirs(t, start, dirs, moduleDirectory, paths);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test('with 1+ moduleDirectory and paths options', function (t) {
|
||||
var start = path.join(__dirname, 'resolver');
|
||||
var paths = ['a', 'b'];
|
||||
var moduleDirectories = ['not node modules', 'other modules'];
|
||||
var dirs = nodeModulesPaths(start, { paths: paths, moduleDirectory: moduleDirectories });
|
||||
|
||||
verifyDirs(t, start, dirs, moduleDirectories, paths);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test('combine paths correctly on Windows', function (t) {
|
||||
var start = 'C:\\Users\\username\\myProject\\src';
|
||||
var paths = [];
|
||||
var moduleDirectories = ['node_modules', start];
|
||||
var dirs = nodeModulesPaths(start, { paths: paths, moduleDirectory: moduleDirectories });
|
||||
|
||||
t.equal(dirs.indexOf(path.resolve(start)) > -1, true, 'should contain start dir');
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
t.test('combine paths correctly on non-Windows', { skip: process.platform === 'win32' }, function (t) {
|
||||
var start = '/Users/username/git/myProject/src';
|
||||
var paths = [];
|
||||
var moduleDirectories = ['node_modules', '/Users/username/git/myProject/src'];
|
||||
var dirs = nodeModulesPaths(start, { paths: paths, moduleDirectory: moduleDirectories });
|
||||
|
||||
t.equal(dirs.indexOf(path.resolve(start)) > -1, true, 'should contain start dir');
|
||||
|
||||
t.end();
|
||||
});
|
||||
});
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var test = require('tape');
|
||||
var resolve = require('../');
|
||||
|
||||
test('$NODE_PATH', function (t) {
|
||||
t.plan(8);
|
||||
|
||||
function isDir(dir, cb) {
|
||||
if (dir === '/node_path' || dir === 'node_path/x') {
|
||||
return cb(null, true);
|
||||
}
|
||||
fs.stat(dir, function (err, stat) {
|
||||
if (!err) {
|
||||
return cb(null, stat.isDirectory());
|
||||
}
|
||||
if (err.code === 'ENOENT' || err.code === 'ENOTDIR') return cb(null, false);
|
||||
return cb(err);
|
||||
});
|
||||
}
|
||||
|
||||
resolve('aaa', {
|
||||
paths: [
|
||||
path.join(__dirname, '/node_path/x'),
|
||||
path.join(__dirname, '/node_path/y')
|
||||
],
|
||||
basedir: __dirname,
|
||||
isDirectory: isDir
|
||||
}, function (err, res) {
|
||||
t.error(err);
|
||||
t.equal(res, path.join(__dirname, '/node_path/x/aaa/index.js'), 'aaa resolves');
|
||||
});
|
||||
|
||||
resolve('bbb', {
|
||||
paths: [
|
||||
path.join(__dirname, '/node_path/x'),
|
||||
path.join(__dirname, '/node_path/y')
|
||||
],
|
||||
basedir: __dirname,
|
||||
isDirectory: isDir
|
||||
}, function (err, res) {
|
||||
t.error(err);
|
||||
t.equal(res, path.join(__dirname, '/node_path/y/bbb/index.js'), 'bbb resolves');
|
||||
});
|
||||
|
||||
resolve('ccc', {
|
||||
paths: [
|
||||
path.join(__dirname, '/node_path/x'),
|
||||
path.join(__dirname, '/node_path/y')
|
||||
],
|
||||
basedir: __dirname,
|
||||
isDirectory: isDir
|
||||
}, function (err, res) {
|
||||
t.error(err);
|
||||
t.equal(res, path.join(__dirname, '/node_path/x/ccc/index.js'), 'ccc resolves');
|
||||
});
|
||||
|
||||
// ensure that relative paths still resolve against the regular `node_modules` correctly
|
||||
resolve('tap', {
|
||||
paths: [
|
||||
'node_path'
|
||||
],
|
||||
basedir: path.join(__dirname, 'node_path/x'),
|
||||
isDirectory: isDir
|
||||
}, function (err, res) {
|
||||
var root = require('tap/package.json').main; // eslint-disable-line global-require
|
||||
t.error(err);
|
||||
t.equal(res.replace('/node_modules/.vlt/··tap@0.4.13/', '/'), path.resolve(__dirname, '..', 'node_modules/tap', root), 'tap resolves');
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user