@lavamoat/aa
Advanced tools
Comparing version 4.0.1 to 4.1.0
# Changelog | ||
## [4.1.0](https://github.com/LavaMoat/LavaMoat/compare/aa-v4.0.1...aa-v4.1.0) (2024-02-29) | ||
### Features | ||
* **aa:** add custom resolver ([90ed26e](https://github.com/LavaMoat/LavaMoat/commit/90ed26e54b62327a240eed47186541afab4aff24)) | ||
* **aa:** support symlinks for workspace and devtime linking compatibility ([66996c7](https://github.com/LavaMoat/LavaMoat/commit/66996c7964fecee08e4fcb0f01ee66047c8d204d)) | ||
## [4.0.1](https://github.com/LavaMoat/LavaMoat/compare/aa-v4.0.0...aa-v4.0.1) (2024-01-18) | ||
@@ -4,0 +12,0 @@ |
{ | ||
"name": "@lavamoat/aa", | ||
"version": "4.0.1", | ||
"version": "4.1.0", | ||
"description": "LavaMoat's secure package naming convention", | ||
@@ -5,0 +5,0 @@ "author": "kumavis", |
119
src/index.js
'use strict' | ||
const { readFileSync } = require('fs') | ||
const path = require('path') | ||
const { readFileSync, realpathSync, lstatSync } = require('node:fs') | ||
const path = require('node:path') | ||
const nodeResolve = require('resolve') | ||
@@ -16,24 +16,48 @@ | ||
/** | ||
* Default resolver if none provided | ||
*/ | ||
const performantResolve = createPerformantResolve() | ||
/** | ||
* A `string` or something coercible to a `string` | ||
* | ||
* @remarks | ||
* This is equivalent to the internal `StringOrToString` type of `resolve` | ||
* @typedef {string | { toString: () => string }} StringOrToString | ||
*/ | ||
/** | ||
* Performant resolve avoids loading package.jsons if their path is what's being | ||
* resolved, offering 2x performance improvement compared to using original | ||
* resolve | ||
* | ||
* This resolver, using the `resolve` package, is incompatible with subpath | ||
* exports. | ||
* | ||
* @returns {Resolver} | ||
* @see {@link https://npm.im/resolve} | ||
* @see {@link https://nodejs.org/api/packages.html#subpath-exports} | ||
*/ | ||
function createPerformantResolve() { | ||
/** | ||
* @param {string} self | ||
* @returns {( | ||
* readFileSync: (file: string) => string | { toString(): string }, | ||
* pkgfile: string | ||
* ) => Record<string, unknown>} | ||
* @param {string} filepath | ||
*/ | ||
const readPackageWithout = (self) => (readFileSync, pkgfile) => { | ||
// avoid loading the package.json we're just trying to resolve | ||
if (pkgfile.endsWith(self)) { | ||
return {} | ||
const readPackageWithout = (filepath) => { | ||
/** | ||
* @param {(path: string) => StringOrToString} readFileSync - Sync file | ||
* reader | ||
* @param {string} otherFilepath - Path to another `package.json` | ||
* @returns {Record<string, unknown> | undefined} | ||
*/ | ||
return (readFileSync, otherFilepath) => { | ||
// avoid loading the package.json we're just trying to resolve | ||
if (otherFilepath.endsWith(filepath)) { | ||
return {} | ||
} | ||
// original readPackageSync implementation from resolve internals: | ||
const body = readFileSync(otherFilepath) | ||
try { | ||
return JSON.parse(`${body}`) | ||
} catch (jsonErr) {} | ||
} | ||
// original readPackageSync implementation from resolve internals: | ||
var body = readFileSync(pkgfile) | ||
try { | ||
// @ts-expect-error - JSON.parse calls toString() on its parameter if not given a string | ||
var pkg = JSON.parse(body) | ||
return pkg | ||
} catch (jsonErr) {} | ||
} | ||
@@ -54,8 +78,8 @@ | ||
*/ | ||
async function loadCanonicalNameMap({ rootDir, includeDevDeps, resolve }) { | ||
async function loadCanonicalNameMap({ | ||
rootDir, | ||
includeDevDeps, | ||
resolve = performantResolve, | ||
}) { | ||
const canonicalNameMap = /** @type {CanonicalNameMap} */ (new Map()) | ||
// performant resolve avoids loading package.jsons if their path is what's being resolved, | ||
// offering 2x performance improvement compared to using original resolve | ||
resolve = resolve || createPerformantResolve() | ||
// resolve = resolve || nodeResolve | ||
// walk tree | ||
@@ -79,17 +103,26 @@ const logicalPathMap = walkDependencyTreeForBestLogicalPaths({ | ||
/** | ||
* @param {Resolver} resolve | ||
* @param {string} depName | ||
* @param {string} packageDir | ||
* Resolves `package.json` of a dependency relative to `basedir` | ||
* | ||
* @param {Resolver} resolve - Resolver function | ||
* @param {string} depName - Dependency name | ||
* @param {string} basedir - Dir to resolve from | ||
* @returns {string | undefined} | ||
*/ | ||
function wrappedResolveSync(resolve, depName, packageDir) { | ||
function wrappedResolveSync(resolve, depName, basedir) { | ||
const depRelativePackageJsonPath = path.join(depName, 'package.json') | ||
try { | ||
return resolve.sync(depRelativePackageJsonPath, { basedir: packageDir }) | ||
} catch (err) { | ||
if (!(/** @type {Error} */ (err).message.includes('Cannot find module'))) { | ||
throw err | ||
return resolve.sync(depRelativePackageJsonPath, { | ||
basedir, | ||
}) | ||
} catch (e) { | ||
const err = /** @type {Error} */ (e) | ||
if ( | ||
err && | ||
typeof err === 'object' && | ||
(('code' in err && err.code === 'MODULE_NOT_FOUND') || | ||
err.message?.startsWith('Cannot find module')) | ||
) { | ||
return | ||
} | ||
// debug: log resolution failures | ||
// console.log('resolve failed', depName, packageDir) | ||
throw err | ||
} | ||
@@ -116,2 +149,10 @@ } | ||
/** | ||
* @param {string} location | ||
*/ | ||
function isSymlink(location) { | ||
const info = lstatSync(location) | ||
return info.isSymbolicLink() | ||
} | ||
/** @type {WalkDepTreeOpts[]} */ | ||
@@ -132,5 +173,4 @@ let currentLevelTodos | ||
visited = new Set(), | ||
resolve, | ||
resolve = performantResolve, | ||
}) { | ||
resolve = resolve ?? createPerformantResolve() | ||
/** @type {Map<string, string[]>} */ | ||
@@ -152,2 +192,11 @@ const preferredPackageLogicalPathMap = new Map() | ||
for (const [ | ||
packageDir, | ||
logicalPath, | ||
] of preferredPackageLogicalPathMap.entries()) { | ||
if (isSymlink(packageDir)) { | ||
const realPath = realpathSync(packageDir) | ||
preferredPackageLogicalPathMap.set(realPath, logicalPath) | ||
} | ||
} | ||
return preferredPackageLogicalPathMap | ||
@@ -154,0 +203,0 @@ } |
@@ -0,1 +1,7 @@ | ||
/** | ||
* A `string` or something coercible to a `string` | ||
*/ | ||
export type StringOrToString = string | { | ||
toString: () => string; | ||
}; | ||
export type Resolver = { | ||
@@ -25,3 +31,3 @@ sync: (path: string, opts: { | ||
*/ | ||
export function loadCanonicalNameMap({ rootDir, includeDevDeps, resolve }: LoadCanonicalNameMapOpts): Promise<CanonicalNameMap>; | ||
export function loadCanonicalNameMap({ rootDir, includeDevDeps, resolve, }: LoadCanonicalNameMapOpts): Promise<CanonicalNameMap>; | ||
/** | ||
@@ -45,5 +51,21 @@ * @param {WalkDepTreeOpts} options | ||
/** | ||
* A `string` or something coercible to a `string` | ||
* | ||
* @remarks | ||
* This is equivalent to the internal `StringOrToString` type of `resolve` | ||
* @typedef {string | { toString: () => string }} StringOrToString | ||
*/ | ||
/** | ||
* Performant resolve avoids loading package.jsons if their path is what's being | ||
* resolved, offering 2x performance improvement compared to using original | ||
* resolve | ||
* | ||
* This resolver, using the `resolve` package, is incompatible with subpath | ||
* exports. | ||
* | ||
* @returns {Resolver} | ||
* @see {@link https://npm.im/resolve} | ||
* @see {@link https://nodejs.org/api/packages.html#subpath-exports} | ||
*/ | ||
export function createPerformantResolve(): Resolver; | ||
//# sourceMappingURL=index.d.ts.map |
Sorry, the diff of this file is not supported yet
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
19279
465
1
1