Skip to content
This repository has been archived by the owner on Jul 28, 2021. It is now read-only.

Commit

Permalink
feat(pkgmap): move package map up and override all node_modules dirs
Browse files Browse the repository at this point in the history
  • Loading branch information
zkat committed Oct 29, 2018
1 parent 219bd08 commit bde11a0
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 46 deletions.
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function checkPkgMap () {
try {
const base = process.cwd()
const lock = JSON.parse(stripBOM(fs.readFileSync(path.join(base, 'package-lock.json'), 'utf8')))
const map = JSON.parse(stripBOM(fs.readFileSync(path.join(base, 'node_modules', '.package-map.json'), 'utf8')))
const map = JSON.parse(stripBOM(fs.readFileSync(path.join(base, '.package-map.json'), 'utf8')))
require('ssri').checkData(
JSON.stringify(lock), map.lockfile_integrity, {error: true}
)
Expand Down
8 changes: 3 additions & 5 deletions lib/installer.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ class Installer {
readJson(prefix, 'package.json'),
readJson(prefix, 'package-lock.json', true),
readJson(prefix, 'npm-shrinkwrap.json', true),
readJson(path.join(prefix, 'node_modules'), '.package-map.json', true),
readJson(prefix, '.package-map.json', true),
(pkg, lock, shrink, map) => {
if (shrink) {
this.log('verbose', 'prepare', 'using npm-shrinkwrap.json')
Expand Down Expand Up @@ -337,7 +337,7 @@ class Installer {
const lockHash = ssri.fromData(lockStr, {algorithms: ['sha256']})
const pkgMap = {
'lockfile_integrity': lockHash.toString(),
'path_prefix': '/.package-map.json'
'path_prefix': '/node_modules'
}
tree.forEach((dep, next) => {
if (dep.isRoot) { return next() }
Expand All @@ -364,9 +364,7 @@ class Installer {
}

async writePackageMap (map) {
const nm = path.join(this.prefix, 'node_modules')
await mkdirp(nm)
await writeFileAsync(path.join(nm, '.package-map.json'), stringifyPkg(map))
await writeFileAsync(path.join(this.prefix, '.package-map.json'), stringifyPkg(map))
}
}
module.exports = treeFrog
Expand Down
1 change: 1 addition & 0 deletions lib/node/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ function overrideNode () {
return stats
}
}
fs.statSync.orig = statSync

const {stat} = fs
fs.stat = function (p, callback) {
Expand Down
7 changes: 1 addition & 6 deletions lib/node/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@

// NOTE: The code here is almost all identical to the regular module.js.
// It's reloaded here because the `process.binding('fs')` override
// can't retroactively affect module.js, and because `.package-map.json`
// needs to be added to the search path. All other functionality is
// can't retroactively affect module.js. All other functionality is
// done by the fs override itself.
module.exports.overrideNode = overrideNode
function overrideNode () {
Expand Down Expand Up @@ -137,10 +136,6 @@ function overrideNode () {

let warned = false
Module._findPath = function (request, paths, isMain) {
paths = paths.reduce((acc, p) => {
acc.push(p, path.join(p, '.package-map.json'))
return acc
}, [])
if (path.isAbsolute(request)) {
paths = ['']
} else if (!paths || paths.length === 0) {
Expand Down
63 changes: 38 additions & 25 deletions lib/pkgmap.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@ const path = require('path')
const ssri = require('ssri')

const pkgMapName = '.package-map.json'
const mapNameLen = pkgMapName.length
const pkgMapCache = new Map()

const envNoPkgMap = process.env.FROG_NO_PKG_MAP
const isPkgMapDisabled = () => process.noPkgMap || envNoPkgMap
const isPkgMapDisabled = () => !process.tink || process.tink.noPkgMap || envNoPkgMap

module.exports.resolve = resolve
module.exports._clearCache = () => pkgMapCache.clear()
Expand All @@ -23,11 +22,15 @@ function resolve (...p) {
// expected package + file hash.
if (isPkgMapDisabled()) { return null }
const resolved = path.resolve(...p)
// If the file already exists in the filesystem, use the filesystem version
try {
(fs.statSync.orig || fs.statSync)(resolved, true)
return null
} catch (e) {}
const result = readPkgMap(resolved)
if (!result) { return result }
const {pkgMap, pkgMapIdx} = result
let {pkgMap, subPath} = result
if (!pkgMap) { return false }
let subPath = resolved.substr(pkgMapIdx - 1)
let pkgName, filePath
let scope = pkgMap
while (subPath) {
Expand Down Expand Up @@ -78,7 +81,7 @@ function resolveEntity (scope, pkgName, filePath) {
const next = split.shift()
if (next === '.') { continue }
location = location[next]
if (typeof location === 'string') {
if (typeof location === 'string' && !split.length) {
return {hash: location, isFile: true}
} else if (!location || typeof location !== 'object') {
return false
Expand All @@ -92,21 +95,31 @@ function resolveEntity (scope, pkgName, filePath) {
module.exports.readPkgMap = readPkgMap
function readPkgMap (...p) {
const resolved = path.resolve(...p)
const pkgMapIdx = resolved.indexOf(pkgMapName)
if (pkgMapIdx === -1 || pkgMapIdx + mapNameLen + 1 >= resolved.length) {
// Not in a pkgmapped path, or reading a .package-map.json itself
return null
}
const pkgMapPath = resolved.substr(0, pkgMapIdx + mapNameLen)
let pkgMap
if (pkgMapCache.has(pkgMapPath)) {
pkgMap = pkgMapCache.get(pkgMapPath)
} else {
const p = path.toNamespacedPath ? path.toNamespacedPath(pkgMapPath) : pkgMapPath
pkgMap = JSON.parse(fs.readFileSync(p))
pkgMapCache.set(pkgMapPath, pkgMap)
let modulesIdx = resolved.lastIndexOf('node_modules')
while (modulesIdx !== -1) {
let substr = resolved.substr(0, modulesIdx - 1)
const pkgMapPath = path.join(substr, pkgMapName)
let pkgMap
if (pkgMapCache.has(pkgMapPath)) {
pkgMap = pkgMapCache.get(pkgMapPath)
} else {
const p = path.toNamespacedPath(pkgMapPath)
try {
pkgMap = JSON.parse(fs.readFileSync(p))
pkgMapCache.set(pkgMapPath, pkgMap)
} catch (e) {
if (e.code !== 'ENOENT') {
throw e
}
}
}
if (pkgMap) {
return {pkgMap, subPath: resolved.substr(modulesIdx - 1)}
} else {
modulesIdx = substr.lastIndexOf('node_modules')
}
}
return {pkgMap, pkgMapIdx}
return null
}

module.exports.read = read
Expand Down Expand Up @@ -143,15 +156,15 @@ function readSync ({cache, hash, pkg, resolvedPath, isFile}) {

module.exports.stat = stat
async function stat ({cache, hash, pkg, resolvedPath, isDir}, verify) {
if (!cache || !hash) {
throw new Error('stat() requires a fully-resolved pkgmap file address')
}
if (isDir || path.basename(resolvedPath) === '.package-map.json') {
return Object.assign(fs.lstatSync(process.tink.cache), {
mode: 16676, // read-only
size: 64
})
}
if (!cache || !hash) {
throw new Error('stat() requires a fully-resolved pkgmap file address')
}
let info
try {
info = await ccRead.hasContent(cache, hash)
Expand Down Expand Up @@ -188,15 +201,15 @@ async function stat ({cache, hash, pkg, resolvedPath, isDir}, verify) {

module.exports.statSync = statSync
function statSync ({cache, hash, pkg, resolvedPath, isDir}, verify) {
if (!cache || !hash) {
throw new Error('statSync() requires a fully-resolved pkgmap file address')
}
if (isDir || path.basename(resolvedPath) === '.package-map.json') {
return Object.assign(fs.lstatSync(process.tink.cache), {
mode: 16676, // read-only
size: 64
})
}
if (!cache || !hash) {
throw new Error('statSync() requires a fully-resolved pkgmap file address')
}
let info
try {
info = ccRead.hasContent.sync(cache, hash)
Expand Down
61 changes: 52 additions & 9 deletions test/pkgmap.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,58 @@ const {File, Dir} = Tacks

const pkgmap = require('../lib/pkgmap.js')

test('UNIT readPkgMap', t => {
process.tink = {
cache: './here'
}
const pkgMap = {
path_prefix: '/node_modules',
packages: {
'eggplant': {
files: {
'hello.js': 'sha1-deadbeef'
}
}
},
scopes: {
'eggplant': {
path_prefix: '/node_modules',
packages: {
'aubergine': {
files: {
'bonjour.js': 'sha1-badc0ffee'
}
},
'@myscope/scoped': {
files: {
'ohmy.js': 'sha1-abcdef',
'lib': {
'hithere.js': 'sha1-badbebe'
}
}
}
}
}
}
}
const fixture = new Tacks(Dir({
'.package-map.json': File(pkgMap)
}))
fixture.create(testDir.path)
t.deepEqual(pkgmap.readPkgMap('node_modules/eggplant'), {
pkgMap,
subPath: '/node_modules/eggplant'
})
t.done()
})

test('resolve: finds an existing path into a .package-map.json', async t => {
process.tink = {
cache: './here'
}
const fixture = new Tacks(Dir({
'.package-map.json': File({
path_prefix: '/.package-map.json',
path_prefix: '/node_modules',
packages: {
'eggplant': {
files: {
Expand Down Expand Up @@ -48,7 +93,7 @@ test('resolve: finds an existing path into a .package-map.json', async t => {
})
}))
fixture.create(testDir.path)
const prefix = path.join(testDir.path, '.package-map.json', 'eggplant')
const prefix = path.join(testDir.path, 'node_modules', 'eggplant')
t.similar(pkgmap.resolve(prefix, 'hello.js'), {
cache: './here',
hash: 'sha1-deadbeef',
Expand Down Expand Up @@ -90,9 +135,7 @@ test('resolve: finds an existing path into a .package-map.json', async t => {
}
}, 'found file even though pkgmap deleted')
pkgmap._clearCache()
t.throws(() => {
pkgmap.resolve(prefix, 'hello.js')
}, /ENOENT/, 'cache gone after clearing')
t.equal(pkgmap.resolve(prefix, 'hello.js'), null, 'cache gone')
pkgmap._clearCache()
})

Expand All @@ -104,7 +147,7 @@ test('read: reads a file defined in a package map', async t => {
}
const fixture = new Tacks(Dir({
'.package-map.json': File({
path_prefix: '/.package-map.json',
path_prefix: '/node_modules',
packages: {
'eggplant': {
files: {
Expand All @@ -116,7 +159,7 @@ test('read: reads a file defined in a package map', async t => {
}))
fixture.create(testDir.path)
const p = pkgmap.resolve(
testDir.path, '.package-map.json', 'eggplant', 'hello.js'
testDir.path, 'node_modules', 'eggplant', 'hello.js'
)
t.equal(
(await pkgmap.read(p)).toString('utf8'),
Expand All @@ -141,7 +184,7 @@ test('stat: get filesystem stats for a file', async t => {
}
const fixture = new Tacks(Dir({
'.package-map.json': File({
path_prefix: '/.package-map.json',
path_prefix: '/node_modules',
packages: {
'eggplant': {
files: {
Expand All @@ -153,7 +196,7 @@ test('stat: get filesystem stats for a file', async t => {
}))
fixture.create(testDir.path)
const p = pkgmap.resolve(
testDir.path, '.package-map.json', 'eggplant', 'hello.js'
testDir.path, 'node_modules', 'eggplant', 'hello.js'
)
const stat = await pkgmap.stat(p)
t.ok(stat, 'got stat from cache file')
Expand Down

0 comments on commit bde11a0

Please sign in to comment.