refactor: move @pnpm/local-resolver to monorepo

This commit is contained in:
Zoltan Kochan
2019-03-06 21:04:36 +02:00
parent f11c4f1eb0
commit bcaffc6c12
15 changed files with 535 additions and 23 deletions

View File

@@ -9,7 +9,7 @@
"suppressImplicitAnyIndexErrors": true,
"allowSyntheticDefaultImports": true,
"strictNullChecks": true,
"target": "es6",
"target": "es2017",
"outDir": "lib",
"module": "commonjs",
"moduleResolution": "node"

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017-2019 Zoltan Kochan <z@kochan.io>
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.

View File

@@ -0,0 +1,38 @@
# @pnpm/local-resolver
> Resolver for local packages
<!--@shields('npm')-->
[![npm version](https://img.shields.io/npm/v/@pnpm/local-resolver.svg)](https://www.npmjs.com/package/@pnpm/local-resolver)
<!--/@-->
## Install
Install it via npm.
npm install @pnpm/local-resolver
## Usage
```js
'use strict'
const resolveFromLocal = require('@pnpm/local-resolver').default
resolveFromLocal({pref: './example-package'}, {prefix: process.cwd()})
.then(resolveResult => console.log(resolveResult))
//> { id: 'link:example-package',
// normalizedPref: 'link:example-package',
// package:
// { name: 'foo',
// version: '1.0.0',
// readme: '# foo\n',
// readmeFilename: 'README.md',
// description: '',
// _id: 'foo@1.0.0' },
// resolution: { directory: 'example-package', type: 'directory' }
// resolvedVia: 'local-filesystem' }
```
## License
[MIT](./LICENSE) © [Zoltan Kochan](https://www.kochan.io/)

View File

@@ -0,0 +1 @@
# foo

View File

@@ -0,0 +1,4 @@
{
"name": "foo",
"version": "1.0.0"
}

View File

@@ -0,0 +1,5 @@
'use strict'
const resolveFromLocal = require('@pnpm/local-resolver').default
resolveFromLocal({pref: './example-package'}, {prefix: process.cwd()})
.then(resolveResult => console.log(resolveResult))

View File

@@ -0,0 +1,62 @@
{
"name": "@pnpm/local-resolver",
"version": "1.0.5",
"description": "Resolver for local packages",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
"files": [
"lib"
],
"engines": {
"node": ">=8"
},
"scripts": {
"lint": "tslint -c tslint.json --project .",
"tsc": "tsc",
"test": "npm run tsc && npm run lint && ts-node test --ts-node",
"md": "mos",
"prepublishOnly": "npm run tsc",
"fix": "tslint -c tslint.json --project . --fix"
},
"repository": "https://github.com/pnpm/pnpm/blob/master/packages/local-resolver",
"keywords": [
"pnpm",
"resolver",
"npm"
],
"author": "Zoltan Kochan <z@kochan.io> (https://www.kochan.io/)",
"license": "MIT",
"bugs": {
"url": "https://github.com/pnpm/pnpm/issues"
},
"homepage": "https://github.com/pnpm/pnpm/blob/master/packages/local-resolver#readme",
"dependencies": {
"@pnpm/read-package-json": "1.1.1",
"@pnpm/resolver-base": "^2.1.0",
"@pnpm/types": "^2.0.0",
"@types/graceful-fs": "^4.1.2",
"@types/node": "^10.0.6",
"graceful-fs": "4.1.15",
"normalize-path": "3.0.0",
"ssri": "6.0.1"
},
"devDependencies": {
"@pnpm/local-resolver": "link:",
"@pnpm/tslint-config": "0.0.0",
"@types/tape": "^4.2.31",
"mos": "^2.0.0-alpha.3",
"mos-plugin-readme": "^1.0.4",
"tape": "^4.8.0",
"ts-node": "^8.0.2",
"tslint": "^5.8.0",
"typescript": "^3.0.0"
},
"mos": {
"plugins": [
"readme"
],
"installation": {
"useShortAlias": true
}
}
}

View File

@@ -0,0 +1,77 @@
import readPackageJson from '@pnpm/read-package-json'
import { ResolveResult } from '@pnpm/resolver-base'
import { PackageJson } from '@pnpm/types'
import fs = require('graceful-fs')
import path = require('path')
import ssri = require('ssri')
import parsePref from './parsePref'
/**
* Resolves a package hosted on the local filesystem
*/
export default async function resolveLocal (
wantedDependency: {pref: string},
opts: {
prefix: string,
shrinkwrapDirectory?: string,
},
): Promise<(ResolveResult & {
id: string,
normalizedPref: string,
resolution: {tarball: string},
} | {
id: string,
normalizedPref: string,
package: PackageJson,
resolution: {directory: string, type: 'directory'},
}) | null> {
const spec = parsePref(wantedDependency.pref, opts.prefix, opts.shrinkwrapDirectory || opts.prefix)
if (!spec) return null
if (spec.type === 'file') {
return {
id: spec.id,
normalizedPref: spec.normalizedPref,
resolution: {
integrity: await getFileIntegrity(spec.fetchSpec),
tarball: spec.id,
},
resolvedVia: 'local-filesystem',
}
}
let localPkg!: PackageJson
try {
localPkg = await readPackageJson(path.join(spec.fetchSpec, 'package.json'))
} catch (internalErr) {
switch (internalErr.code) {
case 'ENOTDIR': {
const err = new Error(`Could not install from "${spec.fetchSpec}" as it is not a directory.`)
err['code'] = 'ERR_PNPM_NOT_PACKAGE_DIRECTORY' // tslint:disable-line:no-string-literal
throw err
}
case 'ENOENT': {
const err = new Error(`Could not install from "${spec.fetchSpec}" as it does not contain a package.json file.`)
err['code'] = 'ERR_PNPM_DIRECTORY_HAS_NO_PACKAGE_JSON' // tslint:disable-line:no-string-literal
throw err
}
default: {
throw internalErr
}
}
}
return {
id: spec.id,
normalizedPref: spec.normalizedPref,
package: localPkg,
resolution: {
directory: spec.dependencyPath,
type: 'directory',
},
resolvedVia: 'local-filesystem',
}
}
async function getFileIntegrity (filename: string) {
return (await ssri.fromStream(fs.createReadStream(filename))).toString()
}

View File

@@ -0,0 +1,101 @@
import normalize = require('normalize-path')
import os = require('os')
import path = require('path')
// tslint:disable-next-line
const isWindows = process.platform === 'win32' || global['FAKE_WINDOWS']
const isFilespec = isWindows ? /^(?:[.]|~[/]|[/\\]|[a-zA-Z]:)/ : /^(?:[.]|~[/]|[/]|[a-zA-Z]:)/
const isFilename = /[.](?:tgz|tar.gz|tar)$/i
const isAbsolutePath = /^[/]|^[A-Za-z]:/
export interface LocalPackageSpec {
dependencyPath: string,
fetchSpec: string,
id: string,
type: 'directory' | 'file',
normalizedPref: string,
}
export default function parsePref (
pref: string,
importerPrefix: string,
shrinkwrapDirectory: string,
): LocalPackageSpec | null {
if (pref.startsWith('link:')) {
return fromLocal(pref, importerPrefix, shrinkwrapDirectory, 'directory')
}
if (pref.endsWith('.tgz')
|| pref.endsWith('.tar.gz')
|| pref.endsWith('.tar')
|| pref.includes(path.sep)
|| pref.startsWith('file:')
|| isFilespec.test(pref)
) {
const type = isFilename.test(pref) ? 'file' : 'directory'
return fromLocal(pref, importerPrefix, shrinkwrapDirectory, type)
}
if (pref.startsWith('path:')) {
const err = new Error('Local dependencies via `path:` protocol are not supported. ' +
'Use the `link:` protocol for folder dependencies and `file:` for local tarballs')
// tslint:disable:no-string-literal
err['code'] = 'ERR_PNPM_PATH_IS_UNSUPPORTED_PROTOCOL'
err['pref'] = pref
err['protocol'] = 'path:'
// tslint:enable:no-string-literal
throw err
}
return null
}
function fromLocal (
pref: string,
importerPrefix: string,
shrinkwrapDirectory: string,
type: 'file' | 'directory',
): LocalPackageSpec {
if (!importerPrefix) importerPrefix = process.cwd()
const spec = pref.replace(/\\/g, '/')
.replace(/^(file|link):[/]*([A-Za-z]:)/, '$2') // drive name paths on windows
.replace(/^(file|link):(?:[/]*([~./]))?/, '$2')
const protocol = type === 'directory' ? 'link:' : 'file:'
let fetchSpec!: string
let normalizedPref!: string
if (/^~[/]/.test(spec)) {
// this is needed for windows and for file:~/foo/bar
fetchSpec = resolvePath(os.homedir(), spec.slice(2))
normalizedPref = `${protocol}${spec}`
} else {
fetchSpec = resolvePath(importerPrefix, spec)
if (isAbsolute(spec)) {
normalizedPref = `${protocol}${spec}`
} else {
normalizedPref = `${protocol}${path.relative(importerPrefix, fetchSpec)}`
}
}
const dependencyPath = normalize(path.relative(importerPrefix, fetchSpec))
const id = type === 'directory' || importerPrefix === shrinkwrapDirectory
? `${protocol}${dependencyPath}`
: `${protocol}${normalize(path.relative(shrinkwrapDirectory, fetchSpec))}`
return {
dependencyPath,
fetchSpec,
id,
normalizedPref,
type,
}
}
function resolvePath (where: string, spec: string) {
if (isAbsolutePath.test(spec)) return spec
return path.resolve(where, spec)
}
function isAbsolute (dir: string) {
if (dir[0] === '/') return true
if (/^[A-Za-z]:/.test(dir)) return true
return false
}

View File

@@ -0,0 +1,124 @@
import path = require('path')
import test = require('tape')
import resolveFromLocal from '@pnpm/local-resolver'
test('resolve directory', async t => {
const resolveResult = await resolveFromLocal({pref: '..'}, {prefix: __dirname})
t.equal(resolveResult!.id, 'link:..')
t.equal(resolveResult!.normalizedPref, 'link:..')
t.equal(resolveResult!['package']!.name, '@pnpm/local-resolver')
t.equal(resolveResult!.resolution!['directory'], '..')
t.equal(resolveResult!.resolution!['type'], 'directory')
t.end()
})
test('resolve directory specified using the file: protocol', async t => {
const resolveResult = await resolveFromLocal({pref: 'file:..'}, {prefix: __dirname})
t.equal(resolveResult!.id, 'link:..')
t.equal(resolveResult!.normalizedPref, 'link:..')
t.equal(resolveResult!['package']!.name, '@pnpm/local-resolver')
t.equal(resolveResult!.resolution!['directory'], '..')
t.equal(resolveResult!.resolution!['type'], 'directory')
t.end()
})
test('resolve directoty specified using the link: protocol', async t => {
const resolveResult = await resolveFromLocal({pref: 'link:..'}, {prefix: __dirname})
t.equal(resolveResult!.id, 'link:..')
t.equal(resolveResult!.normalizedPref, 'link:..')
t.equal(resolveResult!['package']!.name, '@pnpm/local-resolver')
t.equal(resolveResult!.resolution!['directory'], '..')
t.equal(resolveResult!.resolution!['type'], 'directory')
t.end()
})
test('resolve file', async t => {
const wantedDependency = {pref: './pnpm-local-resolver-0.1.1.tgz'}
const resolveResult = await resolveFromLocal(wantedDependency, {prefix: __dirname})
t.deepEqual(resolveResult, {
id: 'file:pnpm-local-resolver-0.1.1.tgz',
normalizedPref: 'file:pnpm-local-resolver-0.1.1.tgz',
resolution: {
integrity: 'sha512-UHd2zKRT/w70KKzFlj4qcT81A1Q0H7NM9uKxLzIZ/VZqJXzt5Hnnp2PYPb5Ezq/hAamoYKIn5g7fuv69kP258w==',
tarball: 'file:pnpm-local-resolver-0.1.1.tgz',
},
resolvedVia: 'local-filesystem',
})
t.end()
})
test("resolve file when shrinkwrap directory differs from the package's dir", async t => {
const wantedDependency = {pref: './pnpm-local-resolver-0.1.1.tgz'}
const resolveResult = await resolveFromLocal(wantedDependency, {
prefix: __dirname,
shrinkwrapDirectory: path.join(__dirname, '..'),
})
t.deepEqual(resolveResult, {
id: 'file:test/pnpm-local-resolver-0.1.1.tgz',
normalizedPref: 'file:pnpm-local-resolver-0.1.1.tgz',
resolution: {
integrity: 'sha512-UHd2zKRT/w70KKzFlj4qcT81A1Q0H7NM9uKxLzIZ/VZqJXzt5Hnnp2PYPb5Ezq/hAamoYKIn5g7fuv69kP258w==',
tarball: 'file:test/pnpm-local-resolver-0.1.1.tgz',
},
resolvedVia: 'local-filesystem',
})
t.end()
})
test('resolve tarball specified with file: protocol', async t => {
const wantedDependency = {pref: 'file:./pnpm-local-resolver-0.1.1.tgz'}
const resolveResult = await resolveFromLocal(wantedDependency, {prefix: __dirname})
t.deepEqual(resolveResult, {
id: 'file:pnpm-local-resolver-0.1.1.tgz',
normalizedPref: 'file:pnpm-local-resolver-0.1.1.tgz',
resolution: {
integrity: 'sha512-UHd2zKRT/w70KKzFlj4qcT81A1Q0H7NM9uKxLzIZ/VZqJXzt5Hnnp2PYPb5Ezq/hAamoYKIn5g7fuv69kP258w==',
tarball: 'file:pnpm-local-resolver-0.1.1.tgz',
},
resolvedVia: 'local-filesystem',
})
t.end()
})
test("fail when resolving tarball specified with the link: protocol", async t => {
try {
const wantedDependency = {pref: 'link:./pnpm-local-resolver-0.1.1.tgz'}
const resolveResult = await resolveFromLocal(wantedDependency, {prefix: __dirname})
t.fail()
} catch (err) {
t.ok(err)
t.equal(err.code, 'ERR_PNPM_NOT_PACKAGE_DIRECTORY')
t.end()
}
})
test("fail when resolving from not existing directory", async t => {
try {
const wantedDependency = {pref: 'link:./dir-does-not-exist'}
const resolveResult = await resolveFromLocal(wantedDependency, {prefix: __dirname})
t.fail()
} catch (err) {
t.ok(err)
t.equal(err.code, 'ERR_PNPM_DIRECTORY_HAS_NO_PACKAGE_JSON')
t.end()
}
})
test('throw error when the path: protocol is used', async t => {
try {
await resolveFromLocal({pref: 'path:..'}, {prefix: __dirname})
t.fail()
} catch (err) {
t.ok(err)
t.equal(err.code, 'ERR_PNPM_PATH_IS_UNSUPPORTED_PROTOCOL')
t.equal(err.pref, 'path:..')
t.equal(err.protocol, 'path:')
t.end()
}
})

View File

Binary file not shown.

View File

@@ -0,0 +1,24 @@
{
"compilerOptions": {
"removeComments": false,
"preserveConstEnums": true,
"sourceMap": true,
"declaration": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"suppressImplicitAnyIndexErrors": true,
"allowSyntheticDefaultImports": true,
"strictNullChecks": true,
"target": "es2017",
"outDir": "lib",
"module": "commonjs",
"moduleResolution": "node"
},
"include": [
"src/**/*.ts",
"typings/**/*.d.ts"
],
"atom": {
"rewriteTsconfig": true
}
}

View File

@@ -0,0 +1,3 @@
{
"extends": "@pnpm/tslint-config"
}

View File

@@ -0,0 +1,24 @@
declare module 'normalize-path' {
const anything: any;
export = anything;
}
declare module 'read-package-json' {
const anything: any;
export = anything;
}
declare module 'util.promisify' {
const anything: any;
export = anything;
}
declare module 'osenv' {
const anything: any;
export = anything;
}
declare module 'ssri' {
const anything: any;
export = anything;
}

72
pnpm-lock.yaml generated
View File

@@ -194,7 +194,7 @@ importers:
packages/default-resolver:
dependencies:
'@pnpm/git-resolver': 'link:../git-resolver'
'@pnpm/local-resolver': 1.0.5
'@pnpm/local-resolver': 'link:../local-resolver'
'@pnpm/npm-resolver': 'link:../npm-resolver'
'@pnpm/tarball-resolver': 1.0.1
devDependencies:
@@ -641,6 +641,44 @@ importers:
ts-node: 7.0.1
tslint: 5.13.1
typescript: 3.3.3333
packages/local-resolver:
dependencies:
'@pnpm/read-package-json': 1.1.1
'@pnpm/resolver-base': 'link:../resolver-base'
'@pnpm/types': 'link:../types'
'@types/graceful-fs': 4.1.3
'@types/node': 10.12.29
graceful-fs: 4.1.15
normalize-path: 3.0.0
ssri: 6.0.1
devDependencies:
'@pnpm/local-resolver': 'link:'
'@pnpm/tslint-config': 'link:../../utils/tslint-config'
'@types/tape': 4.2.33
mos: 2.0.0-alpha.3
mos-plugin-readme: 1.0.4
tape: 4.10.1
ts-node: 8.0.2_typescript@3.3.3333
tslint: 5.13.1_typescript@3.3.3333
typescript: 3.3.3333
specifiers:
'@pnpm/local-resolver': 'link:'
'@pnpm/read-package-json': 1.1.1
'@pnpm/resolver-base': ^2.1.0
'@pnpm/tslint-config': 0.0.0
'@pnpm/types': ^2.0.0
'@types/graceful-fs': ^4.1.2
'@types/node': ^10.0.6
'@types/tape': ^4.2.31
graceful-fs: 4.1.15
mos: ^2.0.0-alpha.3
mos-plugin-readme: ^1.0.4
normalize-path: 3.0.0
ssri: 6.0.1
tape: ^4.8.0
ts-node: ^8.0.2
tslint: ^5.8.0
typescript: ^3.0.0
packages/lockfile-file:
dependencies:
'@pnpm/constants': 'link:../constants'
@@ -1049,7 +1087,7 @@ importers:
util.promisify: 1.0.0
write-json-file: 3.1.0
devDependencies:
'@pnpm/local-resolver': 1.0.5
'@pnpm/local-resolver': 'link:../local-resolver'
'@pnpm/logger': 2.1.0
'@pnpm/npm-resolver': 'link:../npm-resolver'
'@pnpm/package-requester': 'link:'
@@ -2430,20 +2468,6 @@ packages:
node: '>=6'
resolution:
integrity: sha512-AeAxhTVNNtB7u5hVyojTnFsJi/iE/w13t23aQ3wMdpIEIDFVO6nukk8eZShDWlRCTTHjvfON1LpwHqQDRsT0dA==
/@pnpm/local-resolver/1.0.5:
dependencies:
'@pnpm/read-package-json': 1.1.1
'@pnpm/resolver-base': 2.1.0
'@pnpm/types': 2.0.0
'@types/graceful-fs': 4.1.3
'@types/node': 10.12.29
graceful-fs: 4.1.15
normalize-path: 3.0.0
ssri: 6.0.1
engines:
node: '>=6'
resolution:
integrity: sha512-I8aRPY//PnFAGF+ndj/UE9iox9zdKv7T4F7wrh3AH19w7JFC2FhHbTRB2AvJaTEygvCzjsamvXNGPLVTe8zfpA==
/@pnpm/logger/2.1.0:
dependencies:
'@types/node': 10.12.29
@@ -2484,6 +2508,7 @@ packages:
/@pnpm/resolver-base/2.1.0:
dependencies:
'@pnpm/types': 2.0.0
dev: false
engines:
node: '>=6'
resolution:
@@ -2522,22 +2547,22 @@ packages:
node: '>=4'
resolution:
integrity: sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==
/@sinonjs/commons/1.3.1:
/@sinonjs/commons/1.4.0:
dependencies:
type-detect: 4.0.8
dev: true
resolution:
integrity: sha512-rgmZk5CrBGAMATk0HlHOFvo8V44/r+On6cKS80tqid0Eljd+fFBWBOXZp9H2/EB3faxdNdzXTx6QZIKLkbJ7mA==
integrity: sha512-9jHK3YF/8HtJ9wCAbG+j8cD0i0+ATS9A7gXFqS36TblLPNy6rEEc+SB0imo91eCboGaBYGV/MT1/br/J+EE7Tw==
/@sinonjs/formatio/3.2.1:
dependencies:
'@sinonjs/commons': 1.3.1
'@sinonjs/commons': 1.4.0
'@sinonjs/samsam': 3.2.0
dev: true
resolution:
integrity: sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==
/@sinonjs/samsam/3.2.0:
dependencies:
'@sinonjs/commons': 1.3.1
'@sinonjs/commons': 1.4.0
array-from: 2.1.1
lodash: 4.17.11
dev: true
@@ -2595,7 +2620,8 @@ packages:
integrity: sha512-0AbIQzpqQafYPFg26nwg5PfNgPLYwHeTP6z5F1u+5oypLIdpx34o5r8wYTTj3X3YYF2yPHtZPO/KYwlI8Nu/hQ==
/@types/graceful-fs/4.1.3:
dependencies:
'@types/node': 11.9.5
'@types/node': 10.12.29
dev: false
resolution:
integrity: sha512-AiHRaEB50LQg0pZmm659vNBb9f4SJ0qrAnteuzhSeAUcJKxoYgEnprg/83kppCnc2zvtCKbdZry1a5pVY3lOTQ==
/@types/http-proxy-agent/2.0.1:
@@ -4948,6 +4974,7 @@ packages:
resolution:
integrity: sha1-oB6c2cnkkXFcmKdaQtXwu9EH/3Y=
/figgy-pudding/3.5.1:
dev: false
resolution:
integrity: sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==
/figures/1.7.0:
@@ -8929,7 +8956,7 @@ packages:
integrity: sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
/sinon/7.2.6:
dependencies:
'@sinonjs/commons': 1.3.1
'@sinonjs/commons': 1.4.0
'@sinonjs/formatio': 3.2.1
'@sinonjs/samsam': 3.2.0
diff: 3.5.0
@@ -9151,6 +9178,7 @@ packages:
/ssri/6.0.1:
dependencies:
figgy-pudding: 3.5.1
dev: false
resolution:
integrity: sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==
/stacktracey/1.2.106: